Skip to content

Add support for Python 3.13 #27

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 0 additions & 31 deletions .github/workflows/check-deadcode-on-python310.yml

This file was deleted.

39 changes: 39 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python

name: Test

on: [push, pull_request, workflow_dispatch]

permissions:
contents: read

env:
FORCE_COLOR: 1

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true

- name: Install dependencies
run: |
make .venv

- name: Run checks
run: |
make check
5 changes: 2 additions & 3 deletions deadcode/actions/find_python_filenames.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from logging import getLogger
from typing import List
from pathlib import Path

from deadcode.data_types import Args
Expand All @@ -8,9 +7,9 @@
logger = getLogger()


def find_python_filenames(args: Args) -> List[str]:
def find_python_filenames(args: Args) -> list[str]:
filenames = []
paths: List[str] = list(args.paths)
paths: list[str] = list(args.paths)
while paths:
path = Path(paths.pop())

Expand Down
4 changes: 2 additions & 2 deletions deadcode/actions/find_unused_names.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from typing import List, Iterable
from collections.abc import Iterable

from deadcode.data_types import Args, Filename
from deadcode.visitor.code_item import CodeItem
from deadcode.visitor.dead_code_visitor import DeadCodeVisitor


def find_unused_names(
filenames: List[Filename],
filenames: list[Filename],
args: Args,
) -> Iterable[CodeItem]:
dead_code_visitor = DeadCodeVisitor(filenames, args)
Expand Down
2 changes: 1 addition & 1 deletion deadcode/actions/fix_or_show_unused_code.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from collections import defaultdict
from difflib import diff_bytes, unified_diff
from typing import Iterable
from collections.abc import Iterable
import os

from deadcode.actions.merge_overlaping_file_parts import merge_overlaping_file_parts
Expand Down
4 changes: 2 additions & 2 deletions deadcode/actions/get_unused_names_error_message.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from typing import Iterable, Optional
from collections.abc import Iterable

from deadcode.data_types import Args
from deadcode.visitor.code_item import CodeItem
from deadcode.visitor.ignore import _match


def get_unused_names_error_message(unused_names: Iterable[CodeItem], args: Args) -> Optional[str]:
def get_unused_names_error_message(unused_names: Iterable[CodeItem], args: Args) -> str | None:
unused_names = list(unused_names)

if not unused_names:
Expand Down
9 changes: 4 additions & 5 deletions deadcode/actions/merge_overlaping_file_parts.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from typing import List, Optional, Tuple
from deadcode.data_types import Part


Expand All @@ -13,7 +12,7 @@ def does_include(bigger_part: Part, smaller_part: Part) -> bool:
return bool(starts_later and ends_faster)


def sort_parts(bigger_part: Part, smaller_part: Part) -> Tuple[Part, Part]:
def sort_parts(bigger_part: Part, smaller_part: Part) -> tuple[Part, Part]:
"""Returns code part which begins first following by another code part."""
# TODO: Column should go first (tuple comparison would be possible)
line_start_b, line_end_b, col_start_b, col_end_b = bigger_part
Expand All @@ -35,7 +34,7 @@ def does_overlap(bigger_part: Part, smaller_part: Part) -> bool:
return bool((line_end_b > line_start_s) or ((line_end_b == line_start_s) and (col_end_b > col_start_s)))


def merge_parts(p1: Part, p2: Part) -> Optional[Part]:
def merge_parts(p1: Part, p2: Part) -> Part | None:
p1, p2 = sort_parts(p1, p2)

line_start1, line_end1, col_start1, col_end1 = p1
Expand All @@ -58,10 +57,10 @@ def merge_parts(p1: Part, p2: Part) -> Optional[Part]:
return None


def merge_overlaping_file_parts(overlaping_file_parts: List[Part]) -> List[Part]:
def merge_overlaping_file_parts(overlaping_file_parts: list[Part]) -> list[Part]:
# Make algorithm O(n^2) by checking every single part if overlaps

non_overlaping_file_parts: List[Part] = []
non_overlaping_file_parts: list[Part] = []
for p1 in sorted(overlaping_file_parts):
merged_part = None
merged_with_index = None
Expand Down
8 changes: 4 additions & 4 deletions deadcode/actions/parse_arguments.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import argparse
from typing import Any, Dict, List, Optional
from typing import Any
import sys
import os

Expand All @@ -12,7 +12,7 @@
from deadcode.utils.flatten_lists import flatten_lists_of_comma_separated_values


