orm-lens 1.16.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 (77) hide show
  1. orm_lens-1.16.0/PKG-INFO +298 -0
  2. orm_lens-1.16.0/README.md +240 -0
  3. orm_lens-1.16.0/orm_lens/__init__.py +52 -0
  4. orm_lens-1.16.0/orm_lens/__main__.py +17 -0
  5. orm_lens-1.16.0/orm_lens/alembic_risk.py +514 -0
  6. orm_lens-1.16.0/orm_lens/blast_radius.py +458 -0
  7. orm_lens-1.16.0/orm_lens/ci_formats.py +259 -0
  8. orm_lens-1.16.0/orm_lens/cli.py +1083 -0
  9. orm_lens-1.16.0/orm_lens/diff.py +340 -0
  10. orm_lens-1.16.0/orm_lens/drift.py +363 -0
  11. orm_lens-1.16.0/orm_lens/er_formats.py +448 -0
  12. orm_lens-1.16.0/orm_lens/formatters.py +192 -0
  13. orm_lens-1.16.0/orm_lens/impact.py +306 -0
  14. orm_lens-1.16.0/orm_lens/mcp_server.py +777 -0
  15. orm_lens-1.16.0/orm_lens/migrations_parser.py +1400 -0
  16. orm_lens-1.16.0/orm_lens/models.py +384 -0
  17. orm_lens-1.16.0/orm_lens/parser.py +1056 -0
  18. orm_lens-1.16.0/orm_lens/py.typed +0 -0
  19. orm_lens-1.16.0/orm_lens/query_analyzer.py +1748 -0
  20. orm_lens-1.16.0/orm_lens/signals_parser.py +411 -0
  21. orm_lens-1.16.0/orm_lens/sqlalchemy_nplusone.py +745 -0
  22. orm_lens-1.16.0/orm_lens/sqlalchemy_parser.py +1612 -0
  23. orm_lens-1.16.0/orm_lens/stats.py +195 -0
  24. orm_lens-1.16.0/orm_lens/workspace.py +421 -0
  25. orm_lens-1.16.0/orm_lens.egg-info/PKG-INFO +298 -0
  26. orm_lens-1.16.0/orm_lens.egg-info/SOURCES.txt +75 -0
  27. orm_lens-1.16.0/orm_lens.egg-info/dependency_links.txt +1 -0
  28. orm_lens-1.16.0/orm_lens.egg-info/entry_points.txt +3 -0
  29. orm_lens-1.16.0/orm_lens.egg-info/requires.txt +17 -0
  30. orm_lens-1.16.0/orm_lens.egg-info/top_level.txt +1 -0
  31. orm_lens-1.16.0/pyproject.toml +111 -0
  32. orm_lens-1.16.0/setup.cfg +4 -0
  33. orm_lens-1.16.0/tests/test_abstract_and_auth_user.py +157 -0
  34. orm_lens-1.16.0/tests/test_abstract_inheritance_and_indexes.py +463 -0
  35. orm_lens-1.16.0/tests/test_alembic_risk.py +206 -0
  36. orm_lens-1.16.0/tests/test_aliased_module_and_third_party_fields.py +124 -0
  37. orm_lens-1.16.0/tests/test_base_outside_models.py +64 -0
  38. orm_lens-1.16.0/tests/test_blast_radius.py +303 -0
  39. orm_lens-1.16.0/tests/test_bom_prefix.py +40 -0
  40. orm_lens-1.16.0/tests/test_ci_formats.py +289 -0
  41. orm_lens-1.16.0/tests/test_cli_hint.py +65 -0
  42. orm_lens-1.16.0/tests/test_cli_subcommands_smoke.py +118 -0
  43. orm_lens-1.16.0/tests/test_cli_verbose.py +98 -0
  44. orm_lens-1.16.0/tests/test_conditional_models.py +164 -0
  45. orm_lens-1.16.0/tests/test_cross_module_bases.py +44 -0
  46. orm_lens-1.16.0/tests/test_diff_command.py +394 -0
  47. orm_lens-1.16.0/tests/test_drift.py +346 -0
  48. orm_lens-1.16.0/tests/test_er_formats.py +285 -0
  49. orm_lens-1.16.0/tests/test_golden_fixtures.py +46 -0
  50. orm_lens-1.16.0/tests/test_golden_snapshots.py +169 -0
  51. orm_lens-1.16.0/tests/test_impact.py +283 -0
  52. orm_lens-1.16.0/tests/test_kwarg_order_and_auth_user.py +431 -0
  53. orm_lens-1.16.0/tests/test_list_command.py +78 -0
  54. orm_lens-1.16.0/tests/test_manifest_sync.py +178 -0
  55. orm_lens-1.16.0/tests/test_mcp_server.py +639 -0
  56. orm_lens-1.16.0/tests/test_mcp_workspace.py +363 -0
  57. orm_lens-1.16.0/tests/test_migration_conflicts.py +155 -0
  58. orm_lens-1.16.0/tests/test_migration_risk.py +959 -0
  59. orm_lens-1.16.0/tests/test_migration_risk_new_rules.py +242 -0
  60. orm_lens-1.16.0/tests/test_migrations_parser.py +170 -0
  61. orm_lens-1.16.0/tests/test_mptt_models.py +145 -0
  62. orm_lens-1.16.0/tests/test_new_cli_commands.py +587 -0
  63. orm_lens-1.16.0/tests/test_nplusone_cross_function.py +284 -0
  64. orm_lens-1.16.0/tests/test_nplusone_detector.py +931 -0
  65. orm_lens-1.16.0/tests/test_nplusone_manager_cache.py +213 -0
  66. orm_lens-1.16.0/tests/test_nplusone_precision.py +133 -0
  67. orm_lens-1.16.0/tests/test_parity.py +129 -0
  68. orm_lens-1.16.0/tests/test_pep526_type_hints.py +97 -0
  69. orm_lens-1.16.0/tests/test_pep695_generic_classes.py +71 -0
  70. orm_lens-1.16.0/tests/test_query_analyzer.py +182 -0
  71. orm_lens-1.16.0/tests/test_rename_compat.py +68 -0
  72. orm_lens-1.16.0/tests/test_scan_root.py +95 -0
  73. orm_lens-1.16.0/tests/test_signals_parser.py +234 -0
  74. orm_lens-1.16.0/tests/test_sqlalchemy_nplusone.py +288 -0
  75. orm_lens-1.16.0/tests/test_sqlalchemy_parser.py +505 -0
  76. orm_lens-1.16.0/tests/test_stats.py +223 -0
  77. orm_lens-1.16.0/tests/test_tab_indented_models.py +54 -0
