Pydantic model support for Django ORM

Overview

Djantic

PyPI version

Documentation: https://jordaneremieff.github.io/djantic/

Requirements: Python 3.7+, Django 2.2+

Pydantic model support for Django.

This project should be considered a work-in-progress. It should be okay to use, but no specific version support has been determined (#16) and the default model generation behaviour may change across releases.

Please use the issues tracker to report any bugs, or if something seems incorrect.

Quickstart

Install using pip:

pip install djantic

Generating schemas from models

Configure a custom ModelSchema class for a Django model to generate a Pydantic model. This will allow using the Django model information with Pydantic model methods:

from users.models import User
from djantic import ModelSchema

class UserSchema(ModelSchema):
    class Config:
        model = User
        
print(UserSchema.schema())

Output:

{
        "title": "UserSchema",
        "description": "A user of the application.",
        "type": "object",
        "properties": {
            "profile": {"title": "Profile", "description": "None", "type": "integer"},
            "id": {"title": "Id", "description": "id", "type": "integer"},
            "first_name": {
                "title": "First Name",
                "description": "first_name",
                "maxLength": 50,
                "type": "string",
            },
            "last_name": {
                "title": "Last Name",
                "description": "last_name",
                "maxLength": 50,
                "type": "string",
            },
            "email": {
                "title": "Email",
                "description": "email",
                "maxLength": 254,
                "type": "string",
            },
            "created_at": {
                "title": "Created At",
                "description": "created_at",
                "type": "string",
                "format": "date-time",
            },
            "updated_at": {
                "title": "Updated At",
                "description": "updated_at",
                "type": "string",
                "format": "date-time",
            },
        },
        "required": ["first_name", "email", "created_at", "updated_at"],
    }

See https://pydantic-docs.helpmanual.io/usage/models/ for more.

Loading and exporting model data

Use the from_django method on a model schema class to load a Django model instance into a schema class:

user = User.objects.create(
    first_name="Jordan", 
    last_name="Eremieff", 
    email="[email protected]"
)

user_schema = UserSchema.from_django(user)
print(user_schema.json(indent=2))

Output:

{
    "profile": null,
    "id": 1,
    "first_name": "Jordan",
    "last_name": "Eremieff",
    "email": "[email protected]",
    "created_at": "2020-08-15T16:50:30.606345+00:00",
    "updated_at": "2020-08-15T16:50:30.606452+00:00"
}

See https://pydantic-docs.helpmanual.io/usage/exporting_models/ for more.

Issues
  • "ValueError: too many values to unpack" with TextChoices

    django==3.0.5
    pydantic==1.8.2
    djantic=0.3.0
    

    Attempting to create a djantic schema for a model that has a django.db.models.TextChoices subclass assigned to a field's choices will result in a ValueError

    For example:

    from django.db import models
    from djantic import ModelSchema
    
    
    class FoodChoices(models.TextChoices):
        BANANA = 'ba', 'A delicious yellow Banana'
        APPLE = 'ap', 'A delicious red Apple'
    
    
    class A(models.Model):
        name = models.CharField(max_length=128)
        preferred_food = models.CharField(max_length=2, choices=FoodChoices.choices)
    
    
    class ASchema(ModelSchema):
        class Config:
            model = A
    

    the above results in:

    ...
    /usr/local/lib/python3.8/site-packages/djantic/main.py in __new__(mcs, name, bases, namespace)
         95 
         96                     else:
    ---> 97                         python_type, pydantic_field = ModelSchemaField(field)
         98 
         99                     field_values[field_name] = (python_type, pydantic_field)
    
    /usr/local/lib/python3.8/site-packages/djantic/fields.py in ModelSchemaField(field)
         85         if field.choices:
         86             enum_choices = {v: k for k, v in field.choices}
    ---> 87             python_type = Enum(  # type: ignore
         88                 f"{field.name.title().replace('_', '')}Enum",
         89                 enum_choices,
    
    /usr/local/lib/python3.8/enum.py in __call__(cls, value, names, module, qualname, type, start)
        339             return cls.__new__(cls, value)
        340         # otherwise, functional API: we're creating a new Enum type
    --> 341         return cls._create_(
        342                 value,
        343                 names,
    
    /usr/local/lib/python3.8/enum.py in _create_(cls, class_name, names, module, qualname, type, start)
        461                 member_name, member_value = item, names[item]
        462             else:
    --> 463                 member_name, member_value = item
        464             classdict[member_name] = member_value
        465         enum_class = metacls.__new__(metacls, class_name, bases, classdict)
    
    ValueError: too many values to unpack (expected 2)
    
    opened by ievans3024 8
  • Fix: case for handling related ManyToMany extraction.

    Fix: case for handling related ManyToMany extraction.

    I had an issue with extraction of reverse ManyToMany relationship, and it seems like there simply was an unhandled case. I didn't really fix anything, I just copied your code from below and thus added another case. I didn't test this code with any automatic tool or unit tests, so beware. I'm rather new to python so feel free to do with it what you want. But please, if you don't pull - take at least the issue into account and do it your way :) I really appreciate your project and it saves our team a lot of time. Thank you.

    opened by AlterEigo 7
  • Error on django ImageField

    Error on django ImageField

    Error if I add in scheme field avatar

    Model:

    def get_avatar_upload_path(instance, filename):
        return os.path.join(
            "customer_avatar", str(instance.id), filename)
    
    
    class Customer(models.Model):
        """
        params: user* avatar shop* balance permissions deleted
    
        prefetch: -
        """
        id = models.AutoField(primary_key=True)
        user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='customers', verbose_name='Пользователь')
        avatar = models.ImageField(upload_to=get_avatar_upload_path, verbose_name='Аватарка', blank=True, null=True)
        retail = models.ForeignKey('retail.Retail', on_delete=models.CASCADE, related_name='customers', verbose_name='Розница')
        balance = models.DecimalField(max_digits=9, decimal_places=2, verbose_name='Баланс', default=0)
        permissions = models.ManyToManyField('retail.Permission', verbose_name='Права доступа', related_name='customers', blank=True)
        deleted = models.BooleanField(default=False, verbose_name='Удален')
        created = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
    

    Scheme

    class CustomerSchema(ModelSchema):
        user: UserSchema
    
        class Config:
            model = Customer
            include = ['id', 'user', 'avatar', 'retail', 'balance', 'permissions']
    

    Error:

    fastapi_1  | Process SpawnProcess-36:
    fastapi_1  | Traceback (most recent call last):
    fastapi_1  |   File "/usr/local/lib/python3.8/multiprocessing/process.py", line 315, in _bootstrap
    fastapi_1  |     self.run()
    fastapi_1  |   File "/usr/local/lib/python3.8/multiprocessing/process.py", line 108, in run
    fastapi_1  |     self._target(*self._args, **self._kwargs)
    fastapi_1  |   File "/usr/local/lib/python3.8/site-packages/uvicorn/subprocess.py", line 62, in subprocess_started
    fastapi_1  |     target(sockets=sockets)
    fastapi_1  |   File "/usr/local/lib/python3.8/site-packages/uvicorn/main.py", line 390, in run
    fastapi_1  |     loop.run_until_complete(self.serve(sockets=sockets))
    fastapi_1  |   File "uvloop/loop.pyx", line 1494, in uvloop.loop.Loop.run_until_complete
    fastapi_1  |   File "/usr/local/lib/python3.8/site-packages/uvicorn/main.py", line 397, in serve
    fastapi_1  |     config.load()
    fastapi_1  |   File "/usr/local/lib/python3.8/site-packages/uvicorn/config.py", line 278, in load
    fastapi_1  |     self.loaded_app = import_from_string(self.app)
    fastapi_1  |   File "/usr/local/lib/python3.8/site-packages/uvicorn/importer.py", line 20, in import_from_string
    fastapi_1  |     module = importlib.import_module(module_str)
    fastapi_1  |   File "/usr/local/lib/python3.8/importlib/__init__.py", line 127, in import_module
    fastapi_1  |     return _bootstrap._gcd_import(name[level:], package, level)
    fastapi_1  |   File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
    fastapi_1  |   File "<frozen importlib._bootstrap>", line 991, in _find_and_load
    fastapi_1  |   File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
    fastapi_1  |   File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
    fastapi_1  |   File "<frozen importlib._bootstrap_external>", line 783, in exec_module
    fastapi_1  |   File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
    fastapi_1  |   File "/opt/project/config/asgi.py", line 18, in <module>
    fastapi_1  |     from backend.retail.fastapp import app
    fastapi_1  |   File "/opt/project/backend/retail/fastapp.py", line 9, in <module>
    fastapi_1  |     from backend.retail.routers import router
    fastapi_1  |   File "/opt/project/backend/retail/routers.py", line 3, in <module>
    fastapi_1  |     from backend.retail.api import auth, postitem, ping, customer
    fastapi_1  |   File "/opt/project/backend/retail/api/customer.py", line 8, in <module>
    fastapi_1  |     from backend.retail.schemas.customer import CustomerSchema
    fastapi_1  |   File "/opt/project/backend/retail/schemas/customer.py", line 7, in <module>
    fastapi_1  |     class CustomerSchema(ModelSchema):
    fastapi_1  |   File "/usr/local/lib/python3.8/site-packages/djantic/main.py", line 97, in __new__
    fastapi_1  |     python_type, pydantic_field = ModelSchemaField(field)
    fastapi_1  |   File "/usr/local/lib/python3.8/site-packages/djantic/fields.py", line 135, in ModelSchemaField
    fastapi_1  |     python_type,
    fastapi_1  | UnboundLocalError: local variable 'python_type' referenced before assignment
    
    opened by 50Bytes-dev 4
  • Do you accept PRs?

    Do you accept PRs?

    Hey, this project is awesome and I would consider contributing a bit. Do you generally accept PRs?

    Best! Julian

    opened by JulianFeinauer 4
  • ValueError: cannot specify both default and default_factory

    ValueError: cannot specify both default and default_factory

    Hello,

    This is a great library. Looking to contribute if you are needing anything done.

    Getting this error but I see it looks fixed in your main branch.

      File "\.venv\lib\site-packages\djantic\main.py", line 102, in __new__
        p_model = create_model(
      File "pydantic\main.py", line 990, in pydantic.main.create_model
      File ".venv\lib\site-packages\djantic\main.py", line 34, in __new__
        cls = super().__new__(mcs, name, bases, namespace)
      File "pydantic\main.py", line 299, in pydantic.main.ModelMetaclass.__new__
      File "pydantic\fields.py", line 403, in pydantic.fields.ModelField.infer
      File "pydantic\fields.py", line 388, in pydantic.fields.ModelField._get_field_info
      File "pydantic\fields.py", line 173, in pydantic.fields.FieldInfo._validate
    ValueError: cannot specify both default and default_factory
    
    Django==3.2.3
    djantic==0.2.0
    pydantic==1.8.2
    

    So just posting this for tracking incase anyone else has the same issue.

    opened by mmcardle 4
  • Django JSON API view

    Django JSON API view

    Either a re-usable CBV or perhaps just some documentation would be good for how to create a basic JSON API view using the model schemas.

    opened by jordaneremieff 3
  • Testing djantic for django 3.2

    Testing djantic for django 3.2

    Gave the code a try, went to open a PR for BigAutoField django 3.2 changes, but noticed they were in the main branch already.

    Tried install from pypi 0.2.0 and also looked in the tagged zip and didn't see the changes there.

    updated my requirements.txt

    from

    djantic==0.2.0
    

    to:

    -e git+https://github.com/jordaneremieff/[email protected]#egg=djantic
    

    Was there some other stuff being worked on for django 3.2 support?

    Thanks

    opened by allen-munsch 3
  • FastAPI + Djantic - OpenAPI spec

    FastAPI + Djantic - OpenAPI spec

    HI,

    First of all, this is a great project, getting a lot of value from it, so thank you.

    Having an issue generating an openapi specification when using it in combination with FastAPI, specifically when using Django model choices.

    Django Model

    from django.db import models
    
    class SimpleModelWithChoices(models.Model):
        class ChoiceType(models.TextChoices):
            CHOICE1 = "CHOICE1"
            CHOICE2 = "CHOICE2"
    
        choice = models.CharField("Type", max_length=30, choices=ChoiceType.choices)
    

    Djantic Spec

    from djantic import ModelSchema
    
    class Schema1(ModelSchema):
        class Config:
            model = SimpleModelWithChoices
    
    
    class Schema2(ModelSchema):
        class Config:
            model = SimpleModelWithChoices
    

    FastAPI

    from fastapi import APIRouter, FastAPI
    
    router = APIRouter()
    application = FastAPI()
    
    @application.get("path1", response_model=Schema1)
    def get1(instance: Schema1):
        return instance
    
    @application.get("path2", response_model=Schema2)
    def get2(instance: Schema2):
        return instance
    
    application.include_router(router)
    
    assert application.openapi()
    

    Error

    .venv\lib\site-packages\fastapi\openapi\utils.py:387: in get_openapi
        definitions = get_model_definitions(
    
        def get_model_definitions(
            flat_models: Set[Union[Type[BaseModel], Type[Enum]]],
            model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str],
        ) -> Dict[str, Any]:
            definitions: Dict[str, Dict[str, Any]] = {}
            for model in flat_models:
                m_schema, m_definitions, m_nested_models = model_process_schema(
                    model, model_name_map=model_name_map, ref_prefix=REF_PREFIX
                )
                definitions.update(m_definitions)
    >           model_name = model_name_map[model]
    E           KeyError: <enum 'ChoiceEnum'>
    
    .venv\lib\site-packages\fastapi\utils.py:28: KeyError
    

    I think it is due to the way FastAPI keeps a mapping of classes and uses them internally. I have a workaround in my project

    def __fix_enums(new_type):
    
        for item in new_type.__dict__["__fields__"].values():
            if "enum.EnumMeta" in str(item.type_.__class__):
                enum_values = [(i.value, i.value) for i in item.type_]
    
                new_enum_type = Enum(f"{new_type.__name__}_{item.name}_Enum", enum_values, module=__name__)
                setattr(item, "type_", new_enum_type)
    
        return new_type
    

    I would be happy to produce a PR to fix the issue if needed, Maybe adding some sort of uniqueness in the name of the enum class in djantic/fields.py ?

                python_type = Enum(  # type: ignore
                    f"{field.name.title().replace('_', '')}Enum",
                    enum_choices,
                    module=__name__,
                )
    

    Library Versions

    djantic = "^0.3.5"
    Django = "^3.2.5"
    fastapi = "^0.68.0"
    
    opened by mmcardle 2
  • Support for BigAutoField

    Support for BigAutoField

    opened by raratiru 1
  • Support for postgres ArrayField

    Support for postgres ArrayField

    • Adds support for django.contrib.postgres.fields.ArrayField
    opened by mmcardle 1
  • Combine model and schema

    Combine model and schema

    The approach in https://github.com/tiangolo/sqlmodel has me interested exploring a combined model for Django and Pydantic. I would consider doing a PR to that project, though it seems too specific to SQLAlchemy and FastAPI for a Django-specific implementation to be a good fit (also don't know if I'll pursue this at all) - not sure.

    Something like this for the model definition:

    from djantic.models import DjanticModel, Field
    
    class Plant(DjanticModel):
        """A plant."""
    
        common_name: str
        genus_name: str = Field(
            max_length=100,
            help_text="A generic name for a collective group of plants.",
        )
        specific_epithet: str
    
        class Meta:
            ordering = ["genus_name"]
    
        class Config:
            include = ["genus_name", "specific_epithet"]
    

    Then attribute access would (ideally) look like this:

    from myapp.models import Plant
    
    # Schema definition from class
    schema = Plant.schema()
    
    # Export data from instance
    plant = Plant.objects.create(genus_name="Grevillea", specific_epithet="buxifolia")
    data = plant.dict()
    

    Maybe.

    maybe 
    opened by jordaneremieff 0
  • Unable to support more than Level 2 relations

    Unable to support more than Level 2 relations

    class Level1(models.Mode): name = models.CharField()

    class Level2(models.Model): parent = models.ForeignKey(Level1) name2 = models.Charge()

    class Level3(models.Model): parent = models.ForeignKey(Level2) name3 = models.Charge()

    class Level3Schema(ModelSchema): name3: str = Field()

    class Level2Schema(ModelSchema): level3_set:List[Level3Schema] = Field(None) name2: str = Field(None)

    class Level1Schema(ModelSchema): level2_set :List[Level2Schema] = Field(None) name1:str = Field(None)

       as following is not work:
     Level1Schema.from_django(level1_instance)
    
    opened by faner-father 1
  • Fix type hints

    Fix type hints

    Currently mypy is commented out in the test script until this is resolved.

    opened by jordaneremieff 0
  • Documentation, docstrings, comments, changelog

    Documentation, docstrings, comments, changelog

    I didn't include any docstrings or comments throughout the project because things were changing quite rapidly, but at this point I should go through and add these. Also a mkdocs gh-page and changelog.

    documentation 
    opened by jordaneremieff 0
  • Support missing field types

    Support missing field types

    Currently the Postgres field types aren't supported (except JSONField). Maybe others.

    opened by jordaneremieff 2
  • Determine supported versions

    Determine supported versions

    This would include:

    • Pydantic
    • Django
    • Python
    documentation 
    opened by jordaneremieff 1
  • Import exported model data to create/update existing records

    Import exported model data to create/update existing records

    data = SchemaModel.dict()
    imported = SchemaModel.parse_django(data)
    

    The parse_django maybe would have a different name, but this is what I'm generally thinking for the API.

    enhancement 
    opened by jordaneremieff 0
  • Django admin integration for import/export

    Django admin integration for import/export

    Create actions for import and export behavior that supports the following formats:

    • JSON
    • CSV

    This would probably be implemented using a base admin view class or admin view class mixins.

    enhancement maybe 
    opened by jordaneremieff 0
  • Support related include/exclude in configuration

    Support related include/exclude in configuration

    Something like:

    class ArticleSchema(PydanticDjangoModel):
    
        publications: List[PublicationSchema]
    
        class Config:
            model = Article
            exclude = ["publications__created_at", "publications__updated_at"]
    
    enhancement 
    opened by jordaneremieff 0
Releases(0.4.1)
The ormar package is an async mini ORM for Python, with support for Postgres, MySQL, and SQLite.

python async mini orm with fastapi in mind and pydantic validation

null 674 Oct 23, 2021
Piccolo - A fast, user friendly ORM and query builder which supports asyncio.

A fast, user friendly ORM and query builder which supports asyncio.

null 566 Oct 20, 2021
Pydantic model support for Django ORM

Pydantic model support for Django ORM

Jordan Eremieff 189 Oct 23, 2021
SQLModel is a library for interacting with SQL databases from Python code, with Python objects.

SQLModel is a library for interacting with SQL databases from Python code, with Python objects. It is designed to be intuitive, easy to use, highly compatible, and robust.

Sebastián Ramírez 5.5k Oct 24, 2021
A curated list of awesome tools for SQLAlchemy

Awesome SQLAlchemy A curated list of awesome extra libraries and resources for SQLAlchemy. Inspired by awesome-python. (See also other awesome lists!)

Hong Minhee (洪 民憙) 2.3k Oct 21, 2021
a small, expressive orm -- supports postgresql, mysql and sqlite

peewee Peewee is a simple and small ORM. It has few (but expressive) concepts, making it easy to learn and intuitive to use. a small, expressive ORM p

Charles Leifer 8.7k Oct 25, 2021
The Orator ORM provides a simple yet beautiful ActiveRecord implementation.

Orator The Orator ORM provides a simple yet beautiful ActiveRecord implementation. It is inspired by the database part of the Laravel framework, but l

Sébastien Eustace 1.3k Oct 17, 2021
Pony Object Relational Mapper

Downloads Pony Object-Relational Mapper Pony is an advanced object-relational mapper. The most interesting feature of Pony is its ability to write que

null 2.7k Oct 15, 2021
Beanie - is an Asynchronous Python object-document mapper (ODM) for MongoDB

Beanie - is an Asynchronous Python object-document mapper (ODM) for MongoDB, based on Motor and Pydantic.

Roman 277 Oct 18, 2021
A Python Object-Document-Mapper for working with MongoDB

MongoEngine Info: MongoEngine is an ORM-like layer on top of PyMongo. Repository: https://github.com/MongoEngine/mongoengine Author: Harry Marr (http:

MongoEngine 3.6k Oct 24, 2021
A Python Library for Simple Models and Containers Persisted in Redis

Redisco Python Containers and Simple Models for Redis Description Redisco allows you to store objects in Redis. It is inspired by the Ruby library Ohm

sebastien requiem 434 Oct 1, 2021
A very simple CRUD class for SQLModel! ✨

Base SQLModel A very simple CRUD class for SQLModel! ✨ Inspired on: Full Stack FastAPI and PostgreSQL - Base Project Generator FastAPI Microservices I

Marcelo Trylesinski 12 Oct 22, 2021
A pythonic interface to Amazon's DynamoDB

PynamoDB A Pythonic interface for Amazon's DynamoDB. DynamoDB is a great NoSQL service provided by Amazon, but the API is verbose. PynamoDB presents y

null 1.7k Oct 23, 2021
Adds SQLAlchemy support to Flask

Flask-SQLAlchemy Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application. It aims to simplify using SQLAlchemy

The Pallets Projects 3.6k Oct 22, 2021
MongoEngine flask extension with WTF model forms support

Flask-MongoEngine Info: MongoEngine for Flask web applications. Repository: https://github.com/MongoEngine/flask-mongoengine About Flask-MongoEngine i

MongoEngine 788 Oct 14, 2021
Easy-to-use data handling for SQL data stores with support for implicit table creation, bulk loading, and transactions.

dataset: databases for lazy people In short, dataset makes reading and writing data in databases as simple as reading and writing JSON files. Read the

Friedrich Lindenberg 4.1k Oct 18, 2021
Rich Python data types for Redis

Created by Stephen McDonald Introduction HOT Redis is a wrapper library for the redis-py client. Rather than calling the Redis commands directly from

Stephen McDonald 273 Sep 2, 2021