Skip to content

Commit

Permalink
Updates
Browse files Browse the repository at this point in the history
  • Loading branch information
jhassine committed Nov 11, 2024
1 parent bcf0074 commit c0377af
Show file tree
Hide file tree
Showing 14 changed files with 2,319 additions and 3,137 deletions.
3 changes: 2 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"fb-pyre-check.pyre-vscode",
"ms-azuretools.vscode-docker",
"github.vscode-github-actions",
"ms-python.isort"
"ms-python.isort",
"ms-toolsai.jupyter"
]
}
}
Expand Down
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
3 changes: 2 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"fb-pyre-check.pyre-vscode",
"ms-azuretools.vscode-docker",
"github.vscode-github-actions",
"ms-python.isort"
"ms-python.isort",
"ms-toolsai.jupyter"
]
}
24 changes: 11 additions & 13 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,17 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
&& rm -rf /var/lib/apt/lists/*

RUN pip install --no-cache-dir -U pip setuptools wheel
RUN pip install --no-cache-dir poetry
RUN pip install --no-cache-dir uv

# Install pyenv for testing with different python versions
ENV PYENV_ROOT="$HOME/.pyenv"
ENV PATH="$PYENV_ROOT/bin:$PATH"
RUN curl https://pyenv.run | bash
RUN eval "$(pyenv init -)"
RUN pyenv install 3.13
RUN pyenv local 3.13
# Copy from the cache instead of linking since it's a mounted volume
ENV UV_LINK_MODE=copy
ENV UV_SYSTEM_PYTHON=true
ENV UV_BREAK_SYSTEM_PACKAGES=true
ENV UV_PROJECT_ENVIRONMENT=/usr/local

WORKDIR /app

RUN --mount=type=bind,source=./pyproject.toml,target=/app/pyproject.toml \
--mount=type=bind,source=./poetry.lock,target=/app/poetry.lock \
poetry install --no-root
# Install the project's dependencies using the lockfile and settings
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --frozen --no-install-project

105 changes: 17 additions & 88 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,96 +1,25 @@
# Why this package?
# What

# Tags
SuperSchema is a library that allows to define Pydantic schemas based on Django database models.

django,api,rest,json,ninja,infer,django orm, django model, pydantic, schema
Similar libraries:
- [Djantic](https://jordaneremieff.github.io/djantic/)
- [Django Ninja Schema](https://django-ninja.dev/guides/response/django-pydantic/)
- [Ninja Schema](https://github.com/eadwinCode/ninja-schema)

# Key feature
# Why

# Usage
SuperSchema is the most complete Pydantic schemas based on Django models.

```python
# Key features

from django.db import models
- Supports all Django model fields
- Supports all Django model relation fields:
- ForeignKey, OneToOneField, ManyToManyField
- The reverse relations of the above (ManyToOneRel, OneToOneRel, ManyToManyRel)
- Supports defining nested relations
- Provides as complete OpenAPI schema details as possible

class MyModelA(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
description = models.TextField()
# How


class MyModelB(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
organization = models.ForeignKey(MyModelA, on_delete=models.CASCADE)
applications = models.ManyToManyField(MyModelA)
account = models.OneToOneField(MyModelA, on_delete=models.CASCADE)

```

```python


#

# Usage example:

from superschema.types import Infer, InferExcept, ModelFields

# Basic usage:

model: ModelFields = {
"id": Infer,
"name": Infer,
"description": Infer,
"organization": {
"id": Infer,
"name": Infer
},
"applications": {
"id": Infer,
"name": Infer
},
"account": {
"id": Infer,
"name": Infer
},
}

# Overriding some inferred details

from superschema.types import InferExcept

list_fields: ModelFields = {
"name": InferExcept(description="I was not happy what was inferred so I defined here."),
}

# Allows also to use the native Pydantic FieldInfo

from pydantic import Field

list_fields: ModelFields = {
"description": Field(title="some name"), # pass native Pydantic field details
}

# Using already defined schemas in relations

list_fields: ModelFields = {
"id": Infer,
"name": Infer,
"description": Infer,
"organization": {
"id": Infer,
"name": Infer
},
"applications": {
"id": Infer,
"name": Infer
},
"account": {
"id": Infer,
"name": Infer
},
}


```
[See usage example here.](examples.ipynb)
110 changes: 69 additions & 41 deletions examples.ipynb

Large diffs are not rendered by default.

2,942 changes: 0 additions & 2,942 deletions poetry.lock

This file was deleted.

79 changes: 35 additions & 44 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[project]

name = "superschema"

[project.urls]
Homepage = "https://example.com"
Expand Down Expand Up @@ -37,49 +37,40 @@ classifiers = [
"Typing :: Typed",
]

[tool.poetry.dependencies]
python = "^3.12"
django = "^5.1.1"
pydantic = "^2.9.2"
email-validator = "^2.2.0"

[tool.poetry.group.dev.dependencies]
pyright = "^1.1.383"
basedpyright = "^1.18.3"
beartype = "^0.19.0"
typeguard = "^4.3.0"
pyre-check = "^0.9.22"
mypy = "^1.11.2"
wemake-python-styleguide = "^0.19.2"
pytype = "^2024.9.13"
isort = "^5.13.2"
django-stubs = {git = "https://github.com/typeddjango/django-stubs.git", rev = "master", extras = ["compatible-mypy"]}
pytest = "^8.3.3"
pytest-cov = "^5.0.0"
ruff = "^0.7.0"
nox = "^2024.4.15"
pytest-django = "^4.9.0"
hypothesis = "^6.112.4"
pylint-django = "^2.5.5"
pytest-testmon = "^2.1.1"
pytest-watch = "^4.2.0"
pytest-instafail = "^0.5.0"
rich = "^13.9.2"
import-linter = "^2.1"
debugpy = "^1.8.6"
ipykernel = "^6.29.5"


[tool.poetry.group.test.dependencies]
pytest = "^8.3.3"
pytest-django = "^4.9.0"
hypothesis = "^6.115.5"
beartype = "^0.19.0"
typeguard = "^4.3.0"
pydantic = "^2.9.2"
email-validator = "^2.2.0"
django = "^5.1.2"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[dependency-groups]
dev = [
"basedpyright>=1.21.0",
"beartype>=0.19.0",
"debugpy>=1.8.8",
"django>=5.1.3",
"django-ninja>=1.3.0",
"django-stubs[compatible-mypy]>=5.1.1",
"email-validator>=2.2.0",
"hypothesis>=6.118.0",
"import-linter>=2.1",
"ipykernel>=6.29.5",
"isort>=5.13.2",
"mypy>=1.13.0",
"ninja-schema>=0.13.6",
"nox>=2024.10.9",
"pydantic>=2.9.2",
"pylint-django>=2.6.1",
"pyre-check>=0.9.23",
"pyright>=1.1.388",
"pytest>=8.3.3",
"pytest-cov>=6.0.0",
"pytest-django>=4.9.0",
"pytest-instafail>=0.5.0",
"pytest-testmon>=2.1.1",
"pytest-watch>=4.2.0",
"pytype>=2024.10.11",
"rich>=13.9.4",
"ruff>=0.7.3",
"typeguard>=4.4.1",
"wemake-python-styleguide>=0.19.2",
"django-stubs-ext>=5.1.1",
]
13 changes: 8 additions & 5 deletions superschema/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
from django.contrib.contenttypes.fields import GenericForeignKey
from django.core.exceptions import FieldDoesNotExist
from django.db import models
from pydantic import BaseModel, ConfigDict, create_model
from pydantic import BaseModel, create_model
from pydantic._internal._model_construction import ModelMetaclass
from pydantic.fields import FieldInfo
from pydantic_core import PydanticUndefined

from superschema.defaults import field_type_registry
from superschema.mixin import BaseMixins
from superschema.registry import FieldTypeRegistry
from superschema.types import Infer, InferExcept, ModelFields

Expand Down Expand Up @@ -52,8 +53,8 @@ def create_pydantic_model(
django_model: type[models.Model],
field_type_registry: FieldTypeRegistry,
included_fields: ModelFields,
bases: tuple[type[BaseModel], ...] | None = None,
model_name: str | None = None,
config: ConfigDict | None = None,
) -> type[BaseModel]:
"""Create a Pydantic model from a Django model.
Expand Down Expand Up @@ -165,6 +166,7 @@ def create_pydantic_model(
field_type_registry,
related_model_fields,
model_name=f"{model_name}_{related_django_model_name}",
bases=bases,
)

default = PydanticUndefined
Expand Down Expand Up @@ -238,7 +240,7 @@ def create_pydantic_model(
model_name = model_name or f"{django_model.__name__}Schema"
return create_model(
model_name,
__config__={"from_attributes": True},
__base__=bases,
**pydantic_fields,
)

Expand All @@ -258,7 +260,7 @@ def __new__( # pylint: disable=W0222,C0204
bases: Bases,
namespace: Namespace,
**kwargs: Kwargs,
) -> type:
) -> type[BaseModel]:
"""Create a new SuperSchema class."""
if name == "SuperSchema":
return super().__new__(cls, name, bases, namespace, **kwargs)
Expand All @@ -282,10 +284,11 @@ def __new__( # pylint: disable=W0222,C0204
raise ValueError(msg)

model_name = getattr(namespace["Meta"], "name", None)

return create_pydantic_model(
model_class,
field_type_registry,
included_fields=namespace["Meta"].fields,
model_name=model_name,
config=namespace.get("model_config", None),
bases=(BaseMixins, BaseModel),
)
Loading

0 comments on commit c0377af

Please sign in to comment.