@@ -0,0 +1,298 @@
1
+ Metadata-Version: 2.4
2
+ Name: orm-lens
3
+ Version: 1.16.0
4
+ Summary: Static analysis + MCP server for Django and SQLAlchemy models. ER diagrams, N+1, migration risk for Django migrations and Alembic, blast radius and schema drift for CI, terminals and AI agents. No DB, no app boot. Free and MIT.
5
+ Author: frowningdev
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/FROWNINGdev/orm-lens
8
+ Project-URL: Documentation, https://frowningdev.github.io/orm-lens/
9
+ Project-URL: Repository, https://github.com/FROWNINGdev/orm-lens
10
+ Project-URL: Bug Tracker, https://github.com/FROWNINGdev/orm-lens/issues
11
+ Project-URL: Changelog, https://github.com/FROWNINGdev/orm-lens/blob/main/CHANGELOG.md
12
+ Project-URL: VS Code Marketplace, https://marketplace.visualstudio.com/items?itemName=frowningdev.django-orm-lens
13
+ Keywords: django,sqlalchemy,alembic,fastapi,flask,sqlmodel,orm,n+1,migrations,models,er-diagram,mermaid,mcp,ai-agents,cursor,aider,code-analysis,static-analysis
14
+ Classifier: Development Status :: 5 - Production/Stable
15
+ Classifier: Environment :: Console
16
+ Classifier: Framework :: Django
17
+ Classifier: Framework :: Django :: 4.0
18
+ Classifier: Framework :: Django :: 4.1
19
+ Classifier: Framework :: Django :: 4.2
20
+ Classifier: Framework :: Django :: 5.0
21
+ Classifier: Framework :: Django :: 5.1
22
+ Classifier: Framework :: Django :: 5.2
23
+ Classifier: Framework :: Django :: 6.0
24
+ Classifier: Framework :: Django :: 6.1
25
+ Classifier: Framework :: FastAPI
26
+ Classifier: Framework :: Flask
27
+ Classifier: Intended Audience :: Developers
28
+ Classifier: License :: OSI Approved :: MIT License
29
+ Classifier: Operating System :: OS Independent
30
+ Classifier: Programming Language :: Python
31
+ Classifier: Programming Language :: Python :: 3
32
+ Classifier: Programming Language :: Python :: 3 :: Only
33
+ Classifier: Programming Language :: Python :: 3.9
34
+ Classifier: Programming Language :: Python :: 3.10
35
+ Classifier: Programming Language :: Python :: 3.11
36
+ Classifier: Programming Language :: Python :: 3.12
37
+ Classifier: Programming Language :: Python :: 3.13
38
+ Classifier: Programming Language :: Python :: 3.14
39
+ Classifier: Programming Language :: Python :: 3.15
40
+ Classifier: Topic :: Software Development :: Quality Assurance
41
+ Classifier: Topic :: Software Development :: Testing
42
+ Classifier: Topic :: Software Development :: Documentation
43
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
44
+ Classifier: Topic :: Utilities
45
+ Classifier: Typing :: Typed
46
+ Requires-Python: >=3.9
47
+ Description-Content-Type: text/markdown
48
+ Provides-Extra: mcp
49
+ Requires-Dist: mcp<3,>=2.0; python_version >= "3.10" and extra == "mcp"
50
+ Provides-Extra: full
51
+ Requires-Dist: mcp<3,>=2.0; python_version >= "3.10" and extra == "full"
52
+ Provides-Extra: dev
53
+ Requires-Dist: pytest>=8; extra == "dev"
54
+ Requires-Dist: ruff>=0.5; extra == "dev"
55
+ Requires-Dist: mypy>=1.10; extra == "dev"
56
+ Requires-Dist: build>=1.2; extra == "dev"
57
+ Requires-Dist: twine>=5.1; extra == "dev"
58
+
59
+ # orm-lens
60
+
61
+ <!-- mcp-name: io.github.FROWNINGdev/orm-lens -->
62
+
63
+ [![PyPI](https://img.shields.io/pypi/v/orm-lens?color=3775a9&label=PyPI&logo=pypi&logoColor=white)](https://pypi.org/project/orm-lens/)
64
+ [![Python](https://img.shields.io/pypi/pyversions/orm-lens?color=3775a9&logo=python&logoColor=white)](https://pypi.org/project/orm-lens/)
65
+ [![Downloads](https://img.shields.io/pepy/dt/django-orm-lens?color=3775a9&label=downloads&logo=pypi&logoColor=white)](https://pepy.tech/project/django-orm-lens)
66
+ [![License](https://img.shields.io/github/license/FROWNINGdev/orm-lens?color=16a34a)](https://github.com/FROWNINGdev/orm-lens/blob/main/LICENSE)
67
+
68
+ **Static analysis + MCP server for Django and SQLAlchemy models.** Terminal- and AI-agent-friendly.
69
+
70
+ Formerly **django-orm-lens**: `pip install django-orm-lens` still works, installs
71
+ this package and keeps the old `django-orm-lens` command and `import django_orm_lens`.
72
+
73
+ SQLAlchemy (preview): `scan`, `er` and `nplusone` read SQLAlchemy 2.0 / 1.x,
74
+ Flask-SQLAlchemy and SQLModel models, and `migration-risk` reads Alembic
75
+ revisions — see [SQLAlchemy models](https://github.com/FROWNINGdev/orm-lens/blob/main/docs/rules/sqlalchemy.md)
76
+ and [Alembic rules](https://github.com/FROWNINGdev/orm-lens/blob/main/docs/rules/alembic.md).
77
+
78
+ Listed in the official [MCP Registry](https://registry.modelcontextprotocol.io/) as `io.github.FROWNINGdev/orm-lens`.
79
+
80
+ Ships with a zero-dependency parser, a JSON/Markdown/table CLI, and an optional
81
+ MCP (Model Context Protocol) server so any AI coding agent — Cursor, Aider,
82
+ Continue, and any other MCP client — can navigate your Django schema without
83
+ importing Django or spinning up your app.
84
+
85
+ Companion to the [ORM Lens VS Code
86
+ extension](https://marketplace.visualstudio.com/items?itemName=frowningdev.django-orm-lens).
87
+
88
+ ## Paid-tier capabilities, free and MIT
89
+
90
+ Schema review is a paid category nearly everywhere. A bot that reviews every
91
+ pull request, analysis that follows a queryset past the function it was built in, a check that
92
+ catches schema drift, index advice grounded in real table statistics — those
93
+ normally sit behind a per-seat or per-database subscription.
94
+
95
+ All of it is here, MIT-licensed, with no tier gate, no seat count, no account
96
+ and no telemetry:
97
+
98
+ | Capability usually sold as a paid tier | Here |
99
+ |---|---|
100
+ | PR review bot for schema changes — posts once, then updates in place | `blast-radius` + the GitHub Action |
101
+ | Analysis that follows a queryset across functions | `nplusone` |
102
+ | Schema drift detection | `drift` |
103
+ | Index proposals from observed QuerySet usage | `suggest-indexes` |
104
+ | Migration risk weighed against real table sizes | `blast-radius --stats` |
105
+ | Blast radius of a destructive migration | `blast-radius` |
106
+ | Cross-layer impact of removing a field | `impact` |
107
+
108
+ **There is no Pro tier, and none is planned.**
109
+
110
+ ---
111
+
112
+ ## Install
113
+
114
+ ```bash
115
+ # Core CLI (zero third-party deps)
116
+ pip install orm-lens
117
+
118
+ # With the MCP server (adds the `mcp` package)
119
+ pip install "orm-lens[mcp]"
120
+ ```
121
+
122
+ Requires Python **3.9+**; the MCP server needs **3.10+**, like the `mcp` SDK. Works on Linux, macOS, and Windows.
123
+
124
+ ---
125
+
126
+ ## CLI usage
127
+
128
+ ```bash
129
+ # Scan a Django project for models (JSON, Markdown, or table)
130
+ orm-lens scan -f json
131
+ orm-lens scan -f markdown
132
+ orm-lens scan -f table
133
+
134
+ # Describe one model
135
+ orm-lens describe blog.Post
136
+ orm-lens describe Post -f json
137
+
138
+ # Compact hover card (great for pipeing into your editor)
139
+ orm-lens hover blog.Post
140
+
141
+ # Flat list — pipes into fzf, grep, etc.
142
+ orm-lens list | fzf
143
+
144
+ # ER diagram — Mermaid (default), DBML, D2, or PlantUML
145
+ orm-lens er > schema.mmd
146
+ orm-lens er -f dbml > schema.dbml # paste into dbdiagram.io
147
+ orm-lens er -f d2 > schema.d2 # render: d2 schema.d2 schema.svg
148
+ orm-lens er -f plantuml > schema.puml
149
+
150
+ # Diff two schema dumps (exit 1 on changes — CI-friendly)
151
+ orm-lens diff before.json after.json
152
+
153
+ # Static analyzers — text, json, sarif, or github annotation output
154
+ orm-lens nplusone --format github # N+1 findings as PR annotations
155
+ orm-lens migration-risk -f sarif # SARIF for GitHub Code Scanning
156
+ orm-lens suggest-indexes blog.Post # Meta.indexes proposals from usage
157
+ orm-lens signals # sender→signal→handler graph
158
+ orm-lens migration-deps blog -f mermaid # per-app migration DAG
159
+ orm-lens cascade blog.Author # delete blast radius by on_delete
160
+ ```
161
+
162
+ Every command accepts `--path <dir>` and repeatable `--exclude <glob>`. Defaults
163
+ skip `migrations/`, `venv/`, `.venv/`, `env/`, and `node_modules/`.
164
+
165
+ ---
166
+
167
+ ## MCP server — for AI coding agents
168
+
169
+ The MCP server exposes ten read-only tools that any MCP-compatible agent can
170
+ call while it edits your Django project:
171
+
172
+ | Tool | Purpose |
173
+ | --- | --- |
174
+ | `list_apps` | Every Django app in the workspace with model counts |
175
+ | `list_models` | Flat `app.Model` list, optional app filter |
176
+ | `describe_model` | Full field / relation / Meta detail for one model |
177
+ | `find_relations` | Inbound + outbound relations for one model |
178
+ | `cascade_preview` | Blast radius of one `delete()`, grouped by `on_delete` |
179
+ | `er_diagram` | ER diagram — `mermaid` / `dbml` / `d2` / `plantuml` |
180
+ | `describe_migration_dependency` | Per-app migration DAG: roots, leaves, cross-app deps |
181
+ | `suggest_indexes` | `Meta.indexes` proposals from observed QuerySet usage |
182
+ | `signal_graph` | Sender→signal→handler graph from `@receiver` decorators |
183
+ | `nplusone_scan` | Static N+1 findings for the whole workspace |
184
+
185
+ ### Start the server manually
186
+
187
+ ```bash
188
+ orm-lens-mcp # dedicated entry point
189
+ # or
190
+ orm-lens mcp # subcommand
191
+ ```
192
+
193
+ **Workspace resolution (py-1.3.0+).** Priority: explicit `workspace_root`
194
+ argument on the tool call → `ORM_LENS_ROOT` env var → current working
195
+ directory. If none resolves to a Django project (`manage.py`, `django` in
196
+ `pyproject.toml`, or any `models.py`) you get a structured error envelope
197
+ back — `{"error": "WORKSPACE_NOT_DJANGO", "hint": "…"}` — instead of an
198
+ empty list, so the agent knows what to do next.
199
+
200
+ Optional sandbox: set `ORM_LENS_ALLOWED_ROOTS` (`;`-separated on
201
+ Windows, `:`-separated elsewhere) to a whitelist of prefixes; any path
202
+ outside them is rejected with `WORKSPACE_NOT_ALLOWED`.
203
+
204
+ ### Register it with an agent
205
+
206
+ **Cursor** — add to `~/.cursor/mcp.json`:
207
+
208
+ ```json
209
+ {
210
+ "mcpServers": {
211
+ "orm-lens": {
212
+ "command": "orm-lens-mcp",
213
+ "env": { "ORM_LENS_ROOT": "/abs/path/to/your/project" }
214
+ }
215
+ }
216
+ }
217
+ ```
218
+
219
+ **Any MCP client** — same shape, generic tool. Point `command` at the
220
+ installed `orm-lens-mcp` binary. Two ways to tell it which Django
221
+ project to scan:
222
+
223
+ 1. **Set `ORM_LENS_ROOT` in `env`** — the whole session uses one
224
+ project. Simplest for single-repo workflows.
225
+ 2. **Pass `workspace_root` on each tool call** — the agent switches
226
+ projects per call. Useful for mono-repos and multi-workspace setups.
227
+
228
+ The tool signatures include `workspace_root: str = ""` as an optional
229
+ parameter, so any agent inspecting `tools/list` sees it and can supply it.
230
+
231
+ ---
232
+
233
+ ## CI gates
234
+
235
+ `diff` and `nplusone` exit `1` on findings; `migration-risk` exits `1` on
236
+ critical findings (`--exit-zero` for report-only). Two annotation-ready
237
+ formats: `--format github` prints `::warning`/`::error` workflow commands
238
+ (PR annotations with no extra permissions), `--format sarif` emits
239
+ SARIF 2.1.0 for `github/codeql-action/upload-sarif`.
240
+
241
+ pre-commit users get two ready-made hooks:
242
+
243
+ ```yaml
244
+ repos:
245
+ - repo: https://github.com/FROWNINGdev/orm-lens
246
+ rev: py-v1.16.0
247
+ hooks:
248
+ - id: orm-lens-nplusone
249
+ - id: orm-lens-migration-risk
250
+ ```
251
+
252
+ GitHub Actions users get a composite action:
253
+ `uses: FROWNINGdev/orm-lens@action-v1` with `command:` / `format:` inputs.
254
+
255
+ ---
256
+
257
+ ## Why?
258
+
259
+ Django's ORM is Python, and Python is dynamic. AI agents that only see
260
+ `models.py` as raw text miss:
261
+
262
+ * which fields belong to which model;
263
+ * the direction and cardinality of every relation;
264
+ * what `Meta.ordering`, `unique_together`, and `constraints` actually contain;
265
+ * which app owns which model when the project uses split `models/` packages.
266
+
267
+ `orm-lens` gives them a **static, deterministic, JSON view** of the
268
+ schema — no Django boot, no database, no side effects. And you get a nice CLI
269
+ for humans too.
270
+
271
+ ---
272
+
273
+ ## Programmatic API
274
+
275
+ ```python
276
+ from orm_lens import scan_workspace
277
+
278
+ index = scan_workspace(".")
279
+ for app in index.apps:
280
+ for model in app.models:
281
+ print(f"{app.name}.{model.name} — {len(model.fields)} fields")
282
+
283
+ # The full parsed tree serialises to the same JSON schema the VS Code
284
+ # extension emits, so tools can share it interchangeably.
285
+ import json
286
+ json.dumps(index.to_dict(), indent=2)
287
+ ```
288
+
289
+ ---
290
+
291
+ ## Links
292
+
293
+ * Repo: <https://github.com/FROWNINGdev/orm-lens>
294
+ * Issues: <https://github.com/FROWNINGdev/orm-lens/issues>
295
+ * Changelog: <https://github.com/FROWNINGdev/orm-lens/blob/main/CHANGELOG.md>
296
+ * VS Code extension: <https://marketplace.visualstudio.com/items?itemName=frowningdev.django-orm-lens>
297
+
298
+ MIT licensed.
@@ -0,0 +1,240 @@
1
+ # orm-lens
2
+
3
+ <!-- mcp-name: io.github.FROWNINGdev/orm-lens -->
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/orm-lens?color=3775a9&label=PyPI&logo=pypi&logoColor=white)](https://pypi.org/project/orm-lens/)
6
+ [![Python](https://img.shields.io/pypi/pyversions/orm-lens?color=3775a9&logo=python&logoColor=white)](https://pypi.org/project/orm-lens/)
7
+ [![Downloads](https://img.shields.io/pepy/dt/django-orm-lens?color=3775a9&label=downloads&logo=pypi&logoColor=white)](https://pepy.tech/project/django-orm-lens)
8
+ [![License](https://img.shields.io/github/license/FROWNINGdev/orm-lens?color=16a34a)](https://github.com/FROWNINGdev/orm-lens/blob/main/LICENSE)
9
+
10
+ **Static analysis + MCP server for Django and SQLAlchemy models.** Terminal- and AI-agent-friendly.
11
+
12
+ Formerly **django-orm-lens**: `pip install django-orm-lens` still works, installs
13
+ this package and keeps the old `django-orm-lens` command and `import django_orm_lens`.
14
+
15
+ SQLAlchemy (preview): `scan`, `er` and `nplusone` read SQLAlchemy 2.0 / 1.x,
16
+ Flask-SQLAlchemy and SQLModel models, and `migration-risk` reads Alembic
17
+ revisions — see [SQLAlchemy models](https://github.com/FROWNINGdev/orm-lens/blob/main/docs/rules/sqlalchemy.md)
18
+ and [Alembic rules](https://github.com/FROWNINGdev/orm-lens/blob/main/docs/rules/alembic.md).
19
+
20
+ Listed in the official [MCP Registry](https://registry.modelcontextprotocol.io/) as `io.github.FROWNINGdev/orm-lens`.
21
+
22
+ Ships with a zero-dependency parser, a JSON/Markdown/table CLI, and an optional
23
+ MCP (Model Context Protocol) server so any AI coding agent — Cursor, Aider,
24
+ Continue, and any other MCP client — can navigate your Django schema without
25
+ importing Django or spinning up your app.
26
+
27
+ Companion to the [ORM Lens VS Code
28
+ extension](https://marketplace.visualstudio.com/items?itemName=frowningdev.django-orm-lens).
29
+
30
+ ## Paid-tier capabilities, free and MIT
31
+
32
+ Schema review is a paid category nearly everywhere. A bot that reviews every
33
+ pull request, analysis that follows a queryset past the function it was built in, a check that
34
+ catches schema drift, index advice grounded in real table statistics — those
35
+ normally sit behind a per-seat or per-database subscription.
36
+
37
+ All of it is here, MIT-licensed, with no tier gate, no seat count, no account
38
+ and no telemetry:
39
+
40
+ | Capability usually sold as a paid tier | Here |
41
+ |---|---|
42
+ | PR review bot for schema changes — posts once, then updates in place | `blast-radius` + the GitHub Action |
43
+ | Analysis that follows a queryset across functions | `nplusone` |
44
+ | Schema drift detection | `drift` |
45
+ | Index proposals from observed QuerySet usage | `suggest-indexes` |
46
+ | Migration risk weighed against real table sizes | `blast-radius --stats` |
47
+ | Blast radius of a destructive migration | `blast-radius` |
48
+ | Cross-layer impact of removing a field | `impact` |
49
+
50
+ **There is no Pro tier, and none is planned.**
51
+
52
+ ---
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ # Core CLI (zero third-party deps)
58
+ pip install orm-lens
59
+
60
+ # With the MCP server (adds the `mcp` package)
61
+ pip install "orm-lens[mcp]"
62
+ ```
63
+
64
+ Requires Python **3.9+**; the MCP server needs **3.10+**, like the `mcp` SDK. Works on Linux, macOS, and Windows.
65
+
66
+ ---
67
+
68
+ ## CLI usage
69
+
70
+ ```bash
71
+ # Scan a Django project for models (JSON, Markdown, or table)
72
+ orm-lens scan -f json
73
+ orm-lens scan -f markdown
74
+ orm-lens scan -f table
75
+
76
+ # Describe one model
77
+ orm-lens describe blog.Post
78
+ orm-lens describe Post -f json
79
+
80
+ # Compact hover card (great for pipeing into your editor)
81
+ orm-lens hover blog.Post
82
+
83
+ # Flat list — pipes into fzf, grep, etc.
84
+ orm-lens list | fzf
85
+
86
+ # ER diagram — Mermaid (default), DBML, D2, or PlantUML
87
+ orm-lens er > schema.mmd
88
+ orm-lens er -f dbml > schema.dbml # paste into dbdiagram.io
89
+ orm-lens er -f d2 > schema.d2 # render: d2 schema.d2 schema.svg
90
+ orm-lens er -f plantuml > schema.puml
91
+
92
+ # Diff two schema dumps (exit 1 on changes — CI-friendly)
93
+ orm-lens diff before.json after.json
94
+
95
+ # Static analyzers — text, json, sarif, or github annotation output
96
+ orm-lens nplusone --format github # N+1 findings as PR annotations
97
+ orm-lens migration-risk -f sarif # SARIF for GitHub Code Scanning
98
+ orm-lens suggest-indexes blog.Post # Meta.indexes proposals from usage
99
+ orm-lens signals # sender→signal→handler graph
100
+ orm-lens migration-deps blog -f mermaid # per-app migration DAG
101
+ orm-lens cascade blog.Author # delete blast radius by on_delete
102
+ ```
103
+
104
+ Every command accepts `--path <dir>` and repeatable `--exclude <glob>`. Defaults
105
+ skip `migrations/`, `venv/`, `.venv/`, `env/`, and `node_modules/`.
106
+
107
+ ---
108
+
109
+ ## MCP server — for AI coding agents
110
+
111
+ The MCP server exposes ten read-only tools that any MCP-compatible agent can
112
+ call while it edits your Django project:
113
+
114
+ | Tool | Purpose |
115
+ | --- | --- |
116
+ | `list_apps` | Every Django app in the workspace with model counts |
117
+ | `list_models` | Flat `app.Model` list, optional app filter |
118
+ | `describe_model` | Full field / relation / Meta detail for one model |
119
+ | `find_relations` | Inbound + outbound relations for one model |
120
+ | `cascade_preview` | Blast radius of one `delete()`, grouped by `on_delete` |
121
+ | `er_diagram` | ER diagram — `mermaid` / `dbml` / `d2` / `plantuml` |
122
+ | `describe_migration_dependency` | Per-app migration DAG: roots, leaves, cross-app deps |
123
+ | `suggest_indexes` | `Meta.indexes` proposals from observed QuerySet usage |
124
+ | `signal_graph` | Sender→signal→handler graph from `@receiver` decorators |
125
+ | `nplusone_scan` | Static N+1 findings for the whole workspace |
126
+
127
+ ### Start the server manually
128
+
129
+ ```bash
130
+ orm-lens-mcp # dedicated entry point
131
+ # or
132
+ orm-lens mcp # subcommand
133
+ ```
134
+
135
+ **Workspace resolution (py-1.3.0+).** Priority: explicit `workspace_root`
136
+ argument on the tool call → `ORM_LENS_ROOT` env var → current working
137
+ directory. If none resolves to a Django project (`manage.py`, `django` in
138
+ `pyproject.toml`, or any `models.py`) you get a structured error envelope
139
+ back — `{"error": "WORKSPACE_NOT_DJANGO", "hint": "…"}` — instead of an
140
+ empty list, so the agent knows what to do next.
141
+
142
+ Optional sandbox: set `ORM_LENS_ALLOWED_ROOTS` (`;`-separated on
143
+ Windows, `:`-separated elsewhere) to a whitelist of prefixes; any path
144
+ outside them is rejected with `WORKSPACE_NOT_ALLOWED`.
145
+
146
+ ### Register it with an agent
147
+
148
+ **Cursor** — add to `~/.cursor/mcp.json`:
149
+
150
+ ```json
151
+ {
152
+ "mcpServers": {
153
+ "orm-lens": {
154
+ "command": "orm-lens-mcp",
155
+ "env": { "ORM_LENS_ROOT": "/abs/path/to/your/project" }
156
+ }
157
+ }
158
+ }
159
+ ```
160
+
161
+ **Any MCP client** — same shape, generic tool. Point `command` at the
162
+ installed `orm-lens-mcp` binary. Two ways to tell it which Django
163
+ project to scan:
164
+
165
+ 1. **Set `ORM_LENS_ROOT` in `env`** — the whole session uses one
166
+ project. Simplest for single-repo workflows.
167
+ 2. **Pass `workspace_root` on each tool call** — the agent switches
168
+ projects per call. Useful for mono-repos and multi-workspace setups.
169
+
170
+ The tool signatures include `workspace_root: str = ""` as an optional
171
+ parameter, so any agent inspecting `tools/list` sees it and can supply it.
172
+
173
+ ---
174
+
175
+ ## CI gates
176
+
177
+ `diff` and `nplusone` exit `1` on findings; `migration-risk` exits `1` on
178
+ critical findings (`--exit-zero` for report-only). Two annotation-ready
179
+ formats: `--format github` prints `::warning`/`::error` workflow commands
180
+ (PR annotations with no extra permissions), `--format sarif` emits
181
+ SARIF 2.1.0 for `github/codeql-action/upload-sarif`.
182
+
183
+ pre-commit users get two ready-made hooks:
184
+
185
+ ```yaml
186
+ repos:
187
+ - repo: https://github.com/FROWNINGdev/orm-lens
188
+ rev: py-v1.16.0
189
+ hooks:
190
+ - id: orm-lens-nplusone
191
+ - id: orm-lens-migration-risk
192
+ ```
193
+
194
+ GitHub Actions users get a composite action:
195
+ `uses: FROWNINGdev/orm-lens@action-v1` with `command:` / `format:` inputs.
196
+
197
+ ---
198
+
199
+ ## Why?
200
+
201
+ Django's ORM is Python, and Python is dynamic. AI agents that only see
202
+ `models.py` as raw text miss:
203
+
204
+ * which fields belong to which model;
205
+ * the direction and cardinality of every relation;
206
+ * what `Meta.ordering`, `unique_together`, and `constraints` actually contain;
207
+ * which app owns which model when the project uses split `models/` packages.
208
+
209
+ `orm-lens` gives them a **static, deterministic, JSON view** of the
210
+ schema — no Django boot, no database, no side effects. And you get a nice CLI
211
+ for humans too.
212
+
213
+ ---
214
+
215
+ ## Programmatic API
216
+
217
+ ```python
218
+ from orm_lens import scan_workspace
219
+
220
+ index = scan_workspace(".")
221
+ for app in index.apps:
222
+ for model in app.models:
223
+ print(f"{app.name}.{model.name} — {len(model.fields)} fields")
224
+
225
+ # The full parsed tree serialises to the same JSON schema the VS Code
226
+ # extension emits, so tools can share it interchangeably.
227
+ import json
228
+ json.dumps(index.to_dict(), indent=2)
229
+ ```
230
+
231
+ ---
232
+
233
+ ## Links
234
+
235
+ * Repo: <https://github.com/FROWNINGdev/orm-lens>
236
+ * Issues: <https://github.com/FROWNINGdev/orm-lens/issues>
237
+ * Changelog: <https://github.com/FROWNINGdev/orm-lens/blob/main/CHANGELOG.md>
238
+ * VS Code extension: <https://marketplace.visualstudio.com/items?itemName=frowningdev.django-orm-lens>
239
+
240
+ MIT licensed.
@@ -0,0 +1,52 @@
1
+ """ORM Lens — sidebar tree, ER diagrams, and JSON for Django models.
2
+
3
+ Terminal / editor-agnostic parser for Django models.py files.
4
+ Ships a CLI and an optional MCP server for AI coding agents.
5
+ """
6
+
7
+ try:
8
+ from importlib.metadata import PackageNotFoundError
9
+ from importlib.metadata import version as _pkg_version
10
+ try:
11
+ __version__ = _pkg_version("orm-lens")
12
+ except PackageNotFoundError:
13
+ __version__ = "0.0.0+local"
14
+ except ImportError:
15
+ __version__ = "0.0.0+local"
16
+
17
+ from .models import (
18
+ ParsedApp,
19
+ ParsedField,
20
+ ParsedModel,
21
+ WorkspaceIndex,
22
+ cascade_preview,
23
+ find_model,
24
+ find_user_model,
25
+ find_user_model_from_dict,
26
+ resolve_related_tail,
27
+ )
28
+ from .parser import (
29
+ BROAD_SKIP_DIRS,
30
+ iter_workspace_py_files,
31
+ parse_models_file,
32
+ scan_workspace,
33
+ )
34
+
35
+ __all__ = [
36
+ "ParsedApp",
37
+ "ParsedField",
38
+ "ParsedModel",
39
+ "WorkspaceIndex",
40
+ "parse_models_file",
41
+ "scan_workspace",
42
+ # v1.2.0 — shared helpers for downstream tools that build on the parser.
43
+ "BROAD_SKIP_DIRS",
44
+ "find_model",
45
+ "find_user_model",
46
+ "find_user_model_from_dict",
47
+ "iter_workspace_py_files",
48
+ "resolve_related_tail",
49
+ # shared relation analysis used by both the CLI and the MCP server.
50
+ "cascade_preview",
51
+ "__version__",
52
+ ]
@@ -0,0 +1,17 @@
1
+ """Entry point for ``python -m orm_lens``.
2
+
3
+ The console scripts declared in pyproject.toml cover the common case, but
4
+ ``python -m`` is the invocation that does not depend on the scripts directory
5
+ being on PATH — which is exactly the situation inside CI images, tox
6
+ environments and freshly created venvs. Mirrors what pip, pytest and venv do.
7
+
8
+ Delegates to the same ``cli.main`` the console script uses, so the two paths
9
+ cannot drift.
10
+ """
11
+
12
+ import sys
13
+
14
+ from .cli import main
15
+
16
+ if __name__ == "__main__":
17
+ sys.exit(main())