diff --git a/apps/unused_code/README.md b/apps/unused_code/README.md index 0d33a76..479b18f 100644 --- a/apps/unused_code/README.md +++ b/apps/unused_code/README.md @@ -1,4 +1,5 @@ # pyutils-unusedcode + Helper to identify unused code in a pytest repository. It should be run from inside the test repository using this tool. ## Usage @@ -9,18 +10,20 @@ pyutils-unusedcode --help ``` ## Config file + To skip unused code check on specific files or functions of a repository, a config file with the list of names of such files and function prefixes should be added to `~/.config/python-utility-scripts/config.yaml` -### Example: +### Example ```yaml pyutils-unusedcode: exclude_files: - - "my_exclude_file.py" + - "my_exclude_file.py" exclude_function_prefix: - - "my_exclude_function_prefix" + - "my_exclude_function_prefix" ``` + This would exclude any functions with prefix my_exclude_function_prefix and file my_exclude_file.py from unused code check To run from CLI with `--exclude-function-prefixes` @@ -34,3 +37,12 @@ To run from CLI with `--exclude-files` ```bash pyutils-unusedcode --exclude-files 'my_exclude_file1.py,my_exclude_file2.py' ``` + +### Skip single function in file + +Add `# skip-unused-code` comment in the function name list to skip it from check. + +```python +def my_function(): # skip-unused-code + pass +``` diff --git a/apps/unused_code/unused_code.py b/apps/unused_code/unused_code.py index 64a46a5..f975a45 100644 --- a/apps/unused_code/unused_code.py +++ b/apps/unused_code/unused_code.py @@ -9,6 +9,7 @@ from typing import Any, Iterable import click +from ast_comments import parse from simple_logger.logger import get_logger from apps.utils import ListParamType, all_python_files, get_util_config @@ -58,7 +59,7 @@ def process_file(py_file: str, func_ignore_prefix: list[str], file_ignore_list: return "" with open(py_file) as fd: - tree = ast.parse(source=fd.read()) + tree = parse(source=fd.read()) for func in _iter_functions(tree=tree): if func_ignore_prefix and is_ignore_function_list(ignore_prefix_list=func_ignore_prefix, function=func): @@ -69,8 +70,12 @@ def process_file(py_file: str, func_ignore_prefix: list[str], file_ignore_list: LOGGER.debug(f"Skipping `autouse` fixture function: {func.name}") continue + if any(getattr(item, "value", None) == "# skip-unused-code" for item in func.body): + LOGGER.debug(f"Skipping function {func.name}: found `# skip-unused-code`") + continue + used = False - _func_grep_found = subprocess.check_output(["git", "grep", "-w", func.name], shell=False) + _func_grep_found = subprocess.check_output(["git", "grep", "-wE", f"{func.name}(.*)"], shell=False) for entry in _func_grep_found.decode().splitlines(): _, _line = entry.split(":", 1) diff --git a/pyproject.toml b/pyproject.toml index 3640090..33bf6f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,8 @@ dependencies = [ "jira>=3.6.0,<4", "tenacity>=9.0.0,<10", "python-simple-logger>=2.0.0,<3", - "pyhelper-utils>=1.0.1,<2" + "pyhelper-utils>=1.0.1,<2", + "ast-comments>=1.2.2", ] [[project.authors]] diff --git a/tests/unused_code/test_unused_code.py b/tests/unused_code/test_unused_code.py index 48dc8fa..d904a99 100644 --- a/tests/unused_code/test_unused_code.py +++ b/tests/unused_code/test_unused_code.py @@ -32,3 +32,10 @@ def test_unused_code_function_list_exclude(): LOGGER.info(f"Result output: {result.output}, exit code: {result.exit_code}, exceptions: {result.exception}") assert result.exit_code == 1 assert "Is not used anywhere in the code" in result.output + + +def test_unused_code_check_skip_with_comment(): + result = get_cli_runner().invoke(get_unused_functions) + LOGGER.info(f"Result output: {result.output}, exit code: {result.exit_code}, exceptions: {result.exception}") + assert result.exit_code == 1 + assert "skip_with_comment" not in result.output diff --git a/tests/unused_code/unused_code_file_for_test.py b/tests/unused_code/unused_code_file_for_test.py index 795c3fe..714aad5 100644 --- a/tests/unused_code/unused_code_file_for_test.py +++ b/tests/unused_code/unused_code_file_for_test.py @@ -4,3 +4,7 @@ def unused_code_check_fail(): def unused_code_check_file(): pass + + +def skip_with_comment(): # skip-unused-code + pass diff --git a/uv.lock b/uv.lock index 16da7f1..43b2a33 100644 --- a/uv.lock +++ b/uv.lock @@ -6,6 +6,15 @@ resolution-markers = [ "python_full_version < '3.11'", ] +[[package]] +name = "ast-comments" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/85/e2bea931f371bde73c5e6f450c51f173ee451cb68978372f7b222f803133/ast_comments-1.2.2.tar.gz", hash = "sha256:04f86e0a8010569ea279d470b4dcb86f0913a8a39bd80e0fe358e11aa5bfa8d6", size = 5345 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/ab/e7f191cd70fe861367a55449381832d6703b475ab320a9d96f3d61ea672b/ast_comments-1.2.2-py3-none-any.whl", hash = "sha256:6468c6b91d971b3fca2e3cd6f7a6d52e7385b6e4d25809444372960c74a8cbc2", size = 5801 }, +] + [[package]] name = "asttokens" version = "2.4.1" @@ -832,6 +841,7 @@ name = "python-utility-scripts" version = "1.0.16" source = { editable = "." } dependencies = [ + { name = "ast-comments" }, { name = "jira" }, { name = "pyhelper-utils" }, { name = "pylero" }, @@ -853,6 +863,7 @@ test = [ [package.metadata] requires-dist = [ + { name = "ast-comments", specifier = ">=1.2.2" }, { name = "jira", specifier = ">=3.6.0,<4" }, { name = "pyhelper-utils", specifier = ">=1.0.1,<2" }, { name = "pylero", specifier = ">=0.1.0,<0.2" },