Skip to content

Commit

Permalink
Remove support for inner Meta class (#433)
Browse files Browse the repository at this point in the history
This commit removes support for inner `Meta` class to define options.

With this change:
1. All domain elements have to be registered with their respective decorators
2. Elements can be optionally declared and registered separately, passing the
options to the `domain.register` method
3. Subclassing does not inherit Meta options
4. Element options will only be valid after registering with domain
5. Base classes will throw `NotSupportedError` on instantiation, instead of
`TypeError`.
  • Loading branch information
subhashb authored Jun 8, 2024
1 parent da88061 commit d7a8d73
Show file tree
Hide file tree
Showing 146 changed files with 471 additions and 1,059 deletions.
11 changes: 3 additions & 8 deletions src/protean/adapters/event_store/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,14 @@
from protean import BaseAggregate, BaseRepository
from protean.globals import current_domain
from protean.port import BaseEventStore
from protean.utils import DomainObjects
from protean.utils.mixins import MessageMetadata, MessageRecord


class MemoryMessage(BaseAggregate, MessageRecord):
class Meta:
provider = "memory"
pass


class MemoryMessageRepository(BaseRepository):
class Meta:
part_of = MemoryMessage

def is_category(self, stream_name: str) -> bool:
if not stream_name:
return False
Expand Down Expand Up @@ -93,8 +88,8 @@ def __init__(self, domain, conn_info) -> None:
super().__init__("Memory", domain, conn_info)

self.domain = domain
self.domain._register_element(DomainObjects.AGGREGATE, MemoryMessage)
self.domain.register(MemoryMessageRepository)
self.domain.register(MemoryMessage, provider="memory")
self.domain.register(MemoryMessageRepository, part_of=MemoryMessage)

def _write(
self,
Expand Down
6 changes: 4 additions & 2 deletions src/protean/adapters/repository/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,11 @@ def _register_repository(self, part_of, repository_cls):
# }
#
# And repository as:
# @domain.repository(part_of=Post, database="postgresql")
# class CustomPostRepository:
# class Meta:
# database = "postgresql"
# def custom_method(self):
# ...
#
# The value of `database` would be `postgresql`.
#
# In the absence of an explicit database value, the repository is associated with "ALL"
Expand Down
35 changes: 11 additions & 24 deletions src/protean/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,15 @@ def __init__(self, opts: Union[dict, Type] = None) -> None:
if not (attr[0].startswith("__") and attr[0].endswith("__")):
setattr(self, attr[0], attr[1])

self.abstract = getattr(opts, "abstract", None) or False
elif isinstance(opts, dict):
for opt_name, opt_value in opts.items():
setattr(self, opt_name, opt_value)

# Common Meta attributes
self.abstract = getattr(opts, "abstract", None) or False
self.abstract = opts.get("abstract", None) or False
else:
# Common Meta attributes
self.abstract = getattr(opts, "abstract", None) or False

def __setattr__(self, __name: str, __value: Any) -> None:
# Ignore if `_opts` is being set
Expand Down Expand Up @@ -88,23 +91,10 @@ def __init_subclass__(subclass) -> None:
Args:
subclass (Protean Element): Subclass to initialize with metadata
"""
# Retrieve inner Meta class
# Gather `Meta` class/object if defined
options = getattr(subclass, "Meta", None)

# Ensure that options are defined in this element class
# and not in one of its base class, by checking if the parent of the
# inner Meta class is the subclass being initialized
#
# PEP-3155 https://www.python.org/dev/peps/pep-3155/
# `__qualname__` contains the Inner class name in the form of a dot notation:
# <OuterClass>.<InnerClass>.
if options and options.__qualname__.split(".")[-2] == subclass.__name__:
subclass.meta_ = Options(options)
else:
subclass.meta_ = Options()
if not hasattr(subclass, "meta_"):
setattr(subclass, "meta_", Options())

# Assign default options for remaining items
# Assign default options
subclass._set_defaults()

super().__init_subclass__()
Expand All @@ -116,11 +106,8 @@ def _set_defaults(cls):
# Element Roots are `Event`, `Subscriber`, `Repository`, and so on.
for key, default in cls._default_options():
# FIXME Should the `None` check be replaced with a SENTINEL check?
if hasattr(cls.meta_, key) and getattr(cls.meta_, key) is not None:
value = getattr(cls.meta_, key)
else:
value = default
setattr(cls.meta_, key, value)
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)
Expand Down Expand Up @@ -186,7 +173,7 @@ class BaseContainer(metaclass=ContainerMeta):

def __new__(cls, *args, **kwargs):
if cls is BaseContainer:
raise TypeError("BaseContainer cannot be instantiated")
raise NotSupportedError("BaseContainer cannot be instantiated")
return super().__new__(cls)

def __init__(self, *template, **kwargs): # noqa: C901
Expand Down
6 changes: 2 additions & 4 deletions src/protean/core/aggregate.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from protean.container import EventedMixin
from protean.core.entity import BaseEntity
from protean.exceptions import NotSupportedError
from protean.fields import Integer
from protean.utils import DomainObjects, derive_element_class, inflection

Expand Down Expand Up @@ -39,15 +40,12 @@ class Dog:

def __new__(cls, *args, **kwargs):
if cls is BaseAggregate:
raise TypeError("BaseAggregate cannot be instantiated")
raise NotSupportedError("BaseAggregate cannot be instantiated")
return super().__new__(cls)

# Track current version of Aggregate
_version = Integer(default=-1)

class Meta:
abstract = True

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

Expand Down
6 changes: 2 additions & 4 deletions src/protean/core/application_service.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging

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

logger = logging.getLogger(__name__)
Expand All @@ -20,12 +21,9 @@ class BaseApplicationService(Element, OptionsMixin):

element_type = DomainObjects.APPLICATION_SERVICE

class Meta:
abstract = True

def __new__(cls, *args, **kwargs):
if cls is BaseApplicationService:
raise TypeError("BaseApplicationService cannot be instantiated")
raise NotSupportedError("BaseApplicationService cannot be instantiated")
return object.__new__(cls, *args, **kwargs)

@classmethod
Expand Down
13 changes: 10 additions & 3 deletions src/protean/core/command.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
from protean.container import BaseContainer, OptionsMixin
from protean.exceptions import IncorrectUsageError, InvalidDataError, ValidationError
from protean.exceptions import (
IncorrectUsageError,
InvalidDataError,
ValidationError,
NotSupportedError,
)
from protean.fields import Field
from protean.reflection import _ID_FIELD_NAME, declared_fields
from protean.utils import DomainObjects, derive_element_class
Expand All @@ -14,8 +19,10 @@ class BaseCommand(BaseContainer, OptionsMixin):

element_type = DomainObjects.COMMAND

class Meta:
abstract = True
def __new__(cls, *args, **kwargs):
if cls is BaseCommand:
raise NotSupportedError("BaseCommand cannot be instantiated")
return super().__new__(cls)

def __init_subclass__(subclass) -> None:
super().__init_subclass__()
Expand Down
2 changes: 1 addition & 1 deletion src/protean/core/command_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def _default_options(cls):

def __new__(cls, *args, **kwargs):
if cls is BaseCommandHandler:
raise TypeError("BaseCommandHandler cannot be instantiated")
raise NotSupportedError("BaseCommandHandler cannot be instantiated")
return super().__new__(cls)


Expand Down
7 changes: 2 additions & 5 deletions src/protean/core/domain_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

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

logger = logging.getLogger(__name__)
Expand All @@ -22,12 +22,9 @@ class BaseDomainService(Element, OptionsMixin):

element_type = DomainObjects.DOMAIN_SERVICE

class Meta:
abstract = True

def __new__(cls, *args, **kwargs):
if cls is BaseDomainService:
raise TypeError("BaseDomainService cannot be instantiated")
raise NotSupportedError("BaseDomainService cannot be instantiated")
return super().__new__(cls)

@classmethod
Expand Down
13 changes: 0 additions & 13 deletions src/protean/core/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,6 @@ class User(BaseEntity):

element_type = DomainObjects.ENTITY

class Meta:
abstract = True

def __init_subclass__(subclass) -> None:
super().__init_subclass__()

Expand Down Expand Up @@ -513,16 +510,6 @@ def _set_root_and_owner(self, root, owner):
def entity_factory(element_cls, **kwargs):
element_cls = derive_element_class(element_cls, BaseEntity, **kwargs)

if element_cls.meta_.abstract is True:
raise NotSupportedError(
{
"_entity": [
f"`{element_cls.__name__}` class has been marked abstract"
f" and cannot be instantiated"
]
}
)

if not element_cls.meta_.part_of:
raise IncorrectUsageError(
{
Expand Down
8 changes: 5 additions & 3 deletions src/protean/core/event.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging

from protean.container import BaseContainer, OptionsMixin
from protean.exceptions import IncorrectUsageError
from protean.exceptions import IncorrectUsageError, NotSupportedError
from protean.fields import Field
from protean.reflection import _ID_FIELD_NAME, declared_fields
from protean.utils import DomainObjects, derive_element_class
Expand All @@ -18,8 +18,10 @@ class BaseEvent(BaseContainer, OptionsMixin): # FIXME Remove OptionsMixin

element_type = DomainObjects.EVENT

class Meta:
abstract = True
def __new__(cls, *args, **kwargs):
if cls is BaseEvent:
raise NotSupportedError("BaseEvent cannot be instantiated")
return super().__new__(cls)

def __init_subclass__(subclass) -> None:
super().__init_subclass__()
Expand Down
13 changes: 5 additions & 8 deletions src/protean/core/event_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from protean.container import Element, OptionsMixin
from protean.core.event import BaseEvent
from protean.exceptions import IncorrectUsageError
from protean.exceptions import IncorrectUsageError, NotSupportedError
from protean.utils import DomainObjects, derive_element_class, fully_qualified_name
from protean.utils.mixins import HandlerMixin

Expand All @@ -15,8 +15,10 @@ class BaseEventHandler(Element, HandlerMixin, OptionsMixin):

element_type = DomainObjects.EVENT_HANDLER

class Meta:
abstract = True
def __new__(cls, *args, **kwargs):
if cls is BaseEventHandler:
raise NotSupportedError("BaseEventHandler cannot be instantiated")
return super().__new__(cls)

@classmethod
def _default_options(cls):
Expand All @@ -30,11 +32,6 @@ def _default_options(cls):
("source_stream", None),
]

def __new__(cls, *args, **kwargs):
if cls is BaseEventHandler:
raise TypeError("BaseEventHandler cannot be instantiated")
return super().__new__(cls)


def event_handler_factory(element_cls, **opts):
element_cls = derive_element_class(element_cls, BaseEventHandler, **opts)
Expand Down
8 changes: 5 additions & 3 deletions src/protean/core/event_sourced_aggregate.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from protean.container import BaseContainer, EventedMixin, IdentityMixin, OptionsMixin
from protean.core.event import BaseEvent
from protean.exceptions import IncorrectUsageError
from protean.exceptions import IncorrectUsageError, NotSupportedError
from protean.fields import Field, Integer
from protean.reflection import _ID_FIELD_NAME, declared_fields, has_fields, id_field
from protean.utils import (
Expand All @@ -34,8 +34,10 @@ class BaseEventSourcedAggregate(
# Track current version of Aggregate
_version = Integer(default=-1)

class Meta:
abstract = True
def __new__(cls, *args, **kwargs):
if cls is BaseEventSourcedAggregate:
raise NotSupportedError("BaseEventSourcedAggregate cannot be instantiated")
return super().__new__(cls)

@classmethod
def _default_options(cls):
Expand Down
8 changes: 6 additions & 2 deletions src/protean/core/event_sourced_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

from protean import BaseEventSourcedAggregate
from protean.container import Element, OptionsMixin
from protean.exceptions import IncorrectUsageError, ObjectNotFoundError
from protean.exceptions import (
IncorrectUsageError,
ObjectNotFoundError,
NotSupportedError,
)
from protean.fields import Identifier
from protean.globals import current_domain, current_uow
from protean.utils import DomainObjects, derive_element_class
Expand All @@ -20,7 +24,7 @@ def _default_options(cls):
def __new__(cls, *args, **kwargs):
# Prevent instantiation of `BaseEventSourcedRepository itself`
if cls is BaseEventSourcedRepository:
raise TypeError("BaseEventSourcedRepository cannot be instantiated")
raise NotSupportedError("BaseEventSourcedRepository cannot be instantiated")
return super().__new__(cls)

def __init__(self, domain) -> None:
Expand Down
4 changes: 2 additions & 2 deletions src/protean/core/model.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from abc import abstractmethod

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


Expand Down Expand Up @@ -32,7 +32,7 @@ class BaseModel(Element):

def __new__(cls, *args, **kwargs):
if cls is BaseModel:
raise TypeError("BaseModel cannot be instantiated")
raise NotSupportedError("BaseModel cannot be instantiated")
return super().__new__(cls)

@classmethod
Expand Down
4 changes: 2 additions & 2 deletions src/protean/core/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from functools import lru_cache

from protean.container import Element, OptionsMixin
from protean.exceptions import IncorrectUsageError
from protean.exceptions import IncorrectUsageError, NotSupportedError
from protean.fields import HasMany, HasOne
from protean.globals import current_domain
from protean.reflection import association_fields, has_association_fields
Expand Down Expand Up @@ -39,7 +39,7 @@ def _default_options(cls):
def __new__(cls, *args, **kwargs):
# Prevent instantiation of `BaseRepository itself`
if cls is BaseRepository:
raise TypeError("BaseRepository cannot be instantiated")
raise NotSupportedError("BaseRepository cannot be instantiated")
return super().__new__(cls)

def __init__(self, domain, provider) -> None:
Expand Down
2 changes: 1 addition & 1 deletion src/protean/core/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ class Address(BaseSerializer):

def __new__(cls, *args, **kwargs):
if cls is BaseSerializer:
raise TypeError("BaseSerializer cannot be instantiated")
raise NotSupportedError("BaseSerializer cannot be instantiated")
return super().__new__(cls)


Expand Down
Loading

0 comments on commit d7a8d73

Please sign in to comment.