django-orm-lens 1.2.3__tar.gz → 1.2.4__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 (39) hide show
  1. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/PKG-INFO +1 -1
  2. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/django_orm_lens/parser.py +11 -2
  3. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/django_orm_lens.egg-info/PKG-INFO +1 -1
  4. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/django_orm_lens.egg-info/SOURCES.txt +1 -0
  5. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/pyproject.toml +1 -1
  6. django_orm_lens-1.2.4/tests/test_pep695_generic_classes.py +71 -0
  7. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/README.md +0 -0
  8. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/django_orm_lens/__init__.py +0 -0
  9. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/django_orm_lens/cli.py +0 -0
  10. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/django_orm_lens/diff.py +0 -0
  11. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/django_orm_lens/formatters.py +0 -0
  12. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/django_orm_lens/mcp_server.py +0 -0
  13. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/django_orm_lens/migrations_parser.py +0 -0
  14. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/django_orm_lens/models.py +0 -0
  15. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/django_orm_lens/py.typed +0 -0
  16. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/django_orm_lens/query_analyzer.py +0 -0
  17. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/django_orm_lens/signals_parser.py +0 -0
  18. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/django_orm_lens.egg-info/dependency_links.txt +0 -0
  19. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/django_orm_lens.egg-info/entry_points.txt +0 -0
  20. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/django_orm_lens.egg-info/requires.txt +0 -0
  21. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/django_orm_lens.egg-info/top_level.txt +0 -0
  22. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/setup.cfg +0 -0
  23. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/tests/test_abstract_and_auth_user.py +0 -0
  24. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/tests/test_cli_hint.py +0 -0
  25. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/tests/test_cli_subcommands_smoke.py +0 -0
  26. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/tests/test_cli_verbose.py +0 -0
  27. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/tests/test_cross_module_bases.py +0 -0
  28. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/tests/test_diff_command.py +0 -0
  29. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/tests/test_golden_fixtures.py +0 -0
  30. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/tests/test_kwarg_order_and_auth_user.py +0 -0
  31. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/tests/test_list_command.py +0 -0
  32. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/tests/test_migration_risk.py +0 -0
  33. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/tests/test_migrations_parser.py +0 -0
  34. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/tests/test_nplusone_detector.py +0 -0
  35. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/tests/test_parity.py +0 -0
  36. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/tests/test_pep526_type_hints.py +0 -0
  37. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/tests/test_query_analyzer.py +0 -0
  38. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/tests/test_scan_root.py +0 -0
  39. {django_orm_lens-1.2.3 → django_orm_lens-1.2.4}/tests/test_signals_parser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-orm-lens
3
- Version: 1.2.3
3
+ Version: 1.2.4
4
4
  Summary: Static analysis + MCP server for Django models. Sidebar tree, ER diagrams, and JSON output for terminals and AI coding agents.
5
5
  Author: frowningdev
6
6
  License: MIT
