deadcode 2.4.1__tar.gz → 2.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. {deadcode-2.4.1 → deadcode-2.6.0}/.gitignore +1 -0
  2. deadcode-2.6.0/.venv310/bin/activate_this.py +59 -0
  3. deadcode-2.6.0/.venv311/bin/activate_this.py +59 -0
  4. deadcode-2.6.0/.venv314/bin/activate_this.py +59 -0
  5. deadcode-2.6.0/.venv315/bin/activate_this.py +59 -0
  6. deadcode-2.6.0/PKG-INFO +382 -0
  7. deadcode-2.6.0/README.md +349 -0
  8. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/__init__.py +1 -1
  9. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/actions/find_python_filenames.py +2 -18
  10. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/actions/find_unused_names.py +2 -2
  11. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/actions/fix_or_show_unused_code.py +12 -9
  12. deadcode-2.6.0/deadcode/actions/get_unused_import_parts.py +207 -0
  13. deadcode-2.6.0/deadcode/actions/get_unused_names_error_message.py +41 -0
  14. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/actions/merge_overlaping_file_parts.py +4 -5
  15. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/actions/parse_abstract_syntax_tree.py +2 -1
  16. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/actions/parse_arguments.py +35 -17
  17. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/actions/remove_file_parts_from_content.py +33 -11
  18. deadcode-2.6.0/deadcode/cli.py +67 -0
  19. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/constants.py +7 -4
  20. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/data_types.py +2 -1
  21. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/utils/fix_indent.py +4 -12
  22. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/utils/flatten_lists.py +4 -4
  23. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/utils/nested_scopes.py +6 -8
  24. deadcode-2.6.0/deadcode/utils/print_safely.py +29 -0
  25. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/visitor/code_item.py +5 -62
  26. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/visitor/dead_code_visitor.py +333 -98
  27. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/visitor/ignore.py +42 -9
  28. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/visitor/lines.py +2 -3
  29. deadcode-2.6.0/deadcode/visitor/module_exports.py +80 -0
  30. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/visitor/noqa.py +3 -4
  31. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/visitor/utils.py +40 -17
  32. deadcode-2.6.0/docs/conf.py +20 -0
  33. {deadcode-2.4.1 → deadcode-2.6.0}/pyproject.toml +18 -12
  34. deadcode-2.4.1/PKG-INFO +0 -218
  35. deadcode-2.4.1/README.md +0 -191
  36. deadcode-2.4.1/deadcode/actions/get_unused_names_error_message.py +0 -37
  37. deadcode-2.4.1/deadcode/cli.py +0 -46
  38. deadcode-2.4.1/deadcode/utils/base_test_case.py +0 -88
  39. {deadcode-2.4.1 → deadcode-2.6.0}/LICENSE +0 -0
  40. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/actions/__init__.py +0 -0
  41. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/utils/__init__.py +0 -0
  42. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/utils/add_colors_to_diff.py +0 -0
  43. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/utils/print_ast.py +0 -0
  44. {deadcode-2.4.1 → deadcode-2.6.0}/deadcode/visitor/__init__.py +0 -0
@@ -45,6 +45,7 @@ htmlcov/
45
45
  .cache
46
46
  nosetests.xml
47
47
  coverage.xml
48
+ coverage-new.xml
48
49
  *.cover
49
50
  *.py,cover
50
51
  .hypothesis/
