Skip to content

Commit

Permalink
Merge pull request #129 from mauro-andre/MainBaseModel
Browse files Browse the repository at this point in the history
✨ MainBaseModel
  • Loading branch information
mauro-andre authored May 23, 2024
2 parents 2b76383 + 2485a6b commit 063f1c8
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 62 deletions.
2 changes: 1 addition & 1 deletion pyodmongo/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .async_engine.engine import AsyncDbEngine
from .engine.engine import DbEngine
from .models.db_model import DbModel
from .models.db_model import DbModel, MainBaseModel
from .models.id_model import Id
from .models.paginate import ResponsePaginate
from .models.responses import SaveResponse, DeleteResponse
Expand Down
63 changes: 3 additions & 60 deletions pyodmongo/models/db_model.py
Original file line number Diff line number Diff line change
@@ -1,70 +1,13 @@
from pydantic import ConfigDict
from .id_model import Id
from datetime import datetime
from typing import Any, ClassVar
from ..services.model_init import (
resolve_indexes,
resolve_class_fields_db_info,
resolve_reference_pipeline,
# resolve_db_fields,
)
from typing import ClassVar
from pydantic import BaseModel
from pydantic._internal._model_construction import ModelMetaclass
from typing_extensions import dataclass_transform
from typing import ClassVar
import copy


@dataclass_transform(kw_only_default=True)
class DbMeta(ModelMetaclass):
"""
Metaclass for database model entities in a PyODMongo environment. It extends
the functionality of the ModelMetaclass by applying specific behaviors and
transformations related to database operations such as indexing, reference
resolution, and initialization of database fields.
Attributes:
__pyodmongo_complete__ (bool): Attribute used to track the completion of
the meta-level configuration.
Methods:
__new__(cls, name, bases, namespace, **kwargs): Constructs a new class
instance, ensuring database-specific adjustments and initializations
are applied.
__getattr__(cls, name): Custom attribute access handling that supports
dynamic attributes based on database field definitions.
"""

def __new__(
cls, name: str, bases: tuple[Any], namespace: dict, **kwargs: Any
) -> type:
setattr(cls, "__pyodmongo_complete__", False)
for base in bases:
setattr(base, "__pyodmongo_complete__", False)

# TODO finish db_fields after ModelMetaclass
# db_fields = copy.deepcopy(namespace.get("__annotations__"))
# db_fields = resolve_db_fields(bases=bases, db_fields=db_fields)

cls: BaseModel = ModelMetaclass.__new__(cls, name, bases, namespace, **kwargs)

setattr(cls, "__pyodmongo_complete__", True)
for base in bases:
setattr(base, "__pyodmongo_complete__", True)
from .metaclasses import PyOdmongoMeta, DbMeta

resolve_class_fields_db_info(cls=cls)
pipeline = resolve_reference_pipeline(cls=cls, pipeline=[])
setattr(cls, "_reference_pipeline", pipeline)
indexes = resolve_indexes(cls=cls)
setattr(cls, "_init_indexes", indexes)
return cls

def __getattr__(cls, name: str):
if cls.__dict__.get("__pyodmongo_complete__"):
is_attr = name in cls.__dict__.get("model_fields").keys()
if is_attr:
return cls.__dict__.get(name + "__pyodmongo")
ModelMetaclass.__getattr__(cls, name)
class MainBaseModel(BaseModel, metaclass=PyOdmongoMeta): ...


class DbModel(BaseModel, metaclass=DbMeta):
Expand Down
93 changes: 93 additions & 0 deletions pyodmongo/models/metaclasses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from typing import Any
from ..services.model_init import (
resolve_indexes,
resolve_class_fields_db_info,
resolve_reference_pipeline,
)
from pydantic import BaseModel
from pydantic._internal._model_construction import ModelMetaclass
from typing_extensions import dataclass_transform


@dataclass_transform(kw_only_default=True)
class PyOdmongoMeta(ModelMetaclass):
"""
Metaclass for creating and configuring PyODMongo models. This metaclass extends
the functionality of Pydantic's ModelMetaclass to include database-specific
configurations and transformations.
The primary responsibilities of this metaclass are:
- Setting and tracking the `__pyodmongo_complete__` attribute to ensure proper
initialization of database-related features.
- Applying necessary transformations to class fields for database compatibility.
- Providing custom attribute access behavior for dynamically generated attributes.
Attributes:
__pyodmongo_complete__ (bool): Indicator of whether the meta-level
configuration is complete.
Methods:
__new__(cls, name, bases, namespace, **kwargs): Constructs a new class instance,
applying database-specific initializations.
__getattr__(cls, name): Custom attribute access method supporting dynamic
attribute retrieval based on model fields.
"""

def __new__(
cls, name: str, bases: tuple[Any], namespace: dict, **kwargs: Any
) -> type:
setattr(cls, "__pyodmongo_complete__", False)
for base in bases:
setattr(base, "__pyodmongo_complete__", False)

cls: BaseModel = ModelMetaclass.__new__(cls, name, bases, namespace, **kwargs)

setattr(cls, "__pyodmongo_complete__", True)
for base in bases:
setattr(base, "__pyodmongo_complete__", True)

resolve_class_fields_db_info(cls=cls)
return cls

def __getattr__(cls, name: str):
if cls.__dict__.get("__pyodmongo_complete__"):
is_attr = name in cls.__dict__.get("model_fields").keys()
if is_attr:
return cls.__dict__.get(name + "__pyodmongo")
ModelMetaclass.__getattr__(cls, name)


@dataclass_transform(kw_only_default=True)
class DbMeta(PyOdmongoMeta):
"""
Metaclass for database model entities in a PyODMongo environment. It extends
the functionality of the PyOdmongoMeta by applying specific behaviors and
transformations related to database operations such as indexing, reference
resolution, and initialization of database fields.
The primary responsibilities of this metaclass are:
- Constructing new class instances with additional database-specific adjustments
and initializations.
- Setting up pipelines for resolving references within the database context.
- Configuring indexes for efficient database operations.
Attributes:
__pyodmongo_complete__ (bool): Attribute used to track the completion of
the meta-level configuration.
Methods:
__new__(cls, name, bases, namespace, **kwargs): Constructs a new class instance,
ensuring database-specific adjustments and initializations are applied.
"""

def __new__(
cls, name: str, bases: tuple[Any], namespace: dict, **kwargs: Any
) -> type:

cls: BaseModel = PyOdmongoMeta.__new__(cls, name, bases, namespace, **kwargs)

pipeline = resolve_reference_pipeline(cls=cls, pipeline=[])
setattr(cls, "_reference_pipeline", pipeline)
indexes = resolve_indexes(cls=cls)
setattr(cls, "_init_indexes", indexes)
return cls
16 changes: 15 additions & 1 deletion tests/test_class.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
from pyodmongo import DbModel, Id
from pyodmongo import MainBaseModel, DbModel, Id
from pyodmongo.models.db_field_info import DbField
from pydantic import EmailStr, ValidationError
from datetime import datetime
from typing import ClassVar, Union
import pytest


def test_main_base_model():
class MainModel(MainBaseModel):
attr_1: int
attr_2: str

assert issubclass(MainModel, MainBaseModel)
assert isinstance(MainModel.attr_1, DbField)
assert isinstance(MainModel.attr_2, DbField)
obj = MainModel(attr_1=1, attr_2="one")
assert obj.attr_1 == 1
assert obj.attr_2 == "one"


def test_create_class():
class MyClass(DbModel):
_collection: ClassVar = "myclass"
Expand Down

0 comments on commit 063f1c8

Please sign in to comment.