diff --git a/flama/applications.py b/flama/applications.py index 5b0142e2..245ecd5b 100644 --- a/flama/applications.py +++ b/flama/applications.py @@ -4,6 +4,7 @@ import typing as t from flama import asgi, exceptions, http, injection, types, url, validation, websockets +from flama.ddd.components import WorkerComponent from flama.events import Events from flama.middleware import MiddlewareStack from flama.models.modules import ModelsModule @@ -14,10 +15,8 @@ from flama.schemas.modules import SchemaModule try: - from flama.ddd.components import WorkerComponent from flama.resources.workers import FlamaWorker -except AssertionError: - WorkerComponent = None +except exceptions.DependencyNotInstalled: FlamaWorker = None if t.TYPE_CHECKING: @@ -87,10 +86,14 @@ def __init__( } ) + # Initialise components + default_components = [] + # Create worker - worker = FlamaWorker() if FlamaWorker else None + if (worker := FlamaWorker() if FlamaWorker else None) and WorkerComponent: + default_components.append(WorkerComponent(worker=worker)) - # Initialize Modules + # Initialise modules default_modules = [ ResourcesModule(worker=worker), SchemaModule(title, version, description, schema=schema, docs=docs), @@ -99,10 +102,6 @@ def __init__( self.modules = Modules(app=self, modules={*default_modules, *(modules or [])}) # Initialize router - default_components = [] - if worker and WorkerComponent: - default_components.append(WorkerComponent(worker=worker)) - self.app = self.router = Router( routes=routes, components=[*default_components, *(components or [])], lifespan=lifespan ) diff --git a/flama/ddd/repositories/base.py b/flama/ddd/repositories/base.py index 36545aa1..21e09fc5 100644 --- a/flama/ddd/repositories/base.py +++ b/flama/ddd/repositories/base.py @@ -1,10 +1,16 @@ import abc -__all__ = ["AbstractRepository"] +__all__ = ["AbstractRepository", "BaseRepository"] class AbstractRepository(abc.ABC): - """Base class for repositories.""" + """Abstract class for repositories.""" def __init__(self, *args, **kwargs): ... + + +class BaseRepository(AbstractRepository): + """Base class for repositories.""" + + ... diff --git a/flama/ddd/repositories/http.py b/flama/ddd/repositories/http.py index 28cd0bfb..f74ed997 100644 --- a/flama/ddd/repositories/http.py +++ b/flama/ddd/repositories/http.py @@ -6,7 +6,7 @@ import httpx from flama.ddd import exceptions -from flama.ddd.repositories import AbstractRepository +from flama.ddd.repositories import BaseRepository if t.TYPE_CHECKING: from flama.client import Client @@ -14,7 +14,7 @@ __all__ = ["HTTPRepository", "HTTPResourceManager", "HTTPResourceRepository"] -class HTTPRepository(AbstractRepository): +class HTTPRepository(BaseRepository): def __init__(self, client: "Client", *args, **kwargs): super().__init__(*args, **kwargs) self._client = client diff --git a/flama/ddd/repositories/sqlalchemy.py b/flama/ddd/repositories/sqlalchemy.py index 424a2416..c43eb9a1 100644 --- a/flama/ddd/repositories/sqlalchemy.py +++ b/flama/ddd/repositories/sqlalchemy.py @@ -2,7 +2,7 @@ from flama import exceptions from flama.ddd import exceptions as ddd_exceptions -from flama.ddd.repositories import AbstractRepository +from flama.ddd.repositories import BaseRepository try: import sqlalchemy @@ -17,7 +17,7 @@ __all__ = ["SQLAlchemyRepository", "SQLAlchemyTableManager", "SQLAlchemyTableRepository"] -class SQLAlchemyRepository(AbstractRepository): +class SQLAlchemyRepository(BaseRepository): """Base class for SQLAlchemy repositories. It provides a connection to the database.""" def __init__(self, connection: AsyncConnection, *args, **kwargs): diff --git a/flama/ddd/workers/__init__.py b/flama/ddd/workers/__init__.py index 2c7b81d2..982674bc 100644 --- a/flama/ddd/workers/__init__.py +++ b/flama/ddd/workers/__init__.py @@ -1,2 +1,3 @@ from flama.ddd.workers.base import * # noqa from flama.ddd.workers.http import * # noqa +from flama.ddd.workers.noop import * # noqa diff --git a/flama/ddd/workers/base.py b/flama/ddd/workers/base.py index ce4d89b8..32cbe8f8 100644 --- a/flama/ddd/workers/base.py +++ b/flama/ddd/workers/base.py @@ -1,10 +1,9 @@ import abc import asyncio -import inspect import logging import typing as t -from flama.ddd.repositories import AbstractRepository +from flama.ddd.repositories import BaseRepository from flama.exceptions import ApplicationError if t.TYPE_CHECKING: @@ -12,48 +11,19 @@ logger = logging.getLogger(__name__) -Repositories = t.NewType("Repositories", dict[str, type[AbstractRepository]]) +__all__ = ["AbstractWorker", "BaseWorker"] -__all__ = ["WorkerType", "AbstractWorker", "Worker"] - -class WorkerType(abc.ABCMeta): - """Metaclass for workers. - - It will gather all the repositories defined in the class as class attributes as a single dictionary under the name - `_repositories`. - """ - - def __new__(mcs, name: str, bases: tuple[type], namespace: dict[str, t.Any]): - if not mcs._is_abstract(namespace) and "__annotations__" in namespace: - namespace["_repositories"] = Repositories( - { - k: v - for k, v in namespace["__annotations__"].items() - if inspect.isclass(v) and issubclass(v, AbstractRepository) - } - ) - - namespace["__annotations__"] = { - k: v for k, v in namespace["__annotations__"].items() if k not in namespace["_repositories"] - } - - return super().__new__(mcs, name, bases, namespace) - - @staticmethod - def _is_abstract(namespace: dict[str, t.Any]) -> bool: - return namespace.get("__module__") == "flama.ddd.workers" and namespace.get("__qualname__") == "AbstractWorker" +Repositories = t.NewType("Repositories", dict[str, type[BaseRepository]]) -class AbstractWorker(abc.ABC, metaclass=WorkerType): +class AbstractWorker(abc.ABC): """Abstract class for workers. - It will be used to define the workers for the application. A worker consists of a set of repositories that will be - used to interact with entities and a mechanism for isolate a single unit of work. + It will be used to define the workers for the application. A worker must provide a mechanism to isolate a single + unit of work that will be used to interact with the repositories and entities of the application. """ - _repositories: t.ClassVar[dict[str, type[AbstractRepository]]] - def __init__(self, app: t.Optional["Flama"] = None): """Initialize the worker. @@ -125,27 +95,45 @@ async def rollback(self) -> None: ... -class Worker(AbstractWorker): - """Worker class. +class WorkerType(abc.ABCMeta): + """Metaclass for workers. - A basic implementation of the worker class that does not apply any specific behavior. + It will gather all the repositories defined in the class as class attributes as a single dictionary under the name + `_repositories` and remove them from the class annotations. """ - async def begin(self) -> None: - """Start a unit of work.""" - ... + def __new__(mcs, name: str, bases: tuple[type], namespace: dict[str, t.Any]): + if not mcs._is_base(namespace) and "__annotations__" in namespace: + namespace["_repositories"] = Repositories( + {k: v for k, v in namespace["__annotations__"].items() if mcs._is_repository(v)} + ) - async def end(self, *, rollback: bool = False) -> None: - """End a unit of work. + namespace["__annotations__"] = { + k: v for k, v in namespace["__annotations__"].items() if k not in namespace["_repositories"] + } - :param rollback: If the unit of work should be rolled back. - """ - ... + return super().__new__(mcs, name, bases, namespace) - async def commit(self) -> None: - """Commit the unit of work.""" - ... + @staticmethod + def _is_base(namespace: dict[str, t.Any]) -> bool: + return namespace.get("__module__") == "flama.ddd.workers" and namespace.get("__qualname__") == "BaseWorker" - async def rollback(self) -> None: - """Rollback the unit of work.""" - ... + @staticmethod + def _is_repository(obj: t.Any) -> bool: + try: + return issubclass(obj, BaseRepository) + except TypeError: + return False + + +class BaseWorker(AbstractWorker, metaclass=WorkerType): + """Base class for workers. + + It will be used to define the workers for the application. A worker consists of a set of repositories that will be + used to interact with entities and a mechanism for isolate a single unit of work. + + It will gather all the repositories defined in the class as class attributes as a single dictionary under the name + `_repositories` and remove them from the class annotations. + """ + + _repositories: t.ClassVar[dict[str, type[BaseRepository]]] diff --git a/flama/ddd/workers/http.py b/flama/ddd/workers/http.py index 90742fc8..950e7aa9 100644 --- a/flama/ddd/workers/http.py +++ b/flama/ddd/workers/http.py @@ -1,6 +1,6 @@ import typing as t -from flama.ddd.workers.base import AbstractWorker +from flama.ddd.workers.base import BaseWorker if t.TYPE_CHECKING: from flama import Flama @@ -9,7 +9,7 @@ __all__ = ["HTTPWorker"] -class HTTPWorker(AbstractWorker): +class HTTPWorker(BaseWorker): """Worker for HTTP client. It will provide a flama Client and create the repositories for the corresponding resources. diff --git a/flama/ddd/workers/noop.py b/flama/ddd/workers/noop.py new file mode 100644 index 00000000..8afdf52b --- /dev/null +++ b/flama/ddd/workers/noop.py @@ -0,0 +1,27 @@ +from flama.ddd.workers.base import BaseWorker + + +class NoopWorker(BaseWorker): + """Worker that does not apply any specific behavior. + + A basic implementation of the worker class that does not apply any specific behavior. + """ + + async def begin(self) -> None: + """Start a unit of work.""" + ... + + async def end(self, *, rollback: bool = False) -> None: + """End a unit of work. + + :param rollback: If the unit of work should be rolled back. + """ + ... + + async def commit(self) -> None: + """Commit the unit of work.""" + ... + + async def rollback(self) -> None: + """Rollback the unit of work.""" + ... diff --git a/flama/ddd/workers/sqlalchemy.py b/flama/ddd/workers/sqlalchemy.py index bd4cd2d4..8f140c8e 100644 --- a/flama/ddd/workers/sqlalchemy.py +++ b/flama/ddd/workers/sqlalchemy.py @@ -1,7 +1,7 @@ import logging from flama import exceptions -from flama.ddd.workers.base import AbstractWorker +from flama.ddd.workers.base import BaseWorker try: from sqlalchemy.ext.asyncio import AsyncConnection, AsyncTransaction @@ -16,7 +16,7 @@ logger = logging.getLogger(__name__) -class SQLAlchemyWorker(AbstractWorker): +class SQLAlchemyWorker(BaseWorker): """Worker for SQLAlchemy. It will provide a connection and a transaction to the database and create the repositories for the entities. diff --git a/flama/resources/modules.py b/flama/resources/modules.py index cfd7b2d4..21c3dcdf 100644 --- a/flama/resources/modules.py +++ b/flama/resources/modules.py @@ -1,14 +1,16 @@ import inspect import typing as t +from flama import exceptions from flama.modules import Module from flama.resources.resource import Resource from flama.resources.routing import ResourceRoute if t.TYPE_CHECKING: try: + from flama.ddd.repositories.sqlalchemy import SQLAlchemyTableRepository from flama.resources.workers import FlamaWorker - except AssertionError: + except exceptions.DependencyNotInstalled: ... @@ -60,3 +62,20 @@ def decorator(resource: type[Resource]) -> type[Resource]: return resource return decorator + + def add_repository(self, name: str, repository: type["SQLAlchemyTableRepository"]) -> None: + """Register a repository. + + :param name: The name of the repository. + :param repository: The repository class. + """ + if self.worker: + self.worker.add_repository(name, repository) + + def remove_repository(self, name: str) -> None: + """Deregister a repository. + + :param name: The name of the repository. + """ + if self.worker: + self.worker.remove_repository(name) diff --git a/flama/resources/routing.py b/flama/resources/routing.py index 0d51c35d..45db10c9 100644 --- a/flama/resources/routing.py +++ b/flama/resources/routing.py @@ -54,9 +54,10 @@ def build(self, app: t.Optional["Flama"] = None) -> None: super().build(app) if (root := (self.app if isinstance(self.app, Flama) else app)) and "ddd" in self.resource._meta.namespaces: - root.resources.worker._repositories[self.resource._meta.name] = self.resource._meta.namespaces["ddd"][ - "repository" - ] + root.resources.add_repository( + name=self.resource._meta.name, + repository=self.resource._meta.namespaces["ddd"]["repository"], + ) def resource_method( diff --git a/flama/resources/workers.py b/flama/resources/workers.py index e5f79860..e77e544e 100644 --- a/flama/resources/workers.py +++ b/flama/resources/workers.py @@ -1,3 +1,4 @@ +import dataclasses import typing as t from flama.ddd.workers.sqlalchemy import SQLAlchemyWorker @@ -8,6 +9,18 @@ from flama.ddd.repositories.sqlalchemy import SQLAlchemyTableRepository +@dataclasses.dataclass +class Repositories: + registered: dict[str, type["SQLAlchemyTableRepository"]] = dataclasses.field(default_factory=dict) + initialised: t.Optional[dict[str, "SQLAlchemyTableRepository"]] = None + + def init(self, connection: t.Any) -> None: + self.initialised = {r: cls(connection) for r, cls in self.registered.items()} + + def delete(self) -> None: + self.initialised = None + + class FlamaWorker(SQLAlchemyWorker): """The worker used by Flama Resources.""" @@ -20,8 +33,22 @@ def __init__(self, app: t.Optional["Flama"] = None): """ super().__init__(app) - self._repositories: dict[str, type[SQLAlchemyTableRepository]] = {} # type: ignore - self._init_repositories: t.Optional[dict[str, SQLAlchemyTableRepository]] = None + self._resources_repositories = Repositories() + + def add_repository(self, name: str, repository: type["SQLAlchemyTableRepository"]) -> None: + """Register a repository. + + :param name: The name of the repository. + :param repository: The repository class. + """ + self._resources_repositories.registered[name] = repository + + def remove_repository(self, name: str) -> None: + """Deregister a repository. + + :param name: The name of the repository. + """ + del self._resources_repositories.registered[name] @property def repositories(self) -> dict[str, "SQLAlchemyTableRepository"]: @@ -30,10 +57,10 @@ def repositories(self) -> dict[str, "SQLAlchemyTableRepository"]: :retirns: The initialized repositories. :raises ApplicationError: If the repositories are not initialized. """ - if not self._init_repositories: + if not self._resources_repositories.initialised: raise ApplicationError("Repositories not initialized") - return self._init_repositories + return self._resources_repositories.initialised async def begin(self) -> None: """Start a unit of work. @@ -41,7 +68,7 @@ async def begin(self) -> None: Initialize the connection, begin a transaction, and create the repositories. """ await self.begin_transaction() - self._init_repositories = {r: cls(self.connection) for r, cls in self._repositories.items()} + self._resources_repositories.init(self.connection) async def end(self, *, rollback: bool = False) -> None: """End a unit of work. @@ -51,4 +78,4 @@ async def end(self, *, rollback: bool = False) -> None: :param rollback: If the unit of work should be rolled back. """ await self.end_transaction(rollback=rollback) - del self._init_repositories + self._resources_repositories.delete() diff --git a/tests/ddd/conftest.py b/tests/ddd/conftest.py index 0eea6547..23334a6f 100644 --- a/tests/ddd/conftest.py +++ b/tests/ddd/conftest.py @@ -1,39 +1,9 @@ import pytest from flama import Flama -from flama.ddd.repositories import AbstractRepository -from flama.ddd.workers import AbstractWorker from flama.sqlalchemy import SQLAlchemyModule @pytest.fixture(scope="function") def app(): return Flama(schema=None, docs=None, modules={SQLAlchemyModule("sqlite+aiosqlite://")}) - - -@pytest.fixture(scope="function") -def repository(): - class FooRepository(AbstractRepository): - ... - - return FooRepository - - -@pytest.fixture(scope="function") -def worker(repository): - class FooWorker(AbstractWorker): - bar: repository - - async def begin(self): - ... - - async def end(self, *, rollback: bool = False): - ... - - async def commit(self): - ... - - async def rollback(self): - ... - - return FooWorker() diff --git a/tests/ddd/test_components.py b/tests/ddd/test_components.py index 998961d5..afd175ec 100644 --- a/tests/ddd/test_components.py +++ b/tests/ddd/test_components.py @@ -1,12 +1,39 @@ import pytest from flama import types -from flama.ddd import AbstractWorker from flama.ddd.components import WorkerComponent +from flama.ddd.repositories.base import BaseRepository +from flama.ddd.workers.base import BaseWorker from flama.injection.resolver import Parameter class TestCaseWorkerComponent: + @pytest.fixture(scope="function") + def repository(self): + class FooRepository(BaseRepository): + ... + + return FooRepository + + @pytest.fixture(scope="function") + def worker(self, repository): + class FooWorker(BaseWorker): + foo: repository + + async def begin(self): + ... + + async def end(self, *, rollback: bool = False): + ... + + async def commit(self): + ... + + async def rollback(self): + ... + + return FooWorker() + @pytest.fixture(scope="function") def component(self, worker): return WorkerComponent(worker) @@ -17,15 +44,15 @@ def test_init(self, component, worker): @pytest.fixture(scope="function") def parameter_types(self, worker): return { - "abstract_worker": AbstractWorker, - "foo_worker": worker.__class__, + "abstract_worker": BaseWorker, + "implemented_worker": worker.__class__, "int": int, } @pytest.mark.parametrize( ["param_name", "param_type", "expected"], [ - pytest.param("foo", "foo_worker", True, id="handle"), + pytest.param("foo", "implemented_worker", True, id="handle"), pytest.param("foo", "abstract_worker", False, id="not_handle_abstract"), pytest.param("foo", "int", False, id="not_handle_int"), ], diff --git a/tests/ddd/workers/test_base.py b/tests/ddd/workers/test_base.py index 83ae37d1..dc49ad2f 100644 --- a/tests/ddd/workers/test_base.py +++ b/tests/ddd/workers/test_base.py @@ -2,12 +2,36 @@ import pytest +from flama.ddd.repositories.base import BaseRepository +from flama.ddd.workers.base import AbstractWorker, BaseWorker from flama.exceptions import ApplicationError +@pytest.fixture(scope="function") +def repository(): + class FooRepository(BaseRepository): + ... + + return FooRepository + + class TestCaseAbstractWorker: - def test_new(self, worker): - assert hasattr(worker, "_repositories") + @pytest.fixture(scope="function") + def worker(self): + class FooWorker(AbstractWorker): + async def begin(self): + ... + + async def end(self, *, rollback: bool = False): + ... + + async def commit(self): + ... + + async def rollback(self): + ... + + return FooWorker() def test_app(self, app, worker): with pytest.raises(ApplicationError, match="Worker not initialized"): @@ -32,3 +56,28 @@ async def test_async_context(self, app, worker): assert worker.begin.await_args_list == [call()] assert worker.end.await_args_list == [call(rollback=False)] + + +class TestCaseBaseWorker: + @pytest.fixture(scope="function") + def worker(self, repository): + class FooWorker(BaseWorker): + foo: repository + + async def begin(self): + ... + + async def end(self, *, rollback: bool = False): + ... + + async def commit(self): + ... + + async def rollback(self): + ... + + return FooWorker() + + def test_new(self, worker, repository): + assert hasattr(worker, "_repositories") + assert worker._repositories == {"foo": repository} diff --git a/tests/ddd/workers/test_noop.py b/tests/ddd/workers/test_noop.py new file mode 100644 index 00000000..9614c650 --- /dev/null +++ b/tests/ddd/workers/test_noop.py @@ -0,0 +1,17 @@ +import pytest + +from flama.ddd.workers import NoopWorker + + +class TestCaseNoopWorker: + @pytest.fixture(scope="function") + def worker(self, client): + class FooWorker(NoopWorker): + ... + + return FooWorker(client.app) + + def test_init(self, app): + worker = NoopWorker(app) + + assert worker._app == app diff --git a/tests/resources/test_crud.py b/tests/resources/test_crud.py index 8c75ac0e..db2d9b54 100644 --- a/tests/resources/test_crud.py +++ b/tests/resources/test_crud.py @@ -1,5 +1,5 @@ import datetime -import typing +import typing as t import uuid import marshmallow @@ -14,6 +14,7 @@ from flama.resources.crud import CRUDResource from flama.resources.routing import ResourceRoute, resource_method from flama.resources.workers import FlamaWorker +from flama.schemas import SchemaMetadata, SchemaType from flama.sqlalchemy import SQLAlchemyModule from tests.conftest import DATABASE_URL @@ -32,7 +33,7 @@ def add_resources(app, resource): yield - del app.resources.worker._repositories[resource._meta.name] + app.resources.remove_repository(resource._meta.name) @pytest.fixture(scope="function") @@ -60,12 +61,12 @@ class PuppyResource(CRUDResource): async def list( self, worker: FlamaWorker, - order_by: typing.Optional[str] = None, + order_by: t.Optional[str] = None, order_direction: str = "asc", - name: typing.Optional[str] = None, - custom_id__le: typing.Optional[int] = None, + name: t.Optional[str] = None, + custom_id__le: t.Optional[int] = None, **kwargs, - ) -> puppy_schema: + ) -> t.Annotated[list[SchemaType], SchemaMetadata(puppy_schema)]: """ description: Custom list method with filtering by name. """ @@ -146,7 +147,7 @@ class CustomUUIDResource(CRUDResource): yield CustomUUIDResource - del app.resources.worker._repositories[CustomUUIDResource._meta.name] + app.resources.remove_repository(CustomUUIDResource._meta.name) @pytest.fixture(scope="function") async def custom_id_uuid_model(self, app): diff --git a/tests/resources/test_modules.py b/tests/resources/test_modules.py index 3b2ca1ab..86fb8fc4 100644 --- a/tests/resources/test_modules.py +++ b/tests/resources/test_modules.py @@ -51,8 +51,9 @@ class PuppyResource(CRUDResource): ("/", {"PATCH"}, resource.partial_replace, {"tag": "partial-replace"}), ("/", {"DELETE"}, resource.drop, {"tag": "drop"}), ] + assert PuppyResource._meta.name in app.resources.worker._resources_repositories.registered finally: - del app.resources.worker._repositories[PuppyResource._meta.name] + app.resources.remove_repository(PuppyResource._meta.name) def test_add_resource_decorator(self, app, puppy_model, puppy_schema, tags): class PuppyResource(CRUDResource): @@ -79,8 +80,9 @@ class PuppyResource(CRUDResource): ("/", {"PATCH"}, resource.partial_replace, {"tag": "partial-replace"}), ("/", {"DELETE"}, resource.drop, {"tag": "drop"}), ] + assert PuppyResource._meta.name in app.resources.worker._resources_repositories.registered finally: - del app.resources.worker._repositories[PuppyResource._meta.name] + app.resources.remove_repository(PuppyResource._meta.name) def test_add_resource_wrong(self, app): with pytest.raises(ValueError, match=""): @@ -113,6 +115,6 @@ class PuppyResource(CRUDResource): ("/", {"DELETE"}, resource_route.resource.drop, {"tag": "drop"}), ] assert isinstance(resource_route.resource, PuppyResource) - assert PuppyResource._meta.name in app.resources.worker._repositories + assert PuppyResource._meta.name in app.resources.worker._resources_repositories.registered finally: - del app.resources.worker._repositories[PuppyResource._meta.name] + app.resources.remove_repository(PuppyResource._meta.name) diff --git a/tests/resources/test_workers.py b/tests/resources/test_workers.py index 6d41b3fb..2a10c702 100644 --- a/tests/resources/test_workers.py +++ b/tests/resources/test_workers.py @@ -2,6 +2,7 @@ from flama import Flama from flama.client import Client +from flama.ddd.repositories import HTTPRepository from flama.ddd.repositories.sqlalchemy import SQLAlchemyRepository from flama.exceptions import ApplicationError from flama.resources.workers import FlamaWorker @@ -23,23 +24,39 @@ class FooWorker(FlamaWorker): return FooWorker() @pytest.fixture(scope="function") - def repository(self): + def sqlalchemy_repository(self): class FooRepository(SQLAlchemyRepository): ... return FooRepository + @pytest.fixture(scope="function") + def http_repository(self): + class BarRepository(HTTPRepository): + ... + + return BarRepository + def test_init(self): worker = FlamaWorker() - assert not worker._init_repositories + assert not worker._resources_repositories.registered - async def test_async_context(self, app, worker, repository): + async def test_async_context(self, app, worker, http_repository, sqlalchemy_repository): worker.app = app - worker._repositories["foo"] = repository + worker.add_repository("foo", sqlalchemy_repository) + worker.add_repository("bar", http_repository) with pytest.raises(ApplicationError, match="Repositories not initialized"): worker.repositories async with worker: - assert worker.repositories == {"foo": repository(worker.connection)} + assert worker.repositories == { + "foo": sqlalchemy_repository(worker.connection), + "bar": http_repository(worker.connection), + } + + worker.remove_repository("bar") + + async with worker: + assert worker.repositories == {"foo": sqlalchemy_repository(worker.connection)}