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.
- orm_lens-1.16.0/PKG-INFO +298 -0
- orm_lens-1.16.0/README.md +240 -0
- orm_lens-1.16.0/orm_lens/__init__.py +52 -0
- orm_lens-1.16.0/orm_lens/__main__.py +17 -0
- orm_lens-1.16.0/orm_lens/alembic_risk.py +514 -0
- orm_lens-1.16.0/orm_lens/blast_radius.py +458 -0
- orm_lens-1.16.0/orm_lens/ci_formats.py +259 -0
- orm_lens-1.16.0/orm_lens/cli.py +1083 -0
- orm_lens-1.16.0/orm_lens/diff.py +340 -0
- orm_lens-1.16.0/orm_lens/drift.py +363 -0
- orm_lens-1.16.0/orm_lens/er_formats.py +448 -0
- orm_lens-1.16.0/orm_lens/formatters.py +192 -0
- orm_lens-1.16.0/orm_lens/impact.py +306 -0
- orm_lens-1.16.0/orm_lens/mcp_server.py +777 -0
- orm_lens-1.16.0/orm_lens/migrations_parser.py +1400 -0
- orm_lens-1.16.0/orm_lens/models.py +384 -0
- orm_lens-1.16.0/orm_lens/parser.py +1056 -0
- orm_lens-1.16.0/orm_lens/py.typed +0 -0
- orm_lens-1.16.0/orm_lens/query_analyzer.py +1748 -0
- orm_lens-1.16.0/orm_lens/signals_parser.py +411 -0
- orm_lens-1.16.0/orm_lens/sqlalchemy_nplusone.py +745 -0
- orm_lens-1.16.0/orm_lens/sqlalchemy_parser.py +1612 -0
- orm_lens-1.16.0/orm_lens/stats.py +195 -0
- orm_lens-1.16.0/orm_lens/workspace.py +421 -0
- orm_lens-1.16.0/orm_lens.egg-info/PKG-INFO +298 -0
- orm_lens-1.16.0/orm_lens.egg-info/SOURCES.txt +75 -0
- orm_lens-1.16.0/orm_lens.egg-info/dependency_links.txt +1 -0
- orm_lens-1.16.0/orm_lens.egg-info/entry_points.txt +3 -0
- orm_lens-1.16.0/orm_lens.egg-info/requires.txt +17 -0
- orm_lens-1.16.0/orm_lens.egg-info/top_level.txt +1 -0
- orm_lens-1.16.0/pyproject.toml +111 -0
- orm_lens-1.16.0/setup.cfg +4 -0
- orm_lens-1.16.0/tests/test_abstract_and_auth_user.py +157 -0
- orm_lens-1.16.0/tests/test_abstract_inheritance_and_indexes.py +463 -0
- orm_lens-1.16.0/tests/test_alembic_risk.py +206 -0
- orm_lens-1.16.0/tests/test_aliased_module_and_third_party_fields.py +124 -0
- orm_lens-1.16.0/tests/test_base_outside_models.py +64 -0
- orm_lens-1.16.0/tests/test_blast_radius.py +303 -0
- orm_lens-1.16.0/tests/test_bom_prefix.py +40 -0
- orm_lens-1.16.0/tests/test_ci_formats.py +289 -0
- orm_lens-1.16.0/tests/test_cli_hint.py +65 -0
- orm_lens-1.16.0/tests/test_cli_subcommands_smoke.py +118 -0
- orm_lens-1.16.0/tests/test_cli_verbose.py +98 -0
- orm_lens-1.16.0/tests/test_conditional_models.py +164 -0
- orm_lens-1.16.0/tests/test_cross_module_bases.py +44 -0
- orm_lens-1.16.0/tests/test_diff_command.py +394 -0
- orm_lens-1.16.0/tests/test_drift.py +346 -0
- orm_lens-1.16.0/tests/test_er_formats.py +285 -0
- orm_lens-1.16.0/tests/test_golden_fixtures.py +46 -0
- orm_lens-1.16.0/tests/test_golden_snapshots.py +169 -0
- orm_lens-1.16.0/tests/test_impact.py +283 -0
- orm_lens-1.16.0/tests/test_kwarg_order_and_auth_user.py +431 -0
- orm_lens-1.16.0/tests/test_list_command.py +78 -0
- orm_lens-1.16.0/tests/test_manifest_sync.py +178 -0
- orm_lens-1.16.0/tests/test_mcp_server.py +639 -0
- orm_lens-1.16.0/tests/test_mcp_workspace.py +363 -0
- orm_lens-1.16.0/tests/test_migration_conflicts.py +155 -0
- orm_lens-1.16.0/tests/test_migration_risk.py +959 -0
- orm_lens-1.16.0/tests/test_migration_risk_new_rules.py +242 -0
- orm_lens-1.16.0/tests/test_migrations_parser.py +170 -0
- orm_lens-1.16.0/tests/test_mptt_models.py +145 -0
- orm_lens-1.16.0/tests/test_new_cli_commands.py +587 -0
- orm_lens-1.16.0/tests/test_nplusone_cross_function.py +284 -0
- orm_lens-1.16.0/tests/test_nplusone_detector.py +931 -0
- orm_lens-1.16.0/tests/test_nplusone_manager_cache.py +213 -0
- orm_lens-1.16.0/tests/test_nplusone_precision.py +133 -0
- orm_lens-1.16.0/tests/test_parity.py +129 -0
- orm_lens-1.16.0/tests/test_pep526_type_hints.py +97 -0
- orm_lens-1.16.0/tests/test_pep695_generic_classes.py +71 -0
- orm_lens-1.16.0/tests/test_query_analyzer.py +182 -0
- orm_lens-1.16.0/tests/test_rename_compat.py +68 -0
- orm_lens-1.16.0/tests/test_scan_root.py +95 -0
- orm_lens-1.16.0/tests/test_signals_parser.py +234 -0
- orm_lens-1.16.0/tests/test_sqlalchemy_nplusone.py +288 -0
- orm_lens-1.16.0/tests/test_sqlalchemy_parser.py +505 -0
- orm_lens-1.16.0/tests/test_stats.py +223 -0
- orm_lens-1.16.0/tests/test_tab_indented_models.py +54 -0
orm_lens-1.16.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://pypi.org/project/orm-lens/)
|
|
64
|
+
[](https://pypi.org/project/orm-lens/)
|
|
65
|
+
[](https://pepy.tech/project/django-orm-lens)
|
|
66
|
+
[](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
|
+
[](https://pypi.org/project/orm-lens/)
|
|
6
|
+
[](https://pypi.org/project/orm-lens/)
|
|
7
|
+
[](https://pepy.tech/project/django-orm-lens)
|
|
8
|
+
[](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())
|