django-codemod 2.3.6__tar.gz → 2.4.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 (75) hide show
  1. {django_codemod-2.3.6/src/django_codemod.egg-info → django_codemod-2.4.0}/PKG-INFO +5 -5
  2. {django_codemod-2.3.6 → django_codemod-2.4.0}/pyproject.toml +9 -6
  3. django_codemod-2.4.0/src/django_codemod/__init__.py +1 -0
  4. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/cli.py +8 -9
  5. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/path_utils.py +1 -2
  6. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/utils/calls.py +1 -2
  7. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/admin.py +3 -7
  8. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/base.py +9 -10
  9. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/models.py +4 -9
  10. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/signals.py +2 -2
  11. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/template_tags.py +8 -9
  12. {django_codemod-2.3.6 → django_codemod-2.4.0/src/django_codemod.egg-info}/PKG-INFO +5 -5
  13. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod.egg-info/requires.txt +2 -2
  14. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/test_cli.py +2 -2
  15. django_codemod-2.3.6/src/django_codemod/__init__.py +0 -1
  16. {django_codemod-2.3.6 → django_codemod-2.4.0}/LICENSE +0 -0
  17. {django_codemod-2.3.6 → django_codemod-2.4.0}/MANIFEST.in +0 -0
  18. {django_codemod-2.3.6 → django_codemod-2.4.0}/README.md +0 -0
  19. {django_codemod-2.3.6 → django_codemod-2.4.0}/docs/Makefile +0 -0
  20. {django_codemod-2.3.6 → django_codemod-2.4.0}/docs/conf.py +0 -0
  21. {django_codemod-2.3.6 → django_codemod-2.4.0}/docs/make.bat +0 -0
  22. {django_codemod-2.3.6 → django_codemod-2.4.0}/setup.cfg +0 -0
  23. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/commands.py +0 -0
  24. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/constants.py +0 -0
  25. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/feature_flags.py +0 -0
  26. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/utils/__init__.py +0 -0
  27. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/__init__.py +0 -0
  28. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/core.py +0 -0
  29. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/crypto.py +0 -0
  30. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/decorators.py +0 -0
  31. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/encoding.py +0 -0
  32. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/exceptions.py +0 -0
  33. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/forms.py +0 -0
  34. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/html.py +0 -0
  35. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/http.py +0 -0
  36. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/lru_cache.py +0 -0
  37. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/os_utils.py +0 -0
  38. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/paginator.py +0 -0
  39. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/postgres_fields.py +0 -0
  40. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/shortcuts.py +0 -0
  41. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/template_context.py +0 -0
  42. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/timezone.py +0 -0
  43. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/translations.py +0 -0
  44. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod/visitors/urls.py +0 -0
  45. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod.egg-info/SOURCES.txt +0 -0
  46. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod.egg-info/dependency_links.txt +0 -0
  47. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod.egg-info/entry_points.txt +0 -0
  48. {django_codemod-2.3.6 → django_codemod-2.4.0}/src/django_codemod.egg-info/top_level.txt +0 -0
  49. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/__init__.py +0 -0
  50. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/test_e2e.sh +0 -0
  51. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/test_path_utils.py +0 -0
  52. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/__init__.py +0 -0
  53. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/base.py +0 -0
  54. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_admin.py +0 -0
  55. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_base.py +0 -0
  56. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_core.py +0 -0
  57. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_crypto.py +0 -0
  58. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_decorators.py +0 -0
  59. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_encoding.py +0 -0
  60. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_exceptions.py +0 -0
  61. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_forms.py +0 -0
  62. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_html.py +0 -0
  63. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_http.py +0 -0
  64. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_lru_cache.py +0 -0
  65. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_models.py +0 -0
  66. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_os_utils.py +0 -0
  67. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_paginator.py +0 -0
  68. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_postgres_fields.py +0 -0
  69. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_shortcuts.py +0 -0
  70. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_signals.py +0 -0
  71. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_template_context.py +0 -0
  72. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_template_tags.py +0 -0
  73. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_timezone.py +0 -0
  74. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_translations.py +0 -0
  75. {django_codemod-2.3.6 → django_codemod-2.4.0}/tests/visitors/test_urls.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-codemod
