From 2ef1adfcea575599924bd1917b4c7c580c2ac914 Mon Sep 17 00:00:00 2001 From: Douwe van der Meij Date: Fri, 31 Mar 2023 09:10:27 +0200 Subject: [PATCH] add repositories order by, offset & limit, clean-up code --- fractal/__init__.py | 2 +- fractal/contrib/django/repositories.py | 21 ++--- fractal/contrib/fastapi/routers/default.py | 55 ++++++++------ fractal/contrib/gcp/firestore/repositories.py | 53 +++++-------- fractal/contrib/mongo/repositories.py | 26 ++++++- fractal/contrib/sqlalchemy/repositories.py | 70 +++++++++++++---- fractal/core/repositories/__init__.py | 9 ++- .../repositories/file_repository_mixin.py | 28 +------ .../repositories/filter_repository_mixin.py | 7 +- .../repositories/inmemory_repository_mixin.py | 19 ++++- .../repositories/sort_repository_mixin.py | 39 ---------- fractal/core/utils/application_context.py | 3 + pyproject.toml | 2 +- tests/contrib/sqlalchemy/test_repositories.py | 76 +++++++++++++++++++ .../test_inmemory_repository_mixin.py | 44 ++++++++++- tests/core/utils/test_application_context.py | 2 +- tests/fixtures/core/repositories.py | 5 ++ 17 files changed, 309 insertions(+), 152 deletions(-) delete mode 100644 fractal/core/repositories/sort_repository_mixin.py diff --git a/fractal/__init__.py b/fractal/__init__.py index e985dc6..81604bd 100644 --- a/fractal/__init__.py +++ b/fractal/__init__.py @@ -1,6 +1,6 @@ """Fractal is a scaffolding toolkit for building SOLID logic for your Python applications.""" -__version__ = "3.0.3" +__version__ = "3.1.0" from abc import ABC diff --git a/fractal/contrib/django/repositories.py b/fractal/contrib/django/repositories.py index e262b9a..4ab57b8 100644 --- a/fractal/contrib/django/repositories.py +++ b/fractal/contrib/django/repositories.py @@ -66,17 +66,20 @@ def find_one(self, specification: Specification) -> Optional[Entity]: return self._obj_to_domain(self.__get_obj(specification).__dict__) def find( - self, specification: Specification = None + self, + specification: Specification = None, + *, + offset: int = 0, + limit: int = 0, + order_by: str = "id", ) -> Generator[Entity, None, None]: - _filter = DjangoOrmSpecificationBuilder.build(specification) - if type(_filter) is list: - queryset = self.django_model.objects.filter(*_filter) - elif type(_filter) is dict: - queryset = self.django_model.objects.filter(**_filter) - elif type(_filter) is Q: - queryset = self.django_model.objects.filter(_filter) + if _filter := DjangoOrmSpecificationBuilder.build(specification): + queryset = self.django_model.objects.filter(_filter).order_by(order_by) else: - queryset = self.django_model.objects.all() + queryset = self.django_model.objects.all().order_by(order_by) + + if limit: + queryset.offset(offset).limit(limit) for obj in queryset: yield self._obj_to_domain(obj.__dict__) diff --git a/fractal/contrib/fastapi/routers/default.py b/fractal/contrib/fastapi/routers/default.py index 97e4fb3..4281bae 100644 --- a/fractal/contrib/fastapi/routers/default.py +++ b/fractal/contrib/fastapi/routers/default.py @@ -111,44 +111,50 @@ def to_entity(self, **kwargs): class BasicRestRouterService(DefaultRestRouterService): - def add_entity( + def find_entities( self, - contract: Contract, + q: str = "", *, specification: Specification = None, **kwargs, ): - try: - UUID(contract.id) - except (ValueError, TypeError): - contract.id = None - _entity = contract.to_entity( - user_id=kwargs.get("sub"), account_id=kwargs.get("account") - ) - return self.ingress_service.add( - _entity, str(kwargs.get("sub")), specification=specification + return self.ingress_service.find( + account_id=str(kwargs.get("account")), + q=q, + specification=specification, ) - def find_entities( + def get_entity( self, - q: str = "", + entity_id: UUID, *, specification: Specification = None, **kwargs, ): - return self.ingress_service.find( - str(kwargs.get("account")), q, specification=specification + return self.ingress_service.get( + entity_id=str(entity_id), + acount_id=str(kwargs.get("account")), + specification=specification, ) - def get_entity( + def add_entity( self, - entity_id: UUID, + contract: Contract, *, specification: Specification = None, **kwargs, ): - return self.ingress_service.get( - str(entity_id), str(kwargs.get("account")), specification=specification + try: + UUID(contract.id) + except (ValueError, TypeError): + contract.id = None + _entity = contract.to_entity( + user_id=kwargs.get("sub"), account_id=kwargs.get("account") + ) + return self.ingress_service.add( + entity=_entity, + user_id=str(kwargs.get("sub")), + specification=specification, ) def update_entity( @@ -170,7 +176,10 @@ def update_entity( account_id=kwargs.get("account"), ) return self.ingress_service.update( - str(entity_id), _entity, str(kwargs.get("sub")), specification=specification + entity_id=str(entity_id), + entity=_entity, + user_id=str(kwargs.get("sub")), + specification=specification, ) def delete_entity( @@ -181,9 +190,9 @@ def delete_entity( **kwargs, ) -> Dict: self.ingress_service.delete( - str(entity_id), - str(kwargs.get("sub")), - str(kwargs.get("account")), + entity_id=str(entity_id), + user_id=str(kwargs.get("sub")), + account_id=str(kwargs.get("account")), specification=specification, ) return {} diff --git a/fractal/contrib/gcp/firestore/repositories.py b/fractal/contrib/gcp/firestore/repositories.py index f33f08d..d9647e5 100644 --- a/fractal/contrib/gcp/firestore/repositories.py +++ b/fractal/contrib/gcp/firestore/repositories.py @@ -6,14 +6,12 @@ FirestoreSpecificationBuilder, ) from fractal_specifications.generic.specification import Specification -from google.cloud import firestore -from google.cloud.firestore_v1 import Client +from google.cloud.firestore_v1 import Client, Query from fractal import Settings from fractal.contrib.gcp import SettingsMixin from fractal.core.exceptions import ObjectNotFoundException from fractal.core.repositories import Entity, Repository -from fractal.core.repositories.sort_repository_mixin import SortRepositoryMixin def get_firestore_client(settings: Settings): @@ -93,15 +91,33 @@ def find_one(self, specification: Specification) -> Optional[Entity]: raise self.object_not_found_exception raise ObjectNotFoundException(f"{self.entity.__name__} not found!") - def find(self, specification: Specification = None) -> Iterator[Entity]: + def find( + self, + specification: Specification = None, + *, + offset: int = 0, + limit: int = 0, + order_by: str = "id", + ) -> Iterator[Entity]: _filter = FirestoreSpecificationBuilder.build(specification) - collection = self.collection + direction = Query.ASCENDING + if order_by.startswith("-"): + order_by = order_by[1:] + direction = Query.DESCENDING + collection = self.collection.order_by(order_by, direction=direction) if _filter: if isinstance(_filter, list): for f in _filter: collection = collection.where(*f) else: collection = collection.where(*_filter) + if limit: + if offset and (last := list(collection.limit(offset).stream())[-1]): + collection = collection.start_after( + {order_by: last.to_dict().get(order_by)} + ).limit(limit) + else: + collection = collection.limit(limit) for doc in collection.stream(): yield self.entity.from_dict(doc.to_dict()) @@ -123,30 +139,3 @@ def update(self, entity: Entity, *, upsert=False) -> Entity: doc_ref.set(entity.asdict(skip_types=(date,))) return entity return self.add(entity) - - -class FirestoreSortRepositoryMixin(SortRepositoryMixin[Entity]): - def find_sort( - self, specification: Specification = None, *, order_by: str = "", limit: int = 0 - ) -> Iterator[Entity]: - _filter = FirestoreSpecificationBuilder.build(specification) - collection = self.collection - if _filter: - if isinstance(_filter, list): - for f in _filter: - collection = collection.where(*f) - else: - collection = collection.where(*_filter) - if order_by: - if reverse := order_by.startswith("-"): - order_by = order_by[1:] - collection = collection.order_by( - order_by, - direction=firestore.Query.DESCENDING - if reverse - else firestore.Query.ASCENDING, - ) - if limit: - collection = collection.limit(limit) - for doc in collection.stream(): - yield self.entity.from_dict(doc.to_dict()) diff --git a/fractal/contrib/mongo/repositories.py b/fractal/contrib/mongo/repositories.py index 4638ef2..48cdd2c 100644 --- a/fractal/contrib/mongo/repositories.py +++ b/fractal/contrib/mongo/repositories.py @@ -70,10 +70,30 @@ def find_one(self, specification: Specification) -> Optional[Entity]: return self._obj_to_domain(obj) def find( - self, specification: Specification = None + self, + specification: Specification = None, + *, + offset: int = 0, + limit: int = 0, + order_by: str = "id", ) -> Generator[Entity, None, None]: - for obj in self.collection.find(MongoSpecificationBuilder.build(specification)): - yield self._obj_to_domain(obj) + direction = 1 + if order_by.startswith("-"): + order_by = order_by[1:] + direction = -1 + if limit: + for obj in ( + self.collection.find(MongoSpecificationBuilder.build(specification)) + .sort({order_by: direction}) + .skip(offset) + .limit(limit) + ): + yield self._obj_to_domain(obj) + else: + for obj in self.collection.find( + MongoSpecificationBuilder.build(specification) + ).sort({order_by: direction}): + yield self._obj_to_domain(obj) def is_healthy(self) -> bool: ok = self.client.server_info().get("ok", False) diff --git a/fractal/contrib/sqlalchemy/repositories.py b/fractal/contrib/sqlalchemy/repositories.py index 732f4a8..f666d7a 100644 --- a/fractal/contrib/sqlalchemy/repositories.py +++ b/fractal/contrib/sqlalchemy/repositories.py @@ -14,6 +14,7 @@ from sqlalchemy.engine import Engine from sqlalchemy.exc import ArgumentError, IntegrityError from sqlalchemy.orm import Mapper, Session, sessionmaker # NOQA +from sqlalchemy.sql.elements import BooleanClauseList from fractal.core.exceptions import DomainException from fractal.core.repositories import Entity, Repository @@ -217,9 +218,19 @@ def find_one(self, specification: Specification) -> Optional[Entity]: return self._dao_to_domain(entity) def find( - self, specification: Optional[Specification] = None + self, + specification: Optional[Specification] = None, + *, + offset: int = 0, + limit: int = 0, + order_by: str = "id", ) -> Generator[Entity, None, None]: - entities = self._find_raw(specification) + entities = self._find_raw( + specification=specification, + offset=offset, + limit=limit, + order_by=order_by, + ) if specification: entities = filter(lambda i: specification.is_satisfied_by(i), entities) @@ -265,25 +276,58 @@ def _find_raw( specification: Optional[Specification], *, entity_dao_class: Optional[SqlAlchemyDao] = None, + offset: int = 0, + limit: int = 0, + order_by: str = "id", ) -> List[Entity]: _filter = {} if specification: _filter = SqlAlchemyOrmSpecificationBuilder.build(specification) if isinstance(_filter, list): - entities = [] + filters = {} for f in _filter: - entities.extend( - list( - self.session.query( - entity_dao_class or self.entity_dao - ).filter_by(**dict(f)) - ) + filters.update(f) + from sqlalchemy import or_ + + # TODO move to SqlAlchemyOrmSpecificationBuilder + filters = or_( + *[ + getattr(entity_dao_class or self.entity_dao, k) == v + for k, v in filters.items() + ] + ) + else: + from sqlalchemy import and_ + + if len(_filter) > 1: + # TODO move to SqlAlchemyOrmSpecificationBuilder + filters = and_( + *[ + getattr(entity_dao_class or self.entity_dao, k) == v + for k, v in _filter.items() + ] ) - return entities + else: + filters = _filter + + if order_by.startswith("-"): + _order_by = getattr(entity_dao_class or self.entity_dao, order_by[1:]) + desc = True else: - return self.session.query(entity_dao_class or self.entity_dao).filter_by( - **dict(_filter) - ) + _order_by = getattr(entity_dao_class or self.entity_dao, order_by) + desc = False + + ret = self.session.query(entity_dao_class or self.entity_dao) + if type(filters) == dict: + ret = ret.filter_by(**filters) + if type(filters) == BooleanClauseList: + ret = ret.where(filters) + ret = ret.order_by(_order_by.desc() if desc else _order_by) + if limit: + ret = ret.offset(offset) + ret = ret.limit(limit) + return ret + return ret def is_healthy(self) -> bool: try: diff --git a/fractal/core/repositories/__init__.py b/fractal/core/repositories/__init__.py index f9c4ac9..36a101f 100644 --- a/fractal/core/repositories/__init__.py +++ b/fractal/core/repositories/__init__.py @@ -29,7 +29,14 @@ def find_one(self, specification: Specification) -> Optional[Entity]: raise NotImplementedError @abstractmethod - def find(self, specification: Specification = None) -> Iterator[Entity]: + def find( + self, + specification: Specification = None, + *, + offset: int = 0, + limit: int = 0, + order_by: str = "id", + ) -> Iterator[Entity]: raise NotImplementedError @abstractmethod diff --git a/fractal/core/repositories/file_repository_mixin.py b/fractal/core/repositories/file_repository_mixin.py index c1f014f..1c81a80 100644 --- a/fractal/core/repositories/file_repository_mixin.py +++ b/fractal/core/repositories/file_repository_mixin.py @@ -1,7 +1,6 @@ import json import os import uuid -from typing import Iterator, Optional from fractal_specifications.generic.operators import ( EqualsSpecification, @@ -10,7 +9,8 @@ from fractal_specifications.generic.specification import Specification from fractal.core.exceptions import ObjectNotFoundException -from fractal.core.repositories import Entity, FileRepository, Repository +from fractal.core.repositories import Entity, FileRepository +from fractal.core.repositories.inmemory_repository_mixin import InMemoryRepositoryMixin from fractal.core.utils.json_encoder import EnhancedEncoder @@ -21,7 +21,7 @@ def __init__(self, root_dir: str, *args, **kwargs): self.root_dir = root_dir -class FileRepositoryMixin(RootDirMixin, Repository[Entity]): +class FileRepositoryMixin(RootDirMixin, InMemoryRepositoryMixin[Entity]): @property def _filename(self) -> str: return os.path.join(self.root_dir, "db", f"{self.__class__.__name__}.txt") @@ -55,28 +55,6 @@ def remove_one(self, specification: Specification): [json.dumps(e.asdict(), cls=EnhancedEncoder) + "\n" for e in entities] ) - def find_one(self, specification: Specification) -> Optional[Entity]: - for entity in filter( - lambda i: specification.is_satisfied_by(i), self._entities - ): - return entity - if self.object_not_found_exception: - raise self.object_not_found_exception - raise ObjectNotFoundException(f"{self.entity.__name__} not found!") - - def find(self, specification: Optional[Specification] = None) -> Iterator[Entity]: - if specification: - entities = filter( - lambda i: specification.is_satisfied_by(i), self._entities - ) - else: - entities = self._entities - for entity in entities: - yield entity - - def is_healthy(self) -> bool: - return True - class FileFileRepositoryMixin(RootDirMixin, FileRepository): def upload_file(self, data: bytes, content_type: str, reference: str = "") -> str: diff --git a/fractal/core/repositories/filter_repository_mixin.py b/fractal/core/repositories/filter_repository_mixin.py index 8ba4d8c..8ffa8de 100644 --- a/fractal/core/repositories/filter_repository_mixin.py +++ b/fractal/core/repositories/filter_repository_mixin.py @@ -32,9 +32,14 @@ def find_filter( fields: List[str], specification: Specification = None, pre_processor: Optional[Callable[[str], str]] = None, + offset: int = 0, + limit: int = 0, + order_by: str = "id", ) -> Iterator[Entity]: if not q: - return self.find(specification) + return self.find( + specification, offset=offset, limit=limit, order_by=order_by + ) return filter_results( q, fields=fields, diff --git a/fractal/core/repositories/inmemory_repository_mixin.py b/fractal/core/repositories/inmemory_repository_mixin.py index 6b73ee6..9fc7966 100644 --- a/fractal/core/repositories/inmemory_repository_mixin.py +++ b/fractal/core/repositories/inmemory_repository_mixin.py @@ -34,13 +34,30 @@ def find_one(self, specification: Specification) -> Optional[Entity]: raise self.object_not_found_exception raise ObjectNotFoundException(f"{self.entity.__name__} not found!") - def find(self, specification: Optional[Specification] = None) -> Iterator[Entity]: + def find( + self, + specification: Optional[Specification] = None, + *, + offset: int = 0, + limit: int = 0, + order_by: str = "id", + ) -> Iterator[Entity]: if specification: entities = filter( lambda i: specification.is_satisfied_by(i), self.entities.values() ) else: entities = self.entities.values() + + reverse = False + if order_by.startswith("-"): + order_by = order_by[1:] + reverse = True + + entities = sorted(entities, key=lambda i: getattr(i, order_by), reverse=reverse) + + if limit: + entities = entities[offset : offset + limit] for entity in entities: yield entity diff --git a/fractal/core/repositories/sort_repository_mixin.py b/fractal/core/repositories/sort_repository_mixin.py deleted file mode 100644 index 7d3bee3..0000000 --- a/fractal/core/repositories/sort_repository_mixin.py +++ /dev/null @@ -1,39 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Generic, Iterator - -from fractal_specifications.generic.specification import Specification - -from fractal.core.repositories import Entity - - -class SortRepositoryMixin(Generic[Entity], ABC): - @abstractmethod - def find_sort( - self, specification: Specification = None, *, order_by: str = "", limit: int = 0 - ) -> Iterator[Entity]: - ... - - def find_one_sort( - self, specification: Specification = None, *, order_by: str = "" - ) -> Entity: - for release in self.find_sort( - specification=specification, order_by=order_by, limit=1 - ): - return release - - -class InMemorySortRepositoryMixin(SortRepositoryMixin[Entity]): - def find_sort( - self, specification: Specification = None, *, order_by: str = "", limit: int = 0 - ) -> Iterator[Entity]: - entities = self.find(specification) - if order_by: - if reverse := order_by.startswith("-"): - order_by = order_by[1:] - entities = sorted( - entities, key=lambda i: getattr(i, order_by), reverse=reverse - ) - for i, entity in enumerate(entities): - if limit and i == limit: - break - yield entity diff --git a/fractal/core/utils/application_context.py b/fractal/core/utils/application_context.py index 59a259b..05f1670 100644 --- a/fractal/core/utils/application_context.py +++ b/fractal/core/utils/application_context.py @@ -23,6 +23,9 @@ def __new__(cls, dotenv=True, *args, **kwargs): cls.instance.load() return cls.instance + def __getattr__(self, item): + return None + @classmethod def register_repository(cls, name): setattr(cls, name, None) diff --git a/pyproject.toml b/pyproject.toml index 6fdd494..8099b73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "fractal-toolkit" -version = "3.0.3" +version = "3.1.0" description = "Fractal is a scaffolding toolkit for building SOLID logic for your Python applications." authors = ["Douwe van der Meij "] diff --git a/tests/contrib/sqlalchemy/test_repositories.py b/tests/contrib/sqlalchemy/test_repositories.py index c36a0cb..3249bac 100644 --- a/tests/contrib/sqlalchemy/test_repositories.py +++ b/tests/contrib/sqlalchemy/test_repositories.py @@ -28,6 +28,82 @@ def test_find_no_spec(sqlalchemy_test_repository, sqlalchemy_test_model): assert res == [obj1, obj2] +def test_find_order_by_offset_limit(sqlalchemy_test_repository, sqlalchemy_test_model): + obj1 = sqlalchemy_test_model("1", "test1") + obj2 = sqlalchemy_test_model("2", "test2") + sqlalchemy_test_repository.add(obj1) + sqlalchemy_test_repository.add(obj2) + + assert list(sqlalchemy_test_repository.find(limit=1)) == [obj1] + assert list(sqlalchemy_test_repository.find(limit=1, order_by="name")) == [obj1] + assert list(sqlalchemy_test_repository.find(limit=1, order_by="-name")) == [obj2] + assert list( + sqlalchemy_test_repository.find(offset=1, limit=1, order_by="name") + ) == [obj2] + assert list( + sqlalchemy_test_repository.find(offset=1, limit=1, order_by="-name") + ) == [obj1] + assert list(sqlalchemy_test_repository.find(offset=2, limit=1)) == [] + + +def test_find_order_by_offset_limit_with_spec( + sqlalchemy_test_repository, sqlalchemy_test_model +): + from fractal_specifications.generic.operators import EqualsSpecification + + obj1 = sqlalchemy_test_model("1", "test1") + obj2 = sqlalchemy_test_model("2", "test2") + sqlalchemy_test_repository.add(obj1) + sqlalchemy_test_repository.add(obj2) + + spec = EqualsSpecification("id", "1") + + assert list(sqlalchemy_test_repository.find(spec, limit=1)) == [obj1] + assert list(sqlalchemy_test_repository.find(spec, limit=1, order_by="name")) == [ + obj1 + ] + assert list(sqlalchemy_test_repository.find(spec, limit=1, order_by="-name")) == [ + obj1 + ] + assert ( + list(sqlalchemy_test_repository.find(spec, offset=1, limit=1, order_by="name")) + == [] + ) + assert ( + list(sqlalchemy_test_repository.find(spec, offset=1, limit=1, order_by="-name")) + == [] + ) + assert list(sqlalchemy_test_repository.find(spec, offset=2, limit=1)) == [] + + +def test_find_order_by_offset_limit_with_or_spec( + sqlalchemy_test_repository, sqlalchemy_test_model +): + from fractal_specifications.generic.operators import EqualsSpecification + + obj1 = sqlalchemy_test_model("1", "test1") + obj2 = sqlalchemy_test_model("2", "test2") + sqlalchemy_test_repository.add(obj1) + sqlalchemy_test_repository.add(obj2) + + spec = EqualsSpecification("id", "1") | EqualsSpecification("name", "test2") + + assert list(sqlalchemy_test_repository.find(spec, limit=1)) == [obj1] + assert list(sqlalchemy_test_repository.find(spec, limit=1, order_by="name")) == [ + obj1 + ] + assert list(sqlalchemy_test_repository.find(spec, limit=1, order_by="-name")) == [ + obj2 + ] + assert list( + sqlalchemy_test_repository.find(spec, offset=1, limit=1, order_by="name") + ) == [obj2] + assert list( + sqlalchemy_test_repository.find(spec, offset=1, limit=1, order_by="-name") + ) == [obj1] + assert list(sqlalchemy_test_repository.find(spec, offset=2, limit=1)) == [] + + def test_find_with_spec(sqlalchemy_test_repository, sqlalchemy_test_model): from fractal_specifications.generic.operators import EqualsSpecification diff --git a/tests/core/repositories/test_inmemory_repository_mixin.py b/tests/core/repositories/test_inmemory_repository_mixin.py index 7e52ab5..a92a5b2 100644 --- a/tests/core/repositories/test_inmemory_repository_mixin.py +++ b/tests/core/repositories/test_inmemory_repository_mixin.py @@ -55,10 +55,50 @@ def test_find_one(inmemory_repository, an_object): ) -def test_find(inmemory_repository, an_object): +def test_find(inmemory_repository, an_object, another_object, yet_another_object): inmemory_repository.add(an_object) + inmemory_repository.add(another_object) + inmemory_repository.add(yet_another_object) - assert len(list(inmemory_repository.find())) == 1 + assert len(list(inmemory_repository.find())) == 3 + + +def test_find_order_by( + inmemory_repository, an_object, another_object, yet_another_object +): + inmemory_repository.add(an_object) + inmemory_repository.add(another_object) + inmemory_repository.add(yet_another_object) + + assert [i.name for i in inmemory_repository.find(order_by="name")] == [ + another_object.name, + an_object.name, + yet_another_object.name, + ] + assert [i.name for i in inmemory_repository.find(order_by="-name")] == [ + yet_another_object.name, + an_object.name, + another_object.name, + ] + + +def test_find_offset_limit( + inmemory_repository, an_object, another_object, yet_another_object +): + inmemory_repository.add(an_object) + inmemory_repository.add(another_object) + inmemory_repository.add(yet_another_object) + + assert [i.name for i in inmemory_repository.find(limit=1)] == [an_object.name] + assert [i.name for i in inmemory_repository.find(order_by="-name", limit=1)] == [ + yet_another_object.name + ] + assert [i.name for i in inmemory_repository.find(offset=2, limit=1)] == [ + yet_another_object.name + ] + assert [ + i.name for i in inmemory_repository.find(offset=2, order_by="-name", limit=1) + ] == [another_object.name] def test_find_with_specification(inmemory_repository, an_object): diff --git a/tests/core/utils/test_application_context.py b/tests/core/utils/test_application_context.py index fddf36a..9b6d9d2 100644 --- a/tests/core/utils/test_application_context.py +++ b/tests/core/utils/test_application_context.py @@ -1,5 +1,5 @@ def test_load(fake_application_context_class, fake_service_class): - assert not hasattr( + assert not getattr( fake_application_context_class.instance, fake_service_class.__class__.__name__ ) diff --git a/tests/fixtures/core/repositories.py b/tests/fixtures/core/repositories.py index 504efc4..32d2d3e 100644 --- a/tests/fixtures/core/repositories.py +++ b/tests/fixtures/core/repositories.py @@ -19,6 +19,11 @@ def another_object(): return AnObject("2", "another_name") +@pytest.fixture +def yet_another_object(): + return AnObject("3", "yet_another_object") + + @pytest.fixture def inmemory_repository(): from fractal.core.repositories.inmemory_repository_mixin import (