@@ -37,8 +37,17 @@ from .models import (
37
37
 
38
38
  RELATION_TYPES: Tuple[str, ...] = ("ForeignKey", "ManyToManyField", "OneToOneField")
39
39
 
40
- CLASS_RE = re.compile(r"^class\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(([^)]*)\)\s*:")
41
- CLASS_START_RE = re.compile(r"^class\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(")
40
+ # ``(?:\s*\[[^\]]*\])?`` — optional PEP-695 generic type parameter list
41
+ # introduced in Python 3.12, e.g. ``class Container[T](models.Model):``.
42
+ # Only handles a single-line, non-nested bracket group — nested brackets in
43
+ # class-header generics are vanishingly rare and don't warrant a full
44
+ # bracket matcher.
45
+ CLASS_RE = re.compile(
46
+ r"^class\s+([A-Za-z_][A-Za-z0-9_]*)(?:\s*\[[^\]]*\])?\s*\(([^)]*)\)\s*:"
47
+ )
48
+ CLASS_START_RE = re.compile(
49
+ r"^class\s+([A-Za-z_][A-Za-z0-9_]*)(?:\s*\[[^\]]*\])?\s*\("
50
+ )
42
51
 
43
52
  NON_MODEL_TAIL = re.compile(
44
53
  r"^(ModelAdmin|ModelForm|ModelSerializer|ModelChoiceField|"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-orm-lens
3
- Version: 1.2.3
3
+ Version: 1.2.4
4
4
  Summary: Static analysis + MCP server for Django models. Sidebar tree, ER diagrams, and JSON output for terminals and AI coding agents.
5
5
  Author: frowningdev
6
6
  License: MIT
@@ -31,6 +31,7 @@ tests/test_migrations_parser.py
31
31
  tests/test_nplusone_detector.py
32
32
  tests/test_parity.py
33
33
  tests/test_pep526_type_hints.py
34
+ tests/test_pep695_generic_classes.py
34
35
  tests/test_query_analyzer.py
35
36
  tests/test_scan_root.py
36
37
  tests/test_signals_parser.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "django-orm-lens"
7
- version = "1.2.3"
7
+ version = "1.2.4"
8
8
  description = "Static analysis + MCP server for Django models. Sidebar tree, ER diagrams, and JSON output for terminals and AI coding agents."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -0,0 +1,71 @@
1
+ """Regression: PEP-695 generic classes (Python 3.12+) must parse.
2
+
3
+ Before this fix, ``class Container[T](models.Model):`` failed the
4
+ ``CLASS_RE`` pattern entirely — the parser walked past it and reported
5
+ zero models for the whole file. Users on Python 3.12+ writing modern
6
+ typed Django models saw an empty sidebar/ER diagram with no error.
7
+
8
+ The fix adds an optional ``(?:\\s*\\[[^\\]]*\\])?`` group between the
9
+ class name and the opening ``(`` in both ``CLASS_RE`` and
10
+ ``CLASS_START_RE``. Covers single/multi-param, bounded, variadic, and
11
+ paramspec generics — the four common PEP-695 shapes.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import unittest
17
+
18
+ from django_orm_lens.parser import parse_models_file
19
+
20
+
21
+ class Pep695GenericClassTest(unittest.TestCase):
22
+ def test_single_param_generic(self) -> None:
23
+ src = (
24
+ "class Container[T](models.Model):\n"
25
+ " label = models.CharField(max_length=50)\n"
26
+ )
27
+ models = parse_models_file("t.py", src)
28
+ self.assertEqual(len(models), 1)
29
+ self.assertEqual(models[0].name, "Container")
30
+ self.assertEqual([f.name for f in models[0].fields], ["label"])
31
+
32
+ def test_multi_param_generic(self) -> None:
33
+ src = (
34
+ "class Pair[K, V](models.Model):\n"
35
+ " key = models.CharField(max_length=10)\n"
36
+ " val = models.CharField(max_length=10)\n"
37
+ )
38
+ models = parse_models_file("t.py", src)
39
+ self.assertEqual(len(models), 1)
40
+ self.assertEqual([f.name for f in models[0].fields], ["key", "val"])
41
+
42
+ def test_bounded_generic(self) -> None:
43
+ src = (
44
+ "class Bounded[T: str](models.Model):\n"
45
+ " x = models.CharField(max_length=5)\n"
46
+ )
47
+ models = parse_models_file("t.py", src)
48
+ self.assertEqual(len(models), 1)
49
+
50
+ def test_variadic_and_paramspec(self) -> None:
51
+ src = (
52
+ "class Variadic[*Ts, **P](models.Model):\n"
53
+ " x = models.CharField(max_length=5)\n"
54
+ )
55
+ models = parse_models_file("t.py", src)
56
+ self.assertEqual(len(models), 1)
57
+
58
+ def test_non_generic_still_works(self) -> None:
59
+ # Backward-compat guard — the ``[T]`` group is optional, plain
60
+ # class headers must still parse identically.
61
+ src = (
62
+ "class Plain(models.Model):\n"
63
+ " x = models.CharField(max_length=5)\n"
64
+ )
65
+ models = parse_models_file("t.py", src)
66
+ self.assertEqual(len(models), 1)
67
+ self.assertEqual(models[0].name, "Plain")
68
+
69
+
70
+ if __name__ == "__main__":
71
+ unittest.main()