def parse_arguments(args: Optional[List[str]]) -> Args:
def parse_arguments(args: list[str] | None) -> Args:
"""Parses arguments (execution options) for deadcode tool.

Arguments for DeadCode can be provided via:
Expand Down Expand Up @@ -219,7 +219,7 @@ def parse_arguments(args: Optional[List[str]]) -> Args:
return Args(**parsed_args)


def parse_pyproject_toml() -> Dict[str, Any]:
def parse_pyproject_toml() -> dict[str, Any]:
"""Parse a pyproject toml file, pulling out relevant parts for Black.

If parsing fails, will raise a tomllib.TOMLDecodeError.
Expand All @@ -232,6 +232,6 @@ def parse_pyproject_toml() -> Dict[str, Any]:
with open(pyproject_toml_filename, 'rb') as f:
pyproject_toml = tomllib.load(f)

config: Dict[str, Any] = pyproject_toml.get('tool', {}).get('deadcode', {})
config: dict[str, Any] = pyproject_toml.get('tool', {}).get('deadcode', {})
config = {k.replace('--', '').replace('-', '_'): v for k, v in config.items()}
return config
10 changes: 5 additions & 5 deletions deadcode/actions/remove_file_parts_from_content.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import re
from typing import List, Optional, TypeVar
from typing import TypeVar

from deadcode.data_types import Part

T = TypeVar('T')


def list_get(list_: List[T], index: int) -> Optional[T]:
def list_get(list_: list[T], index: int) -> T | None:
if len(list_) > index:
return list_[index]
return None
Expand Down Expand Up @@ -41,7 +41,7 @@ def remove_comma_from_begining(line: bytes) -> bytes:
return line.lstrip()[1:].lstrip()


def remove_file_parts_from_content(content_lines: List[bytes], unused_file_parts: List[Part]) -> List[bytes]:
def remove_file_parts_from_content(content_lines: list[bytes], unused_file_parts: list[Part]) -> list[bytes]:
""" """
# How should move through the lines of content?
updated_content_lines = []
Expand All @@ -53,8 +53,8 @@ def remove_file_parts_from_content(content_lines: List[bytes], unused_file_parts
was_block_removed = False
next_line_after_removed_block = None
indentation_of_first_removed_line = b''
empty_lines_in_a_row_list: List[bytes] = []
empty_lines_before_removed_block_list: List[bytes] = []
empty_lines_in_a_row_list: list[bytes] = []
empty_lines_before_removed_block_list: list[bytes] = []

for current_lineno, line in enumerate(content_lines, start=1):
from_line, to_line, from_col, to_col = 0, 0, 0, 0
Expand Down
6 changes: 2 additions & 4 deletions deadcode/cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from typing import List, Optional
import sys

from deadcode import __version__
Expand All @@ -12,9 +11,8 @@


def main(
command_line_args: Optional[List[str]] = None,
) -> Optional[str]:

command_line_args: list[str] | None = None,
) -> str | None:
if command_line_args and '--version' in command_line_args or '--version' in sys.argv:
return __version__

Expand Down
4 changes: 2 additions & 2 deletions deadcode/constants.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Dict, Literal
from typing import Literal


UnusedCodeType = Literal[
Expand Down Expand Up @@ -33,7 +33,7 @@
]


