Pydantic: Autogenerating models with a metaclass, or is there a better way?

Created on 7 Sep 2020  路  2Comments  路  Source: samuelcolvin/pydantic

Question

Output of python -c "import pydantic.utils; print(pydantic.utils.version_info())":

             pydantic version: 1.6.1
            pydantic compiled: True
                 install path: /home/robyoung/dev/playground/python/venv/lib/python3.8/site-packages/pydantic
               python version: 3.8.3 (default, Jul 30 2020, 21:11:12)  [GCC 9.3.0]
                     platform: Linux-5.4.0-45-generic-x86_64-with-glibc2.29
     optional deps. installed: ['typing-extensions']

We have different needs for a model based on the context in which it is used. For example, when creating a new entity we MUST NOT provide the the id field as that will be generated, however when retrieving an existing entity it MUST be included in the response. We are currently defining separate models for the separate contexts. Using inheritance is difficult because mypy does not like the type of the field changing and is brittle because we have to repeat the same rules for each model.

So, I am experimenting with auto-generating these models with a metaclass however, before going down that route I want to be sure there is not a more idiomatic solution available.

Here is an example of what I am planning with metaclasses:

from typing import Optional

import pydantic

def _create_model(name, dct, annotations):
    return type(
        name,
        (pydantic.BaseModel,),
        {"__qualname__": name, "__annotations__": annotations, **dct},
    )

def _optionalise(annotations):
    return {name: Optional[value] for name, value in annotations.items()}

class MyMetaclass(type):
    def __new__(cls, name, bases, dct):
        annotations = dct.pop("__annotations__")
        qual_name = dct.pop("__qualname__")

        model = super().__new__(
            cls,
            name,
            bases,
            {
                "Create": _create_model(f"Create{name}", dct, annotations),
                "Update": _create_model(f"Update{name}", dct, _optionalise(annotations)),
                "Get": _create_model(f"Get{name}", dct, {"id": int, **annotations}),
                "__annotations__": annotations,
                "__qualname__": qual_name,
            },
        )
        return model

class User(metaclass=MyMetaclass):
    name: str
    age: int

create_data = {"name": "dave", "age": 31}
update_data = {"age": 32}
get_data = {"id": 123, **create_data}

create_user = User.Create(**create_data)
update_user = User.Update(**update_data)
get_user = User.Get(**get_data)
question

All 2 comments

I have seen that, yes. It doesn't quite fit my problem as my models aren't really dynamic I just want to have more of them defined from a single source. If I were to go down the dynamic model creation route I would lose the ability to use the nice, easy to read model definition syntax.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

drpoggi picture drpoggi  路  3Comments

timonbimon picture timonbimon  路  3Comments

engstrom picture engstrom  路  3Comments

sbv-trueenergy picture sbv-trueenergy  路  3Comments

AlbertMukhammadiev picture AlbertMukhammadiev  路  3Comments