@@ -0,0 +1,59 @@
1
+ # Copyright (c) 2020-202x The virtualenv developers
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ """
23
+ Activate virtualenv for current interpreter:
24
+
25
+ import runpy
26
+ runpy.run_path(this_file)
27
+
28
+ This can be used when you must use an existing Python interpreter, not the virtualenv bin/python.
29
+ """ # noqa: D415
30
+
31
+ from __future__ import annotations
32
+
33
+ import os
34
+ import site
35
+ import sys
36
+
37
+ try:
38
+ abs_file = os.path.abspath(__file__)
39
+ except NameError as exc:
40
+ msg = "You must use import runpy; runpy.run_path(this_file)"
41
+ raise AssertionError(msg) from exc
42
+
43
+ bin_dir = os.path.dirname(abs_file)
44
+ base = bin_dir[: -len("bin") - 1] # strip away the bin part from the __file__, plus the path separator
45
+
46
+ # prepend bin to PATH (this file is inside the bin directory)
47
+ os.environ["PATH"] = os.pathsep.join([bin_dir, *os.environ.get("PATH", "").split(os.pathsep)])
48
+ os.environ["VIRTUAL_ENV"] = base # virtual env is right above bin directory
49
+ os.environ["VIRTUAL_ENV_PROMPT"] = "" or os.path.basename(base) # noqa: SIM222
50
+
51
+ # add the virtual environments libraries to the host python import mechanism
52
+ prev_length = len(sys.path)
53
+ for lib in "../lib/python3.10/site-packages".split(os.pathsep):
54
+ path = os.path.realpath(os.path.join(bin_dir, lib))
55
+ site.addsitedir(path)
56
+ sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]
57
+
58
+ sys.real_prefix = sys.prefix
59
+ sys.prefix = base
@@ -0,0 +1,59 @@
1
+ # Copyright (c) 2020-202x The virtualenv developers
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ """
23
+ Activate virtualenv for current interpreter:
24
+
25
+ import runpy
26
+ runpy.run_path(this_file)
27
+
28
+ This can be used when you must use an existing Python interpreter, not the virtualenv bin/python.
29
+ """ # noqa: D415
30
+
31
+ from __future__ import annotations
32
+
33
+ import os
34
+ import site
35
+ import sys
36
+
37
+ try:
38
+ abs_file = os.path.abspath(__file__)
39
+ except NameError as exc:
40
+ msg = "You must use import runpy; runpy.run_path(this_file)"
41
+ raise AssertionError(msg) from exc
42
+
43
+ bin_dir = os.path.dirname(abs_file)
44
+ base = bin_dir[: -len("bin") - 1] # strip away the bin part from the __file__, plus the path separator
45
+
46
+ # prepend bin to PATH (this file is inside the bin directory)
47
+ os.environ["PATH"] = os.pathsep.join([bin_dir, *os.environ.get("PATH", "").split(os.pathsep)])
48
+ os.environ["VIRTUAL_ENV"] = base # virtual env is right above bin directory
49
+ os.environ["VIRTUAL_ENV_PROMPT"] = "" or os.path.basename(base) # noqa: SIM222
50
+
51
+ # add the virtual environments libraries to the host python import mechanism
52
+ prev_length = len(sys.path)
53
+ for lib in "../lib/python3.11/site-packages".split(os.pathsep):
54
+ path = os.path.realpath(os.path.join(bin_dir, lib))
55
+ site.addsitedir(path)
56
+ sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]
57
+
58
+ sys.real_prefix = sys.prefix
59
+ sys.prefix = base
@@ -0,0 +1,59 @@
1
+ # Copyright (c) 2020-202x The virtualenv developers
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ """
23
+ Activate virtualenv for current interpreter:
24
+
25
+ import runpy
26
+ runpy.run_path(this_file)
27
+
28
+ This can be used when you must use an existing Python interpreter, not the virtualenv bin/python.
29
+ """ # noqa: D415
30
+
31
+ from __future__ import annotations
32
+
33
+ import os
34
+ import site
35
+ import sys
36
+
37
+ try:
38
+ abs_file = os.path.abspath(__file__)
39
+ except NameError as exc:
40
+ msg = "You must use import runpy; runpy.run_path(this_file)"
41
+ raise AssertionError(msg) from exc
42
+
43
+ bin_dir = os.path.dirname(abs_file)
44
+ base = bin_dir[: -len("bin") - 1] # strip away the bin part from the __file__, plus the path separator
45
+
46
+ # prepend bin to PATH (this file is inside the bin directory)
47
+ os.environ["PATH"] = os.pathsep.join([bin_dir, *os.environ.get("PATH", "").split(os.pathsep)])
48
+ os.environ["VIRTUAL_ENV"] = base # virtual env is right above bin directory
49
+ os.environ["VIRTUAL_ENV_PROMPT"] = "" or os.path.basename(base) # noqa: SIM222
50
+
51
+ # add the virtual environments libraries to the host python import mechanism
52
+ prev_length = len(sys.path)
53
+ for lib in "../lib/python3.14/site-packages".split(os.pathsep):
54
+ path = os.path.realpath(os.path.join(bin_dir, lib))
55
+ site.addsitedir(path)
56
+ sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]
57
+
58
+ sys.real_prefix = sys.prefix
59
+ sys.prefix = base
@@ -0,0 +1,59 @@
1
+ # Copyright (c) 2020-202x The virtualenv developers
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ """
23
+ Activate virtualenv for current interpreter:
24
+
25
+ import runpy
26
+ runpy.run_path(this_file)
27
+
28
+ This can be used when you must use an existing Python interpreter, not the virtualenv bin/python.
29
+ """ # noqa: D415
30
+
31
+ from __future__ import annotations
32
+
33
+ import os
34
+ import site
35
+ import sys
36
+
37
+ try:
38
+ abs_file = os.path.abspath(__file__)
39
+ except NameError as exc:
40
+ msg = "You must use import runpy; runpy.run_path(this_file)"
41
+ raise AssertionError(msg) from exc
42
+
43
+ bin_dir = os.path.dirname(abs_file)
44
+ base = bin_dir[: -len("bin") - 1] # strip away the bin part from the __file__, plus the path separator
45
+
46
+ # prepend bin to PATH (this file is inside the bin directory)
47
+ os.environ["PATH"] = os.pathsep.join([bin_dir, *os.environ.get("PATH", "").split(os.pathsep)])
48
+ os.environ["VIRTUAL_ENV"] = base # virtual env is right above bin directory
49
+ os.environ["VIRTUAL_ENV_PROMPT"] = "" or os.path.basename(base) # noqa: SIM222
50
+
51
+ # add the virtual environments libraries to the host python import mechanism
52
+ prev_length = len(sys.path)
53
+ for lib in "../lib/python3.15/site-packages".split(os.pathsep):
54
+ path = os.path.realpath(os.path.join(bin_dir, lib))
55
+ site.addsitedir(path)
56
+ sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]
57
+
58
+ sys.real_prefix = sys.prefix
59
+ sys.prefix = base
@@ -0,0 +1,382 @@
1
+ Metadata-Version: 2.5
2
+ Name: deadcode
3
+ Version: 2.6.0
4
+ Summary: Find and remove dead code.
5
+ Project-URL: homepage, https://github.com/albertas/deadcode
6
+ Project-URL: documentation, https://deadcode.readthedocs.io/
7
+ Author-email: Albertas Gimbutas <albertasgim@gmail.com>
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3
11
+ Classifier: Natural Language :: English
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: 3.15
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: tomli>=2.0.1; python_version < '3.11'
22
+ Provides-Extra: test
23
+ Requires-Dist: diff-cover==10.6.0; extra == 'test'
24
+ Requires-Dist: hatch; extra == 'test'
25
+ Requires-Dist: mypy==1.10.1; extra == 'test'
26
+ Requires-Dist: pip-audit; extra == 'test'
27
+ Requires-Dist: pre-commit; extra == 'test'
28
+ Requires-Dist: pytest-cov==5.0.0; extra == 'test'
29
+ Requires-Dist: pytest-xdist==3.8.0; extra == 'test'
30
+ Requires-Dist: pytest==9.0.3; extra == 'test'
31
+ Requires-Dist: ruff==0.5.1; extra == 'test'
32
+ Description-Content-Type: text/markdown
33
+
34
+ [![Deadcode Logo](https://raw.githubusercontent.com/albertas/deadcode/main/docs/_static/deadcode-logo-readme.png)](https://deadcode.readthedocs.io/)
35
+
36
+ <h2 align="center">Find and Fix Unused Python Code</h2>
37
+
38
+ <p align="center">
39
+ <a href="https://github.com/pre-commit/pre-commit"><img src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit" alt="pre-commit" style="max-width:100%;"></a>
40
+ <a href="https://pypi.org/project/deadcode/"><img alt="PyPI" src="https://img.shields.io/pypi/v/deadcode"></a>
41
+ <a href="https://www.python.org/downloads/"><img alt="Python 3.10+" src="https://img.shields.io/badge/python-3.10%2B-blue"></a>
42
+ <a href="https://pepy.tech/project/deadcode"><img alt="Downloads" src="https://static.pepy.tech/badge/deadcode"></a>
43
+ <a href="https://github.com/albertas/deadcode/blob/main/LICENSE"><img alt="License: AGPLv3" src="https://raw.githubusercontent.com/albertas/deadcode/main/docs/_static/AGPLv3-license.svg"></a>
44
+ <a href="https://codecov.io/gh/albertas/deadcode"><img alt="Coverage" src="https://codecov.io/gh/albertas/deadcode/branch/main/graph/badge.svg"></a>
45
+ <a href="https://github.com/albertas/deadcode/actions/workflows/test.yml"><img alt="CI" src="https://github.com/albertas/deadcode/actions/workflows/test.yml/badge.svg?branch=main"></a>
46
+ <br/><a href='https://ko-fi.com/V7V21681GD' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi3.png?v=6' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
47
+ </p>
48
+
49
+
50
+ <p align="center">
51
+ <a href="https://deadcode.readthedocs.io/"><img src="https://img.shields.io/badge/docs-Read%20the%20Docs-blue?style=for-the-badge&logo=readthedocs&logoColor=white" alt="Documentation" height="40"></a>
52
+ </p>
53
+
54
+ ## Installation
55
+ ```shell
56
+ pip install deadcode
57
+ ```
58
+
59
+ ## Usage
60
+
61
+ > **Full documentation:** [deadcode.readthedocs.io](https://deadcode.readthedocs.io/)
62
+
63
+ To see unused code findings:
64
+ ```shell
65
+ deadcode .
66
+ ```
67
+
68
+ To see suggested fixes for all files:
69
+ ```shell
70
+ deadcode . --fix --dry
71
+ ```
72
+
73
+ To see suggested fixes only for `foo.py` file:
74
+ ```shell
75
+ deadcode . --fix --dry --only foo.py
76
+ ```
77
+
78
+ To fix:
79
+ ```shell
80
+ deadcode . --fix
81
+ ```
82
+
83
+ Tune out some of the false positives, e.g.:
84
+ ```
85
+ deadcode . --exclude=venv,tests --ignore-names=BaseTestCase,*Mixin --ignore-names-in-files=migrations
86
+ ```
87
+
88
+ The same options can be provided in `pyproject.toml` settings file:
89
+ ```
90
+ [tool.deadcode]
91
+ exclude = ["venv", "tests"]
92
+ ignore-names = ["BaseTestCase", "*Mixin"]
93
+ ignore-names-in-files = ["migrations"]
94
+ ignore-names-if-inherits-from = ["BaseModel", "*.ObjectType"]
95
+ ```
96
+
97
+ ## Using deadcode on a FastAPI project
98
+
99
+ FastAPI registers handlers through decorators and populates Pydantic model fields at runtime, so both look unused to a static analyser.
100
+ Configure deadcode like this:
101
+
102
+ ```toml
103
+ [tool.deadcode]
104
+ ignore-names-if-decorated-with = ["@app.*", "@router.*"]
105
+ ignore-bodies-if-inherits-from = ["BaseModel"]
106
+ ```
107
+
108
+ Read the [FastAPI guide](https://deadcode.readthedocs.io/en/latest/fastapi.html) for common use cases, the complete example application,
109
+ the dependency injection strategy, and the expected output with no wrong detections.
110
+
111
+ ## Pre-commit hook
112
+ Create a `.pre-commit-config.yaml` file in the root of your project directory, if it doesn't exist, and add the following to the file:
113
+
114
+ ```
115
+ # See https://pre-commit.com for more information
116
+ # See https://pre-commit.com/hooks.html for more hooks
117
+ repos:
118
+ - repo: https://github.com/albertas/deadcode
119
+ rev: v2.6.0
120
+ hooks:
121
+ - id: deadcode
122
+ ```
123
+
124
+ The `rev` is the release tag, so it matches the version published on PyPI. To
125
+ run the same hook while developing deadcode itself, use `make pre-commit`.
126
+
127
+ ## Command line options
128
+
129
+ | Option&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | Type | Meaning |
130
+ |-------------------------------------------|------|----------------------------------------------------------------------|
131
+ |`--fix` | - | Automatically remove detected unused code expressions from the code base. |
132
+ |`--dry` | - | Show changes which would be made in files. |
133
+ |`--only` | list | Filenames (or path expressions), that will be reflected in the output (and modified if needed). |
134
+ |`--exclude` | list | Filenames (or path expressions), which will be completely skipped without being analysed. |
135
+ |`--ignore-names` | list | Removes provided list of names from the output. Glob expressions to match multiple names can also be provided, e.g. `*Mixin` will match all classes ending with `Mixin`. |
136
+ |`--ignore-names-in-files` | list | Ignores unused names in files, which filenames match provided path expressions. |
137
+ |`--ignore-names-if-inherits-from` | list | Ignores names of classes which inherit from any of the provided class names. The class name may be a plain name, a dotted path such as `graphene.ObjectType`, or a glob such as `*.ObjectType`; a dotted base also matches its final component (`ObjectType`). Only the class name is suppressed; its members and body are controlled by `--ignore-bodies-if-inherits-from` and `--ignore-definitions-if-inherits-from`. |
138
+ |`--ignore-names-if-decorated-with` | list | Ignores names of an expression, which is decorated with one of the provided decorator names. |
139
+ |`--ignore-bodies-of` | list | Ignores body of an expression if its name matches any of the provided names. |
140
+ |`--ignore-bodies-if-decorated-with` | list | Ignores body of an expression if its decorated with one of the provided decorator names. |
141
+ |`--ignore-bodies-if-inherits-from` | list | Ignores body of a class if it inherits from any of the provided class names. The class name may be a plain name, a dotted path such as `graphene.ObjectType`, or a glob such as `*.ObjectType`; a dotted base also matches its final component (`ObjectType`). |
142
+ |`--ignore-definitions` | list | Ignores definition (including name and body) if a name of an expression matches any of the provided ones. Applies to class, function, method and property definitions, not to variables, attributes or imports; use `--ignore-names` for those. |
143
+ |`--ignore-definitions-if-inherits-from` | list | Ignores definition (including name and body) of a class if it inherits from any of the provided class names. The class name may be a plain name, a dotted path such as `graphene.ObjectType`, or a glob such as `*.ObjectType`; a dotted base also matches its final component (`ObjectType`). |
144
+ |`--ignore-definitions-if-decorated-with` | list | Ignores definition (including name and body) of an expression, which is decorated with any of the provided decorator names. |
145
+ |`--no-color` | - | Removes colors from the output. |
146
+ |`--count` | - | Provides the count of the detected unused names instead of printing them all out. |
147
+ |`--quiet` | - | Does not output anything. The command exits with code 1 if unused names are found. |
148
+ |`-V`, `--version` | - | Shows `deadcode` version. |
149
+ |`-v`, `--verbose` | - | Shows verbose logging output. |
150
+
151
+ ### Decorator matching
152
+ Decorator names are written as in the source, including the leading `@`; a pattern without `@` matches nothing.
153
+ Entries are glob patterns matched with `fnmatch.fnmatchcase`, so `*`, `?` and `[seq]` apply and `*` also crosses dots
154
+ (`@app.*` matches `@app.route`). Matching is case-sensitive.
155
+ Decorator call arguments are ignored, so `@app.route("/")` is matched as `@app.route`.
156
+ Stacked decorators match when any one decorator matches.
157
+ All three decorated-with options behave the same way, covering classes, functions, async functions, methods and properties;
158
+ variables, attributes and imports are not covered.
159
+ Definitions decorated with `@abstractmethod` or `@override` are always ignored, with no option needed.
160
+ The recognized spellings are `@abstractmethod`, `@abc.abstractmethod`, `@override`, `@typing.override` and `@typing_extensions.override`.
161
+ The built-in markers match the decorator name only and, like the options, still analyse the decorated body for its own unused code.
162
+ Imports and aliases are not resolved, so `@aliased.route` needs `@aliased.*` rather than `@app.*`, and
163
+ `from abc import abstractmethod as abstract` renders `@abstract` and is not ignored.
164
+
165
+ ```toml
166
+ [tool.deadcode]
167
+ ignore-names-if-decorated-with = ["@app.route"]
168
+ ```
169
+
170
+ ```python
171
+ @app.route("/", methods=["POST"])
172
+ def post_tax_adder_customer():
173
+ pass
174
+ ```
175
+
176
+ ### Exit status
177
+
178
+ - `0` — no dead code found.
179
+ - `1` — dead code found (in any output mode).
180
+ - `2` — invalid command line usage.
181
+ - Unexpected errors print a Python traceback and exit `1`.
182
+
183
+ The status is independent of `--quiet`, `--count`, `--only`, `--dry`, and `--fix`.
184
+ A `--fix` run exits `1` on the run that found items and `0` on a subsequent run
185
+ over the cleaned tree.
186
+
187
+ ### Glossary
188
+ - `name` - variable, function or class name.
189
+ - `body` - code block which follows after `:` in function or class definition. A body ignore suppresses every definition inside the expression, including nested classes and functions.
190
+ - `definition` - whole class or function definition expression including its name and body.
191
+
192
+
193
+ ## Ignoring checks with noqa comments
194
+ Inline `# noqa` comments can be used to ignore `deadcode` checks.
195
+ E.g. unused `Foo` class wont be detected/fixed because `# noqa: DC03` comment is used:
196
+
197
+ ```python
198
+ class Foo: # noqa: DC03
199
+ pass
200
+ ```
201
+
202
+ For a decorated function, method, property, or class, place the comment on the physical line of the `def`, `async def`, or `class` keyword:
203
+
204
+ ```python
205
+ @cache
206
+ def foo(): # noqa: DC02
207
+ pass
208
+ ```
209
+
210
+ A comment on the first decorator line is still accepted.
211
+
212
+ ### Rules
213
+ | Code | Name | Message |
214
+ |--------|--------------------|----------------|
215
+ | DC01 | unused-variable | Variable `{name}` is never used
216
+ | DC02 | unused-function | Function `{name}` is never used
217
+ | DC03 | unused-class | Class `{name}` is never used
218
+ | DC04 | unused-method | Method `{name}` is never used
219
+ | DC05 | unused-attribute | Attribute `{name}` is never used
220
+ | DC06 | unused-name | Name `{name}` is never used
221
+ | DC07 | unused-import | Import `{name}` is never used
222
+ | DC08 | unused-property | Property `{name}` is never used
223
+ | DC09 | unreachable-body | Unreachable body of a conditional statement or loop
224
+ | DC11 | empty-file | Empty Python file
225
+ | DC12 | commented-out-code | Commented out code
226
+ | DC13 | unreachable-code | Code after terminal statement, e.g. `return`, `raise`, `continue`, `break`
227
+ | DC | ignore-expression | Do not show any findings for an expression, which starts on current line (this code can only be used in `# noqa: DC` comments)
228
+
229
+ ## Ignoring unittest lifecycle methods
230
+ `unittest` lifecycle methods (`setUp`, `tearDown`, `setUpClass`, `tearDownClass`, `asyncSetUp`, `asyncTearDown`, `setUpModule`, `tearDownModule`) declared in test files are ignored by default, alongside the existing pytest hooks.
231
+ Declarations with those names outside test files are still reported as unused code.
232
+
233
+ ## Contributing
234
+
235
+ Create a feature branch from `main`, open a pull request targeting `main`, and
236
+ wait for review. Do not commit feature changes directly to `main`; release
237
+ commits are made by the maintainer on `main`.
238
+
239
+ - `make check` - runs unit tests and other checks using virtual environment.
240
+ - `make test` - runs the test suite in parallel with `pytest-xdist`, using one worker per logical CPU minus one.
241
+ Set `PYTEST_XDIST_WORKERS` to change the worker count (`make test PYTEST_XDIST_WORKERS=2`), or pass
242
+ `PYTEST_ME_PLEASE="-n 0"` to run serially.
243
+
244
+ ## Rationale
245
+ [ruff](https://pypi.org/project/ruff/) and
246
+ [flake8](https://pypi.org/project/flake8/) - don't have rules for unused global
247
+ code detection, only for local ones `F823`, `F841`, `F842`. `deadcode` package
248
+ tries to add new `DCXXX` checks for detecting variables/functions/classes/files
249
+ which are not used in a whole code base.
250
+
251
+ `deadcode` - is supposed to be used inline with other static code checkers like `ruff`.
252
+
253
+ There is an alternative [vulture](https://pypi.org/project/vulture/) package.
254
+
255
+ ## Known limitations
256
+ In case there are several definitions using the same name - they all wont be
257
+ reported if at least one usage of that name is being detected.
258
+
259
+ Files with syntax errors will be ignored, because `deadcode` uses `ast` to
260
+ build abstract syntax tree for name usage detection.
261
+
262
+ It is assumed that `deadcode` will be run using the same or higher Python version as the
263
+ code base is implemented in.
264
+
265
+ Wildcard imports are resolved by module basename, without package paths, so modules with the same basename are treated as one.
266
+ `__all__` is honoured only when it is a statically readable module-level list or tuple of strings; modules with unknown exports
267
+ (dynamic `__all__`, unreadable or unparsable files, or modules outside the analysed file set) are not restricted. Without `__all__`,
268
+ the public module-level names are exported.
269
+
270
+ ## Feature requests
271
+ A checked entry is implemented on `main` and is included in the next release; published releases may lag behind `main`.
272
+
273
+ - [x] Replace `.*` with only `*` in regexp matching.
274
+ - [x] Add unused class method detection DC04 check.
275
+ - [x] Add `--fix` option to automatically remove detected dead code occourencies
276
+ - [x] Add a check for empty python files.
277
+ - [x] Split error codes into DC01, DC02, DC03 for variables, functions, class.
278
+ - [x] Should have different codes for ignoring name and ignoring whole definition (reserved DCxx0 - ignore name, DCxx1 - ignore definition).
279
+ - [ ] Allow to disable each check separately using:
280
+ - [ ] inline comment.
281
+ - [ ] pyproject.toml file
282
+ - [x] Add a check for code in comments.
283
+ - [x] DC06: detect an unused name.
284
+ - [x] DC09: report an unreachable body, e.g. an `if` or `while` block that cannot run.
285
+ - [ ] Add target python version option, if specified it will be used for code base check.
286
+ - [ ] Add a `--depth` parameter to ignore nested code.. (To only check global scope use 0).
287
+ - [ ] Add options:
288
+ - [x] --ignore-definitions
289
+ - [x] --ignore-definitions-if-inherits-from
290
+ - [x] --ignore-definitions-if-decorated-with
291
+ - [x] --ignore-names-if-inherits-from
292
+ - [x] --ignore-names-if-decorated-with
293
+ - [x] --ignore-bodies-of
294
+ - [x] --ignore-bodies-if-decorated-with
295
+ - [x] --ignore-bodies-if-inherits-from
296
+ - Question: would it be possible to ignore only certain types of checks for a body, e.g. only variable attributes of TypedDict and still check usage of methods and properties?
297
+ - What expression would allow this type of precission?
298
+ - [ ] Distinguish between definitions with same name, but different files.
299
+ - [ ] Repeated application of `deadcode` till the output stops changing.
300
+ - [x] Unreachable code detection and fixing: this should only be scoped for if statements and only limited to primitive variables.
301
+ - [ ] Benchmarking performance with larger projects (time, CPU and memory consumption) in order to optimize.
302
+ - [ ] `--fix` could accept a list of filenames as well (only those files would be changed, but the summary could would be full).
303
+ (This might be confusing, because filenames, which have to be considered are provided without any flag, --fix is expected to not accept arguments)
304
+ - [ ] pre-commit-hook.
305
+ - [ ] language server.
306
+ - [x] DC13: remove code after terminal statements like `raise`, `return`, `break`, `continue` and comes in the same scope.
307
+ - [ ] Add `ignore` and `per-file-ignores` command line and pyproject.toml options, which allows to skip some rules.
308
+ - [ ] Make sure that all rules are being skipped by `noqa` comment and all rules react to `noqa: rule_id` comments.
309
+ - [ ] Include package names into code item scope (dot-separated path), e.g. "package1.package2.module.class.method.variable".
310
+ - [ ] All options should be able to accept dot-separated path or a generic name, e.g. "marshmallow.Schema" vs "Schema",
311
+ documentation should cleary demonstrate the behaviour/example that "Schema" means "*.Schema".
312
+ - [ ] Redefinition of an existing name makes previous name unreachable, unless it is assigned somehow.
313
+ - [ ] Check if file is still valid/parsable after automatic fixing, if not: halt the change and report error.
314
+ - [ ] Investigate ways of extracting and backporting Python3.10+ `ast` implementation to lower Python versions.
315
+
316
+ ## Release notes
317
+ - **v2.6.0** `2026-11-08`:
318
+ - Features:
319
+ - Add support for Python 3.13, 3.14 and 3.15.
320
+ - Documentation is now published on Read the Docs at https://deadcode.readthedocs.io/.
321
+ - Add `-V` as a short alias for `--version`.
322
+ - `--ignore-definitions-if-decorated-with` now ignores whole decorated definitions
323
+ - `--ignore-bodies-of` now suppresses the bodies of matching classes and functions
324
+ - `--ignore-bodies-if-decorated-with` now suppresses the bodies of decorated definitions
325
+ - `--ignore-names-if-inherits-from` now ignores class names matching a base class
326
+ - `--ignore-definitions` now reliably ignores function, method, property and nested class definitions; its limits are documented
327
+ - Add a code coverage badge to the README, fed by the coverage report uploaded from CI
328
+ - Add a CI status badge for the test workflow to the README
329
+ - Definitions decorated with `@abstractmethod` or `@override` are ignored by default (issue #40)
330
+ - Implement DC06 unused names, DC09 unreachable bodies, DC12 commented-out code and DC13 unreachable code rules.
331
+ - Enforce 100% line coverage of the `deadcode` package and keep every documented feature mapped to its source and tests.
332
+ - Build the released version, stable and latest documentation automatically when `make publish` publishes a release
333
+ - Add a documentation banner and a usage-section documentation callout to the README
334
+ - Bugfixes:
335
+ - Exit with code 1 when unused code is found, so CI jobs and pre-commit hooks can fail.
336
+ - Fix `--fix` leaving invalid or partially removed imports when unused names are deleted from parenthesized or multi-line import statements.
337
+ - Fix `--fix` leaving invalid or fragmented imports when removing names from backslash-continued, semicolon-separated, or commented import statements.
338
+ - Fix `--fix` leaving the continuation lines of multi-line unused assignments behind and producing invalid Python (issue #26).
339
+ - Do not crash when the terminal cannot encode CLI output, including the success message, warnings and verbose logs.
340
+ - Fix `deadcode --fix` removing the `f` from the `from` keyword in multi-line parenthesized imports (issue #5)
341
+ - Remove fully unused import statements, including multi-line parenthesized ones, without leaving broken syntax behind
342
+ - Remove only the unused names from partially used import statements while keeping the used names and their order
343
+ - Honor `# noqa` comments on the definition line of decorated functions, methods and classes.
344
+ - Wildcard imports now honour a module `__all__` list or tuple, so excluded names are reported as unused. Wildcard imports stay unrestricted when `__all__` is dynamic or unknown, and explicit imports keep working. Without `__all__`, wildcard imports expose public module-level names.
345
+ - Honour `--ignore-names-if-decorated-with` for dotted decorators such as `@app.route`, so route handlers are not reported as unused (issue #30).
346
+ - Decorator names are reported as dotted identifiers such as `@app.get` instead of memory addresses
347
+ - `--ignore-names-if-decorated-with` now suppresses decorated functions, methods, properties, and classes
348
+ - Print verbose log messages as readable ASCII instead of a `bytes` repr when the output stream cannot encode a character.
349
+ - `--ignore-bodies-if-inherits-from` and `--ignore-definitions-if-inherits-from` now recognize dotted base classes such as `graphene.ObjectType` and match their final component.
350
+ - Render decorator names deterministically, including dotted names, and stop unusual decorators such as `@Class().function()` from crashing or producing memory addresses.
351
+ - Stop reporting unittest lifecycle methods declared in test files as unused code (issue #21).
352
+ - Use pytest instead of unittest.TestCase
353
+ - Fix `ignore-names-if-inherits-from` having no effect when set in `pyproject.toml`; its accepted base-class forms and class-name-only scope are now documented (issue #25)
354
+ - A scalar string in `pyproject.toml` is now honoured as one pattern instead of being split into characters
355
+ - Names referenced from quoted type annotations (forward references) are no longer reported as unused imports.
356
+ - Fix `--no-color` leaving colour codes in the success banner; Make `--count` count only findings reflected by `--only`; Make `--verbose` print INFO messages about ignored and scanned files
357
+ - Rules now sit under Ignoring checks with noqa comments, the Glossary under Command line options, and Decorator matching is more concise.
358
+ - Python 3.10+ requirement is now shown as a badge in the README
359
+ - **v2.5.0** `2026-11-07`:
360
+ - Bugfixes:
361
+ - Fix a crash when a decorator has no `id` attribute.
362
+ - **v2.4.1** `2024-08-09`:
363
+ - Features:
364
+ - Add `--version` option to show `deadcode` version.
365
+ - Bugfixes:
366
+ - Use stdout for `deadcode` output.
367
+ - **v2.4.0** `2024-07-12`:
368
+ - Features:
369
+ - Add `--only` option that accepts filenames only which will be reflected in the output and modified.
370
+ This option can be used with `--fix` and `--fix --dry` options as well as for simple unused code detection without fixing.
371
+ - **v2.3.2** `2024-07-10`:
372
+ - Features:
373
+ - Add `pre-commit` hook support.
374
+ - Drop support for Python 3.8 and 3.9 versions, since their `ast` implementation is lacking features.
375
+ - **v2.3.1** `2024-07-03`:
376
+ - Bugfixes:
377
+ - Started analysing files in bytes instead of trying to convert them into UTF-8 encoded strings.
378
+ - Improved automatic removal of unused imports.
379
+ - **v2.3.0** `2024-04-07`:
380
+ - Features:
381
+ - Add `--dry` option.
382
+ - Update error codes to use DCXX format instead of DCXXX.