ERROR_TYPE_TO_ERROR_CODE: Dict[UnusedCodeType, UnusedCodeErrorCode] = {
ERROR_TYPE_TO_ERROR_CODE: dict[UnusedCodeType, UnusedCodeErrorCode] = {
'variable': 'DC01',
'function': 'DC02',
'class': 'DC03',
Expand Down
3 changes: 2 additions & 1 deletion deadcode/data_types.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import ast

from dataclasses import dataclass
from typing import Iterable, NamedTuple
from typing import NamedTuple
from collections.abc import Iterable


AbstractSyntaxTree = ast.Module # Should be module instead of ast
Expand Down
14 changes: 7 additions & 7 deletions deadcode/utils/base_test_case.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from typing import Any, Dict, List, Optional, Union
from typing import Any
from unittest import TestCase
from unittest.mock import MagicMock, patch

Expand All @@ -8,7 +8,7 @@


class BaseTestCase(TestCase):
files: Dict[str, bytes] = {}
files: dict[str, bytes] = {}

maxDiff = None

Expand All @@ -17,10 +17,10 @@ def patch(self, path: str) -> MagicMock:
self.addCleanup(patcher.stop)
return patcher.start()

def _get_filenames(self, *args: Any, **kwargs: Any) -> List[str]:
def _get_filenames(self, *args: Any, **kwargs: Any) -> list[str]:
return list(self.files.keys())

def _read_file_side_effect(self, filename: Union[str, Path], *args: Any, **kwargs: Any) -> MagicMock:
def _read_file_side_effect(self, filename: str | Path, *args: Any, **kwargs: Any) -> MagicMock:
mock = MagicMock()
mock.filename = str(filename)

Expand All @@ -35,7 +35,7 @@ def cache_file_content(file_content: bytes) -> int:
return mock

def setUp(self) -> None:
self.updated_files: Dict[str, bytes] = {}
self.updated_files: dict[str, bytes] = {}

self.find_python_filenames_mock = self.patch('deadcode.cli.find_python_filenames')
self.find_python_filenames_mock.side_effect = self._get_filenames
Expand All @@ -50,7 +50,7 @@ def setUp(self) -> None:

self.args = Args()

def assertFiles(self, files: Dict[str, bytes], removed: Optional[List[str]] = None) -> None:
def assertFiles(self, files: dict[str, bytes], removed: list[str] | None = None) -> None:
expected_removed_files = removed
expected_files = files

Expand All @@ -76,7 +76,7 @@ def assertFiles(self, files: Dict[str, bytes], removed: Optional[List[str]] = No
fix_indent(self.updated_files.get(filename) or unchanged_files.get(filename) or ''),
)

def assertUpdatedFiles(self, expected_updated_files: Dict[str, bytes]) -> None:
def assertUpdatedFiles(self, expected_updated_files: dict[str, bytes]) -> None:
"""Checks if updated files match expected updated files."""

self.assertListEqual(list(expected_updated_files.keys()), list(self.updated_files.keys()))
Expand Down
4 changes: 2 additions & 2 deletions deadcode/utils/fix_indent.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import sys
from typing import Optional, TypeVar
from typing import TypeVar

T = TypeVar('T')


def fix_indent(doc: T) -> Optional[T]:
def fix_indent(doc: T) -> T | None:
"""Finds indentation of a first line and removes it from all following lines.

Implemented based on inspect.cleandoc by keeping trailing lines.
Expand Down
8 changes: 4 additions & 4 deletions deadcode/utils/flatten_lists.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
from typing import List, Optional, TypeVar
from typing import TypeVar

T = TypeVar('T')


def flatten_lists_of_comma_separated_values(
list_of_comma_separated_values: Optional[List[List[str]]],
) -> List[str]:
list_of_comma_separated_values: list[list[str]] | None,
) -> list[str]:
"""Concatenates lists into one list."""
if not list_of_comma_separated_values:
return []
return flatten_list([v.split(',') for v in flatten_list(list_of_comma_separated_values)])


def flatten_list(list_of_lists: Optional[List[List[T]]]) -> List[T]:
def flatten_list(list_of_lists: list[list[T]] | None) -> list[T]:
"""Concatenates lists into one list."""
if not list_of_lists:
return []
Expand Down
8 changes: 4 additions & 4 deletions deadcode/utils/nested_scopes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Dict, List, Optional, Union
from typing import Any

from deadcode.visitor.code_item import CodeItem

Expand All @@ -15,7 +15,7 @@ class NestedScope:
"""

def __init__(self) -> None:
self._scopes: Dict[Union[str, CodeItem], Any] = {}
self._scopes: dict[str | CodeItem, Any] = {}

def add(self, code_item: CodeItem) -> None:
"""Adds code item to nested scope."""
Expand All @@ -34,7 +34,7 @@ def add(self, code_item: CodeItem) -> None:
# > TODO: leaf should be replaced. Is it replaced with new code item?
current_scope[code_item] = {}

def get(self, name: str, scope: str) -> Optional[Union[CodeItem, str]]:
def get(self, name: str, scope: str) -> CodeItem | str | None:
"""Returns CodeItem which matches scoped_name (e.g. package.class.method.variable)
from the given scope or None if its not found."""

Expand All @@ -45,7 +45,7 @@ def get(self, name: str, scope: str) -> Optional[Union[CodeItem, str]]:
# projects.models, billing.models, auth.models: only one root scope called models would be registered.

# Create a stack of scopes begining from nearest and following with parent one
scopes: List[Dict[Union[CodeItem, str], Dict[Any, Any]]] = []
scopes: list[dict[CodeItem | str, dict[Any, Any]]] = []
next_scope = self._scopes
for scope_part in scope.split('.'):
if scope_part not in next_scope:
Expand Down
Loading