Problem
erdify does not recognise schemas from the django-ninja ecosystem. Reproduced on v0.13.0:
# models.py
from ninja import Schema
class AuthorOut(Schema):
id: int
name: str
$ erdify ./app --infer-keys
Error: No tables found in ./app
Scanned 1 .py/.sql file(s); 1 matched --include 'models.py'.
Files matched but held no recognized models.
Replace Schema with BaseModel in the same file and it is detected immediately.
Cause
_classify_source() treats a class as Pydantic only when BaseModel appears in its bases, directly or transitively — and "transitively" is resolved only across the scanned files (parser.py, _is_pydantic_model). ninja.Schema does subclass pydantic.BaseModel, but that inheritance lives inside the installed ninja package, which erdify never reads. The base is therefore unresolvable and the class is skipped.
This is the documented "only scanned files exist" limitation applied to classification rather than to relationship targets, and it is not specific to django-ninja. It hits any Pydantic-derived base defined outside the scan:
ninja.Schema (django-ninja)
- a shared
BaseSchema in an internal company library
SQLModel subclasses re-exported from another package
Two separate problems
These have very different costs and should not be conflated.
1. Base-class recognition — small, and worth doing
For schemas with explicit field annotations, everything erdify needs is already in the class body; only the classification gate is wrong.
Proposal: --base-classes NAME [NAME ...] and [tool.erdify] base_classes, naming extra base classes to treat as Pydantic models.
erdify ./api --base-classes Schema --infer-keys
[tool.erdify]
base_classes = ["Schema", "BaseSchema"]
Config-driven rather than a hardcoded list of known third-party names: the internal-library case is at least as common as the django-ninja one, and a hardcoded list can never solve it. A small curated default set could be layered on later if it proves worth it.
This alone covers:
- django-ninja
Schema subclasses with explicit annotations
- django-ninja-extra — it turns out this needs nothing of its own. It adds controllers, permissions and dependency injection (
@api_controller, ControllerBase), and reuses ninja.Schema/Pydantic for schemas. Fixing (1) covers it entirely; no separate work.
2. Model-derived schemas — much larger, probably a separate issue
Both ninja.ModelSchema and ninja_schema.ModelSchema derive their fields from a Django model through an inner class, with no annotations in the class body at all:
# django-ninja — inner class is Meta
from ninja import ModelSchema
class UserSchema(ModelSchema):
class Meta:
model = User
fields = ["id", "username", "first_name"]
# also: exclude, fields_optional
# ninja-schema — inner class is Config
from ninja_schema import ModelSchema
class UserSchema(ModelSchema):
class Config:
model = UserModel
include = ["id", "first_name", "email"]
# also: exclude, optional, depth
Recognising the base class here buys nothing — erdify would find an entity with zero fields, which is worse than skipping it. Supporting these properly means:
- resolving
Meta.model / Config.model to a Django model whose source is also in the scan (otherwise there is nothing to read, and this fails the same way for a model imported from another app that --include misses),
- reusing the existing Django field extraction for that model,
- applying
fields / include / exclude / fields_optional / optional,
- deciding what the result even means in an ERD. A response schema is a projection of a table, not a table. Rendering it as a second entity next to the model it mirrors is arguably noise — it may belong behind
--sources ninja or be excluded by default.
depth (ninja-schema) pulls in nested related schemas, which compounds all of the above.
Note the two libraries disagree on both the inner class name (Meta vs Config) and the field-selection keyword (fields vs include), so this is two mappings, not one.
Suggestion: land (1) on its own; open a separate issue for (2) once there is a clear answer to the "is a response schema an entity?" question. Interest from django-ninja users would be good evidence either way.
Why this is worth doing
django-ninja is one of the faster-growing Django API frameworks, its users have exactly the problem erdify solves, and no existing ERD tool covers them — django-extensions graph_models sees the Django models but not the schemas, and importing-based tools need the full app loaded.
Scope for (1)
Problem
erdify does not recognise schemas from the django-ninja ecosystem. Reproduced on v0.13.0:
Replace
SchemawithBaseModelin the same file and it is detected immediately.Cause
_classify_source()treats a class as Pydantic only whenBaseModelappears in its bases, directly or transitively — and "transitively" is resolved only across the scanned files (parser.py,_is_pydantic_model).ninja.Schemadoes subclasspydantic.BaseModel, but that inheritance lives inside the installedninjapackage, which erdify never reads. The base is therefore unresolvable and the class is skipped.This is the documented "only scanned files exist" limitation applied to classification rather than to relationship targets, and it is not specific to django-ninja. It hits any Pydantic-derived base defined outside the scan:
ninja.Schema(django-ninja)BaseSchemain an internal company librarySQLModelsubclasses re-exported from another packageTwo separate problems
These have very different costs and should not be conflated.
1. Base-class recognition — small, and worth doing
For schemas with explicit field annotations, everything erdify needs is already in the class body; only the classification gate is wrong.
Proposal:
--base-classes NAME [NAME ...]and[tool.erdify] base_classes, naming extra base classes to treat as Pydantic models.Config-driven rather than a hardcoded list of known third-party names: the internal-library case is at least as common as the django-ninja one, and a hardcoded list can never solve it. A small curated default set could be layered on later if it proves worth it.
This alone covers:
Schemasubclasses with explicit annotations@api_controller,ControllerBase), and reusesninja.Schema/Pydantic for schemas. Fixing (1) covers it entirely; no separate work.2. Model-derived schemas — much larger, probably a separate issue
Both
ninja.ModelSchemaandninja_schema.ModelSchemaderive their fields from a Django model through an inner class, with no annotations in the class body at all:Recognising the base class here buys nothing — erdify would find an entity with zero fields, which is worse than skipping it. Supporting these properly means:
Meta.model/Config.modelto a Django model whose source is also in the scan (otherwise there is nothing to read, and this fails the same way for a model imported from another app that--includemisses),fields/include/exclude/fields_optional/optional,--sources ninjaor be excluded by default.depth(ninja-schema) pulls in nested related schemas, which compounds all of the above.Note the two libraries disagree on both the inner class name (
MetavsConfig) and the field-selection keyword (fieldsvsinclude), so this is two mappings, not one.Suggestion: land (1) on its own; open a separate issue for (2) once there is a clear answer to the "is a response schema an entity?" question. Interest from django-ninja users would be good evidence either way.
Why this is worth doing
django-ninja is one of the faster-growing Django API frameworks, its users have exactly the problem erdify solves, and no existing ERD tool covers them —
django-extensions graph_modelssees the Django models but not the schemas, and importing-based tools need the full app loaded.Scope for (1)
--base-classesflag and[tool.erdify] base_classeskey, threaded throughASTDatabaseParserto_is_pydantic_modelninja.Schema-style subclasses (a local stand-in base — no runtime dependency on django-ninja, consistent with the existing fixtures)docs/usage/filtering.mdorusage/cli.md, plus regenerate the CLI blockdocs/frameworks/limitations.md— update the base-class note to point at the new escape hatchCHANGELOG.mdunderAdded