django-orm-lens 1.2.5__tar.gz → 1.2.7__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 (42) hide show
  1. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/PKG-INFO +1 -1
  2. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/django_orm_lens/parser.py +11 -1
  3. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/django_orm_lens.egg-info/PKG-INFO +1 -1
  4. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/django_orm_lens.egg-info/SOURCES.txt +3 -1
  5. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/pyproject.toml +1 -1
  6. django_orm_lens-1.2.7/tests/test_bom_prefix.py +40 -0
  7. django_orm_lens-1.2.7/tests/test_tab_indented_models.py +54 -0
  8. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/README.md +0 -0
  9. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/django_orm_lens/__init__.py +0 -0
  10. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/django_orm_lens/cli.py +0 -0
  11. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/django_orm_lens/diff.py +0 -0
  12. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/django_orm_lens/formatters.py +0 -0
  13. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/django_orm_lens/mcp_server.py +0 -0
  14. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/django_orm_lens/migrations_parser.py +0 -0
  15. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/django_orm_lens/models.py +0 -0
  16. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/django_orm_lens/py.typed +0 -0
  17. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/django_orm_lens/query_analyzer.py +0 -0
  18. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/django_orm_lens/signals_parser.py +0 -0
  19. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/django_orm_lens.egg-info/dependency_links.txt +0 -0
  20. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/django_orm_lens.egg-info/entry_points.txt +0 -0
  21. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/django_orm_lens.egg-info/requires.txt +0 -0
  22. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/django_orm_lens.egg-info/top_level.txt +0 -0
  23. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/setup.cfg +0 -0
  24. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/tests/test_abstract_and_auth_user.py +0 -0
  25. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/tests/test_aliased_module_and_third_party_fields.py +0 -0
  26. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/tests/test_cli_hint.py +0 -0
  27. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/tests/test_cli_subcommands_smoke.py +0 -0
  28. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/tests/test_cli_verbose.py +0 -0
  29. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/tests/test_cross_module_bases.py +0 -0
  30. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/tests/test_diff_command.py +0 -0
  31. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/tests/test_golden_fixtures.py +0 -0
  32. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/tests/test_kwarg_order_and_auth_user.py +0 -0
  33. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/tests/test_list_command.py +0 -0
  34. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/tests/test_migration_risk.py +0 -0
  35. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/tests/test_migrations_parser.py +0 -0
  36. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/tests/test_nplusone_detector.py +0 -0
  37. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/tests/test_parity.py +0 -0
  38. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/tests/test_pep526_type_hints.py +0 -0
  39. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/tests/test_pep695_generic_classes.py +0 -0
  40. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/tests/test_query_analyzer.py +0 -0
  41. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/tests/test_scan_root.py +0 -0
  42. {django_orm_lens-1.2.5 → django_orm_lens-1.2.7}/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.5
3
+ Version: 1.2.7
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
@@ -398,7 +398,17 @@ def _collect_defs(file_path: str, content: str) -> List[ParsedModel]:
398
398
  result with definitions from other files before calling
399
399
  ``_resolve_and_filter``.
