From v1 to v2
Here, you will find comprehensive information and guidelines on migrating to a new version (v2) of petisco framework. Upgrading to the latest version can bring numerous benefits, including enhanced features, improved performance, and increased security. However, we understand that the migration process can be complex and challenging, which is why we have compiled this documentation to assist you every step of the way.
Breaking Changes¶
Following changes may require modifications to your existing code. While we apologize for any inconvenience, we believe these updates will enhance functionality and improve user experience. Please review the accompanying documentation for specific modifications and reach out to our support team for assistance. We value your feedback and appreciate your understanding during this transition. Thank you for your continued support as we strive to provide you with an improved library experience.
Dependencies¶
Warning
On dependency definition, name
and default_builder
are no longer available.
For more info about new way of defining dependencies, you can check the Dependency Injection Container section.
Controller Result¶
Warning
After some attempts to remove the forbidden petisco magic behind the controller returning type while keeping the useful mapping of the result (e.g. mapping Result values to outputs or Http errors in FastAPI) like in the Issue 333 , a new proposal have been validated.
The main idea is simplify controller result to avoid the following question: Is petisco controller going to return
me a meiga.Result
or instead is going to directly map the Result using the Controller Config parameters (See Doc).?
Now, the Controller
implementation of petisco v2 version always returns a meiga.Result
type. And this Result
type will have set a tranformed function to map domain result to the framework expected result (e.g. agging mapping
Result values to outputs or Http errors in FastAPI).
from petisco import DomainError, Controller, PrintMiddleware
from meiga import Result, Success, Error
import random
class MyError(DomainError): ...
class MyController(Controller):
class Config:
success_handler = lambda result: {"message": "ok"}
error_map = {NotFound: HttpError(status_code=404, detail="Task not Found")}
middlewares = [PrintMiddleware]
def execute(self) -> Result[bool, Error]:
return Success(random.choice([True, False]))
result = MyController().execute()
from petisco import DomainError, Controller, PrintMiddleware
from meiga import Result, Success, Error
import random
class MyError(DomainError): ...
class MyController(Controller):
class Config: #
success_handler = lambda result: {"message": "ok"}
error_map = {NotFound: HttpError(status_code=404, detail="Task not Found")}
middlewares = [PrintMiddleware]
def execute(self) -> Result[bool, Error]:
return Success(random.choice([True, False]))
result = MyController().execute()
# If you want to transform you can do it with result.transform() o with a better semantic funciton `as_fastapi`
from petisco.extras.fastapi import as_fastapi
mapped_result = as_fastapi(result)
To use this in a FastAPI router, is quite easy:
MessageBuses¶
Warning
DomainEventBus
and CommandBus
do not implement publish_list
anymore. Now is necessary to rename it to publish
.
The new publish
implementation available in petisco v2 allows to pass individual and list of messages.
Message Private Properties¶
Warning
Petisco Message classes (DomainEvent
and Commnad
) had some limitations on version 1. Some protected
class
attribute names cannot be chosen as were used to store meta information of the message.
For example, you cannot use name
attribute in your definition, or version
.
Version 2 resolves this issues but needs to break compatibility.
Now, the access to private attributes must be performed using a sort of getters.
from petisco import DomainEvent
class UserCreated(DomainEvent):
name: str
version: int
user_created = UserCreated(name"Alice", version=2)
print(user_created.name) # the given name conflicts with private name attribute
print(user_created.version) # the given version conflicts with private version attribute
print(user_created.attributes)
print(user_created.message_id)
print(user_created.ocurred_on)
print(user_created.meta)
from petisco import DomainEvent
class UserCreated(DomainEvent):
name: str
version: int
user_created = UserCreated(name"Alice", version=2)
print(user_created.get_message_name())
print(user_created.get_message_version())
print(user_created.get_message_attributes())
print(user_created.get_message_id())
print(user_created.get_message_ocurred_on())
print(user_created.get_message_meta())
Message (From metaclass to Pydantic)¶
Warning
Petisco Message
base class is now a pydantic.BaseModel
. This will help on dev experience improvement of
DomainEvent
and Command
and also validation. https://github.com/alice-biometrics/petisco/issues/360
To ilustrate main changes we will use the following DomainEvent
definition:
from petisco import DomainEvent
class UserCreated(DomainEvent):
name: str
age: int
user_created = UserCreated(name="acme", age=50)
Main changes:
- Serialization
- Use
.format()
instead of.dict()
to convert your Message to specific message format. - Use
.format_json()
instead of.json()
to convert your Message to specific message format and convert to str. - Use
.model_dump()
(old pydantic.dict()
) to get model attributes dictionary: - Use
.model_dump_json()
(old pydantic.json()
) to get model attributes dictionary:
- Use
- Deserialization
- Use
.from_format(formatted_message: dict | str | bytes)
to convert from specific message format (.format()
and.format_json()
output). Don't use legacy.from_dict()
.
- Use
-
envar_modifier with alias
The below dependency will automatically creates a
envar_modifier
with valueUSER_REPOSITORY_TYPE
Dependency(UserRepository, builders={ "default": Builder(SqlUserRepository), "fake": Builder(FakeUserRepository), }, ),
Now, if you uses an alias, envar_modifier will be different as well (in the following case
USER_REPOSITORY_ALIAS_DASHBOARD_TYPE
)Dependency(UserRepository, alias="dashboard", builders={ "default": Builder(SqlUserRepository), "fake": Builder(FakeUserRepository), }, ),
This allows us to have different configuration for different implementation (different alias)
β οΈCheck your configurations (maybe you have to add
_ALIAS_{YOUR_ALIAS}
to some env vars)!
- New features thanks to pydantic
- Attribute auto-completion
- Attribute and model validation as we are extending from
pydantic.BaseModel
- Automatic serialization
Dev Experience Improvements¶
Retrieved Message are better typed¶
Info
In the Issue 340 is implemented an improvement to access attributes of retrieved Messages (DomainEvent and Commands).
from typing import Type
from meiga import isSuccess
from petisco import DomainEventSubscriber, DomainEvent, Container
class TaskCreated(DomainEvent):
name: str
class SendNotificationOnTaskModifications(DomainEventSubscriber):
def subscribed_to(self) -> list[Type[DomainEvent]]:
return [TaskCreated]
def handle(self, domain_event: DomainEvent) -> BoolResult:
task_name = domain_event.attributes.get("name")
# Do your stuff here
return isSuccess
from typing import Type
from meiga import isSuccess
from petisco import DomainEventSubscriber, DomainEvent, Container
class TaskCreated(DomainEvent):
name: str
class SendNotificationOnTaskModifications(DomainEventSubscriber):
def subscribed_to(self) -> list[Type[DomainEvent]]:
return [TaskCreated]
def handle(self, domain_event: TaskCreated) -> BoolResult:
task_name = domain_event.name # With typehint and autocompletion.
# Do your stuff here
return isSuccess
Async Implementations¶
Info
Petisco v2 provides async implementation for every base elements. This allows petisco users to migrate step their use cases, controllers and app services keeping back compatibility with current sync implementations.
from meiga import BoolResult
from petisco import Container, DomainEventBus
from petisco.extra.fastapi import FastAPIController
from app.src.task.create.application.task_creator import TaskCreator
from app.src.task.shared.domain.task import Task
class CreateTaskController(FastAPIController):
def execute(self, task: Task) -> BoolResult:
task_creator = TaskCreator(
labeler=Container.get(TaskLabeler),
repository=Container.get(TaskRepository),
domain_event_bus=Container.get(DomainEventBus),
)
return task_creator.execute(task=task)
from meiga import BoolResult
from petisco import Container, DomainEventBus
from petisco.extra.fastapi import FastAPIController
from app.src.task.create.application.task_creator import TaskCreator
from app.src.task.shared.domain.task import Task
class CreateTaskController(FastAPIController):
def execute(self, task: Task) -> BoolResult:
task_creator = TaskCreator(
labeler=Container.get(TaskLabeler),
repository=Container.get(TaskRepository),
domain_event_bus=Container.get(DomainEventBus),
)
return task_creator.execute(task=task)
from meiga import BoolResult
from petisco import Container, DomainEventBus
from petisco.extra.fastapi import AsyncFastAPIController
from app.src.task.create.application.task_creator import AsyncTaskCreator
from app.src.task.shared.domain.task import Task
class CreateTaskController(AsyncFastAPIController):
async def execute(self, task: Task) -> BoolResult:
task_creator = AsyncTaskCreator(
labeler=Container.get(TaskLabeler),
repository=Container.get(TaskRepository),
domain_event_bus=Container.get(DomainEventBus),
)
return await task_creator.execute(task=task)
Use shared_error_map
¶
Info
Petisco v2 provides an implementation for configure shared error maps
Check error_map
documentation in Application/Configuration to know hot to ease
your error handling.