django-codemod 2.5.0__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 (75) hide show
  1. {django_codemod-2.5.0/src/django_codemod.egg-info → django_codemod-2.6.0}/PKG-INFO +2 -2
  2. {django_codemod-2.5.0 → django_codemod-2.6.0}/pyproject.toml +3 -3
  3. django_codemod-2.6.0/src/django_codemod/__init__.py +1 -0
  4. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/path_utils.py +3 -3
  5. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/__init__.py +2 -0
  6. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/base.py +4 -1
  7. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/models.py +51 -0
  8. {django_codemod-2.5.0 → django_codemod-2.6.0/src/django_codemod.egg-info}/PKG-INFO +2 -2
  9. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod.egg-info/SOURCES.txt +0 -1
  10. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod.egg-info/requires.txt +1 -1
  11. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/base.py +4 -2
  12. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_models.py +94 -0
  13. django_codemod-2.5.0/src/django_codemod/__init__.py +0 -1
  14. django_codemod-2.5.0/tests/test_e2e.sh +0 -39
  15. {django_codemod-2.5.0 → django_codemod-2.6.0}/LICENSE +0 -0
  16. {django_codemod-2.5.0 → django_codemod-2.6.0}/MANIFEST.in +0 -0
  17. {django_codemod-2.5.0 → django_codemod-2.6.0}/README.md +0 -0
  18. {django_codemod-2.5.0 → django_codemod-2.6.0}/docs/Makefile +0 -0
  19. {django_codemod-2.5.0 → django_codemod-2.6.0}/docs/conf.py +0 -0
  20. {django_codemod-2.5.0 → django_codemod-2.6.0}/docs/make.bat +0 -0
  21. {django_codemod-2.5.0 → django_codemod-2.6.0}/setup.cfg +0 -0
  22. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/cli.py +0 -0
  23. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/commands.py +0 -0
  24. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/constants.py +0 -0
  25. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/feature_flags.py +0 -0
  26. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/utils/__init__.py +0 -0
  27. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/utils/calls.py +0 -0
  28. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/admin.py +0 -0
  29. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/core.py +0 -0
  30. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/crypto.py +0 -0
  31. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/decorators.py +0 -0
  32. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/encoding.py +0 -0
  33. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/exceptions.py +0 -0
  34. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/forms.py +0 -0
  35. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/html.py +0 -0
  36. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/http.py +0 -0
  37. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/lru_cache.py +0 -0
  38. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/os_utils.py +0 -0
  39. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/paginator.py +0 -0
  40. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/postgres_fields.py +0 -0
  41. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/shortcuts.py +0 -0
  42. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/signals.py +0 -0
  43. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/template_context.py +0 -0
  44. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/template_tags.py +0 -0
  45. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/timezone.py +0 -0
  46. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/translations.py +0 -0
  47. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod/visitors/urls.py +0 -0
  48. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod.egg-info/dependency_links.txt +0 -0
  49. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod.egg-info/entry_points.txt +0 -0
  50. {django_codemod-2.5.0 → django_codemod-2.6.0}/src/django_codemod.egg-info/top_level.txt +0 -0
  51. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/__init__.py +0 -0
  52. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/test_cli.py +0 -0
  53. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/test_path_utils.py +0 -0
  54. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/__init__.py +0 -0
  55. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_admin.py +0 -0
  56. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_base.py +0 -0
  57. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_core.py +0 -0
  58. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_crypto.py +0 -0
  59. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_decorators.py +0 -0
  60. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_encoding.py +0 -0
  61. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_exceptions.py +0 -0
  62. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_forms.py +0 -0
  63. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_html.py +0 -0
  64. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_http.py +0 -0
  65. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_lru_cache.py +0 -0
  66. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_os_utils.py +0 -0
  67. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_paginator.py +0 -0
  68. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_postgres_fields.py +0 -0
  69. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_shortcuts.py +0 -0
  70. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_signals.py +0 -0
  71. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_template_context.py +0 -0
  72. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_template_tags.py +0 -0
  73. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_timezone.py +0 -0
  74. {django_codemod-2.5.0 → django_codemod-2.6.0}/tests/visitors/test_translations.py +0 -0
  75. {django_codemod-2.5.0 → django_codemod-2.6.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.5.0
3
+ Version: 2.6.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
@@ -26,7 +26,7 @@ Classifier: Topic :: Software Development :: Libraries
26
26
  Requires-Python: >=3.10
27
27
  Description-Content-Type: text/markdown
28
28
  License-File: LICENSE
29
- Requires-Dist: click<8.5
29
+ Requires-Dist: click<8.6
30
30
  Requires-Dist: libcst==1.9
31
31
  Requires-Dist: pathspec<2,>=0.6
32
32
  Requires-Dist: rich>=10
@@ -4,7 +4,7 @@ requires = [ "setuptools>=77.0.3" ]
4
4
 
5
5
  [project]
6
6
  name = "django-codemod"
7
- version = "2.5.0"
7
+ version = "2.6.0"
8
8
  description = "A command line tool to automatically fix Django deprecations."
9
9
  readme = "README.md"
10
10
  keywords = [
@@ -32,7 +32,7 @@ classifiers = [
32
32
  "Topic :: Software Development :: Libraries",
33
33
  ]
34
34
  dependencies = [
35
- "click<8.5",
35
+ "click<8.6",
36
36
  "libcst==1.9",
37
37
  "pathspec>=0.6,<2",
38
38
  "rich>=10",
@@ -52,7 +52,7 @@ dev = [
52
52
  "pytest>=9,<10",
53
53
  "pytest-cov>=7,<8",
54
54
  "pytest-mock>=3.3,<4",
55
- "ty==0.0.69",
55
+ "ty==0.0.99",
56
56
  ]
57
57
  docs = [
58
58
  "furo>=2023.5.20; python_version>='3.11'",
@@ -0,0 +1 @@
1
+ __version__ = "2.6.0"
@@ -5,13 +5,13 @@ Inspired or taken from black:
5
5
  https://github.com/psf/black
6
6
  """
7
7
 
8
- from collections.abc import Iterable, Iterator
8
+ from collections.abc import Collection, Iterable, Iterator
9
9
  from pathlib import Path
10
10
 
11
11
  from pathspec import PathSpec
12
12
 
13
13
 
14
- def get_sources(src: Iterable[str]) -> list[Path]:
14
+ def get_sources(src: Collection[str]) -> list[Path]:
15
15
  """
16
16
  Return a list of sources to codemod.
17
17
 
@@ -33,7 +33,7 @@ def get_sources(src: Iterable[str]) -> list[Path]:
33
33
  return sorted(sources)
34
34
 
35
35
 
36
- def find_project_root(sources: Iterable[str]) -> Path:
36
+ def find_project_root(sources: Collection[str]) -> Path:
37
37
  """
38
38
  Return a directory containing .git, .hg, or pyproject.toml.
39
39
 
@@ -29,6 +29,7 @@ from .models import (
29
29
  ModelsPermalinkTransformer,
30
30
  NullBooleanFieldTransformer,
31
31
  OnDeleteTransformer,
32
+ QuerySetDateFieldTransformer,
32
33
  )
33
34
  from .os_utils import AbsPathTransformer
34
35
  from .paginator import QuerySetPaginatorTransformer
@@ -88,6 +89,7 @@ __all__ = (
88
89
  "OnDeleteTransformer",
89
90
  "PrettyNameTransformer",
90
91
  "QueryEmptyResultSetTransformer",
92
+ "QuerySetDateFieldTransformer",
91
93
  "QuerySetPaginatorTransformer",
92
94
  "RenderToResponseTransformer",
93
95
  "RequestContextTransformer",
@@ -267,7 +267,10 @@ class BaseRenameTransformer(BaseDjCodemodTransformer, ABC):
267
267
 
268
268
  def resolve_scope(self, node: CSTNode) -> Scope:
269
269
  scopes_map = self.context.wrapper.resolve(ScopeProvider) # type: ignore
270
- return scopes_map[node]
270
+ scope = scopes_map[node]
271
+ if scope is None:
272
+ raise KeyError(f"Scope not found for node {node}")
273
+ return scope
271
274
 
272
275
  def save_import_scope(self, import_from: ImportFrom) -> None:
273
276
  scope = self.resolve_scope(import_from)
@@ -2,10 +2,12 @@ from collections.abc import Sequence
2
2
 
3
3
  from libcst import (
4
4
  Arg,
5
+ AssignEqual,
5
6
  BaseExpression,
6
7
  BaseSmallStatement,
7
8
  BaseStatement,
8
9
  Call,
10
+ ConcatenatedString,
9
11
  FlattenSentinel,
10
12
  FunctionDef,
11
13
  ImportFrom,
@@ -15,6 +17,8 @@ from libcst import (
15
17
  RemovalSentinel,
16
18
  RemoveFromParent,
17
19
  Return,
20
+ SimpleString,
21
+ SimpleWhitespace,
18
22
  )
19
23
  from libcst import matchers as m
20
24
  from libcst.codemod.visitors import AddImportsVisitor
@@ -24,6 +28,7 @@ from django_codemod.constants import (
24
28
  DJANGO_1_11,
25
29
  DJANGO_2_0,
26
30
  DJANGO_2_1,
31
+ DJANGO_3_0,
27
32
  DJANGO_3_1,
28
33
  DJANGO_4_0,
29
34
  )
@@ -214,3 +219,49 @@ class NullBooleanFieldTransformer(BaseFuncRenameTransformer):
214
219
  *node.args,
215
220
  parse_arg("null=True"),
216
221
  )
222
+
223
+
224
+ class QuerySetDateFieldTransformer(BaseDjCodemodTransformer):
225
+ """Pass literal field names positionally to `earliest()` and `latest()`."""
226
+
227
+ deprecated_in = DJANGO_2_0
228
+ removed_in = DJANGO_3_0
229
+
230
+ def leave_Call(self, original_node: Call, updated_node: Call) -> BaseExpression:
231
+ if not m.matches(
232
+ updated_node.func,
233
+ m.Attribute(attr=m.Name("earliest") | m.Name("latest")),
234
+ ):
235
+ return updated_node
236
+ arg = find_keyword_arg(updated_node.args, "field_name")
237
+ if arg is None:
238
+ return updated_node
239
+ if not isinstance(
240
+ arg.value, (SimpleString, ConcatenatedString)
241
+ ) or not isinstance(arg.value.evaluated_value, str):
242
+ self.warn(
243
+ "Cannot safely rewrite non-literal field_name in earliest()/latest(); "
244
+ "review this call manually."
245
+ )
246
+ return updated_node
247
+ if len(updated_node.args) != 1:
248
+ return updated_node
249
+ if isinstance(arg.equal, AssignEqual) and (
250
+ any(
251
+ not isinstance(whitespace, SimpleWhitespace)
252
+ or "\n" in whitespace.value
253
+ or "\r" in whitespace.value
254
+ for whitespace in (
255
+ arg.equal.whitespace_before,
256
+ arg.equal.whitespace_after,
257
+ )
258
+ )
259
+ ):
260
+ self.warn(
261
+ "Cannot safely rewrite field_name with comments or line breaks "
262
+ "around '=' in earliest()/latest(); review this call manually."
263
+ )
264
+ return updated_node
265
+ return updated_node.with_changes(
266
+ args=[arg.with_changes(keyword=None, equal=MaybeSentinel.DEFAULT)]
267
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-codemod
3
- Version: 2.5.0
3
+ Version: 2.6.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
@@ -26,7 +26,7 @@ Classifier: Topic :: Software Development :: Libraries
26
26
  Requires-Python: >=3.10
27
27
  Description-Content-Type: text/markdown
28
28
  License-File: LICENSE
29
- Requires-Dist: click<8.5
29
+ Requires-Dist: click<8.6
30
30
  Requires-Dist: libcst==1.9
31
31
  Requires-Dist: pathspec<2,>=0.6
32
32
  Requires-Dist: rich>=10
@@ -44,7 +44,6 @@ src/django_codemod/visitors/translations.py
44
44
  src/django_codemod/visitors/urls.py
45
45
  tests/__init__.py
46
46
  tests/test_cli.py
47
- tests/test_e2e.sh
48
47
  tests/test_path_utils.py
49
48
  tests/visitors/__init__.py
50
49
  tests/visitors/base.py
@@ -1,4 +1,4 @@
1
- click<8.5
1
+ click<8.6
2
2
  libcst==1.9
3
3
  pathspec<2,>=0.6
4
4
  rich>=10
@@ -1,4 +1,4 @@
1
- from libcst.codemod import CodemodTest
1
+ from libcst.codemod import CodemodContext, CodemodTest
2
2
 
3
3
  from django_codemod.commands import BaseCodemodCommand
4
4
  from django_codemod.visitors.base import BaseDjCodemodTransformer
@@ -9,6 +9,8 @@ class BaseVisitorTest(CodemodTest):
9
9
 
10
10
  transformer: type[BaseDjCodemodTransformer]
11
11
 
12
- def TRANSFORM(self, context):
12
+ def TRANSFORM( # ty: ignore[invalid-attribute-override]
13
+ self, context: CodemodContext
14
+ ) -> BaseCodemodCommand:
13
15
  """Create a command for the transformer under test."""
14
16
  return BaseCodemodCommand([self.transformer], context)
@@ -5,6 +5,7 @@ from django_codemod.visitors.models import (
5
5
  ModelsPermalinkTransformer,
6
6
  NullBooleanFieldTransformer,
7
7
  OnDeleteTransformer,
8
+ QuerySetDateFieldTransformer,
8
9
  )
9
10
  from tests.visitors.base import BaseVisitorTest
10
11
 
@@ -382,3 +383,96 @@ class TestNullBooleanFieldTransformer(BaseVisitorTest):
382
383
  is_active = models.BooleanField(null=True)
383
384
  """
384
385
  self.assertCodemod(before, after)
386
+
387
+
388
+ class TestQuerySetDateFieldTransformer(BaseVisitorTest):
389
+ transformer = QuerySetDateFieldTransformer
390
+
391
+ @parameterized.expand([("earliest",), ("latest",)])
392
+ def test_literal_field(self, method):
393
+ self.assertCodemod(
394
+ f"result = Article.objects.{method}(field_name='pub_date')",
395
+ f"result = Article.objects.{method}('pub_date')",
396
+ )
397
+
398
+ def test_chained_multiline_call(self):
399
+ self.assertCodemod(
400
+ """
401
+ result = Article.objects.filter(published=True).latest(
402
+ field_name = "pub_date", # Keep the field comment.
403
+ )
404
+ """,
405
+ """
406
+ result = Article.objects.filter(published=True).latest(
407
+ "pub_date", # Keep the field comment.
408
+ )
409
+ """,
410
+ )
411
+
412
+ @parameterized.expand(
413
+ [
414
+ ("qs.latest()",),
415
+ ("qs.earliest('pub_date')",),
416
+ ("qs.in_bulk(field_name='slug')",),
417
+ ("latest(field_name='pub_date')",),
418
+ ("qs.latest(**kwargs)",),
419
+ ("qs.latest('pub_date', field_name='created')",),
420
+ ]
421
+ )
422
+ def test_other_calls_unchanged(self, source):
423
+ self.assertCodemod(source, source)
424
+
425
+ @parameterized.expand(
426
+ [
427
+ (extra_args, value)
428
+ for extra_args in ("", "'other', ", "**kwargs, ")
429
+ for value in (
430
+ "None",
431
+ "field",
432
+ "get_field()",
433
+ "b'pub_date'",
434
+ "f'{field}'",
435
+ "'pub_' f'{field}'",
436
+ "b'pub_' b'date'",
437
+ )
438
+ ]
439
+ )
440
+ def test_non_literal_requires_review(self, extra_args, value):
441
+ source = f"qs.latest({extra_args}field_name={value})"
442
+ self.assertCodemod(
443
+ source,
444
+ source,
445
+ expected_warnings=[
446
+ "Cannot safely rewrite non-literal field_name in earliest()/latest(); "
447
+ "review this call manually."
448
+ ],
449
+ )
450
+
451
+ @parameterized.expand([("earliest",), ("latest",)])
452
+ def test_adjacent_literal_strings(self, method):
453
+ self.assertCodemod(
454
+ f"qs.{method}(field_name=('pub_' 'date'))",
455
+ f"qs.{method}(('pub_' 'date'))",
456
+ )
457
+
458
+ @parameterized.expand(
459
+ [
460
+ (" # Before the equals.\n =",),
461
+ ("= # After the equals.\n ",),
462
+ (" # Before.\n = # After.\n ",),
463
+ ("=\n # On its own line.\n ",),
464
+ ("=\n ",),
465
+ ("= \\\n ",),
466
+ (" \\\n =",),
467
+ ]
468
+ )
469
+ def test_multiline_keyword_requires_review(self, equal):
470
+ source = f"qs.latest(field_name{equal}'pub_date')"
471
+ self.assertCodemod(
472
+ source,
473
+ source,
474
+ expected_warnings=[
475
+ "Cannot safely rewrite field_name with comments or line breaks "
476
+ "around '=' in earliest()/latest(); review this call manually."
477
+ ],
478
+ )
@@ -1 +0,0 @@
1
- __version__ = "2.5.0"
@@ -1,39 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- set -xu
4
-
5
- mkdir -p e2e
6
- cd e2e
7
- django-admin startproject djcme2e
8
- cd djcme2e
9
- django-admin startapp app1
10
-
11
- echo "from django.db import models
12
- from django.utils.encoding import force_text
13
- from django.utils.translation import ugettext_lazy as _
14
-
15
-
16
- class AppModel(models.Model):
17
- name = models.CharField(_('Name'))
18
- slug = models.SlugField(_('Name'))
19
-
20
- def __str__(self):
21
- return force_text(self.name)
22
- " > app1/models.py
23
-
24
- cp -r ../djcme2e ../djcme2e_orig
25
-
26
- djcodemod run --deprecated-in 3.0 .
27
-
28
- cd ..
29
-
30
- OUT=$(diff -qr djcme2e_orig/ djcme2e/ | grep -v 'models.py differ')
31
-
32
- if [ "${OUT}" != "" ]
33
- then
34
- echo "ERROR: unexpected differences found:"
35
- diff -r djcme2e_orig/ djcme2e/
36
- exit 1
37
- else
38
- exit 0
39
- fi
File without changes
File without changes
File without changes