diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 4d1cefd..3285abf 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -15,10 +15,10 @@ jobs: env: VERSION: ${{ github.event.inputs.release_version }} steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13.0-rc.2' - name: version run: sed -i "s/__version__ = '.*'/__version__ = '$VERSION'/g" aioddd/__init__.py - name: deps @@ -28,7 +28,8 @@ jobs: TWINE_USERNAME: ${{ secrets.POETRY_HTTP_BASIC_PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.POETRY_HTTP_BASIC_PYPI_PASSWORD }} run: | - git config user.name ${{ github.actor }} + git config user.name "${{ github.actor }}" + git config user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com" git commit . -m "release: v${VERSION}" git push origin main git tag $VERSION diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2bb6682..e932980 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,10 +14,10 @@ jobs: strategy: matrix: os: [ ubuntu-latest ] - python-version: [ '3.8', '3.9', '3.10', '3.11', '3.12' ] + python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13.0-rc.2' ] steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: deps diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..7340777 --- /dev/null +++ b/Containerfile @@ -0,0 +1,33 @@ +FROM docker.io/continuumio/miniconda3:latest AS miniconda3 + +WORKDIR /app + +RUN conda install -y --download-only "python=3.12" && \ + conda install -y --download-only "python=3.11" && \ + conda install -y --download-only "python=3.10" && \ + conda install -y --download-only "python=3.9" + +COPY . ./ + +ENTRYPOINT ["python3"] +CMD ["run-script"] + +FROM miniconda3 AS py312 + +RUN conda install -y "python=3.12" +RUN --mount=type=cache,target=/root/.cache/pip python3 run-script dev-install + +FROM miniconda3 AS py311 + +RUN conda install -y "python=3.11" +RUN --mount=type=cache,target=/root/.cache/pip python3 run-script dev-install + +FROM miniconda3 AS py310 + +RUN conda install -y "python=3.10" +RUN --mount=type=cache,target=/root/.cache/pip python3 run-script dev-install + +FROM miniconda3 AS py39 + +RUN conda install -y "python=3.9" +RUN --mount=type=cache,target=/root/.cache/pip python3 run-script dev-install diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 29e48e3..0000000 --- a/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -FROM docker.io/library/python:3.7-slim AS base - -RUN apt update -y && python3 -m pip install --upgrade pip - -WORKDIR /app - -FROM base AS production - -COPY LICENSE README.md pyproject.toml ./ -COPY aioddd ./aioddd/ - -RUN python3 -m pip install . - -ENTRYPOINT ["python3"] -CMD [] - -FROM production AS development - -COPY .pre-commit-config.yaml run-script ./ - -RUN python3 -m pip install .[dev,deploy,docs,fmt,security-analysis,static-analysis,test] - -COPY docs_src ./docs_src -COPY tests ./tests - -ENTRYPOINT ["python3", "run-script"] diff --git a/LICENSE b/LICENSE index 8cbdc57..814f12b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 - 2023 aiopy +Copyright (c) 2020 - 2024 aiopy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 54091c1..773f737 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ if __name__ == '__main__': ## Requirements -- Python >= 3.7 +- Python >= 3.9 ## Contributing diff --git a/aioddd/testing.py b/aioddd/testing.py index 5e90a99..5631693 100644 --- a/aioddd/testing.py +++ b/aioddd/testing.py @@ -1,7 +1,6 @@ from typing import Any, Dict, List, Optional, Type, Union from unittest.mock import AsyncMock, MagicMock, Mock, patch - SanitizeObject = Union[Dict[Any, Any], List[Any]] diff --git a/aioddd/utils.py b/aioddd/utils.py index 5d814b4..34f8395 100644 --- a/aioddd/utils.py +++ b/aioddd/utils.py @@ -83,4 +83,4 @@ def env(key: Optional[str] = None, typ: Optional[Type[_T]] = None) -> _T: return val # type: ignore -env.resolver = lambda: None # type: ignore +env.resolver = lambda: {} # type: ignore diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..3a7da92 --- /dev/null +++ b/compose.yml @@ -0,0 +1,15 @@ +services: + aioddd: + build: + context: . + dockerfile: Containerfile +# target: py39 +# target: py310 +# target: py311 + target: py312 +# image: ghcr.io/aiopy/python-aioddd:py39-${VERSION:-latest} +# image: ghcr.io/aiopy/python-aioddd:py310-${VERSION:-latest} +# image: ghcr.io/aiopy/python-aioddd:py311-${VERSION:-latest} + image: ghcr.io/aiopy/python-aioddd:py312-${VERSION:-latest} + volumes: + - .:/app diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 6922d7a..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,11 +0,0 @@ -services: - aioddd: - build: - context: . - dockerfile: Dockerfile - target: development - image: ghcr.io/aiopy/python-aioddd:${VERSION:-latest} - volumes: - - .:/app - -version: "3.7" diff --git a/docs_src/docs/en/index.md b/docs_src/docs/en/index.md index cc51a0d..2df5f7e 100644 --- a/docs_src/docs/en/index.md +++ b/docs_src/docs/en/index.md @@ -12,7 +12,7 @@ Key Features: ## Requirements -- Python 3.8+ +- Python 3.9+ ## Installation diff --git a/docs_src/docs/es/index.md b/docs_src/docs/es/index.md index ae2b408..e0c935a 100644 --- a/docs_src/docs/es/index.md +++ b/docs_src/docs/es/index.md @@ -12,7 +12,7 @@ ## Requisitos -- Python 3.8+ +- Python 3.9+ ## Instalación diff --git a/docs_src/generated/en/index.html b/docs_src/generated/en/index.html index 3d0200a..3dcab02 100644 --- a/docs_src/generated/en/index.html +++ b/docs_src/generated/en/index.html @@ -433,7 +433,7 @@
python3 -m pip install aioddd
diff --git a/docs_src/generated/en/search/search_index.json b/docs_src/generated/en/search/search_index.json
index 2e06cb0..ec5938c 100644
--- a/docs_src/generated/en/search/search_index.json
+++ b/docs_src/generated/en/search/search_index.json
@@ -1 +1 @@
-{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Async Python DDD utilities library Key Features: Aggregates : Aggregate & AggregateRoot ValueObjects : Id, Timestamp & StrDateTime CQRS : Command, CommandBus, SimpleCommandBus, Query, Response, QueryHandler, QueryBus & SimpleQueryBus EventSourcing : Event, EventMapper, EventPublisher, EventHandler, EventBus, SimpleEventBus & InternalEventPublisher Errors : raise_, BaseError, NotFoundError, ConflictError, BadRequestError, UnauthorizedError, ForbiddenError, UnknownError, IdInvalidError, TimestampInvalidError, DateTimeInvalidError, EventMapperNotFoundError, EventNotPublishedError, CommandNotRegisteredError & QueryNotRegisteredError Tests : AsyncMock & mock Utils : get_env & get_simple_logger Requirements Python 3.8+ Installation python3 -m pip install aioddd Example from asyncio import get_event_loop from dataclasses import dataclass from typing import Type from aioddd import NotFoundError , \\ Command , CommandHandler , SimpleCommandBus , \\ Query , QueryHandler , OptionalResponse , SimpleQueryBus , Event _products = [] class ProductStored ( Event ): @dataclass class Attributes : ref : str attributes : Attributes class StoreProductCommand ( Command ): def __init__ ( self , ref : str ): self . ref = ref class StoreProductCommandHandler ( CommandHandler ): def subscribed_to ( self ) -> Type [ Command ]: return StoreProductCommand async def handle ( self , command : StoreProductCommand ) -> None : _products . append ( command . ref ) class ProductNotFoundError ( NotFoundError ): _code = 'product_not_found' _title = 'Product not found' class FindProductQuery ( Query ): def __init__ ( self , ref : str ): self . ref = ref class FindProductQueryHandler ( QueryHandler ): def subscribed_to ( self ) -> Type [ Query ]: return FindProductQuery async def handle ( self , query : FindProductQuery ) -> OptionalResponse : if query . ref != '123' : raise ProductNotFoundError . create ( detail = { 'ref' : query . ref }) return { 'ref' : query . ref } async def main () -> None : commands_bus = SimpleCommandBus ([ StoreProductCommandHandler ()]) await commands_bus . dispatch ( StoreProductCommand ( '123' )) query_bus = SimpleQueryBus ([ FindProductQueryHandler ()]) response = await query_bus . ask ( FindProductQuery ( '123' )) print ( response ) if __name__ == '__main__' : get_event_loop () . run_until_complete ( main ()) License MIT WIP","title":"aioddd"},{"location":"#async-python-ddd-utilities-library","text":"Key Features: Aggregates : Aggregate & AggregateRoot ValueObjects : Id, Timestamp & StrDateTime CQRS : Command, CommandBus, SimpleCommandBus, Query, Response, QueryHandler, QueryBus & SimpleQueryBus EventSourcing : Event, EventMapper, EventPublisher, EventHandler, EventBus, SimpleEventBus & InternalEventPublisher Errors : raise_, BaseError, NotFoundError, ConflictError, BadRequestError, UnauthorizedError, ForbiddenError, UnknownError, IdInvalidError, TimestampInvalidError, DateTimeInvalidError, EventMapperNotFoundError, EventNotPublishedError, CommandNotRegisteredError & QueryNotRegisteredError Tests : AsyncMock & mock Utils : get_env & get_simple_logger","title":"Async Python DDD utilities library"},{"location":"#requirements","text":"Python 3.8+","title":"Requirements"},{"location":"#installation","text":"python3 -m pip install aioddd","title":"Installation"},{"location":"#example","text":"from asyncio import get_event_loop from dataclasses import dataclass from typing import Type from aioddd import NotFoundError , \\ Command , CommandHandler , SimpleCommandBus , \\ Query , QueryHandler , OptionalResponse , SimpleQueryBus , Event _products = [] class ProductStored ( Event ): @dataclass class Attributes : ref : str attributes : Attributes class StoreProductCommand ( Command ): def __init__ ( self , ref : str ): self . ref = ref class StoreProductCommandHandler ( CommandHandler ): def subscribed_to ( self ) -> Type [ Command ]: return StoreProductCommand async def handle ( self , command : StoreProductCommand ) -> None : _products . append ( command . ref ) class ProductNotFoundError ( NotFoundError ): _code = 'product_not_found' _title = 'Product not found' class FindProductQuery ( Query ): def __init__ ( self , ref : str ): self . ref = ref class FindProductQueryHandler ( QueryHandler ): def subscribed_to ( self ) -> Type [ Query ]: return FindProductQuery async def handle ( self , query : FindProductQuery ) -> OptionalResponse : if query . ref != '123' : raise ProductNotFoundError . create ( detail = { 'ref' : query . ref }) return { 'ref' : query . ref } async def main () -> None : commands_bus = SimpleCommandBus ([ StoreProductCommandHandler ()]) await commands_bus . dispatch ( StoreProductCommand ( '123' )) query_bus = SimpleQueryBus ([ FindProductQueryHandler ()]) response = await query_bus . ask ( FindProductQuery ( '123' )) print ( response ) if __name__ == '__main__' : get_event_loop () . run_until_complete ( main ())","title":"Example"},{"location":"#license","text":"MIT","title":"License"},{"location":"#wip","text":"","title":"WIP"}]}
\ No newline at end of file
+{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Async Python DDD utilities library Key Features: Aggregates : Aggregate & AggregateRoot ValueObjects : Id, Timestamp & StrDateTime CQRS : Command, CommandBus, SimpleCommandBus, Query, Response, QueryHandler, QueryBus & SimpleQueryBus EventSourcing : Event, EventMapper, EventPublisher, EventHandler, EventBus, SimpleEventBus & InternalEventPublisher Errors : raise_, BaseError, NotFoundError, ConflictError, BadRequestError, UnauthorizedError, ForbiddenError, UnknownError, IdInvalidError, TimestampInvalidError, DateTimeInvalidError, EventMapperNotFoundError, EventNotPublishedError, CommandNotRegisteredError & QueryNotRegisteredError Tests : AsyncMock & mock Utils : get_env & get_simple_logger Requirements Python 3.9+ Installation python3 -m pip install aioddd Example from asyncio import get_event_loop from dataclasses import dataclass from typing import Type from aioddd import NotFoundError , \\ Command , CommandHandler , SimpleCommandBus , \\ Query , QueryHandler , OptionalResponse , SimpleQueryBus , Event _products = [] class ProductStored ( Event ): @dataclass class Attributes : ref : str attributes : Attributes class StoreProductCommand ( Command ): def __init__ ( self , ref : str ): self . ref = ref class StoreProductCommandHandler ( CommandHandler ): def subscribed_to ( self ) -> Type [ Command ]: return StoreProductCommand async def handle ( self , command : StoreProductCommand ) -> None : _products . append ( command . ref ) class ProductNotFoundError ( NotFoundError ): _code = 'product_not_found' _title = 'Product not found' class FindProductQuery ( Query ): def __init__ ( self , ref : str ): self . ref = ref class FindProductQueryHandler ( QueryHandler ): def subscribed_to ( self ) -> Type [ Query ]: return FindProductQuery async def handle ( self , query : FindProductQuery ) -> OptionalResponse : if query . ref != '123' : raise ProductNotFoundError . create ( detail = { 'ref' : query . ref }) return { 'ref' : query . ref } async def main () -> None : commands_bus = SimpleCommandBus ([ StoreProductCommandHandler ()]) await commands_bus . dispatch ( StoreProductCommand ( '123' )) query_bus = SimpleQueryBus ([ FindProductQueryHandler ()]) response = await query_bus . ask ( FindProductQuery ( '123' )) print ( response ) if __name__ == '__main__' : get_event_loop () . run_until_complete ( main ()) License MIT WIP","title":"aioddd"},{"location":"#async-python-ddd-utilities-library","text":"Key Features: Aggregates : Aggregate & AggregateRoot ValueObjects : Id, Timestamp & StrDateTime CQRS : Command, CommandBus, SimpleCommandBus, Query, Response, QueryHandler, QueryBus & SimpleQueryBus EventSourcing : Event, EventMapper, EventPublisher, EventHandler, EventBus, SimpleEventBus & InternalEventPublisher Errors : raise_, BaseError, NotFoundError, ConflictError, BadRequestError, UnauthorizedError, ForbiddenError, UnknownError, IdInvalidError, TimestampInvalidError, DateTimeInvalidError, EventMapperNotFoundError, EventNotPublishedError, CommandNotRegisteredError & QueryNotRegisteredError Tests : AsyncMock & mock Utils : get_env & get_simple_logger","title":"Async Python DDD utilities library"},{"location":"#requirements","text":"Python 3.9+","title":"Requirements"},{"location":"#installation","text":"python3 -m pip install aioddd","title":"Installation"},{"location":"#example","text":"from asyncio import get_event_loop from dataclasses import dataclass from typing import Type from aioddd import NotFoundError , \\ Command , CommandHandler , SimpleCommandBus , \\ Query , QueryHandler , OptionalResponse , SimpleQueryBus , Event _products = [] class ProductStored ( Event ): @dataclass class Attributes : ref : str attributes : Attributes class StoreProductCommand ( Command ): def __init__ ( self , ref : str ): self . ref = ref class StoreProductCommandHandler ( CommandHandler ): def subscribed_to ( self ) -> Type [ Command ]: return StoreProductCommand async def handle ( self , command : StoreProductCommand ) -> None : _products . append ( command . ref ) class ProductNotFoundError ( NotFoundError ): _code = 'product_not_found' _title = 'Product not found' class FindProductQuery ( Query ): def __init__ ( self , ref : str ): self . ref = ref class FindProductQueryHandler ( QueryHandler ): def subscribed_to ( self ) -> Type [ Query ]: return FindProductQuery async def handle ( self , query : FindProductQuery ) -> OptionalResponse : if query . ref != '123' : raise ProductNotFoundError . create ( detail = { 'ref' : query . ref }) return { 'ref' : query . ref } async def main () -> None : commands_bus = SimpleCommandBus ([ StoreProductCommandHandler ()]) await commands_bus . dispatch ( StoreProductCommand ( '123' )) query_bus = SimpleQueryBus ([ FindProductQueryHandler ()]) response = await query_bus . ask ( FindProductQuery ( '123' )) print ( response ) if __name__ == '__main__' : get_event_loop () . run_until_complete ( main ())","title":"Example"},{"location":"#license","text":"MIT","title":"License"},{"location":"#wip","text":"","title":"WIP"}]}
\ No newline at end of file
diff --git a/docs_src/generated/es/index.html b/docs_src/generated/es/index.html
index fe8bb13..e0fd2cd 100644
--- a/docs_src/generated/es/index.html
+++ b/docs_src/generated/es/index.html
@@ -433,7 +433,7 @@ Librería de utilidades Asyn
Requisitos
-- Python 3.8+
+- Python 3.9+
Instalación
python3 -m pip install aioddd
diff --git a/docs_src/generated/es/search/search_index.json b/docs_src/generated/es/search/search_index.json
index 7d5a240..6ab0ce1 100644
--- a/docs_src/generated/es/search/search_index.json
+++ b/docs_src/generated/es/search/search_index.json
@@ -1 +1 @@
-{"config":{"indexing":"full","lang":["es"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Librer\u00eda de utilidades Async Python de DDD \u00bfQu\u00e9 ofrece?: Aggregates : Aggregate & AggregateRoot ValueObjects : Id, Timestamp & StrDateTime CQRS : Command, CommandBus, SimpleCommandBus, Query, Response, QueryHandler, QueryBus & SimpleQueryBus EventSourcing : Event, EventMapper, EventPublisher, EventHandler, EventBus, SimpleEventBus & InternalEventPublisher Errors : raise_, BaseError, NotFoundError, ConflictError, BadRequestError, UnauthorizedError, ForbiddenError, UnknownError, IdInvalidError, TimestampInvalidError, DateTimeInvalidError, EventMapperNotFoundError, EventNotPublishedError, CommandNotRegisteredError & QueryNotRegisteredError Tests : AsyncMock & mock Utils : get_env & get_simple_logger Requisitos Python 3.8+ Instalaci\u00f3n python3 -m pip install aioddd Ejemplo from asyncio import get_event_loop from dataclasses import dataclass from typing import Type from aioddd import NotFoundError , \\ Command , CommandHandler , SimpleCommandBus , \\ Query , QueryHandler , OptionalResponse , SimpleQueryBus , Event _products = [] class ProductStored ( Event ): @dataclass class Attributes : ref : str attributes : Attributes class StoreProductCommand ( Command ): def __init__ ( self , ref : str ): self . ref = ref class StoreProductCommandHandler ( CommandHandler ): def subscribed_to ( self ) -> Type [ Command ]: return StoreProductCommand async def handle ( self , command : StoreProductCommand ) -> None : _products . append ( command . ref ) class ProductNotFoundError ( NotFoundError ): _code = 'product_not_found' _title = 'Product not found' class FindProductQuery ( Query ): def __init__ ( self , ref : str ): self . ref = ref class FindProductQueryHandler ( QueryHandler ): def subscribed_to ( self ) -> Type [ Query ]: return FindProductQuery async def handle ( self , query : FindProductQuery ) -> OptionalResponse : if query . ref != '123' : raise ProductNotFoundError . create ( detail = { 'ref' : query . ref }) return { 'ref' : query . ref } async def main () -> None : commands_bus = SimpleCommandBus ([ StoreProductCommandHandler ()]) await commands_bus . dispatch ( StoreProductCommand ( '123' )) query_bus = SimpleQueryBus ([ FindProductQueryHandler ()]) response = await query_bus . ask ( FindProductQuery ( '123' )) print ( response ) if __name__ == '__main__' : get_event_loop () . run_until_complete ( main ()) Licencia MIT WIP","title":"aioddd"},{"location":"#libreria-de-utilidades-async-python-de-ddd","text":"\u00bfQu\u00e9 ofrece?: Aggregates : Aggregate & AggregateRoot ValueObjects : Id, Timestamp & StrDateTime CQRS : Command, CommandBus, SimpleCommandBus, Query, Response, QueryHandler, QueryBus & SimpleQueryBus EventSourcing : Event, EventMapper, EventPublisher, EventHandler, EventBus, SimpleEventBus & InternalEventPublisher Errors : raise_, BaseError, NotFoundError, ConflictError, BadRequestError, UnauthorizedError, ForbiddenError, UnknownError, IdInvalidError, TimestampInvalidError, DateTimeInvalidError, EventMapperNotFoundError, EventNotPublishedError, CommandNotRegisteredError & QueryNotRegisteredError Tests : AsyncMock & mock Utils : get_env & get_simple_logger","title":"Librer\u00eda de utilidades Async Python de DDD"},{"location":"#requisitos","text":"Python 3.8+","title":"Requisitos"},{"location":"#instalacion","text":"python3 -m pip install aioddd","title":"Instalaci\u00f3n"},{"location":"#ejemplo","text":"from asyncio import get_event_loop from dataclasses import dataclass from typing import Type from aioddd import NotFoundError , \\ Command , CommandHandler , SimpleCommandBus , \\ Query , QueryHandler , OptionalResponse , SimpleQueryBus , Event _products = [] class ProductStored ( Event ): @dataclass class Attributes : ref : str attributes : Attributes class StoreProductCommand ( Command ): def __init__ ( self , ref : str ): self . ref = ref class StoreProductCommandHandler ( CommandHandler ): def subscribed_to ( self ) -> Type [ Command ]: return StoreProductCommand async def handle ( self , command : StoreProductCommand ) -> None : _products . append ( command . ref ) class ProductNotFoundError ( NotFoundError ): _code = 'product_not_found' _title = 'Product not found' class FindProductQuery ( Query ): def __init__ ( self , ref : str ): self . ref = ref class FindProductQueryHandler ( QueryHandler ): def subscribed_to ( self ) -> Type [ Query ]: return FindProductQuery async def handle ( self , query : FindProductQuery ) -> OptionalResponse : if query . ref != '123' : raise ProductNotFoundError . create ( detail = { 'ref' : query . ref }) return { 'ref' : query . ref } async def main () -> None : commands_bus = SimpleCommandBus ([ StoreProductCommandHandler ()]) await commands_bus . dispatch ( StoreProductCommand ( '123' )) query_bus = SimpleQueryBus ([ FindProductQueryHandler ()]) response = await query_bus . ask ( FindProductQuery ( '123' )) print ( response ) if __name__ == '__main__' : get_event_loop () . run_until_complete ( main ())","title":"Ejemplo"},{"location":"#licencia","text":"MIT","title":"Licencia"},{"location":"#wip","text":"","title":"WIP"}]}
\ No newline at end of file
+{"config":{"indexing":"full","lang":["es"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Librer\u00eda de utilidades Async Python de DDD \u00bfQu\u00e9 ofrece?: Aggregates : Aggregate & AggregateRoot ValueObjects : Id, Timestamp & StrDateTime CQRS : Command, CommandBus, SimpleCommandBus, Query, Response, QueryHandler, QueryBus & SimpleQueryBus EventSourcing : Event, EventMapper, EventPublisher, EventHandler, EventBus, SimpleEventBus & InternalEventPublisher Errors : raise_, BaseError, NotFoundError, ConflictError, BadRequestError, UnauthorizedError, ForbiddenError, UnknownError, IdInvalidError, TimestampInvalidError, DateTimeInvalidError, EventMapperNotFoundError, EventNotPublishedError, CommandNotRegisteredError & QueryNotRegisteredError Tests : AsyncMock & mock Utils : get_env & get_simple_logger Requisitos Python 3.9+ Instalaci\u00f3n python3 -m pip install aioddd Ejemplo from asyncio import get_event_loop from dataclasses import dataclass from typing import Type from aioddd import NotFoundError , \\ Command , CommandHandler , SimpleCommandBus , \\ Query , QueryHandler , OptionalResponse , SimpleQueryBus , Event _products = [] class ProductStored ( Event ): @dataclass class Attributes : ref : str attributes : Attributes class StoreProductCommand ( Command ): def __init__ ( self , ref : str ): self . ref = ref class StoreProductCommandHandler ( CommandHandler ): def subscribed_to ( self ) -> Type [ Command ]: return StoreProductCommand async def handle ( self , command : StoreProductCommand ) -> None : _products . append ( command . ref ) class ProductNotFoundError ( NotFoundError ): _code = 'product_not_found' _title = 'Product not found' class FindProductQuery ( Query ): def __init__ ( self , ref : str ): self . ref = ref class FindProductQueryHandler ( QueryHandler ): def subscribed_to ( self ) -> Type [ Query ]: return FindProductQuery async def handle ( self , query : FindProductQuery ) -> OptionalResponse : if query . ref != '123' : raise ProductNotFoundError . create ( detail = { 'ref' : query . ref }) return { 'ref' : query . ref } async def main () -> None : commands_bus = SimpleCommandBus ([ StoreProductCommandHandler ()]) await commands_bus . dispatch ( StoreProductCommand ( '123' )) query_bus = SimpleQueryBus ([ FindProductQueryHandler ()]) response = await query_bus . ask ( FindProductQuery ( '123' )) print ( response ) if __name__ == '__main__' : get_event_loop () . run_until_complete ( main ()) Licencia MIT WIP","title":"aioddd"},{"location":"#libreria-de-utilidades-async-python-de-ddd","text":"\u00bfQu\u00e9 ofrece?: Aggregates : Aggregate & AggregateRoot ValueObjects : Id, Timestamp & StrDateTime CQRS : Command, CommandBus, SimpleCommandBus, Query, Response, QueryHandler, QueryBus & SimpleQueryBus EventSourcing : Event, EventMapper, EventPublisher, EventHandler, EventBus, SimpleEventBus & InternalEventPublisher Errors : raise_, BaseError, NotFoundError, ConflictError, BadRequestError, UnauthorizedError, ForbiddenError, UnknownError, IdInvalidError, TimestampInvalidError, DateTimeInvalidError, EventMapperNotFoundError, EventNotPublishedError, CommandNotRegisteredError & QueryNotRegisteredError Tests : AsyncMock & mock Utils : get_env & get_simple_logger","title":"Librer\u00eda de utilidades Async Python de DDD"},{"location":"#requisitos","text":"Python 3.9+","title":"Requisitos"},{"location":"#instalacion","text":"python3 -m pip install aioddd","title":"Instalaci\u00f3n"},{"location":"#ejemplo","text":"from asyncio import get_event_loop from dataclasses import dataclass from typing import Type from aioddd import NotFoundError , \\ Command , CommandHandler , SimpleCommandBus , \\ Query , QueryHandler , OptionalResponse , SimpleQueryBus , Event _products = [] class ProductStored ( Event ): @dataclass class Attributes : ref : str attributes : Attributes class StoreProductCommand ( Command ): def __init__ ( self , ref : str ): self . ref = ref class StoreProductCommandHandler ( CommandHandler ): def subscribed_to ( self ) -> Type [ Command ]: return StoreProductCommand async def handle ( self , command : StoreProductCommand ) -> None : _products . append ( command . ref ) class ProductNotFoundError ( NotFoundError ): _code = 'product_not_found' _title = 'Product not found' class FindProductQuery ( Query ): def __init__ ( self , ref : str ): self . ref = ref class FindProductQueryHandler ( QueryHandler ): def subscribed_to ( self ) -> Type [ Query ]: return FindProductQuery async def handle ( self , query : FindProductQuery ) -> OptionalResponse : if query . ref != '123' : raise ProductNotFoundError . create ( detail = { 'ref' : query . ref }) return { 'ref' : query . ref } async def main () -> None : commands_bus = SimpleCommandBus ([ StoreProductCommandHandler ()]) await commands_bus . dispatch ( StoreProductCommand ( '123' )) query_bus = SimpleQueryBus ([ FindProductQueryHandler ()]) response = await query_bus . ask ( FindProductQuery ( '123' )) print ( response ) if __name__ == '__main__' : get_event_loop () . run_until_complete ( main ())","title":"Ejemplo"},{"location":"#licencia","text":"MIT","title":"Licencia"},{"location":"#wip","text":"","title":"WIP"}]}
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 78712d0..0679d15 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,11 +4,11 @@ requires = ["setuptools"]
[project]
authors = [
- { name = "ticdenis", email = "denisnavarroalcaide@outlook.es" },
+ { name = "ticdenis", email = "navarrodenis940503@outlook.com" },
{ name = "yasti4", email = "adria_4_@hotmail.com" },
]
maintainers = [
- { name = "ticdenis", email = "denisnavarroalcaide@outlook.es" }
+ { name = "ticdenis", email = "navarrodenis940503@outlook.com" }
]
classifiers = [
"Intended Audience :: Information Technology",
@@ -24,11 +24,11 @@ classifiers = [
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3 :: Only",
- "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
]
dependencies = []
description = "Async Python DDD utilities library."
@@ -37,44 +37,45 @@ keywords = ["ddd", "hexagonal", "cqrs", "aio", "async"]
license = { text = "MIT" }
name = "aioddd"
readme = "README.md"
-requires-python = ">=3.7"
+requires-python = ">=3.9"
[project.optional-dependencies]
dev = [
- "pre-commit>=2.19.0",
+ "pre-commit>=3.8.0",
"tomli>=2.0.1",
"types-backports>=0.1.3",
- "types-toml>=0.10.8",
+ "types-toml>=0.10.8.20240310",
]
deploy = [
- "build>=0.8.0",
- "setuptools>=65.3.0",
- "twine>=4.0.1",
- "wheel>=0.37.1",
+ "build>=1.2.2",
+ "setuptools>=75.1.0",
+ "twine>=5.1.1",
+ "wheel>=0.44.0",
]
docs = [
- "mkdocs>=1.3.0",
- "mkdocs-material>=8.2.15",
+ "mkdocs>=1.6.1",
+ "mkdocs-material>=9.5.37",
]
fmt = [
- "black>=22.8.0",
- "isort>=5.10.1",
+ "black>=24.8.0",
+ "isort>=5.13.2",
]
security-analysis = [
"bandit>=1.7.4",
- "liccheck>=0.7.2",
+ "liccheck>=0.9.2",
+ "setuptools>=75.1.0", # https://github.com/dhatim/python-license-check/issues/114
]
static-analysis = [
- "mypy>=0.971",
- "pylint>=2.15.0",
+ "mypy>=1.11.2",
+ "pylint>=3.3.1",
]
test = [
- "nest-asyncio>=1.5.5",
- "psutil>=5.9.2",
- "pytest>=7.1.3",
- "pytest-asyncio>=0.19.0",
- "pytest-cov>=3.0.0",
- "pytest-xdist>=2.5.0",
+ "nest-asyncio>=1.6.0",
+ "psutil>=6.0.0",
+ "pytest>=8.3.3",
+ "pytest-asyncio>=0.24.0",
+ "pytest-cov>=5.0.0",
+ "pytest-xdist>=3.6.1",
]
[project.urls]
@@ -91,6 +92,7 @@ include = ["aioddd*"]
"aioddd" = ["py.typed"]
[tool.bandit]
+exclude_dirs = ["docs", "docs_src", "var"]
skips = ["B101", "B311"]
[tool.black]
@@ -127,6 +129,8 @@ jobs = "0"
disable = "C0103,C0114,C0115,C0116,C0205,C0209,C0301,E0401,E0611,E1135,E1136,R0801,R0903,R0913,R1704,R1725,W0108,W0212,W0235,W0236,W0603,W0611,W0622,W0707,W1202"
[tool.pytest.ini_options]
+asyncio_default_fixture_loop_scope = "function"
+asyncio_mode = "auto"
cache_dir = "var/pytest"
addopts = "-q -n auto -p no:warnings --no-cov-on-fail"
testpaths = ["tests"]
@@ -145,7 +149,6 @@ unit-tests = "python3 -m pytest tests/unit"
integration-tests = "python3 -m pytest tests/integration"
functional-tests = "python3 -m pytest tests/functional"
coverage = "python3 -m pytest --cov --cov-report=html"
-tox = "python3 -m pip install -U tox tox-gh-actions && tox"
clean = """python3 -c \"
from glob import iglob
from shutil import rmtree
@@ -154,26 +157,44 @@ for pathname in ['./build', './*.egg-info', './dist', './var', '**/__pycache__']
for path in iglob(pathname, recursive=True):
rmtree(path, ignore_errors=True)
\""""
-
-[tool.tox]
-legacy_tox_ini = """
-[tox]
-envlist = py38, py39, py310, py311, py312
-isolated_build = True
-skipsdist = True
-skip_missing_interpreters = True
-toxworkdir = var/tox
-
-[gh-actions]
-python =
- 3.7: py37
- 3.8: py38
- 3.9: py39
- 3.10: py310
- 3.11: py311
- 3.12: py312
-
-[testenv]
-deps = .[dev,deploy,docs,fmt,security-analysis,static-analysis,test]
-commands = pytest
-"""
+check-dependency-update = """python3 -c \"
+from contextlib import suppress
+from json import loads
+from re import match
+try:
+ from tomllib import load # Python 3.11+
+except ModuleNotFoundError:
+ try:
+ from tomli import load
+ except ModuleNotFoundError:
+ from subprocess import run
+ run(['python3 -m pip install -U tomli'], stdout=-3, shell=True)
+ from tomli import load
+from urllib.request import urlopen
+
+def get_latest_version(package_name, pypi_url = 'https://pypi.org'):
+ with suppress(Exception), urlopen(f'{pypi_url}/pypi/{package_name}/json') as res:
+ return loads(res.read().decode()).get('info', {}).get('version')
+
+def parse_dependency(dep):
+ result = match(r'([a-zA-Z0-9_-]+)(\\[.*\\])?(?:([<>=!~]+)(.+))?', dep)
+ return result.groups() if result else (dep, '', '', '')
+
+def compare_deps(deps):
+ for pkg, extras, op, ver in [parse_dependency(dep) for dep in deps]:
+ if pkg and op and ver and (last := get_latest_version(pkg)):
+ if (op == '==' and ver != last) or (op == '>=' and last > ver) or (op == '<=' and last < ver):
+ yield (pkg + (extras or '') + ' (Current: ' + ver + ', Latest: ' + last + ')')
+
+with open('pyproject.toml', 'rb') as f:
+ pyproject = load(f).get('project', {})
+ deps = compare_deps(pyproject.get('dependencies', []))
+ opt_deps = {group: compare_deps(deps) for group, deps in pyproject.get('optional-dependencies', {}).items()}
+ parsed_deps = '\\n'.join(deps)
+ parsed_opt_deps = [(f'[{opt_dep_group}]\\n', '\\n'.join(opt_deps[opt_dep_group])) for opt_dep_group in opt_deps]
+ if not parsed_deps and not (''.join([v for _, v in parsed_opt_deps])):
+ print('Already up-to-date.')
+ else:
+ print('# pyproject.toml\\n[project.dependencies]\\n{0}\\n[project.optional-dependencies]{1}'.format(parsed_deps, ''.join([k + v for k, v in parsed_opt_deps])))
+ exit(1)
+\""""
diff --git a/run-script b/run-script
index ddaf2b7..36c7972 100755
--- a/run-script
+++ b/run-script
@@ -1,41 +1,28 @@
#!/usr/bin/env python3
-
from subprocess import run
from sys import argv
try:
- from tomllib import loads # type: ignore
+ from tomllib import loads # Python 3.11+
except ModuleNotFoundError:
- from tomli import loads # Python < 3.11
-
-
-def main() -> None:
- with open('pyproject.toml') as file:
- scripts = loads(file.read()).get('tool', {}).get('run-script', {})
-
- args = ['python3 run-script', *argv[1:]]
-
- if len(args) == 1:
- args.append('-h')
-
- if args[1] == 'run-script' or args[1] == 'run_script':
- args = [args[0], *(args[2:] or ['-h'])]
-
- if args[1] == '-h' or args[1] == '--help':
- commands = (chr(10) + ' ').join(scripts.keys())
- print('Usage: {0} [COMMAND]\n\nCommands:\n {1}\n\nOptions:\n -h,--help'.format(args[0], commands))
- exit(0)
-
- script = scripts.get(args[1])
- if not script:
- print('Missing command!')
- exit(1)
-
try:
- exit(run('{0}{1}'.format(script, ' '.join(args[2:])), shell=True).returncode)
- except KeyboardInterrupt:
- exit(130)
-
-
-if __name__ == '__main__':
- main()
+ from tomli import loads
+ except ModuleNotFoundError:
+ run(['python3 -m pip install -U tomli'], stdout=-3, shell=True)
+ from tomli import loads
+
+argv = argv if len(argv) > 1 else [argv[0], '-h']
+with open('pyproject.toml') as file:
+ scripts = loads(file.read()).get('tool', {}).get('run-script', {})
+
+if argv[1] in ['-h', '--help']:
+ commands = (chr(10) + ' ').join(scripts.keys())
+ print("Usage: {0} [COMMAND]\n\nCommands:\n {1}\n\nOptions:\n -h,--help".format(argv[0], commands))
+ exit(0)
+
+script = scripts.get(argv[1])
+if not script:
+ print('Missing command!')
+ exit(1)
+
+exit(run(script, shell=True).returncode)
diff --git a/tests/conftest.py b/tests/conftest.py
deleted file mode 100644
index 06e6bbd..0000000
--- a/tests/conftest.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from asyncio import AbstractEventLoop, get_event_loop_policy
-
-from _pytest.main import Session
-from pytest import fixture
-
-
-def pytest_sessionfinish(session: Session, exitstatus: int) -> None:
- # --suppress-no-test-exit-code
- if exitstatus == 5:
- session.exitstatus = 0
-
-
-@fixture(scope='session')
-def event_loop() -> AbstractEventLoop:
- return get_event_loop_policy().new_event_loop()
diff --git a/tests/unit/aioddd/test_aggregates.py b/tests/unit/aioddd/test_aggregates.py
index 3659a21..b3b964c 100644
--- a/tests/unit/aioddd/test_aggregates.py
+++ b/tests/unit/aioddd/test_aggregates.py
@@ -9,7 +9,7 @@ class _TestEvent(Event):
pass
agg = _TestAggregateRoot()
- evt = _TestEvent({})
+ evt = _TestEvent(_TestEvent.Attributes())
assert not len(agg.pull_aggregate_events())
diff --git a/tests/unit/aioddd/test_cqrs.py b/tests/unit/aioddd/test_cqrs.py
index 370b6c5..e213731 100644
--- a/tests/unit/aioddd/test_cqrs.py
+++ b/tests/unit/aioddd/test_cqrs.py
@@ -1,6 +1,4 @@
-from unittest.mock import Mock
-
-import pytest
+from pytest import raises
from aioddd import (
Command,
@@ -10,10 +8,9 @@
SimpleCommandBus,
SimpleQueryBus,
)
-from aioddd.testing import AsyncMock
+from aioddd.testing import AsyncMock, Mock
-@pytest.mark.asyncio
async def test_simple_command_bus() -> None:
command_handler_mock1 = Mock()
command_handler_mock2 = Mock()
@@ -48,7 +45,6 @@ class _CommandTest3(Command):
command_handler_mock3.handle.assert_not_called()
-@pytest.mark.asyncio
async def test_simple_command_bus_fails_because_command_was_not_registered() -> None:
class _CommandTest(Command):
pass
@@ -56,11 +52,10 @@ class _CommandTest(Command):
command = _CommandTest()
bus = SimpleCommandBus(handlers=[])
- with pytest.raises(CommandNotRegisteredError):
+ with raises(CommandNotRegisteredError):
await bus.dispatch(command=command)
-@pytest.mark.asyncio
async def test_simple_query_bus() -> None:
query_handler_mock1 = Mock()
query_handler_mock2 = Mock()
@@ -96,7 +91,6 @@ class _QueryTest3(Query):
assert res == 'test'
-@pytest.mark.asyncio
async def test_simple_query_bus_fails_because_query_was_not_registered() -> None:
class _QueryTest(Query):
pass
@@ -104,5 +98,5 @@ class _QueryTest(Query):
query = _QueryTest()
bus = SimpleQueryBus(handlers=[])
- with pytest.raises(QueryNotRegisteredError):
+ with raises(QueryNotRegisteredError):
await bus.ask(query=query)
diff --git a/tests/unit/aioddd/test_errors.py b/tests/unit/aioddd/test_errors.py
index 75a8e1c..634abcb 100644
--- a/tests/unit/aioddd/test_errors.py
+++ b/tests/unit/aioddd/test_errors.py
@@ -1,4 +1,4 @@
-import pytest
+from pytest import raises
from aioddd import (
BadRequestError,
@@ -19,7 +19,7 @@
def test_base_error_raise_a_system_exit_exception_when_it_is() -> None:
- pytest.raises(SystemExit, lambda: BaseError().with_exception(SystemExit()))
+ raises(SystemExit, lambda: BaseError().with_exception(SystemExit()))
def test_base_error() -> None:
diff --git a/tests/unit/aioddd/test_events.py b/tests/unit/aioddd/test_events.py
index 2131045..e210dc7 100644
--- a/tests/unit/aioddd/test_events.py
+++ b/tests/unit/aioddd/test_events.py
@@ -1,7 +1,6 @@
from dataclasses import asdict, dataclass
-from unittest.mock import Mock
-import pytest
+from pytest import raises
from aioddd import (
ConfigEventMappers,
@@ -17,7 +16,7 @@
find_event_mapper_by_name,
find_event_mapper_by_type,
)
-from aioddd.testing import AsyncMock, mock
+from aioddd.testing import AsyncMock, Mock, mock
def test_event_and_event_mapper() -> None:
@@ -66,7 +65,7 @@ class _TestEventMapper(EventMapper):
def test_not_find_event_mapper_by_name() -> None:
- pytest.raises(EventMapperNotFoundError, lambda: find_event_mapper_by_name(name='svc.name', mappers=[]))
+ raises(EventMapperNotFoundError, lambda: find_event_mapper_by_name(name='svc.name', mappers=[]))
def test_find_event_mapper_by_type() -> None:
@@ -85,7 +84,9 @@ def test_not_find_event_mapper_by_type() -> None:
class _EventTest(Event):
pass
- pytest.raises(EventMapperNotFoundError, lambda: find_event_mapper_by_type(msg=_EventTest({}), mappers=[]))
+ raises(
+ EventMapperNotFoundError, lambda: find_event_mapper_by_type(msg=_EventTest(_EventTest.Attributes()), mappers=[])
+ )
def test_config_event_mappers() -> None:
@@ -111,7 +112,6 @@ class _TestEventMapper3(EventMapper):
assert isinstance(sut.all()[2], _TestEventMapper3)
-@pytest.mark.asyncio
async def test_event_publishers() -> None:
event_publisher_mock1 = mock(EventPublisher, ['publish'])
event_publisher_mock2 = mock(EventPublisher, ['publish'])
@@ -132,7 +132,6 @@ async def test_event_publishers() -> None:
event_publisher_mock3.publish.assert_called_once()
-@pytest.mark.asyncio
async def test_simple_event_bus() -> None:
event_handler_mock1 = Mock()
event_handler_mock2 = Mock()
@@ -161,7 +160,6 @@ class _EventTest(Event):
event_handler_mock3.handle.assert_called_once()
-@pytest.mark.asyncio
async def test_internal_event_publisher() -> None:
event_bus_mock = mock(EventBus, ['notify'])