400
400
  """
401
- lines = content.splitlines()
401
+ # Strip leading BOM (U+FEFF) — Windows editors (Notepad, VS Code with
402
+ # certain settings) save UTF-8 files with a byte-order mark. Without this
403
+ # the first line becomes "class Foo..." and CLASS_RE fails to match.
404
+ if content.startswith(""):
405
+ content = content[1:]
406
+ # Expand tab characters to 4 spaces so the ``\s{indent}`` prefix used in
407
+ # FIELD_RE / BARE_FIELD_RE / META_ITEM_RE matches tab-indented code too.
408
+ # A single ``\t`` character otherwise contributes 1 to ``\s`` even though
409
+ # it's visually 4 columns — tab-indented models used to have zero fields
410
+ # detected. Line numbers are preserved (per-line expansion only).
411
+ lines = [line.expandtabs(4) for line in content.splitlines()]
402
412
  all_defs: List[ParsedModel] = []
403
413
  parent = os.path.basename(os.path.dirname(file_path))
404
414
  if parent == "models":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-orm-lens
3
- Version: 1.2.5
3
+ Version: 1.2.7
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
@@ -19,6 +19,7 @@ django_orm_lens.egg-info/requires.txt
19
19
  django_orm_lens.egg-info/top_level.txt
20
20
  tests/test_abstract_and_auth_user.py
21
21
  tests/test_aliased_module_and_third_party_fields.py
22
+ tests/test_bom_prefix.py
22
23
  tests/test_cli_hint.py
23
24
  tests/test_cli_subcommands_smoke.py
24
25
  tests/test_cli_verbose.py
@@ -35,4 +36,5 @@ tests/test_pep526_type_hints.py
35
36
  tests/test_pep695_generic_classes.py
36
37
  tests/test_query_analyzer.py
37
38
  tests/test_scan_root.py
38
- tests/test_signals_parser.py
39
+ tests/test_signals_parser.py
40
+ tests/test_tab_indented_models.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.5"
7
+ version = "1.2.7"
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,40 @@
1
+ """Regression: leading BOM (U+FEFF) must be stripped before parsing.
2
+
3
+ Windows editors - Notepad, older Sublime, VS Code with certain UTF-8
4
+ encoding settings - save files with a byte-order mark. Without this
5
+ fix the first line becomes "class Foo..." and CLASS_RE fails
6
+ to match. Result: zero models parsed for the whole file, silent.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import unittest
12
+
13
+ from django_orm_lens.parser import parse_models_file
14
+
15
+ BOM = ""
16
+
17
+
18
+ class BomPrefixTest(unittest.TestCase):
19
+ def test_bom_prefixed_file_parses(self) -> None:
20
+ src = (
21
+ BOM
22
+ + "class Article(models.Model):\n"
23
+ " title = models.CharField(max_length=100)\n"
24
+ " body = models.TextField()\n"
25
+ )
26
+ models = parse_models_file("blog/models.py", src)
27
+ self.assertEqual(len(models), 1)
28
+ self.assertEqual(models[0].name, "Article")
29
+ self.assertEqual(
30
+ [f.name for f in models[0].fields], ["title", "body"]
31
+ )
32
+
33
+ def test_no_bom_still_works(self) -> None:
34
+ src = "class A(models.Model):\n x = models.CharField(max_length=5)\n"
35
+ models = parse_models_file("t/models.py", src)
36
+ self.assertEqual([f.name for f in models[0].fields], ["x"])
37
+
38
+
39
+ if __name__ == "__main__":
40
+ unittest.main()
@@ -0,0 +1,54 @@
1
+ """Regression: tab-indented model bodies must parse.
2
+
3
+ Editors that default to tabs (or projects that use PEP-8 exceptions with
4
+ tabs) produced empty models - every field silently dropped. Root cause:
5
+ ``FIELD_RE`` uses ``\\s{indent}`` as its column prefix and
6
+ ``_detect_class_indent`` expands tabs to width 4, but a single ``\\t``
7
+ character is only one match for ``\\s``, not four. Fixed by pre-expanding
8
+ tabs to spaces in the line buffer before regex matching.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import unittest
14
+
15
+ from django_orm_lens.parser import parse_models_file
16
+
17
+
18
+ class TabIndentationTest(unittest.TestCase):
19
+ def test_pure_tab_indented_body(self) -> None:
20
+ src = (
21
+ "class Post(models.Model):\n"
22
+ "\ttitle = models.CharField(max_length=100)\n"
23
+ "\tauthor = models.CharField(max_length=100)\n"
24
+ "\tbody = models.TextField()\n"
25
+ )
26
+ models = parse_models_file("blog/models.py", src)
27
+ self.assertEqual(len(models), 1)
28
+ self.assertEqual(
29
+ [f.name for f in models[0].fields],
30
+ ["title", "author", "body"],
31
+ )
32
+
33
+ def test_mixed_tab_then_space(self) -> None:
34
+ # Editors that mid-migrate to tabs sometimes end up with a tab
35
+ # followed by a space; that used to fail equally silently.
36
+ src = (
37
+ "class M(models.Model):\n"
38
+ "\t x = models.CharField(max_length=5)\n"
39
+ )
40
+ models = parse_models_file("t/models.py", src)
41
+ self.assertEqual([f.name for f in models[0].fields], ["x"])
42
+
43
+ def test_space_indented_still_works(self) -> None:
44
+ # Backward-compat guard.
45
+ src = (
46
+ "class M(models.Model):\n"
47
+ " x = models.CharField(max_length=5)\n"
48
+ )
49
+ models = parse_models_file("t/models.py", src)
50
+ self.assertEqual([f.name for f in models[0].fields], ["x"])
51
+
52
+
53
+ if __name__ == "__main__":
54
+ unittest.main()