3
- Version: 2.3.6
3
+ Version: 2.4.0
4
4
  Summary: A command line tool to automatically fix Django deprecations.
5
5
  Author-email: Bruno Alla <alla.brunoo@gmail.com>
6
6
  License-Expression: MIT
@@ -16,17 +16,17 @@ Classifier: Intended Audience :: Developers
16
16
  Classifier: Natural Language :: English
17
17
  Classifier: Operating System :: OS Independent
18
18
  Classifier: Programming Language :: Python :: 3 :: Only
19
- Classifier: Programming Language :: Python :: 3.9
20
19
  Classifier: Programming Language :: Python :: 3.10
21
20
  Classifier: Programming Language :: Python :: 3.11
22
21
  Classifier: Programming Language :: Python :: 3.12
23
22
  Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
24
  Classifier: Topic :: Software Development :: Libraries
25
- Requires-Python: >=3.9
25
+ Requires-Python: >=3.10
26
26
  Description-Content-Type: text/markdown
27
27
  License-File: LICENSE
28
- Requires-Dist: click<8.2
29
- Requires-Dist: libcst==1.8.2
28
+ Requires-Dist: click<8.3
29
+ Requires-Dist: libcst==1.8.4
30
30
  Requires-Dist: pathspec<1,>=0.6
31
31
  Requires-Dist: rich>=10
32
32
  Requires-Dist: rich-click>=1
@@ -4,7 +4,7 @@ requires = [ "setuptools>=77.0.3" ]
4
4
 
5
5
  [project]
6
6
  name = "django-codemod"
7
- version = "2.3.6"
7
+ version = "2.4.0"
8
8
  description = "A command line tool to automatically fix Django deprecations."
9
9
  readme = "README.md"
