Skip to content

Commit

Permalink
build(ormar): Update ormar to Pydantic 2 release, fix Atsume subclassing
Browse files Browse the repository at this point in the history
  • Loading branch information
pmdevita committed Aug 24, 2024
1 parent c11d268 commit 546bce5
Show file tree
Hide file tree
Showing 3 changed files with 297 additions and 154 deletions.
129 changes: 87 additions & 42 deletions atsume/db/models.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,53 @@
import dataclasses

import ormar
import typing
import inspect

from ormar import OrmarConfig
from ormar.models.helpers import merge_or_generate_pydantic_config
from ormar.models.metaclass import ModelMetaclass as OrmarModelMetaclass
from pydantic._internal._generics import PydanticGenericMetadata

from atsume.component.manager import manager
from atsume.db.manager import database

if typing.TYPE_CHECKING:
from atsume.component.component_config import ComponentConfig


class ModelMeta(ormar.ModelMeta):
"""
A subclass of the Ormar ModelMeta that carries a few extra properties.
"""

_qual_name: str
@dataclasses.dataclass
class AtsumeConfig:
qual_name: str
component_config: "ComponentConfig"
name: typing.Optional[str] = None
plural_name: typing.Optional[str] = None

@classmethod
def get_name(cls) -> str:
return cls.name if cls.name else cls._qual_name
def get_name(self) -> str:
return self.name if self.name else self.qual_name

def get_plural_name(self) -> str:
return self.plural_name if self.plural_name else f"{self.get_name()}s"

def get_default_table_name(self) -> str:
return f"{self.component_config.name.lower()}_{self.get_plural_name()}"


class OrmarAttrs(dict[str, typing.Any]):
"""Ormar currently sets (not appends) ignored_types so we need to stop it from doing that"""

def __init__(self, data: dict[str, typing.Any]) -> None:
super().__init__()
self._data = data

@classmethod
def get_plural_name(cls) -> str:
return cls.plural_name if cls.plural_name else f"{cls.get_name()}s"
def __getitem__(self, item: str) -> typing.Any:
if item == "model_config":
return self._data[item].copy()
return self._data[item]

def __setitem__(self, key: str, value: typing.Any) -> None:
if key == "model_config":
return
self._data[key] = value


class ModelMetaclass(OrmarModelMetaclass):
Expand All @@ -34,13 +56,18 @@ class ModelMetaclass(OrmarModelMetaclass):
Model class creation.
"""

# Ormar is ignoring the types for this, guess we have to too ¯\_(ツ)_/¯
def __new__( # type: ignore
def __new__( # type: ignore # noqa: CCR001
mcs: "ModelMetaclass",
name: str,
bases: typing.Any,
attrs: dict[str, typing.Any],
) -> OrmarModelMetaclass:
__pydantic_generic_metadata__: typing.Union[
PydanticGenericMetadata, None
] = None,
__pydantic_reset_parent_namespace__: bool = True,
_create_model_module: typing.Union[str, None] = None,
**kwargs,
) -> type:
# This condition is true when the Atsume ModelMetaclass itself is being created.
# Since this isn't a normal model class creation, many properties are missing so
# the rest of this should be skipped.
Expand All @@ -51,34 +78,52 @@ def __new__( # type: ignore
config = manager.get_config_from_models_path(attrs["__module__"])
assert config is not None

# If this model doesn't have a Meta class property, create it
if "Meta" not in attrs:

class Meta(ModelMeta):
pass
qual_name = attrs["__qualname__"].lower()

attrs["Meta"] = Meta
meta = Meta
# If this model doesn't have a Meta class property, create it
if "atsume_config" not in attrs:
atsume_config = AtsumeConfig(qual_name, config)
attrs["atsume_config"] = atsume_config
else:
meta = attrs["Meta"]

meta._qual_name = attrs["__qualname__"].lower()

if not hasattr(meta, "component_config"):
meta.component_config = config

if not hasattr(meta, "tablename"):
meta.tablename = (
f"{meta.component_config.name.lower()}_{meta.get_plural_name()}"
)

if not hasattr(meta, "database") and database.database:
meta.database = database.database

if not hasattr(meta, "metadata"):
meta.metadata = config._model_metadata

model_class = super().__new__(mcs, name, bases, attrs)
atsume_config = attrs["atsume_config"]
if not isinstance(atsume_config, AtsumeConfig):
raise Exception(
f"Model {name}'s atsume_config property needs to be an AtsumeConfig instance."
)

atsume_config.qual_name = attrs["__qualname__"].lower()
atsume_config.component_config = config

if "ormar_config" not in attrs:
ormar_config = OrmarConfig()
attrs["ormar_config"] = ormar_config
else:
ormar_config = attrs["ormar_config"]

if ormar_config.tablename is None:
ormar_config.tablename = atsume_config.get_default_table_name()

if database.database:
ormar_config.database = database.database

ormar_config.metadata = config._model_metadata

# Since we are blocking Ormar from touching the model_config,
# we need to perform some operations for it
merge_or_generate_pydantic_config(attrs, name)
attrs["model_config"]["ignored_types"] = attrs["model_config"].get(
"ignored_types", tuple()
) + (OrmarConfig, AtsumeConfig)
attrs["model_config"]["from_attributes"] = True

model_class = typing.cast(
ModelMetaclass,
super().__new__(mcs, name, bases, OrmarAttrs(attrs)),
__pydantic_generic_metadata__,
__pydantic_reset_parent_namespace__,
_create_model_module,
**kwargs,
)
config._models.append(model_class)
return model_class

Expand Down
Loading

0 comments on commit 546bce5

Please sign in to comment.