Skip to content

Commit

Permalink
Clean up Exception structures (#449)
Browse files Browse the repository at this point in the history
Most exceptions had a messages dictionary that only made sense in
Validation and Data-related exceptions. This PR changes exception
structures to be simpler and to use the messages dictionary only where necessary.

We also subclass Exception and create a new exception class, `ProteanException`
from which all exceptions are subclassed.

The following exceptions were simplified:
- `NoDomainException`
- `ObjectNotFoundError`
- `IncorrectUsageError`
  • Loading branch information
subhashb authored Jul 27, 2024
1 parent 6206f27 commit ff78fc0
Show file tree
Hide file tree
Showing 58 changed files with 327 additions and 449 deletions.
5 changes: 1 addition & 4 deletions docs/guides/change-state/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,6 @@ Out[3]: <PublishArticle: PublishArticle object ({'article_id': '1', 'published_a

In [4]: publish_article_command.published_at = datetime.now() - timedelta(hours=24)
...
IncorrectUsageError: {
'_command': [
'Command Objects are immutable and cannot be modified once created'
]
IncorrectUsageError: 'Command Objects are immutable and cannot be modified once created'
}
```
4 changes: 2 additions & 2 deletions docs/guides/domain-definition/entities.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ specified while defining the identity, you will see an `IncorrectUsageError`:
... class Comment:
... content = String(max_length=500)
...
IncorrectUsageError: {'_entity': ['Entity `Comment` needs to be associated with an Aggregate']}
IncorrectUsageError: 'Entity `Comment` needs to be associated with an Aggregate'
```

An Entity cannot enclose another Entity (or Aggregate). Trying to do so will
Expand All @@ -44,7 +44,7 @@ throw `IncorrectUsageError`.
... class SubComment:
... parent = Comment()
...
IncorrectUsageError: {'_entity': ['Entity `Comment` needs to be associated with an Aggregate']}
IncorrectUsageError: 'Entity `Comment` needs to be associated with an Aggregate'
```
<!-- FIXME Ensure entities cannot enclose other entities. When entities
enclose something other than permitted fields, through an error-->
Expand Down
6 changes: 1 addition & 5 deletions docs/guides/domain-definition/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,5 @@ In [2]: renamed = UserRenamed(user_id=user.id, name="John Doe Jr.")

In [3]: renamed.name = "John Doe Sr."
...
IncorrectUsageError: {
'_message': [
'Event/Command Objects are immutable and cannot be modified once created'
]
}
IncorrectUsageError: 'Event/Command Objects are immutable and cannot be modified once created'
```
6 changes: 3 additions & 3 deletions docs/guides/domain-definition/value-objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ In [3]: class Balance(BaseValueObject):
...: currency = String(max_length=3, unique=True)
...: amount = Float()
...
IncorrectUsageError: {'_value_object': ["Value Objects cannot contain fields marked 'unique' (field 'currency')"]}
IncorrectUsageError: "Value Objects cannot contain fields marked 'unique' (field 'currency')"
```

Same case if you try to find a Value Object's `id_field`:
Expand All @@ -238,7 +238,7 @@ In [4]: from protean.reflection import id_field

In [5]: id_field(Balance)
...
IncorrectUsageError: {"identity": ["<class '__main__.Balance'> does not have identity fields"]}
IncorrectUsageError: "<class '__main__.Balance'> does not have identity fields"
```

## Immutability
Expand All @@ -250,5 +250,5 @@ In [1]: bal1 = Balance(currency='USD', amount=100.0)

In [2]: bal1.currency = "CAD"
...
IncorrectUsageError: {'_value_object': ["Value Objects are immutable and cannot be modified once created"]}
IncorrectUsageError: "Value Objects are immutable and cannot be modified once created"
```
6 changes: 2 additions & 4 deletions src/protean/adapters/repository/elasticsearch.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,8 @@ def _update(self, model_obj: Any):
except NotFoundError as exc:
logger.error(f"Database Record not found: {exc}")
raise ObjectNotFoundError(
{
"_entity": f"`{self.entity_cls.__name__}` object with identifier {identifier} "
f"does not exist."
}
f"`{self.entity_cls.__name__}` object with identifier {identifier} "
f"does not exist."
)

try:
Expand Down
12 changes: 4 additions & 8 deletions src/protean/adapters/repository/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,10 +390,8 @@ def _update(self, model_obj):
# Check if object is present
if identifier not in conn._db["data"][self.schema_name]:
raise ObjectNotFoundError(
{
"_entity": f"`{self.__class__.__name__}` object with identifier {identifier} "
f"does not exist."
}
f"`{self.__class__.__name__}` object with identifier {identifier} "
f"does not exist."
)

conn._db["data"][self.schema_name][identifier] = model_obj
Expand Down Expand Up @@ -434,10 +432,8 @@ def _delete(self, model_obj):
# Check if object is present
if identifier not in conn._db["data"][self.schema_name]:
raise ObjectNotFoundError(
{
"_entity": f"`{self.entity_cls.__name__}` object with identifier {identifier} "
f"does not exist."
}
f"`{self.entity_cls.__name__}` object with identifier {identifier} "
f"does not exist."
)

del conn._db["data"][self.schema_name][identifier]
Expand Down
12 changes: 4 additions & 8 deletions src/protean/adapters/repository/sqlalchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,10 +383,8 @@ def _update(self, model_obj):
conn.rollback()
conn.close()
raise ObjectNotFoundError(
{
"_entity": f"`{self.entity_cls.__name__}` object with identifier {identifier} "
f"does not exist."
}
f"`{self.entity_cls.__name__}` object with identifier {identifier} "
f"does not exist."
)

# Sync DB Record with current changes. When the session is committed, changes are automatically synced
Expand Down Expand Up @@ -447,10 +445,8 @@ def _delete(self, model_obj):
conn.rollback()
conn.close()
raise ObjectNotFoundError(
{
"_entity": f"`{self.entity_cls.__name__}` object with identifier {identifier} "
f"does not exist."
}
f"`{self.entity_cls.__name__}` object with identifier {identifier} "
f"does not exist."
)

try:
Expand Down
2 changes: 1 addition & 1 deletion src/protean/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def server(
try:
domain = derive_domain(domain)
except NoDomainException as exc:
logger.error(f"Error loading Protean domain: {exc.messages}")
logger.error(f"Error loading Protean domain: {exc.args[0]}")
raise typer.Abort()

from protean.server import Engine
Expand Down
2 changes: 1 addition & 1 deletion src/protean/cli/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def shell(
try:
domain_instance = derive_domain(domain)
except NoDomainException as exc:
logger.error(f"Error loading Protean domain: {exc.messages}")
logger.error(f"Error loading Protean domain: {exc.args[0]}")
raise typer.Abort()

if traverse:
Expand Down
12 changes: 2 additions & 10 deletions src/protean/core/aggregate.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,7 @@ def apply(fn):

if len(typing.get_type_hints(fn)) > 2:
raise IncorrectUsageError(
{
"_entity": [
f"Handler method `{fn.__name__}` has incorrect number of arguments"
]
}
f"Handler method `{fn.__name__}` has incorrect number of arguments"
)

try:
Expand All @@ -276,11 +272,7 @@ def apply(fn):
)
except StopIteration:
raise IncorrectUsageError(
{
"_entity": [
f"Apply method `{fn.__name__}` should accept an argument annotated with the Event class"
]
}
f"Apply method `{fn.__name__}` should accept an argument annotated with the Event class"
)

@functools.wraps(fn)
Expand Down
6 changes: 1 addition & 5 deletions src/protean/core/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,7 @@ def command_factory(element_cls, domain, **opts):

if not element_cls.meta_.part_of and not element_cls.meta_.abstract:
raise IncorrectUsageError(
{
"_command": [
f"Command `{element_cls.__name__}` needs to be associated with an aggregate or a stream"
]
}
f"Command `{element_cls.__name__}` needs to be associated with an aggregate or a stream"
)

return element_cls
6 changes: 1 addition & 5 deletions src/protean/core/command_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@ def command_handler_factory(element_cls, domain, **opts):

if not element_cls.meta_.part_of:
raise IncorrectUsageError(
{
"_entity": [
f"Command Handler `{element_cls.__name__}` needs to be associated with an Aggregate"
]
}
f"Command Handler `{element_cls.__name__}` needs to be associated with an Aggregate"
)

return element_cls
6 changes: 1 addition & 5 deletions src/protean/core/domain_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,7 @@ def domain_service_factory(element_cls, domain, **opts):

if not element_cls.meta_.part_of or len(element_cls.meta_.part_of) < 2:
raise IncorrectUsageError(
{
"_entity": [
f"Domain Service `{element_cls.__name__}` needs to be associated with two or more Aggregates"
]
}
f"Domain Service `{element_cls.__name__}` needs to be associated with two or more Aggregates"
)

# Iterate through methods marked as `@invariant` and record them for later use
Expand Down
6 changes: 1 addition & 5 deletions src/protean/core/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -621,11 +621,7 @@ def entity_factory(element_cls, domain, **opts):

if not element_cls.meta_.part_of:
raise IncorrectUsageError(
{
"_entity": [
f"Entity `{element_cls.__name__}` needs to be associated with an Aggregate"
]
}
f"Entity `{element_cls.__name__}` needs to be associated with an Aggregate"
)

# Set up reference fields
Expand Down
6 changes: 1 addition & 5 deletions src/protean/core/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,7 @@ def domain_event_factory(element_cls, domain, **opts):

if not element_cls.meta_.part_of and not element_cls.meta_.abstract:
raise IncorrectUsageError(
{
"_event": [
f"Event `{element_cls.__name__}` needs to be associated with an aggregate or a stream"
]
}
f"Event `{element_cls.__name__}` needs to be associated with an aggregate or a stream"
)

return element_cls
6 changes: 1 addition & 5 deletions src/protean/core/event_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,7 @@ def event_handler_factory(element_cls, domain, **opts):

if not (element_cls.meta_.part_of or element_cls.meta_.stream_category):
raise IncorrectUsageError(
{
"_entity": [
f"Event Handler `{element_cls.__name__}` needs to be associated with an aggregate or a stream"
]
}
f"Event Handler `{element_cls.__name__}` needs to be associated with an aggregate or a stream"
)

return element_cls
22 changes: 5 additions & 17 deletions src/protean/core/event_sourced_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ def __init__(self, domain) -> None:

def add(self, aggregate: BaseAggregate) -> None:
if aggregate is None:
raise IncorrectUsageError(
{"_entity": ["Aggregate object to persist is invalid"]}
)
raise IncorrectUsageError("Aggregate object to persist is invalid")

# Proceed only if aggregate has events
if len(aggregate._events) > 0:
Expand Down Expand Up @@ -91,10 +89,8 @@ def get(self, identifier: Identifier) -> BaseAggregate:

if not aggregate:
raise ObjectNotFoundError(
{
"_entity": f"`{self.meta_.part_of.__name__}` object with identifier {identifier} "
f"does not exist."
}
f"`{self.meta_.part_of.__name__}` object with identifier {identifier} "
f"does not exist."
)

aggregate._event_position = aggregate._version
Expand All @@ -107,20 +103,12 @@ def event_sourced_repository_factory(element_cls, domain, **opts):

if not element_cls.meta_.part_of:
raise IncorrectUsageError(
{
"_entity": [
f"Repository `{element_cls.__name__}` should be associated with an Aggregate"
]
}
f"Repository `{element_cls.__name__}` should be associated with an Aggregate"
)

if not element_cls.meta_.part_of.meta_.is_event_sourced:
raise IncorrectUsageError(
{
"_entity": [
f"Repository `{element_cls.__name__}` can only be associated with an Event Sourced Aggregate"
]
}
f"Repository `{element_cls.__name__}` can only be associated with an Event Sourced Aggregate"
)

return element_cls
6 changes: 1 addition & 5 deletions src/protean/core/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,7 @@ def model_factory(element_cls, domain, **opts):

if not element_cls.meta_.part_of:
raise IncorrectUsageError(
{
"_entity": [
f"Model `{element_cls.__name__}` should be associated with an Entity or Aggregate"
]
}
f"Model `{element_cls.__name__}` should be associated with an Entity or Aggregate"
)

return element_cls
12 changes: 2 additions & 10 deletions src/protean/core/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,11 +266,7 @@ def repository_factory(element_cls, domain, **opts):

if not element_cls.meta_.part_of:
raise IncorrectUsageError(
{
"_entity": [
f"Repository `{element_cls.__name__}` should be associated with an Aggregate"
]
}
f"Repository `{element_cls.__name__}` should be associated with an Aggregate"
)

# FIXME Uncomment
Expand All @@ -284,11 +280,7 @@ def repository_factory(element_cls, domain, **opts):
database.value for database in Database
]:
raise IncorrectUsageError(
{
"_entity": [
f"Repository `{element_cls.__name__}` should be associated with a valid Database"
]
}
f"Repository `{element_cls.__name__}` should be associated with a valid Database"
)

return element_cls
12 changes: 2 additions & 10 deletions src/protean/core/subscriber.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,12 @@ def subscriber_factory(element_cls, domain, **opts):

if not element_cls.meta_.event:
raise IncorrectUsageError(
{
"_entity": [
f"Subscriber `{element_cls.__name__}` needs to be associated with an Event"
]
}
f"Subscriber `{element_cls.__name__}` needs to be associated with an Event"
)

if not element_cls.meta_.broker:
raise IncorrectUsageError(
{
"_entity": [
f"Subscriber `{element_cls.__name__}` needs to be associated with a Broker"
]
}
f"Subscriber `{element_cls.__name__}` needs to be associated with a Broker"
)

return element_cls
Loading

0 comments on commit ff78fc0

Please sign in to comment.