From 28caa067716645bb7385afc5de8691f95311a8b3 Mon Sep 17 00:00:00 2001 From: bit Date: Sun, 29 May 2022 14:06:43 +0200 Subject: [PATCH] initial commit --- README.md | 3 +++ pyproject.toml | 3 +++ setup.cfg | 22 ++++++++++++++++++ src/pydantic_uuid_model/__init__.py | 12 ++++++++++ src/pydantic_uuid_model/error.py | 16 +++++++++++++ src/pydantic_uuid_model/meta.py | 36 +++++++++++++++++++++++++++++ src/pydantic_uuid_model/model.py | 11 +++++++++ 7 files changed, 103 insertions(+) create mode 100644 README.md create mode 100644 pyproject.toml create mode 100644 setup.cfg create mode 100644 src/pydantic_uuid_model/__init__.py create mode 100644 src/pydantic_uuid_model/error.py create mode 100644 src/pydantic_uuid_model/meta.py create mode 100644 src/pydantic_uuid_model/model.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..feb6005 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# pydantic_uuid_model + +Add support for deserialization of pydantic child models by saving class unique ids in the serialized data. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2f21011 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=40.8.0", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..547d7e2 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,22 @@ +[metadata] +name = pydantic_uuid_model +version = attr: pydantic_uuid_model.__version__ +description = Add support for deserialization of pydantic child models. +long_description = file: README.md +long_description_content_type = text/markdown +classifiers = + Development Status :: 2 - Pre-Alpha + Intended Audience :: Science/Research + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + +[options] +install_requires = + pydantic +package_dir= + =src +packages=find: + +[options.packages.find] +where=src diff --git a/src/pydantic_uuid_model/__init__.py b/src/pydantic_uuid_model/__init__.py new file mode 100644 index 0000000..d1f2cbe --- /dev/null +++ b/src/pydantic_uuid_model/__init__.py @@ -0,0 +1,12 @@ +__version__ = "1.1.0" + +__all__ = [ + "UUIDAlreadyExistsException", + "UUIDModelMetaclass", + "UUIDBaseModel", +] + +from .error import UUIDAlreadyExistsException +from .meta import UUIDModelMetaclass +from .model import UUIDBaseModel + diff --git a/src/pydantic_uuid_model/error.py b/src/pydantic_uuid_model/error.py new file mode 100644 index 0000000..beb3674 --- /dev/null +++ b/src/pydantic_uuid_model/error.py @@ -0,0 +1,16 @@ +from typing import Optional + + +class UUIDAlreadyExistsException(Exception): + uuid: str + name: Optional[str] + + def __init__(self, uuid: str, name: Optional[str] = None): + if name is not None: + msg = f"Cannot register class '{name}' with UUID '{uuid}', as another class with this UUID is already registered" + else: + msg = f"Cannot register class with UUID '{uuid}' as another class with this UUID is already registered" + super().__init__(msg) + self.uuid = uuid + self.name = name + diff --git a/src/pydantic_uuid_model/meta.py b/src/pydantic_uuid_model/meta.py new file mode 100644 index 0000000..da493ae --- /dev/null +++ b/src/pydantic_uuid_model/meta.py @@ -0,0 +1,36 @@ +from typing import Any, Optional + +from pydantic.main import ModelMetaclass + +from .error import UUIDAlreadyExistsException + + +class UUIDModelMetaclass(ModelMetaclass): + + def __call__(cls, *args: Any, **kwargs: Any) -> Any: + uuid_name = cls.__sub_classes__[None] + if uuid_name not in kwargs: + return super().__call__(*args, **kwargs) + uuid = kwargs.pop(uuid_name) + if uuid is not None: + cls = cls.__sub_classes__.get(str(uuid), cls) + return cls(*args, **kwargs) + + def __new__(mcs, name, bases, namespace, uuid_name: Optional[str] = None, base: bool = False, **kwargs): + return super().__new__(mcs, name, bases, namespace, **kwargs) + + def __init__(cls, name, bases, namespace, uuid_name: Optional[str] = None, base: bool = False, **kwargs): + if hasattr(cls, "__sub_classes__") and uuid_name is None and not base: + uuid_name = cls.__sub_classes__[None] + uuid = namespace.get(uuid_name, None) + if uuid is not None: + uuid = str(uuid) + if uuid in cls.__sub_classes__: + raise UUIDAlreadyExistsException(uuid, name) + cls.__sub_classes__[uuid] = cls + else: + uuid_name = uuid_name or "muuid" + d = dict() + d[None] = uuid_name + setattr(cls, "__sub_classes__", d) + diff --git a/src/pydantic_uuid_model/model.py b/src/pydantic_uuid_model/model.py new file mode 100644 index 0000000..2c8e4ea --- /dev/null +++ b/src/pydantic_uuid_model/model.py @@ -0,0 +1,11 @@ +from typing import ClassVar, Dict, Optional, Union + +from pydantic import BaseModel + +from .meta import UUIDModelMetaclass + + +class UUIDBaseModel(BaseModel, metaclass=UUIDModelMetaclass): + muuid: Optional[str] + __sub_classes__: ClassVar[Dict[Optional[str], Union[str, "UUIDBaseModel"]]] +