10
10
  keywords = [
@@ -16,24 +16,24 @@ license = "MIT"
16
16
  authors = [
17
17
  { name = "Bruno Alla", email = "alla.brunoo@gmail.com" },
18
18
  ]
19
- requires-python = ">=3.9"
19
+ requires-python = ">=3.10"
20
20
  classifiers = [
21
21
  "Development Status :: 5 - Production/Stable",
22
22
  "Intended Audience :: Developers",
23
23
  "Natural Language :: English",
24
24
  "Operating System :: OS Independent",
25
25
  "Programming Language :: Python :: 3 :: Only",
26
- "Programming Language :: Python :: 3.9",
27
26
  "Programming Language :: Python :: 3.10",
28
27
  "Programming Language :: Python :: 3.11",
29
28
  "Programming Language :: Python :: 3.12",
30
29
  "Programming Language :: Python :: 3.13",
30
+ "Programming Language :: Python :: 3.14",
31
31
  "Topic :: Software Development :: Libraries",
32
32
  ]
33
33
 
34
34
  dependencies = [
35
- "click<8.2",
36
- "libcst==1.8.2",
35
+ "click<8.3",
36
+ "libcst==1.8.4",
37
37
  "pathspec>=0.6,<1",
38
38
  "rich>=10",
39
39
  "rich-click>=1",
@@ -50,7 +50,7 @@ scripts.djcodemod = "django_codemod.cli:djcodemod"
50
50
  dev = [
51
51
  "parameterized>=0.9,<1",
52
52
  "pytest>=8,<9",
53
- "pytest-cov>=6,<7",
53
+ "pytest-cov>=7,<8",
54
54
  "pytest-mock>=3.3,<4",
55
55
  ]
56
56
  docs = [
@@ -96,6 +96,9 @@ lint.per-file-ignores."tests/**/*" = [
96
96
  ]
97
97
  lint.isort.known-first-party = [ "django_codemod", "tests" ]
98
98
 
99
+ [tool.pyproject-fmt]
100
+ max_supported_python = "3.14"
101
+
99
102
  [tool.pytest.ini_options]
100
103
  addopts = "-v -Wdefault --cov=django_codemod --cov-report=term-missing:skip-covered"
101
104
  pythonpath = [ "src" ]
@@ -0,0 +1 @@
1
+ __version__ = "2.4.0"
@@ -1,9 +1,8 @@
1
1
  import inspect
2
2
  from collections import defaultdict
3
- from collections.abc import Generator
3
+ from collections.abc import Callable, Generator
4
4
  from operator import attrgetter
5
5
  from pathlib import Path
6
- from typing import Callable, Optional
7
6
 
8
7
  import rich_click as click
9
8
  from libcst.codemod import CodemodContext, parallel_exec_transform_with_prettyprint
@@ -68,8 +67,8 @@ class VersionParamType(click.ParamType):
68
67
  def convert( # type: ignore[return]
69
68
  self,
70
69
  value: str,
71
- param: Optional[click.Parameter],
72
- ctx: Optional[click.Context],
70
+ param: click.Parameter | None,
71
+ ctx: click.Context | None,
73
72
  ) -> tuple[int, int]:
74
73
  """Parse version to keep only major an minor digits."""
75
74
  try:
@@ -84,8 +83,8 @@ class VersionParamType(click.ParamType):
84
83
  def _parse_unsafe(
85
84
  self,
86
85
  value: str,
87
- param: Optional[click.Parameter],
88
- ctx: Optional[click.Context],
86
+ param: click.Parameter | None,
87
+ ctx: click.Context | None,
89
88
  ) -> tuple[int, int]:
90
89
  """Parse version and validate it's a supported one."""
91
90
  parsed_version = self._split_digits(value, param, ctx)
@@ -104,8 +103,8 @@ class VersionParamType(click.ParamType):
104
103
  def _split_digits(
105
104
  self,
106
105
  value: str,
107
- param: Optional[click.Parameter],
108
- ctx: Optional[click.Context],
106
+ param: click.Parameter | None,
107
+ ctx: click.Context | None,
109
108
  ) -> tuple[int, int]:
110
109
  """Split version into 2-tuple of digits, ignoring patch digit."""
111
110
  values_parts = tuple(int(v) for v in value.split("."))
@@ -115,7 +114,7 @@ class VersionParamType(click.ParamType):
115
114
  param,
116
115
  ctx,
117
116
  )
118
- major, minor, *patches = values_parts
117
+ major, minor, *_patches = values_parts
119
118
  return major, minor
120
119
 
121
120
 
@@ -7,7 +7,6 @@ https://github.com/psf/black
7
7
 
8
8
  from collections.abc import Iterable, Iterator
9
9
  from pathlib import Path
10
- from typing import Optional
11
10
 
12
11
  from pathspec import PathSpec
13
12
 
@@ -113,7 +112,7 @@ def gen_python_files(
113
112
  yield child
114
113
 
115
114
 
116
- def normalize_path(path: Path, root: Path) -> Optional[str]:
115
+ def normalize_path(path: Path, root: Path) -> str | None:
117
116
  """Normalize `path`. May return `None` if `path` was ignored."""
118
117
  try:
119
118
  abspath = path if path.is_absolute() else Path.cwd() / path
@@ -1,11 +1,10 @@
1
1
  from collections.abc import Sequence
2
- from typing import Optional
3
2
 
4
3
  from libcst import Arg, Call, FunctionDef, Param, parse_expression, parse_statement
5
4
  from libcst import matchers as m
6
5
 
7
6
 
8
- def find_keyword_arg(args: Sequence[Arg], keyword_name: str) -> Optional[Arg]:
7
+ def find_keyword_arg(args: Sequence[Arg], keyword_name: str) -> Arg | None:
9
8
  """Find a kwarg among a sequence of arguments."""
10
9
  matcher = m.Arg(keyword=m.Name(keyword_name))
11
10
  return next((arg for arg in args if m.matches(arg, matcher)), None)
@@ -1,5 +1,3 @@
1
- from typing import Union
2
-
3
1
  from libcst import (
4
2
  BaseExpression,
5
3
  BaseSmallStatement,
@@ -35,9 +33,7 @@ class InlineHasAddPermissionsTransformer(BaseDjCodemodTransformer):
35
33
 
36
34
  def leave_ImportFrom(
37
35
  self, original_node: ImportFrom, updated_node: ImportFrom
38
- ) -> Union[
39
- BaseSmallStatement, FlattenSentinel[BaseSmallStatement], RemovalSentinel
40
- ]:
36
+ ) -> BaseSmallStatement | FlattenSentinel[BaseSmallStatement] | RemovalSentinel:
41
37
  if isinstance(updated_node.names, ImportStar):
42
38
  return super().leave_ImportFrom(original_node, updated_node)
43
39
  base_cls_matcher = []
@@ -98,7 +94,7 @@ class InlineHasAddPermissionsTransformer(BaseDjCodemodTransformer):
98
94
 
99
95
  def leave_ClassDef(
100
96
  self, original_node: ClassDef, updated_node: ClassDef
101
- ) -> Union[BaseStatement, FlattenSentinel[BaseStatement], RemovalSentinel]:
97
+ ) -> BaseStatement | FlattenSentinel[BaseStatement] | RemovalSentinel:
102
98
  self.context.scratch.pop(self.ctx_key_visiting_subclass, None)
103
99
  return super().leave_ClassDef(original_node, updated_node)
104
100
 
@@ -108,7 +104,7 @@ class InlineHasAddPermissionsTransformer(BaseDjCodemodTransformer):
108
104
 
109
105
  def leave_FunctionDef(
110
106
  self, original_node: FunctionDef, updated_node: FunctionDef
111
- ) -> Union[BaseStatement, FlattenSentinel[BaseStatement], RemovalSentinel]:
107
+ ) -> BaseStatement | FlattenSentinel[BaseStatement] | RemovalSentinel:
112
108
  if (
113
109
  self.is_visiting_subclass
114
110
  and m.matches(
@@ -2,7 +2,6 @@
2
2
 
3
3
  from abc import ABC
4
4
  from collections.abc import Sequence
5
- from typing import Optional, Union
6
5
 
7
6
  from libcst import (
8
7
  Arg,
@@ -53,7 +52,7 @@ class BaseDjCodemodTransformer(ContextAwareTransformer, ABC):
53
52
 
54
53
  def module_matcher(
55
54
  import_parts: Sequence[str],
56
- ) -> Union[m.Attribute, m.Name]:
55
+ ) -> m.Attribute | m.Name:
57
56
  """Build matcher for a module given sequence of import parts."""
58
57
  # If only one element, it is just a Name
59
58
  if len(import_parts) == 1:
@@ -112,7 +111,7 @@ class BaseRenameTransformer(BaseDjCodemodTransformer, ABC):
112
111
 
113
112
  def leave_ImportFrom(
114
113
  self, original_node: ImportFrom, updated_node: ImportFrom
115
- ) -> Union[BaseSmallStatement, RemovalSentinel]:
114
+ ) -> BaseSmallStatement | RemovalSentinel:
116
115
  """Update import statements for matching old module name."""
117
116
  return (
118
117
  self._check_import_from_exact(original_node, updated_node)
@@ -123,7 +122,7 @@ class BaseRenameTransformer(BaseDjCodemodTransformer, ABC):
123
122
 
124
123
  def _check_import_from_exact(
125
124
  self, original_node: ImportFrom, updated_node: ImportFrom
126
- ) -> Optional[Union[BaseSmallStatement, RemovalSentinel]]:
125
+ ) -> BaseSmallStatement | RemovalSentinel | None:
127
126
  """
128
127
  Check for when the thing to replace is imported exactly.
129
128
 
@@ -171,7 +170,7 @@ class BaseRenameTransformer(BaseDjCodemodTransformer, ABC):
171
170
 
172
171
  def _check_import_from_parent(
173
172
  self, original_node: ImportFrom, updated_node: ImportFrom
174
- ) -> Optional[Union[BaseSmallStatement, RemovalSentinel]]:
173
+ ) -> BaseSmallStatement | RemovalSentinel | None:
175
174
  """
176
175
  Check for when the parent module of thing to replace is imported.
177
176
 
@@ -200,7 +199,7 @@ class BaseRenameTransformer(BaseDjCodemodTransformer, ABC):
200
199
  value=m.Name(module_name_str),
201
200
  attr=m.Name(self.old_name),
202
201
  )
203
- new_as_name: Optional[str] = None
202
+ new_as_name: str | None = None
204
203
  if import_alias.evaluated_alias:
205
204
  # The import alias would be the same before and after
206
205
  # Add a `_` to differentiate the old alias from the new one.
@@ -214,7 +213,7 @@ class BaseRenameTransformer(BaseDjCodemodTransformer, ABC):
214
213
  if self.old_parent_module_parts != self.new_parent_module_parts:
215
214
  # import statement might need updating: build arguments for
216
215
  # AddImportsVisitor and RemoveImportsVisitor to update imports
217
- add_import_module: Optional[str] = None
216
+ add_import_module: str | None = None
218
217
  if self.new_parent_module_parts:
219
218
  add_import_module = ".".join(self.new_parent_module_parts)
220
219
  self.context.scratch[self.ctx_key_add_import_kwargs] = {
@@ -223,7 +222,7 @@ class BaseRenameTransformer(BaseDjCodemodTransformer, ABC):
223
222
  "obj": self.new_parent_name,
224
223
  "asname": new_as_name,
225
224
  }
226
- remove_import_module: Optional[str] = None
225
+ remove_import_module: str | None = None
227
226
  if self.old_parent_module_parts:
228
227
  remove_import_module = ".".join(self.old_parent_module_parts)
229
228
  self.context.scratch[self.ctx_key_remove_import_kwargs] = {
@@ -236,7 +235,7 @@ class BaseRenameTransformer(BaseDjCodemodTransformer, ABC):
236
235
 
237
236
  def _check_import_from_child(
238
237
  self, original_node: ImportFrom, updated_node: ImportFrom
239
- ) -> Optional[Union[BaseSmallStatement, RemovalSentinel]]:
238
+ ) -> BaseSmallStatement | RemovalSentinel | None:
240
239
  """
241
240
  Check import of a member of the module being codemodded.
242
241
 
@@ -275,7 +274,7 @@ class BaseRenameTransformer(BaseDjCodemodTransformer, ABC):
275
274
  self.context.scratch[self.ctx_key_import_scope] = scope
276
275
 
277
276
  @property
278
- def import_scope(self) -> Optional[Scope]:
277
+ def import_scope(self) -> Scope | None:
279
278
  return self.context.scratch.get(self.ctx_key_import_scope, None)
280
279
 
281
280
  def update_imports(self):
@@ -1,5 +1,4 @@
1
1
  from collections.abc import Sequence
2
- from typing import Optional, Union
3
2
 
4
3
  from libcst import (
5
4
  Arg,
@@ -47,9 +46,7 @@ class ModelsPermalinkTransformer(BaseDjCodemodTransformer):
47
46
 
48
47
  def leave_ImportFrom(
49
48
  self, original_node: ImportFrom, updated_node: ImportFrom
50
- ) -> Union[
51
- BaseSmallStatement, FlattenSentinel[BaseSmallStatement], RemovalSentinel
52
- ]:
49
+ ) -> BaseSmallStatement | FlattenSentinel[BaseSmallStatement] | RemovalSentinel:
53
50
  if isinstance(updated_node.names, ImportStar):
54
51
  return super().leave_ImportFrom(original_node, updated_node)
55
52
  if m.matches(
@@ -105,7 +102,7 @@ class ModelsPermalinkTransformer(BaseDjCodemodTransformer):
105
102
  return matchers_list[0]
106
103
  return m.OneOf(*iter(matchers_list))
107
104
 
108
- def visit_FunctionDef(self, node: FunctionDef) -> Optional[bool]:
105
+ def visit_FunctionDef(self, node: FunctionDef) -> bool | None:
109
106
  for decorator in node.decorators:
110
107
  if m.matches(decorator, self.decorator_matcher):
111
108
  self.context.scratch[self.ctx_key_inside_method] = True
@@ -113,7 +110,7 @@ class ModelsPermalinkTransformer(BaseDjCodemodTransformer):
113
110
 
114
111
  def leave_FunctionDef(
115
112
  self, original_node: FunctionDef, updated_node: FunctionDef
116
- ) -> Union[BaseStatement, FlattenSentinel[BaseStatement], RemovalSentinel]:
113
+ ) -> BaseStatement | FlattenSentinel[BaseStatement] | RemovalSentinel:
117
114
  if self.visiting_permalink_method:
118
115
  for decorator in updated_node.decorators:
119
116
  if m.matches(decorator, self.decorator_matcher):
@@ -136,9 +133,7 @@ class ModelsPermalinkTransformer(BaseDjCodemodTransformer):
136
133
 
137
134
  def leave_Return(
138
135
  self, original_node: Return, updated_node: Return
139
- ) -> Union[
140
- BaseSmallStatement, FlattenSentinel[BaseSmallStatement], RemovalSentinel
141
- ]:
136
+ ) -> BaseSmallStatement | FlattenSentinel[BaseSmallStatement] | RemovalSentinel:
142
137
  if self.visiting_permalink_method and m.matches(
143
138
  updated_node.value,
144
139
  m.Tuple(), # type: ignore
@@ -1,4 +1,4 @@
1
- from typing import ClassVar, Optional
1
+ from typing import ClassVar
2
2
 
3
3
  from libcst import BaseExpression, Call, ImportFrom, ImportStar, MaybeSentinel, Module
4
4
  from libcst import matchers as m
@@ -45,7 +45,7 @@ class SignalDisconnectWeakTransformer(BaseDjCodemodTransformer):
45
45
  self.context.scratch.pop(self.ctx_key_call_matchers, None)
46
46
  return super().leave_Module(original_node, updated_node)
47
47
 
48
- def visit_ImportFrom(self, node: ImportFrom) -> Optional[bool]:
48
+ def visit_ImportFrom(self, node: ImportFrom) -> bool | None:
49
49
  """Set the `Call` matcher depending on which signals are imported.."""
50
50
  if not import_from_matches(
51
51
  node, ["django", "db", "models", "signals"]
@@ -1,5 +1,4 @@
1
1
  from collections.abc import Generator, Sequence
2
- from typing import Optional, Union
3
2
 
4
3
  from libcst import (
5
4
  Assign,
@@ -29,11 +28,11 @@ class AssignmentTagTransformer(BaseDjCodemodTransformer):
29
28
  ctx_key_decorator_matcher = f"{ctx_key_prefix}-decorator_matcher"
30
29
 
31
30
  @property
32
- def library_call_matcher(self) -> Optional[m.Call]:
31
+ def library_call_matcher(self) -> m.Call | None:
33
32
  return self.context.scratch.get(self.ctx_key_library_call_matcher, None)
34
33
 
35
34
  @property
36
- def decorators_matcher(self) -> Optional[m.BaseMatcherNode]:
35
+ def decorators_matcher(self) -> m.BaseMatcherNode | None:
37
36
  return self.context.scratch.get(self.ctx_key_decorator_matcher, None)
38
37
 
39
38
  def leave_Module(self, original_node: Module, updated_node: Module) -> Module:
@@ -42,7 +41,7 @@ class AssignmentTagTransformer(BaseDjCodemodTransformer):
42
41
  self.context.scratch.pop(self.ctx_key_decorator_matcher, None)
43
42
  return super().leave_Module(original_node, updated_node)
44
43
 
45
- def visit_ImportFrom(self, node: ImportFrom) -> Optional[bool]:
44
+ def visit_ImportFrom(self, node: ImportFrom) -> bool | None:
46
45
  """Record whether an interesting import is detected."""
47
46
  import_matcher = (
48
47
  # django.template
@@ -54,7 +53,7 @@ class AssignmentTagTransformer(BaseDjCodemodTransformer):
54
53
  self.context.scratch[self.ctx_key_library_call_matcher] = import_matcher
55
54
  return None
56
55
 
57
- def _template_import_matcher(self, node: ImportFrom) -> Optional[m.Call]:
56
+ def _template_import_matcher(self, node: ImportFrom) -> m.Call | None:
58
57
  """Return matcher if django.template is imported."""
59
58
  imported_name_str = self._get_imported_name(node, "django.template")
60
59
  if not imported_name_str:
@@ -66,7 +65,7 @@ class AssignmentTagTransformer(BaseDjCodemodTransformer):
66
65
  )
67
66
  )
68
67
 
69
- def _library_import_matcher(self, node: ImportFrom) -> Optional[m.Call]:
68
+ def _library_import_matcher(self, node: ImportFrom) -> m.Call | None:
70
69
  """Return matcher if django.template.Library is imported."""
71
70
  imported_name_str = self._get_imported_name(node, "django.template.Library")
72
71
  if not imported_name_str:
@@ -75,7 +74,7 @@ class AssignmentTagTransformer(BaseDjCodemodTransformer):
75
74
  return m.Call(func=m.Name(imported_name_str))
76
75
 
77
76
  @staticmethod
78
- def _get_imported_name(node: ImportFrom, import_path: str) -> Optional[str]:
77
+ def _get_imported_name(node: ImportFrom, import_path: str) -> str | None:
79
78
  """Resolve the imported name if present."""
80
79
  if isinstance(node.names, ImportStar):
81
80
  return None
@@ -92,7 +91,7 @@ class AssignmentTagTransformer(BaseDjCodemodTransformer):
92
91
  return imported_name_str
93
92
  return None
94
93
 
95
- def visit_Assign(self, node: Assign) -> Optional[bool]:
94
+ def visit_Assign(self, node: Assign) -> bool | None:
96
95
  """Record variable name the `Library()` call is assigned to."""
97
96
  if self.library_call_matcher and m.matches(
98
97
  node,
@@ -127,7 +126,7 @@ class AssignmentTagTransformer(BaseDjCodemodTransformer):
127
126
 
128
127
  def leave_Decorator(
129
128
  self, original_node: Decorator, updated_node: Decorator
130
- ) -> Union[Decorator, FlattenSentinel[Decorator], RemovalSentinel]:
129
+ ) -> Decorator | FlattenSentinel[Decorator] | RemovalSentinel:
131
130
  """Update decorator call if all conditions are met."""
132
131
  if self.decorators_matcher and m.matches(updated_node, self.decorators_matcher):
133
132
  # If we have a decorator matcher, and it matches,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-codemod
3
- Version: 2.3.6
3
+ Version: 2.4.0
4
4
  Summary: A command line tool to automatically fix Django deprecations.
5
5
  Author-email: Bruno Alla <alla.brunoo@gmail.com>
6
6
  License-Expression: MIT
@@ -16,17 +16,17 @@ Classifier: Intended Audience :: Developers
16
16
  Classifier: Natural Language :: English
17
17
  Classifier: Operating System :: OS Independent
18
18
  Classifier: Programming Language :: Python :: 3 :: Only
19
- Classifier: Programming Language :: Python :: 3.9
20
19
  Classifier: Programming Language :: Python :: 3.10
21
20
  Classifier: Programming Language :: Python :: 3.11
22
21
  Classifier: Programming Language :: Python :: 3.12
23
22
  Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
24
  Classifier: Topic :: Software Development :: Libraries
25
- Requires-Python: >=3.9
25
+ Requires-Python: >=3.10
26
26
  Description-Content-Type: text/markdown
27
27
  License-File: LICENSE
28
- Requires-Dist: click<8.2
29
- Requires-Dist: libcst==1.8.2
28
+ Requires-Dist: click<8.3
29
+ Requires-Dist: libcst==1.8.4
30
30
  Requires-Dist: pathspec<1,>=0.6
31
31
  Requires-Dist: rich>=10
32
32
  Requires-Dist: rich-click>=1
@@ -1,5 +1,5 @@
1
- click<8.2
2
- libcst==1.8.2
1
+ click<8.3
2
+ libcst==1.8.4
3
3
  pathspec<1,>=0.6
4
4
  rich>=10
5
5
  rich-click>=1
@@ -94,8 +94,8 @@ def test_run_help(cli_runner):
94
94
  ["run", "--help"],
95
95
  )
96
96
 
97
- assert result.exit_code == 0
98
- assert "djcodemod list" in result.output
97
+ assert result.exit_code == 1
98
+ assert "Automatically fixes deprecations" in result.output
99
99
 
100
100
 
101
101
  @pytest.mark.parametrize(
@@ -1 +0,0 @@
1
- __version__ = "2.3.6"
File without changes
File without changes
File without changes