diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a249d76..3dd6929 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: strategy: matrix: os: [ubuntu-20.04, windows-latest, macos-latest] - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.12"] steps: - uses: actions/checkout@v4 diff --git a/docs/AUTHORS.md b/docs/AUTHORS.md index dff9c25..12a0355 100644 --- a/docs/AUTHORS.md +++ b/docs/AUTHORS.md @@ -18,6 +18,7 @@ - Alex Waygood ([@AlexWaygood](https://github.com/AlexWaygood)) +- Per Fagrell ([@perfa](https://github.com/perfa)) - Perchun Pak ([@PerchunPak](https://github.com/PerchunPak)) - Pierre Mourlanne ([@pmourlanne](https://github.com/pmourlanne)) - RooTer UrbaƄski ([@rooterkyberian](https://github.com/rooterkyberian)) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 7092723..3355714 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,6 +10,12 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +## Added + +- [Add support for Python 3.12 by @perfa](https://github.com/hadialqattan/pycln/pull/224) + +## Changed + - [Drop Python3.6 by @hadialqattan](https://github.com/hadialqattan/pycln/pull/225) ## [2.3.0] - 2023-10-14 diff --git a/pycln/utils/pathu.py b/pycln/utils/pathu.py index 55540ff..4ce4cd7 100644 --- a/pycln/utils/pathu.py +++ b/pycln/utils/pathu.py @@ -135,7 +135,6 @@ def get_standard_lib_paths() -> Set[Path]: paths: Set[Path] = set() for lib_path in PYTHON_STDLIB_PATHS: - for path in os.listdir(lib_path): paths.add(Path(os.path.join(lib_path, path))) @@ -143,7 +142,6 @@ def get_standard_lib_paths() -> Set[Path]: lib_dynload_path = os.path.join(lib_path, LIB_DYNLOAD) if os.path.isdir(lib_dynload_path): - for path in os.listdir(lib_dynload_path): paths.add(Path(os.path.join(lib_dynload_path, path))) @@ -160,7 +158,6 @@ def get_standard_lib_names() -> Set[str]: paths: Set[Path] = get_standard_lib_paths() for path in paths: - name = str(path.parts[-1]) if name.startswith("_") or "-" in name: @@ -194,7 +191,6 @@ def get_third_party_lib_paths() -> Tuple[Set[Path], Set[Path]]: packages_paths.add(path) for path in packages_paths: - for name in os.listdir(path): if name.endswith(PTH_EXTENSION): for pth_path in _site.addpackage(path, name): @@ -220,7 +216,6 @@ def get_local_import_path(path: Path, module: str) -> Optional[Path]: # Test different levels. for i in [None] + list(range(-10, -0)): # type: ignore - # If it's a file. fpath = os.path.join(*dirnames[:i], *names[:-1], f"{names[-1]}{PY_EXTENSION}") if os.path.isfile(fpath): diff --git a/pycln/utils/refactor.py b/pycln/utils/refactor.py index c42ef53..37ed4cc 100644 --- a/pycln/utils/refactor.py +++ b/pycln/utils/refactor.py @@ -1,8 +1,8 @@ """Pycln code refactoring utility.""" import ast import os +import sys from importlib import import_module -from pathlib import Path, _posix_flavour, _windows_flavour # type: ignore from typing import Iterable, List, Optional, Set, Tuple, Union, cast from .. import ISWIN @@ -20,6 +20,15 @@ from .config import Config from .report import Report +if sys.version_info < (3, 12): + from pathlib import Path, _posix_flavour, _windows_flavour # type: ignore + + _flavour = _windows_flavour if ISWIN else _posix_flavour +else: + from pathlib import Path + + _flavour = os.path + # Constants. NOPYCLN = "nopycln" CHANGE_MARK = "\n_CHANGED_" @@ -28,13 +37,15 @@ class PyPath(Path): - """Path subclass that has `is_stub` property.""" - _flavour = _windows_flavour if ISWIN else _posix_flavour + _flavour = _flavour def __init__(self, *args) -> None: # pylint: disable=unused-argument - super().__init__() # Path.__init__ does not take any args. + if sys.version_info < (3, 12): + super().__init__() # Path.__init__ does not take any args. + else: + super().__init__(*args) self._is_stub = regexu.is_stub_file(self) @property @@ -128,7 +139,6 @@ def remove_from_children( tree = ast.parse("".join(source_lines)) for parent in ast.walk(tree): - body = getattr(parent, "body", None) if body and hasattr(body, "__len__"): body_len = len(body) @@ -272,9 +282,7 @@ def _refactor(self, original_lines: List[str]) -> str: """ fixed_lines = original_lines.copy() for type_ in self._import_stats: - for node in type_: - # Skip any import that has `# noqa` or `# nopycln: import` comment. s_lineno = node.location.start.line - 1 e_lineno = node.location.end.line - 1 @@ -399,7 +407,6 @@ def _expand_import_star( try: is_star = False if node.names[0].name == "*": - #: [for `.pyi` files] PEP 484 - Star Imports rule: #: #: >>> from X import * # exported (should be treated as used) diff --git a/pycln/utils/scan.py b/pycln/utils/scan.py index 441f5d6..a339cb7 100644 --- a/pycln/utils/scan.py +++ b/pycln/utils/scan.py @@ -751,7 +751,6 @@ def _add_list_names(self, node: List[ast.expr]) -> None: def _compute_not_importables(self, node: Union[FunctionDefT, ast.ClassDef]): # Compute class/function not-importables. for node_ in ast.iter_child_nodes(node): - if isinstance(node_, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): self._not_importables.add(cast(str, node_.name)) @@ -857,7 +856,6 @@ def visit_ImportFrom(self, node: ast.ImportFrom): def _check_names(names: List[ast.alias]) -> HasSideEffects: # Check if imported names has side effects or not. for alias in names: - # All standard lib modules doesn't has side effects # except `pathu.IMPORTS_WITH_SIDE_EFFECTS`. if alias.name in pathu.get_standard_lib_names(): diff --git a/tests/test_pathu.py b/tests/test_pathu.py index abedbbf..c83264e 100644 --- a/tests/test_pathu.py +++ b/tests/test_pathu.py @@ -387,8 +387,8 @@ def test_get_module_path(self, paths, module, expec_path): id="import file : local", ), pytest.param( - "distutils", - Path("distutils/__init__.py"), + "asyncio", + Path("asyncio/__init__.py"), id="import module : standard", ), pytest.param( @@ -453,9 +453,9 @@ def test_get_import_path(self, module: str, expec_path: Path): ), pytest.param( "*", - "distutils", + "asyncio", 0, - Path("distutils/__init__.py"), + Path("asyncio/__init__.py"), id="from package import * : standard", ), pytest.param(