Skip to content

Commit

Permalink
Revamp BaseModel for parity with other domain elements (#434)
Browse files Browse the repository at this point in the history
Also, remove `ModelMeta` and use the general `Options` class for meta options.
  • Loading branch information
subhashb authored Jun 9, 2024
1 parent d7a8d73 commit c9813bf
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 59 deletions.
21 changes: 17 additions & 4 deletions src/protean/adapters/repository/elasticsearch.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from elasticsearch.exceptions import NotFoundError
from elasticsearch_dsl import Document, Index, Keyword, Mapping, Search, query

from protean.container import Options
from protean.exceptions import ObjectNotFoundError
from protean.fields import Reference
from protean.globals import current_domain
Expand Down Expand Up @@ -399,10 +400,24 @@ def decorate_model_class(self, entity_cls, model_cls):

# Construct Inner Index class with options
options = {}
options["name"] = model_cls.meta_.schema_name or schema_name

# Set schema name intelligently
# model_cls.meta_.schema_name - would come from custom model's options
# model_cls._index._name - would come from custom model's `Index` inner class
# schema_name - is derived
index_name = (
model_cls._index._name if hasattr(model_cls, "_index") else None
)
options["name"] = model_cls.meta_.schema_name or index_name or schema_name

# Gather adapter settings
if "SETTINGS" in self.conn_info and self.conn_info["SETTINGS"]:
options["settings"] = self.conn_info["SETTINGS"]

# Set options into `Index` inner class for ElasticsearchModel
index_cls = type("Index", (object,), options)

# Add the Index class to the custom attributes
custom_attrs.update({"Index": index_cls})

# FIXME Ensure the custom model attributes are constructed properly
Expand All @@ -423,9 +438,7 @@ def construct_model_class(self, entity_cls):
if entity_cls.meta_.schema_name in self._model_classes:
model_cls = self._model_classes[entity_cls.meta_.schema_name]
else:
from protean.core.model import ModelMeta

meta_ = ModelMeta()
meta_ = Options()
meta_.entity_cls = entity_cls

# Construct Inner Index class with options
Expand Down
9 changes: 3 additions & 6 deletions src/protean/adapters/repository/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing import Any
from uuid import UUID

from protean.container import Options
from protean.core.model import BaseModel
from protean.exceptions import ObjectNotFoundError, ValidationError
from protean.fields.basic import Auto
Expand Down Expand Up @@ -148,9 +149,7 @@ def decorate_model_class(self, entity_cls, model_cls):
if key not in ["Meta", "__module__", "__doc__", "__weakref__"]
}

from protean.core.model import ModelMeta

meta_ = ModelMeta()
meta_ = Options()
meta_.entity_cls = entity_cls

custom_attrs.update({"meta_": meta_})
Expand All @@ -172,9 +171,7 @@ def construct_model_class(self, entity_cls):
if entity_cls.meta_.schema_name in self._model_classes:
model_cls = self._model_classes[entity_cls.meta_.schema_name]
else:
from protean.core.model import ModelMeta

meta_ = ModelMeta()
meta_ = Options()
meta_.entity_cls = entity_cls

attrs = {
Expand Down
9 changes: 3 additions & 6 deletions src/protean/adapters/repository/sqlalchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from sqlalchemy.exc import DatabaseError
from sqlalchemy.types import CHAR, TypeDecorator

from protean.container import Options
from protean.core.value_object import BaseValueObject
from protean.core.model import BaseModel
from protean.exceptions import (
Expand Down Expand Up @@ -695,9 +696,7 @@ def decorate_model_class(self, entity_cls, model_cls):
# Add the earlier copied columns to the custom attributes
custom_attrs = {**custom_attrs, **columns}

from protean.core.model import ModelMeta

meta_ = ModelMeta(model_cls.meta_)
meta_ = Options(model_cls.meta_)
meta_.entity_cls = entity_cls
meta_.schema_name = (
schema_name if meta_.schema_name is None else meta_.schema_name
Expand All @@ -724,10 +723,8 @@ def construct_model_class(self, entity_cls):
if entity_cls.meta_.schema_name in self._model_classes:
model_cls = self._model_classes[entity_cls.meta_.schema_name]
else:
from protean.core.model import ModelMeta

# Construct a new Meta object with existing values
meta_ = ModelMeta()
meta_ = Options()
meta_.entity_cls = entity_cls
# If schema_name is not provided, sqlalchemy can throw
# sqlalchemy.exc.InvalidRequestError: Class does not
Expand Down
11 changes: 6 additions & 5 deletions src/protean/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,16 @@ def __init__(self, opts: Union[dict, Type] = None) -> None:
self._opts = set()

if opts:
if inspect.isclass(opts):
# FIXME Remove support passing a class as opts after revamping BaseSerializer
# The `inspect.isclass` check will not be necessary
if isinstance(opts, (self.__class__)) or inspect.isclass(opts):
attributes = inspect.getmembers(
opts, lambda a: not (inspect.isroutine(a))
)
for attr in attributes:
if not (attr[0].startswith("__") and attr[0].endswith("__")):
if not (
attr[0].startswith("__") and attr[0].endswith("__")
) and attr[0] not in ["_opts"]:
setattr(self, attr[0], attr[1])

self.abstract = getattr(opts, "abstract", None) or False
Expand Down Expand Up @@ -109,9 +113,6 @@ def _set_defaults(cls):
if not (hasattr(cls.meta_, key) and getattr(cls.meta_, key) is not None):
setattr(cls.meta_, key, default)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)


class ContainerMeta(type):
"""
Expand Down
50 changes: 12 additions & 38 deletions src/protean/core/model.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,11 @@
from abc import abstractmethod

from protean.container import Element
from protean.container import Element, OptionsMixin
from protean.exceptions import IncorrectUsageError, NotSupportedError
from protean.utils import DomainObjects
from protean.utils import DomainObjects, derive_element_class


class ModelMeta:
"""Metadata info for the Model.
Options:
- ``entity_cls``: The Entity that this model is associated with
"""

def __init__(self, meta=None):
if meta:
self.entity_cls = getattr(meta, "entity_cls", None)
self.schema_name = getattr(meta, "schema_name", None)
self.database = getattr(meta, "database", None)
else:
self.entity_cls = None
self.schema_name = None
self.database = None


class BaseModel(Element):
class BaseModel(Element, OptionsMixin):
"""This is a Model representing a data schema in the persistence store. A concrete implementation of this
model has to be provided by each persistence store plugin.
"""
Expand All @@ -35,6 +17,14 @@ def __new__(cls, *args, **kwargs):
raise NotSupportedError("BaseModel cannot be instantiated")
return super().__new__(cls)

@classmethod
def _default_options(cls):
return [
("entity_cls", None),
("schema_name", None),
("database", None),
]

@classmethod
@abstractmethod
def from_entity(cls, entity):
Expand All @@ -47,23 +37,7 @@ def to_entity(cls, *args, **kwargs):


def model_factory(element_cls, **kwargs):
element_cls.element_type = DomainObjects.MODEL

if hasattr(element_cls, "Meta"):
element_cls.meta_ = ModelMeta(element_cls.Meta)
else:
element_cls.meta_ = ModelMeta()

if not (hasattr(element_cls.meta_, "entity_cls") and element_cls.meta_.entity_cls):
element_cls.meta_.entity_cls = kwargs.pop("entity_cls", None)

if not (
hasattr(element_cls.meta_, "schema_name") and element_cls.meta_.schema_name
):
element_cls.meta_.schema_name = kwargs.pop("schema_name", None)

if not (hasattr(element_cls.meta_, "database") and element_cls.meta_.database):
element_cls.meta_.database = kwargs.pop("database", None)
element_cls = derive_element_class(element_cls, BaseModel, **kwargs)

if not element_cls.meta_.entity_cls:
raise IncorrectUsageError(
Expand Down

0 comments on commit c9813bf

Please sign in to comment.