From 3f4602a13ae6ecfff4ef947266b2adbec8faa099 Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Mon, 2 Dec 2024 13:58:40 +0000 Subject: [PATCH 1/9] Update `Dockerfile` --- Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Dockerfile b/Dockerfile index da651d466..9c6d2cb9e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,8 @@ FROM pypy:3.9-7.3.10 +RUN pip install --upgrade pip +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +ENV PATH="/root/.cargo/bin:${PATH}" RUN pip install poetry EXPOSE 8000 @@ -16,3 +19,4 @@ COPY ./docs ./docs RUN chmod -R a-wx ./docs CMD ["poetry", "run", "waitress-serve", "--port=8000", "--connection-limit=500", "--call", "ebl.app:get_app"] + From 17dee98c4e5f1782d6453200cc5bd8b26c069474 Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Mon, 2 Dec 2024 14:06:14 +0000 Subject: [PATCH 2/9] Update `main.yml` --- .github/workflows/main.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 58d128985..7d22838b1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,10 +28,16 @@ jobs: - uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + + - name: Install Rust + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + source $HOME/.cargo/env - name: Install id: install run: | + pip install --upgrade pip pip install poetry poetry install --no-root @@ -85,4 +91,4 @@ jobs: push: true tags: | ebl.badw.de/ebl-api:master - ${{format('ebl.badw.de/ebl-api:master.{0}', github.run_number)}} + ${{format('ebl.badw.de/ebl-api:master.{0}', github.run_number)}} \ No newline at end of file From 235aa1591cf90bf8624c700b25840e650dd801dd Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Wed, 4 Dec 2024 17:05:56 +0000 Subject: [PATCH 3/9] Implement dossier classes & tests (WiP) --- ebl/context.py | 2 + ebl/dossier/application/dossier_repository.py | 10 ++ ebl/dossier/domain/dossier_record.py | 20 ++++ .../mongo_dossier_record_repository.py | 51 ++++++++++ ebl/dossier/web/bootstrap.py | 11 +++ ebl/dossier/web/dossier_records.py | 21 ++++ ebl/tests/dossier/test_dossier.py | 96 +++++++++++++++++++ ebl/tests/dossier/test_dossier_repository.py | 84 ++++++++++++++++ ebl/tests/dossier/test_dossier_route.py | 80 ++++++++++++++++ ebl/tests/factories/dossier.py | 36 +++++++ 10 files changed, 411 insertions(+) create mode 100644 ebl/dossier/application/dossier_repository.py create mode 100644 ebl/dossier/domain/dossier_record.py create mode 100644 ebl/dossier/infrastructure/mongo_dossier_record_repository.py create mode 100644 ebl/dossier/web/bootstrap.py create mode 100644 ebl/dossier/web/dossier_records.py create mode 100644 ebl/tests/dossier/test_dossier.py create mode 100644 ebl/tests/dossier/test_dossier_repository.py create mode 100644 ebl/tests/dossier/test_dossier_route.py create mode 100644 ebl/tests/factories/dossier.py diff --git a/ebl/context.py b/ebl/context.py index ab8681eec..6c4754eed 100644 --- a/ebl/context.py +++ b/ebl/context.py @@ -29,6 +29,7 @@ from ebl.fragmentarium.infrastructure.mongo_findspot_repository import ( MongoFindspotRepository, ) +from ebl.dossier.application.dossier_repository import DossierRepository @attr.s(auto_attribs=True, frozen=True) @@ -53,6 +54,7 @@ class Context: cache: Cache parallel_line_injector: ParallelLineInjector afo_register_repository: AfoRegisterRepository + dossier_repository: DossierRepository def get_bibliography(self): return Bibliography(self.bibliography_repository, self.changelog) diff --git a/ebl/dossier/application/dossier_repository.py b/ebl/dossier/application/dossier_repository.py new file mode 100644 index 000000000..994f38a1b --- /dev/null +++ b/ebl/dossier/application/dossier_repository.py @@ -0,0 +1,10 @@ +from abc import ABC, abstractmethod + +from ebl.afo_register.domain.afo_register_record import ( + DossierRecord, +) + +class AfoRegisterRepository(ABC): + + @abstractmethod + def fetch(self, query, *args, **kwargs) -> DossierRecord: ... diff --git a/ebl/dossier/domain/dossier_record.py b/ebl/dossier/domain/dossier_record.py new file mode 100644 index 000000000..5b2b69df4 --- /dev/null +++ b/ebl/dossier/domain/dossier_record.py @@ -0,0 +1,20 @@ +import attr +from typing import Sequence, Optional + +from ebl.common.domain.provenance import Provenance +from ebl.fragmentarium.domain.fragment import Script +from ebl.bibliography.domain.reference import ReferenceType + + +@attr.s(frozen=True, auto_attribs=True) +class AfoRegisterRecord: + id: int + name: str + description: str + is_approximate_date: bool + year_range_from: Optional[int] = None + year_range_to: Optional[int] = None + related_kings: Sequence[int] + provenance: Provenance + script: Script + references: Sequence[ReferenceType] diff --git a/ebl/dossier/infrastructure/mongo_dossier_record_repository.py b/ebl/dossier/infrastructure/mongo_dossier_record_repository.py new file mode 100644 index 000000000..b75920dcc --- /dev/null +++ b/ebl/dossier/infrastructure/mongo_dossier_record_repository.py @@ -0,0 +1,51 @@ +from marshmallow import Schema, fields, post_load, EXCLUDE +from pymongo.database import Database +from ebl.mongo_collection import MongoCollection +from ebl.dossier.domain.dossier_record import DossierRecord +from ebl.dossier.application.dossier_repository import DossierRepository +from ebl.common.domain.provenance import Provenance +from ebl.fragmentarium.application.fragment_schema import ScriptSchema +from ebl.schemas import NameEnumField +from ebl.bibliography.domain.reference import ReferenceType + +COLLECTION = "dossier" + +provenance_field = fields.Function( + lambda object_: getattr(object_.site, "long_name", None), + lambda value: Provenance.from_name(value) if value else None, + allow_none=True, +) + + +class DossierRecordSchema(Schema): + class Meta: + unknown = EXCLUDE + + id: fields.Integer(required=True) + name: fields.String(required=True) + description: fields.String(required=True) + is_approximate_date: fields.Boolean(load_default=False) + year_range_from: fields.String( + data_key="yearRangeFrom", allow_none=True, load_default=None + ) + year_range_to: fields.String( + data_key="yearRangeTo", allow_none=True, load_default=None + ) + related_kings: fields.List(fields.Integer(), data_key="relatedKings") + provenance: provenance_field + script: fields.Nested(ScriptSchema, required=True, load_default=()) + references: fields.Nested( + NameEnumField(ReferenceType, required=True), required=True, many=True + ) + + @post_load + def make_record(self, data, **kwargs): + return DossierRecord(**data) + + +class MongoDossierRepository(DossierRepository): + def __init__(self, database: Database): + self._afo_register = MongoCollection(database, COLLECTION) + + def fetch(self, query, *args, **kwargs) -> DossierRecord: + return diff --git a/ebl/dossier/web/bootstrap.py b/ebl/dossier/web/bootstrap.py new file mode 100644 index 000000000..0bdeacaa8 --- /dev/null +++ b/ebl/dossier/web/bootstrap.py @@ -0,0 +1,11 @@ +import falcon +from ebl.context import Context + +from ebl.afo_register.web.dossier_records import ( + DossierResource, +) + + +def create_afo_register_routes(api: falcon.App, context: Context): + dossier_resourse = DossierResource(context.dossier_repository) + api.add_route("/dossier", dossier_resourse) diff --git a/ebl/dossier/web/dossier_records.py b/ebl/dossier/web/dossier_records.py new file mode 100644 index 000000000..f6e5f48a8 --- /dev/null +++ b/ebl/dossier/web/dossier_records.py @@ -0,0 +1,21 @@ +from falcon import Request, Response +from ebl.errors import NotFoundError + +from ebl.dossier.domain.dossier_repository import DossierRepository +from ebl.dossier.infrastructure.mongo_dossier_repository import ( + DossierRecordSchema, +) + + +class DossierResource: + def __init__(self, _dossierRepository: DossierRepository): + self._dossierRepository = _dossierRepository + + def on_get(self, req: Request, resp: Response) -> None: + try: + response = self._dossierRepository.fetch(req.params) + except ValueError as error: + raise NotFoundError( + f"No dossier entries matching {str(req.params)} found." + ) from error + resp.media = DossierRecordSchema().dump(response, many=True) diff --git a/ebl/tests/dossier/test_dossier.py b/ebl/tests/dossier/test_dossier.py new file mode 100644 index 000000000..6012c38d2 --- /dev/null +++ b/ebl/tests/dossier/test_dossier.py @@ -0,0 +1,96 @@ +""" +import pytest +from ebl.afo_register.domain.afo_register_record import ( + AfoRegisterRecord, + AfoRegisterRecordSuggestion, +) +from ebl.afo_register.infrastructure.mongo_afo_register_repository import ( + AfoRegisterRecordSchema, + AfoRegisterRecordSuggestionSchema, +) +from ebl.tests.factories.afo_register import ( + AfoRegisterRecordFactory, + AfoRegisterRecordSuggestionFactory, +) + + +@pytest.fixture +def afo_register_record(): + return AfoRegisterRecordFactory.build() + + +@pytest.fixture +def afo_register_record_suggestion(): + return AfoRegisterRecordSuggestionFactory.build() + + +def test_afo_register_record_creation(afo_register_record: AfoRegisterRecord) -> None: + assert afo_register_record.afo_number is not None + assert afo_register_record.page is not None + assert afo_register_record.text is not None + assert afo_register_record.text_number is not None + assert afo_register_record.lines_discussed is not None + assert afo_register_record.discussed_by is not None + assert afo_register_record.discussed_by_notes is not None + + +def test_afo_register_record_defaults() -> None: + afo_register_record = AfoRegisterRecord() + + assert afo_register_record.page == "" + assert afo_register_record.text == "" + assert afo_register_record.text_number == "" + assert afo_register_record.lines_discussed == "" + assert afo_register_record.discussed_by == "" + assert afo_register_record.discussed_by_notes == "" + + +def test_afo_register_record_to_dict(afo_register_record: AfoRegisterRecord) -> None: + assert AfoRegisterRecordSchema().dump(afo_register_record) == { + "afoNumber": afo_register_record.afo_number, + "page": afo_register_record.page, + "text": afo_register_record.text, + "textNumber": afo_register_record.text_number, + "linesDiscussed": afo_register_record.lines_discussed, + "discussedBy": afo_register_record.discussed_by, + "discussedByNotes": afo_register_record.discussed_by_notes, + } + + +def test_afo_register_record_suggestion_to_dict( + afo_register_record_suggestion: AfoRegisterRecordSuggestion, +) -> None: + assert AfoRegisterRecordSuggestionSchema().dump(afo_register_record_suggestion) == { + "text": afo_register_record_suggestion.text, + "textNumbers": afo_register_record_suggestion.text_numbers, + } + + +def test_afo_register_record_from_dict(afo_register_record: AfoRegisterRecord) -> None: + serialized_data = AfoRegisterRecordSchema().dump(afo_register_record) + deserialized_object = AfoRegisterRecordSchema().load(serialized_data) + + assert deserialized_object.afo_number == afo_register_record.afo_number + assert deserialized_object.page == afo_register_record.page + assert deserialized_object.text == afo_register_record.text + assert deserialized_object.text_number == afo_register_record.text_number + assert deserialized_object.lines_discussed == afo_register_record.lines_discussed + assert deserialized_object.discussed_by == afo_register_record.discussed_by + assert ( + deserialized_object.discussed_by_notes == afo_register_record.discussed_by_notes + ) + + +def test_afo_register_record_suggestion_from_dict( + afo_register_record_suggestion: AfoRegisterRecordSuggestion, +) -> None: + serialized_data = AfoRegisterRecordSuggestionSchema().dump( + afo_register_record_suggestion + ) + deserialized_object = AfoRegisterRecordSuggestionSchema().load(serialized_data) + + assert deserialized_object.text == afo_register_record_suggestion.text + assert ( + deserialized_object.text_numbers == afo_register_record_suggestion.text_numbers + ) +""" diff --git a/ebl/tests/dossier/test_dossier_repository.py b/ebl/tests/dossier/test_dossier_repository.py new file mode 100644 index 000000000..0bee2fc1d --- /dev/null +++ b/ebl/tests/dossier/test_dossier_repository.py @@ -0,0 +1,84 @@ +""" +from ebl.tests.factories.afo_register import ( + AfoRegisterRecordFactory, + AfoRegisterRecordSuggestionFactory, +) +from ebl.afo_register.application.afo_register_repository import AfoRegisterRepository +from natsort import natsorted + + +def test_find_by_id(afo_register_repository: AfoRegisterRepository): + afo_register_record = AfoRegisterRecordFactory.build() + id = afo_register_repository.create(afo_register_record) + afo_register_repository.create(AfoRegisterRecordFactory.build()) + + assert afo_register_repository.search({"_id": id}) == [afo_register_record] + + +def test_find_by_afo_number_and_page(afo_register_repository: AfoRegisterRepository): + afo_register_record = AfoRegisterRecordFactory.build() + afo_register_repository.create(afo_register_record) + afo_register_repository.create(AfoRegisterRecordFactory.build()) + + assert afo_register_repository.search( + { + "afoNumber": afo_register_record.afo_number, + "page": afo_register_record.page, + } + ) == [afo_register_record] + + +def test_find_by_all_record_parameters(afo_register_repository: AfoRegisterRepository): + afo_register_record = AfoRegisterRecordFactory.build() + afo_register_repository.create(afo_register_record) + afo_register_repository.create(AfoRegisterRecordFactory.build()) + + assert afo_register_repository.search( + { + "afoNumber": afo_register_record.afo_number, + "page": afo_register_record.page, + "text": afo_register_record.text, + "textNumber": afo_register_record.text_number, + "linesDiscussed": afo_register_record.lines_discussed, + "discussedBy": afo_register_record.discussed_by, + "discussedByNotes": afo_register_record.discussed_by_notes, + } + ) == [afo_register_record] + + +def test_search_by_texts_and_numbers(afo_register_repository: AfoRegisterRepository): + record1 = AfoRegisterRecordFactory.build(text="Text1", text_number="1") + record2 = AfoRegisterRecordFactory.build(text="Text2", text_number="2") + record3 = AfoRegisterRecordFactory.build(text="Text3", text_number="3") + afo_register_repository.create(record1) + afo_register_repository.create(record2) + afo_register_repository.create(record3) + query = ["Text1 1", "Text3 3"] + results = afo_register_repository.search_by_texts_and_numbers(query) + + assert len(results) == 2 + assert record1 in results + assert record3 in results + + +def test_find_record_suggestions(afo_register_repository: AfoRegisterRepository): + afo_register_record = AfoRegisterRecordFactory.build() + another_afo_register_record = AfoRegisterRecordFactory.build( + text=afo_register_record.text + ) + afo_register_repository.create(afo_register_record) + afo_register_repository.create(another_afo_register_record) + text_numbers = natsorted( + [ + afo_register_record.text_number, + another_afo_register_record.text_number, + ] + ) + afo_register_record_suggestion = AfoRegisterRecordSuggestionFactory.build( + text=afo_register_record.text, text_numbers=text_numbers + ) + + assert afo_register_repository.search_suggestions( + afo_register_record.text[:-2], + ) == [afo_register_record_suggestion] +""" diff --git a/ebl/tests/dossier/test_dossier_route.py b/ebl/tests/dossier/test_dossier_route.py new file mode 100644 index 000000000..c00c2e340 --- /dev/null +++ b/ebl/tests/dossier/test_dossier_route.py @@ -0,0 +1,80 @@ +""" +import falcon +import pytest +import json + +from ebl.afo_register.domain.afo_register_record import AfoRegisterRecord +from ebl.tests.factories.afo_register import ( + AfoRegisterRecordFactory, + AfoRegisterRecordSuggestionFactory, +) +from ebl.afo_register.application.afo_register_repository import ( + AfoRegisterRepository, +) +from ebl.afo_register.infrastructure.mongo_afo_register_repository import ( + AfoRegisterRecordSchema, + AfoRegisterRecordSuggestionSchema, +) + + +@pytest.fixture +def afo_register_record() -> AfoRegisterRecord: + return AfoRegisterRecordFactory.build() + + +def test_search_afo_register_record_route( + afo_register_record, afo_register_repository: AfoRegisterRepository, client +) -> None: + params = { + "afoNumber": afo_register_record.afo_number, + "page": afo_register_record.page, + "text": afo_register_record.text, + "textNumber": afo_register_record.text_number, + "linesDiscussed": afo_register_record.lines_discussed, + "discussedBy": afo_register_record.discussed_by, + "discussedByNotes": afo_register_record.discussed_by_notes, + } + afo_register_repository.create(afo_register_record) + get_result = client.simulate_get("/afo-register", params=params) + + assert get_result.status == falcon.HTTP_OK + assert get_result.json == [AfoRegisterRecordSchema().dump(afo_register_record)] + + +def test_search_by_texts_and_numbers_route( + afo_register_repository: AfoRegisterRepository, client +) -> None: + record1 = AfoRegisterRecordFactory.build(text="Text1", text_number="1") + record2 = AfoRegisterRecordFactory.build(text="Text2", text_number="2") + record3 = AfoRegisterRecordFactory.build(text="Text3", text_number="3") + afo_register_repository.create(record1) + afo_register_repository.create(record2) + afo_register_repository.create(record3) + get_result = client.simulate_post( + "/afo-register/texts-numbers", body=json.dumps(["Text1 1", "Text3 3"]) + ) + expected_results = [ + AfoRegisterRecordSchema().dump(record) for record in [record1, record3] + ] + + assert get_result.status == falcon.HTTP_OK + assert get_result.json == expected_results + + +def test_search_afo_register_suggestions_route( + afo_register_record, afo_register_repository: AfoRegisterRepository, client +) -> None: + afo_register_repository.create(afo_register_record) + get_result = client.simulate_get( + "/afo-register/suggestions", + params={"text_query": afo_register_record.text[:-2]}, + ) + afo_register_record_suggestion = AfoRegisterRecordSuggestionFactory.build( + text=afo_register_record.text, text_numbers=[afo_register_record.text_number] + ) + + assert get_result.status == falcon.HTTP_OK + assert get_result.json == [ + AfoRegisterRecordSuggestionSchema().dump(afo_register_record_suggestion) + ] +""" diff --git a/ebl/tests/factories/dossier.py b/ebl/tests/factories/dossier.py new file mode 100644 index 000000000..34c76a78a --- /dev/null +++ b/ebl/tests/factories/dossier.py @@ -0,0 +1,36 @@ +import factory +from ebl.dossier.domain.dossier_record import DossierRecord +from ebl.tests.factories.bibliography import ReferenceFactory +from ebl.tests.factories.collections import TupleFactory +from ebl.common.domain.provenance import Provenance +from ebl.tests.factories.fragment import ScriptFactory + + +class DossierRecordFactory(factory.Factory): + class Meta: + model = DossierRecord + + id = factory.Faker("random_int", min=0, max=1000) + name = factory.Faker("word") + description = factory.Faker("sentence") + is_approximate_date = factory.Faker("boolean") + year_range_from = factory.Maybe("is_approximate_date", factory.Faker("year"), None) + year_range_to = factory.Maybe( + "is_approximate_date", + factory.LazyAttribute( + lambda obj: obj.year_range_from + + factory.Faker("random_int").generate({"min": 1, "max": 50}) + ), + None, + ) + related_kings = factory.LazyAttribute( + lambda _: [ + factory.Faker("random_int", min=1, max=100).generate() + for _ in range(factory.Faker("random_int", min=0, max=5).generate()) + ] + ) + provenance = factory.fuzzy.FuzzyChoice(set(Provenance) - {Provenance.STANDARD_TEXT}) + script = factory.SubFactory(ScriptFactory) + references = factory.List( + [factory.SubFactory(ReferenceFactory, with_document=True)], TupleFactory + ) From 1e89adb9d2bef700ab1bdb0359146e7214d7bb27 Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Thu, 5 Dec 2024 21:26:44 +0000 Subject: [PATCH 4/9] Update & add tests (WiP) --- ebl/chronology/chronology.py | 4 +- ebl/dossier/application/dossier_repository.py | 9 +- ebl/dossier/domain/dossier_record.py | 17 ++-- .../mongo_dossier_record_repository.py | 51 ----------- .../mongo_dossier_repository.py | 76 ++++++++++++++++ ebl/dossier/web/bootstrap.py | 2 +- ebl/dossier/web/dossier_records.py | 2 +- ebl/tests/conftest.py | 10 +++ ebl/tests/dossier/test_dossier_repository.py | 88 ++----------------- ebl/tests/factories/dossier.py | 19 ++-- 10 files changed, 119 insertions(+), 159 deletions(-) delete mode 100644 ebl/dossier/infrastructure/mongo_dossier_record_repository.py create mode 100644 ebl/dossier/infrastructure/mongo_dossier_repository.py diff --git a/ebl/chronology/chronology.py b/ebl/chronology/chronology.py index b685af73c..1b2225033 100644 --- a/ebl/chronology/chronology.py +++ b/ebl/chronology/chronology.py @@ -6,7 +6,7 @@ @attr.s(auto_attribs=True, frozen=True) class King: - order_global: int + order_global: float group_with: int dynasty_number: str dynasty_name: str @@ -30,7 +30,7 @@ def find_king_by_name(self, king_name: str) -> Optional[King]: class KingSchema(Schema): - order_global = fields.Integer(data_key="orderGlobal") + order_global = fields.Float(data_key="orderGlobal") group_with = fields.Integer( data_key="groupWith", allow_none=True, load_default=None ) diff --git a/ebl/dossier/application/dossier_repository.py b/ebl/dossier/application/dossier_repository.py index 994f38a1b..5d3f32f6c 100644 --- a/ebl/dossier/application/dossier_repository.py +++ b/ebl/dossier/application/dossier_repository.py @@ -1,10 +1,13 @@ from abc import ABC, abstractmethod -from ebl.afo_register.domain.afo_register_record import ( +from ebl.dossier.domain.dossier_record import ( DossierRecord, ) -class AfoRegisterRepository(ABC): +class DossierRepository(ABC): @abstractmethod - def fetch(self, query, *args, **kwargs) -> DossierRecord: ... + def fetch(self, name: str) -> DossierRecord: ... + + @abstractmethod + def create(self, dossier_record: DossierRecord) -> str: ... diff --git a/ebl/dossier/domain/dossier_record.py b/ebl/dossier/domain/dossier_record.py index 5b2b69df4..517d1d58b 100644 --- a/ebl/dossier/domain/dossier_record.py +++ b/ebl/dossier/domain/dossier_record.py @@ -1,5 +1,5 @@ import attr -from typing import Sequence, Optional +from typing import Sequence, Optional, FrozenSet from ebl.common.domain.provenance import Provenance from ebl.fragmentarium.domain.fragment import Script @@ -7,14 +7,13 @@ @attr.s(frozen=True, auto_attribs=True) -class AfoRegisterRecord: - id: int +class DossierRecord: name: str - description: str - is_approximate_date: bool + description: Optional[str] = None + is_approximate_date: bool = False year_range_from: Optional[int] = None year_range_to: Optional[int] = None - related_kings: Sequence[int] - provenance: Provenance - script: Script - references: Sequence[ReferenceType] + related_kings: Optional[Sequence[float]] = [] + provenance: Optional[Provenance] = None + script: Optional[Script] = None + references: FrozenSet[ReferenceType] = frozenset() diff --git a/ebl/dossier/infrastructure/mongo_dossier_record_repository.py b/ebl/dossier/infrastructure/mongo_dossier_record_repository.py deleted file mode 100644 index b75920dcc..000000000 --- a/ebl/dossier/infrastructure/mongo_dossier_record_repository.py +++ /dev/null @@ -1,51 +0,0 @@ -from marshmallow import Schema, fields, post_load, EXCLUDE -from pymongo.database import Database -from ebl.mongo_collection import MongoCollection -from ebl.dossier.domain.dossier_record import DossierRecord -from ebl.dossier.application.dossier_repository import DossierRepository -from ebl.common.domain.provenance import Provenance -from ebl.fragmentarium.application.fragment_schema import ScriptSchema -from ebl.schemas import NameEnumField -from ebl.bibliography.domain.reference import ReferenceType - -COLLECTION = "dossier" - -provenance_field = fields.Function( - lambda object_: getattr(object_.site, "long_name", None), - lambda value: Provenance.from_name(value) if value else None, - allow_none=True, -) - - -class DossierRecordSchema(Schema): - class Meta: - unknown = EXCLUDE - - id: fields.Integer(required=True) - name: fields.String(required=True) - description: fields.String(required=True) - is_approximate_date: fields.Boolean(load_default=False) - year_range_from: fields.String( - data_key="yearRangeFrom", allow_none=True, load_default=None - ) - year_range_to: fields.String( - data_key="yearRangeTo", allow_none=True, load_default=None - ) - related_kings: fields.List(fields.Integer(), data_key="relatedKings") - provenance: provenance_field - script: fields.Nested(ScriptSchema, required=True, load_default=()) - references: fields.Nested( - NameEnumField(ReferenceType, required=True), required=True, many=True - ) - - @post_load - def make_record(self, data, **kwargs): - return DossierRecord(**data) - - -class MongoDossierRepository(DossierRepository): - def __init__(self, database: Database): - self._afo_register = MongoCollection(database, COLLECTION) - - def fetch(self, query, *args, **kwargs) -> DossierRecord: - return diff --git a/ebl/dossier/infrastructure/mongo_dossier_repository.py b/ebl/dossier/infrastructure/mongo_dossier_repository.py new file mode 100644 index 000000000..47f262941 --- /dev/null +++ b/ebl/dossier/infrastructure/mongo_dossier_repository.py @@ -0,0 +1,76 @@ +from marshmallow import Schema, fields, post_load, EXCLUDE +from pymongo.database import Database +from ebl.mongo_collection import MongoCollection +from ebl.dossier.domain.dossier_record import ( + DossierRecord, +) +from ebl.dossier.application.dossier_repository import DossierRepository +from ebl.common.domain.provenance import Provenance +from ebl.fragmentarium.application.fragment_schema import ScriptSchema +from ebl.schemas import NameEnumField +from ebl.bibliography.domain.reference import ReferenceType + +COLLECTION = "dossier" + +provenance_field = fields.Function( + lambda object_: getattr(object_.provenance, "long_name", None), + lambda value: Provenance.from_name(value) if value else None, + allow_none=True, +) + + +class DossierRecordSchema(Schema): + class Meta: + unknown = EXCLUDE + + name = fields.String(required=True, unique=True) + description = fields.String(load_default=None) + is_approximate_date = fields.Boolean( + data_key="isApproximateDate", load_default=False + ) + year_range_from = fields.Integer( + data_key="yearRangeFrom", allow_none=True, load_default=None + ) + year_range_to = fields.Integer( + data_key="yearRangeTo", allow_none=True, load_default=None + ) + related_kings = fields.List( + fields.Float(), data_key="relatedKings", load_default=list + ) + provenance = provenance_field + script = fields.Nested(ScriptSchema, allow_none=True, load_default=None) + references = fields.List(NameEnumField(ReferenceType), load_default=list) + + @post_load + def make_record(self, data, **kwargs): + return ( + DossierRecord( + data["name"], + data["description"], + data["is_approximate_date"], + data["year_range_from"], + data["year_range_to"], + data["related_kings"], + data["provenance"], + data["script"], + frozenset(data["references"]), + ), + ) + + +class MongoDossierRepository(DossierRepository): + def __init__(self, database: Database): + self._dossier = MongoCollection(database, COLLECTION) + + def fetch(self, name: str) -> DossierRecord: + query = {"name": name} + record_data = self._dossier.find_one(query) + if not record_data: + raise ValueError(f"No dossier record found for the name: {name}") + + print("!!!!!!!!!!", record_data) + input() + return DossierRecordSchema().load(record_data) + + def create(self, dossier_record: DossierRecord) -> str: + return self._dossier.insert_one(DossierRecordSchema().dump(dossier_record)) diff --git a/ebl/dossier/web/bootstrap.py b/ebl/dossier/web/bootstrap.py index 0bdeacaa8..301d07571 100644 --- a/ebl/dossier/web/bootstrap.py +++ b/ebl/dossier/web/bootstrap.py @@ -1,7 +1,7 @@ import falcon from ebl.context import Context -from ebl.afo_register.web.dossier_records import ( +from ebl.dossier.web.dossier_records import ( DossierResource, ) diff --git a/ebl/dossier/web/dossier_records.py b/ebl/dossier/web/dossier_records.py index f6e5f48a8..69fcaa3c8 100644 --- a/ebl/dossier/web/dossier_records.py +++ b/ebl/dossier/web/dossier_records.py @@ -1,7 +1,7 @@ from falcon import Request, Response from ebl.errors import NotFoundError -from ebl.dossier.domain.dossier_repository import DossierRepository +from ebl.dossier.application.dossier_repository import DossierRepository from ebl.dossier.infrastructure.mongo_dossier_repository import ( DossierRecordSchema, ) diff --git a/ebl/tests/conftest.py b/ebl/tests/conftest.py index 3eca9f194..e29e30436 100644 --- a/ebl/tests/conftest.py +++ b/ebl/tests/conftest.py @@ -84,6 +84,9 @@ from ebl.afo_register.infrastructure.mongo_afo_register_repository import ( MongoAfoRegisterRepository, ) +from ebl.dossier.infrastructure.mongo_dossier_repository import ( + MongoDossierRepository, +) from ebl.users.domain.user import Guest, User from ebl.users.infrastructure.auth0 import Auth0User from ebl.fragmentarium.web.annotations import AnnotationResource @@ -434,6 +437,11 @@ def user() -> User: ) +@pytest.fixture +def dossier_repository(database): + return MongoDossierRepository(database) + + @pytest.fixture def context( ebl_ai_client, @@ -451,6 +459,7 @@ def context( annotations_repository, lemma_repository, afo_register_repository, + dossier_repository, findspot_repository, user, parallel_line_injector, @@ -473,6 +482,7 @@ def context( annotations_repository=annotations_repository, lemma_repository=lemma_repository, afo_register_repository=afo_register_repository, + dossier_repository=dossier_repository, findspot_repository=findspot_repository, cache=Cache({"CACHE_TYPE": "null"}), custom_cache=ChapterCache(mongo_cache_repository), diff --git a/ebl/tests/dossier/test_dossier_repository.py b/ebl/tests/dossier/test_dossier_repository.py index 0bee2fc1d..6d17eb150 100644 --- a/ebl/tests/dossier/test_dossier_repository.py +++ b/ebl/tests/dossier/test_dossier_repository.py @@ -1,84 +1,10 @@ -""" -from ebl.tests.factories.afo_register import ( - AfoRegisterRecordFactory, - AfoRegisterRecordSuggestionFactory, -) -from ebl.afo_register.application.afo_register_repository import AfoRegisterRepository -from natsort import natsorted +from ebl.tests.factories.dossier import DossierRecordFactory +from ebl.dossier.application.dossier_repository import DossierRepository -def test_find_by_id(afo_register_repository: AfoRegisterRepository): - afo_register_record = AfoRegisterRecordFactory.build() - id = afo_register_repository.create(afo_register_record) - afo_register_repository.create(AfoRegisterRecordFactory.build()) +def test_fetch(dossier_repository: DossierRepository): + dossier_record = DossierRecordFactory.build() + dossier_repository.create(dossier_record) + dossier_repository.create(DossierRecordFactory.build()) - assert afo_register_repository.search({"_id": id}) == [afo_register_record] - - -def test_find_by_afo_number_and_page(afo_register_repository: AfoRegisterRepository): - afo_register_record = AfoRegisterRecordFactory.build() - afo_register_repository.create(afo_register_record) - afo_register_repository.create(AfoRegisterRecordFactory.build()) - - assert afo_register_repository.search( - { - "afoNumber": afo_register_record.afo_number, - "page": afo_register_record.page, - } - ) == [afo_register_record] - - -def test_find_by_all_record_parameters(afo_register_repository: AfoRegisterRepository): - afo_register_record = AfoRegisterRecordFactory.build() - afo_register_repository.create(afo_register_record) - afo_register_repository.create(AfoRegisterRecordFactory.build()) - - assert afo_register_repository.search( - { - "afoNumber": afo_register_record.afo_number, - "page": afo_register_record.page, - "text": afo_register_record.text, - "textNumber": afo_register_record.text_number, - "linesDiscussed": afo_register_record.lines_discussed, - "discussedBy": afo_register_record.discussed_by, - "discussedByNotes": afo_register_record.discussed_by_notes, - } - ) == [afo_register_record] - - -def test_search_by_texts_and_numbers(afo_register_repository: AfoRegisterRepository): - record1 = AfoRegisterRecordFactory.build(text="Text1", text_number="1") - record2 = AfoRegisterRecordFactory.build(text="Text2", text_number="2") - record3 = AfoRegisterRecordFactory.build(text="Text3", text_number="3") - afo_register_repository.create(record1) - afo_register_repository.create(record2) - afo_register_repository.create(record3) - query = ["Text1 1", "Text3 3"] - results = afo_register_repository.search_by_texts_and_numbers(query) - - assert len(results) == 2 - assert record1 in results - assert record3 in results - - -def test_find_record_suggestions(afo_register_repository: AfoRegisterRepository): - afo_register_record = AfoRegisterRecordFactory.build() - another_afo_register_record = AfoRegisterRecordFactory.build( - text=afo_register_record.text - ) - afo_register_repository.create(afo_register_record) - afo_register_repository.create(another_afo_register_record) - text_numbers = natsorted( - [ - afo_register_record.text_number, - another_afo_register_record.text_number, - ] - ) - afo_register_record_suggestion = AfoRegisterRecordSuggestionFactory.build( - text=afo_register_record.text, text_numbers=text_numbers - ) - - assert afo_register_repository.search_suggestions( - afo_register_record.text[:-2], - ) == [afo_register_record_suggestion] -""" + assert dossier_repository.fetch(dossier_record.name) == dossier_record diff --git a/ebl/tests/factories/dossier.py b/ebl/tests/factories/dossier.py index 34c76a78a..0ad9cfb67 100644 --- a/ebl/tests/factories/dossier.py +++ b/ebl/tests/factories/dossier.py @@ -1,33 +1,30 @@ import factory -from ebl.dossier.domain.dossier_record import DossierRecord +from random import randint +from ebl.dossier.domain.dossier_record import ( + DossierRecord, +) from ebl.tests.factories.bibliography import ReferenceFactory from ebl.tests.factories.collections import TupleFactory from ebl.common.domain.provenance import Provenance from ebl.tests.factories.fragment import ScriptFactory +from ebl.chronology.chronology import chronology class DossierRecordFactory(factory.Factory): class Meta: model = DossierRecord - id = factory.Faker("random_int", min=0, max=1000) name = factory.Faker("word") description = factory.Faker("sentence") is_approximate_date = factory.Faker("boolean") - year_range_from = factory.Maybe("is_approximate_date", factory.Faker("year"), None) + year_range_from = factory.Maybe("is_approximate_date", randint(-2500, -400), None) year_range_to = factory.Maybe( "is_approximate_date", - factory.LazyAttribute( - lambda obj: obj.year_range_from - + factory.Faker("random_int").generate({"min": 1, "max": 50}) - ), + factory.LazyAttribute(lambda obj: obj.year_range_from + randint(0, 500)), None, ) related_kings = factory.LazyAttribute( - lambda _: [ - factory.Faker("random_int", min=1, max=100).generate() - for _ in range(factory.Faker("random_int", min=0, max=5).generate()) - ] + lambda _: [chronology.kings[i].order_global for i in range(randint(0, 100))] ) provenance = factory.fuzzy.FuzzyChoice(set(Provenance) - {Provenance.STANDARD_TEXT}) script = factory.SubFactory(ScriptFactory) From 5415cd87012856b043d5d843602c6b08a6d3ef7b Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Tue, 10 Dec 2024 00:32:09 +0000 Subject: [PATCH 5/9] Update & fix test --- ebl/context.py | 2 +- .../application/dossier_repository.py | 2 +- .../domain/dossier_record.py | 8 ++-- .../mongo_dossier_repository.py | 23 ++--------- ebl/{dossier => dossiers}/web/bootstrap.py | 4 +- .../web/dossier_records.py | 6 +-- ebl/tests/conftest.py | 2 +- ebl/tests/dossier/test_dossier.py | 27 ------------- ebl/tests/dossier/test_dossier_repository.py | 2 +- ebl/tests/dossier/test_dossier_route.py | 38 ------------------- ebl/tests/factories/dossier.py | 9 ++--- 11 files changed, 21 insertions(+), 102 deletions(-) rename ebl/{dossier => dossiers}/application/dossier_repository.py (83%) rename ebl/{dossier => dossiers}/domain/dossier_record.py (74%) rename ebl/{dossier => dossiers}/infrastructure/mongo_dossier_repository.py (75%) rename ebl/{dossier => dossiers}/web/bootstrap.py (68%) rename ebl/{dossier => dossiers}/web/dossier_records.py (72%) diff --git a/ebl/context.py b/ebl/context.py index 6c4754eed..28511dbd6 100644 --- a/ebl/context.py +++ b/ebl/context.py @@ -29,7 +29,7 @@ from ebl.fragmentarium.infrastructure.mongo_findspot_repository import ( MongoFindspotRepository, ) -from ebl.dossier.application.dossier_repository import DossierRepository +from ebl.dossiers.application.dossier_repository import DossierRepository @attr.s(auto_attribs=True, frozen=True) diff --git a/ebl/dossier/application/dossier_repository.py b/ebl/dossiers/application/dossier_repository.py similarity index 83% rename from ebl/dossier/application/dossier_repository.py rename to ebl/dossiers/application/dossier_repository.py index 5d3f32f6c..3bebcb852 100644 --- a/ebl/dossier/application/dossier_repository.py +++ b/ebl/dossiers/application/dossier_repository.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod -from ebl.dossier.domain.dossier_record import ( +from ebl.dossiers.domain.dossier_record import ( DossierRecord, ) diff --git a/ebl/dossier/domain/dossier_record.py b/ebl/dossiers/domain/dossier_record.py similarity index 74% rename from ebl/dossier/domain/dossier_record.py rename to ebl/dossiers/domain/dossier_record.py index 517d1d58b..5b422a6fc 100644 --- a/ebl/dossier/domain/dossier_record.py +++ b/ebl/dossiers/domain/dossier_record.py @@ -1,5 +1,5 @@ import attr -from typing import Sequence, Optional, FrozenSet +from typing import Sequence, Optional from ebl.common.domain.provenance import Provenance from ebl.fragmentarium.domain.fragment import Script @@ -8,12 +8,12 @@ @attr.s(frozen=True, auto_attribs=True) class DossierRecord: - name: str + _id: str description: Optional[str] = None is_approximate_date: bool = False year_range_from: Optional[int] = None year_range_to: Optional[int] = None - related_kings: Optional[Sequence[float]] = [] + related_kings: Sequence[float] = [] provenance: Optional[Provenance] = None script: Optional[Script] = None - references: FrozenSet[ReferenceType] = frozenset() + references: Sequence[ReferenceType] = [] diff --git a/ebl/dossier/infrastructure/mongo_dossier_repository.py b/ebl/dossiers/infrastructure/mongo_dossier_repository.py similarity index 75% rename from ebl/dossier/infrastructure/mongo_dossier_repository.py rename to ebl/dossiers/infrastructure/mongo_dossier_repository.py index 47f262941..b6531be19 100644 --- a/ebl/dossier/infrastructure/mongo_dossier_repository.py +++ b/ebl/dossiers/infrastructure/mongo_dossier_repository.py @@ -1,10 +1,10 @@ from marshmallow import Schema, fields, post_load, EXCLUDE from pymongo.database import Database from ebl.mongo_collection import MongoCollection -from ebl.dossier.domain.dossier_record import ( +from ebl.dossiers.domain.dossier_record import ( DossierRecord, ) -from ebl.dossier.application.dossier_repository import DossierRepository +from ebl.dossiers.application.dossier_repository import DossierRepository from ebl.common.domain.provenance import Provenance from ebl.fragmentarium.application.fragment_schema import ScriptSchema from ebl.schemas import NameEnumField @@ -23,7 +23,7 @@ class DossierRecordSchema(Schema): class Meta: unknown = EXCLUDE - name = fields.String(required=True, unique=True) + _id = fields.String(required=True, unique=True) description = fields.String(load_default=None) is_approximate_date = fields.Boolean( data_key="isApproximateDate", load_default=False @@ -43,19 +43,7 @@ class Meta: @post_load def make_record(self, data, **kwargs): - return ( - DossierRecord( - data["name"], - data["description"], - data["is_approximate_date"], - data["year_range_from"], - data["year_range_to"], - data["related_kings"], - data["provenance"], - data["script"], - frozenset(data["references"]), - ), - ) + return DossierRecord(**data) class MongoDossierRepository(DossierRepository): @@ -67,9 +55,6 @@ def fetch(self, name: str) -> DossierRecord: record_data = self._dossier.find_one(query) if not record_data: raise ValueError(f"No dossier record found for the name: {name}") - - print("!!!!!!!!!!", record_data) - input() return DossierRecordSchema().load(record_data) def create(self, dossier_record: DossierRecord) -> str: diff --git a/ebl/dossier/web/bootstrap.py b/ebl/dossiers/web/bootstrap.py similarity index 68% rename from ebl/dossier/web/bootstrap.py rename to ebl/dossiers/web/bootstrap.py index 301d07571..b843ca2ac 100644 --- a/ebl/dossier/web/bootstrap.py +++ b/ebl/dossiers/web/bootstrap.py @@ -1,11 +1,11 @@ import falcon from ebl.context import Context -from ebl.dossier.web.dossier_records import ( +from ebl.dossiers.web.dossier_records import ( DossierResource, ) def create_afo_register_routes(api: falcon.App, context: Context): dossier_resourse = DossierResource(context.dossier_repository) - api.add_route("/dossier", dossier_resourse) + api.add_route("/dossiers", dossier_resourse) diff --git a/ebl/dossier/web/dossier_records.py b/ebl/dossiers/web/dossier_records.py similarity index 72% rename from ebl/dossier/web/dossier_records.py rename to ebl/dossiers/web/dossier_records.py index 69fcaa3c8..c3a71a39f 100644 --- a/ebl/dossier/web/dossier_records.py +++ b/ebl/dossiers/web/dossier_records.py @@ -1,8 +1,8 @@ from falcon import Request, Response from ebl.errors import NotFoundError -from ebl.dossier.application.dossier_repository import DossierRepository -from ebl.dossier.infrastructure.mongo_dossier_repository import ( +from ebl.dossiers.application.dossier_repository import DossierRepository +from ebl.dossiers.infrastructure.mongo_dossier_repository import ( DossierRecordSchema, ) @@ -16,6 +16,6 @@ def on_get(self, req: Request, resp: Response) -> None: response = self._dossierRepository.fetch(req.params) except ValueError as error: raise NotFoundError( - f"No dossier entries matching {str(req.params)} found." + f"No dossier records matching {str(req.params)} found." ) from error resp.media = DossierRecordSchema().dump(response, many=True) diff --git a/ebl/tests/conftest.py b/ebl/tests/conftest.py index e29e30436..af5d5dde6 100644 --- a/ebl/tests/conftest.py +++ b/ebl/tests/conftest.py @@ -84,7 +84,7 @@ from ebl.afo_register.infrastructure.mongo_afo_register_repository import ( MongoAfoRegisterRepository, ) -from ebl.dossier.infrastructure.mongo_dossier_repository import ( +from ebl.dossiers.infrastructure.mongo_dossier_repository import ( MongoDossierRepository, ) from ebl.users.domain.user import Guest, User diff --git a/ebl/tests/dossier/test_dossier.py b/ebl/tests/dossier/test_dossier.py index 6012c38d2..64d4cee6b 100644 --- a/ebl/tests/dossier/test_dossier.py +++ b/ebl/tests/dossier/test_dossier.py @@ -8,11 +8,6 @@ AfoRegisterRecordSchema, AfoRegisterRecordSuggestionSchema, ) -from ebl.tests.factories.afo_register import ( - AfoRegisterRecordFactory, - AfoRegisterRecordSuggestionFactory, -) - @pytest.fixture def afo_register_record(): @@ -57,15 +52,6 @@ def test_afo_register_record_to_dict(afo_register_record: AfoRegisterRecord) -> } -def test_afo_register_record_suggestion_to_dict( - afo_register_record_suggestion: AfoRegisterRecordSuggestion, -) -> None: - assert AfoRegisterRecordSuggestionSchema().dump(afo_register_record_suggestion) == { - "text": afo_register_record_suggestion.text, - "textNumbers": afo_register_record_suggestion.text_numbers, - } - - def test_afo_register_record_from_dict(afo_register_record: AfoRegisterRecord) -> None: serialized_data = AfoRegisterRecordSchema().dump(afo_register_record) deserialized_object = AfoRegisterRecordSchema().load(serialized_data) @@ -80,17 +66,4 @@ def test_afo_register_record_from_dict(afo_register_record: AfoRegisterRecord) - deserialized_object.discussed_by_notes == afo_register_record.discussed_by_notes ) - -def test_afo_register_record_suggestion_from_dict( - afo_register_record_suggestion: AfoRegisterRecordSuggestion, -) -> None: - serialized_data = AfoRegisterRecordSuggestionSchema().dump( - afo_register_record_suggestion - ) - deserialized_object = AfoRegisterRecordSuggestionSchema().load(serialized_data) - - assert deserialized_object.text == afo_register_record_suggestion.text - assert ( - deserialized_object.text_numbers == afo_register_record_suggestion.text_numbers - ) """ diff --git a/ebl/tests/dossier/test_dossier_repository.py b/ebl/tests/dossier/test_dossier_repository.py index 6d17eb150..6f1bc952f 100644 --- a/ebl/tests/dossier/test_dossier_repository.py +++ b/ebl/tests/dossier/test_dossier_repository.py @@ -1,5 +1,5 @@ from ebl.tests.factories.dossier import DossierRecordFactory -from ebl.dossier.application.dossier_repository import DossierRepository +from ebl.dossiers.application.dossier_repository import DossierRepository def test_fetch(dossier_repository: DossierRepository): diff --git a/ebl/tests/dossier/test_dossier_route.py b/ebl/tests/dossier/test_dossier_route.py index c00c2e340..0ec79acd0 100644 --- a/ebl/tests/dossier/test_dossier_route.py +++ b/ebl/tests/dossier/test_dossier_route.py @@ -39,42 +39,4 @@ def test_search_afo_register_record_route( assert get_result.status == falcon.HTTP_OK assert get_result.json == [AfoRegisterRecordSchema().dump(afo_register_record)] - - -def test_search_by_texts_and_numbers_route( - afo_register_repository: AfoRegisterRepository, client -) -> None: - record1 = AfoRegisterRecordFactory.build(text="Text1", text_number="1") - record2 = AfoRegisterRecordFactory.build(text="Text2", text_number="2") - record3 = AfoRegisterRecordFactory.build(text="Text3", text_number="3") - afo_register_repository.create(record1) - afo_register_repository.create(record2) - afo_register_repository.create(record3) - get_result = client.simulate_post( - "/afo-register/texts-numbers", body=json.dumps(["Text1 1", "Text3 3"]) - ) - expected_results = [ - AfoRegisterRecordSchema().dump(record) for record in [record1, record3] - ] - - assert get_result.status == falcon.HTTP_OK - assert get_result.json == expected_results - - -def test_search_afo_register_suggestions_route( - afo_register_record, afo_register_repository: AfoRegisterRepository, client -) -> None: - afo_register_repository.create(afo_register_record) - get_result = client.simulate_get( - "/afo-register/suggestions", - params={"text_query": afo_register_record.text[:-2]}, - ) - afo_register_record_suggestion = AfoRegisterRecordSuggestionFactory.build( - text=afo_register_record.text, text_numbers=[afo_register_record.text_number] - ) - - assert get_result.status == falcon.HTTP_OK - assert get_result.json == [ - AfoRegisterRecordSuggestionSchema().dump(afo_register_record_suggestion) - ] """ diff --git a/ebl/tests/factories/dossier.py b/ebl/tests/factories/dossier.py index 0ad9cfb67..45615afee 100644 --- a/ebl/tests/factories/dossier.py +++ b/ebl/tests/factories/dossier.py @@ -1,13 +1,12 @@ import factory from random import randint -from ebl.dossier.domain.dossier_record import ( +from ebl.dossiers.domain.dossier_record import ( DossierRecord, ) -from ebl.tests.factories.bibliography import ReferenceFactory -from ebl.tests.factories.collections import TupleFactory from ebl.common.domain.provenance import Provenance from ebl.tests.factories.fragment import ScriptFactory from ebl.chronology.chronology import chronology +from ebl.bibliography.domain.reference import ReferenceType class DossierRecordFactory(factory.Factory): @@ -28,6 +27,6 @@ class Meta: ) provenance = factory.fuzzy.FuzzyChoice(set(Provenance) - {Provenance.STANDARD_TEXT}) script = factory.SubFactory(ScriptFactory) - references = factory.List( - [factory.SubFactory(ReferenceFactory, with_document=True)], TupleFactory + references = factory.LazyAttribute( + lambda _: list({list(ReferenceType)[i] for i in range(randint(1, 6))}) ) From 2cf68307701319bac9e4c1746c6e1ff2bcc84504 Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Tue, 10 Dec 2024 18:22:52 +0000 Subject: [PATCH 6/9] Update & add tests --- ebl/dossiers/domain/dossier_record.py | 2 +- .../mongo_dossier_repository.py | 8 +- ebl/tests/dossier/test_dossier.py | 69 ---------------- ebl/tests/dossiers/test_dossier.py | 80 +++++++++++++++++++ .../test_dossier_repository.py | 2 +- .../test_dossier_route.py | 0 ebl/tests/factories/dossier.py | 4 +- 7 files changed, 88 insertions(+), 77 deletions(-) delete mode 100644 ebl/tests/dossier/test_dossier.py create mode 100644 ebl/tests/dossiers/test_dossier.py rename ebl/tests/{dossier => dossiers}/test_dossier_repository.py (82%) rename ebl/tests/{dossier => dossiers}/test_dossier_route.py (100%) diff --git a/ebl/dossiers/domain/dossier_record.py b/ebl/dossiers/domain/dossier_record.py index 5b422a6fc..f93943937 100644 --- a/ebl/dossiers/domain/dossier_record.py +++ b/ebl/dossiers/domain/dossier_record.py @@ -8,7 +8,7 @@ @attr.s(frozen=True, auto_attribs=True) class DossierRecord: - _id: str + id: str description: Optional[str] = None is_approximate_date: bool = False year_range_from: Optional[int] = None diff --git a/ebl/dossiers/infrastructure/mongo_dossier_repository.py b/ebl/dossiers/infrastructure/mongo_dossier_repository.py index b6531be19..78be66f5b 100644 --- a/ebl/dossiers/infrastructure/mongo_dossier_repository.py +++ b/ebl/dossiers/infrastructure/mongo_dossier_repository.py @@ -23,7 +23,7 @@ class DossierRecordSchema(Schema): class Meta: unknown = EXCLUDE - _id = fields.String(required=True, unique=True) + id = fields.String(required=True, unique=True, data_key="_id") description = fields.String(load_default=None) is_approximate_date = fields.Boolean( data_key="isApproximateDate", load_default=False @@ -50,11 +50,11 @@ class MongoDossierRepository(DossierRepository): def __init__(self, database: Database): self._dossier = MongoCollection(database, COLLECTION) - def fetch(self, name: str) -> DossierRecord: - query = {"name": name} + def fetch(self, id: str) -> DossierRecord: + query = {"_id": id} record_data = self._dossier.find_one(query) if not record_data: - raise ValueError(f"No dossier record found for the name: {name}") + raise ValueError(f"No dossier record found for the id: {id}") return DossierRecordSchema().load(record_data) def create(self, dossier_record: DossierRecord) -> str: diff --git a/ebl/tests/dossier/test_dossier.py b/ebl/tests/dossier/test_dossier.py deleted file mode 100644 index 64d4cee6b..000000000 --- a/ebl/tests/dossier/test_dossier.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -import pytest -from ebl.afo_register.domain.afo_register_record import ( - AfoRegisterRecord, - AfoRegisterRecordSuggestion, -) -from ebl.afo_register.infrastructure.mongo_afo_register_repository import ( - AfoRegisterRecordSchema, - AfoRegisterRecordSuggestionSchema, -) - -@pytest.fixture -def afo_register_record(): - return AfoRegisterRecordFactory.build() - - -@pytest.fixture -def afo_register_record_suggestion(): - return AfoRegisterRecordSuggestionFactory.build() - - -def test_afo_register_record_creation(afo_register_record: AfoRegisterRecord) -> None: - assert afo_register_record.afo_number is not None - assert afo_register_record.page is not None - assert afo_register_record.text is not None - assert afo_register_record.text_number is not None - assert afo_register_record.lines_discussed is not None - assert afo_register_record.discussed_by is not None - assert afo_register_record.discussed_by_notes is not None - - -def test_afo_register_record_defaults() -> None: - afo_register_record = AfoRegisterRecord() - - assert afo_register_record.page == "" - assert afo_register_record.text == "" - assert afo_register_record.text_number == "" - assert afo_register_record.lines_discussed == "" - assert afo_register_record.discussed_by == "" - assert afo_register_record.discussed_by_notes == "" - - -def test_afo_register_record_to_dict(afo_register_record: AfoRegisterRecord) -> None: - assert AfoRegisterRecordSchema().dump(afo_register_record) == { - "afoNumber": afo_register_record.afo_number, - "page": afo_register_record.page, - "text": afo_register_record.text, - "textNumber": afo_register_record.text_number, - "linesDiscussed": afo_register_record.lines_discussed, - "discussedBy": afo_register_record.discussed_by, - "discussedByNotes": afo_register_record.discussed_by_notes, - } - - -def test_afo_register_record_from_dict(afo_register_record: AfoRegisterRecord) -> None: - serialized_data = AfoRegisterRecordSchema().dump(afo_register_record) - deserialized_object = AfoRegisterRecordSchema().load(serialized_data) - - assert deserialized_object.afo_number == afo_register_record.afo_number - assert deserialized_object.page == afo_register_record.page - assert deserialized_object.text == afo_register_record.text - assert deserialized_object.text_number == afo_register_record.text_number - assert deserialized_object.lines_discussed == afo_register_record.lines_discussed - assert deserialized_object.discussed_by == afo_register_record.discussed_by - assert ( - deserialized_object.discussed_by_notes == afo_register_record.discussed_by_notes - ) - -""" diff --git a/ebl/tests/dossiers/test_dossier.py b/ebl/tests/dossiers/test_dossier.py new file mode 100644 index 000000000..9d6f59d86 --- /dev/null +++ b/ebl/tests/dossiers/test_dossier.py @@ -0,0 +1,80 @@ +import pytest +from ebl.dossiers.domain.dossier_record import ( + DossierRecord, +) +from ebl.dossiers.infrastructure.mongo_dossier_repository import ( + DossierRecordSchema, +) +from ebl.tests.factories.dossier import DossierRecordFactory +from ebl.fragmentarium.domain.fragment import Script +from ebl.fragmentarium.application.fragment_schema import ScriptSchema +from ebl.common.domain.provenance import Provenance + + +@pytest.fixture +def dossier_record(): + return DossierRecordFactory.build() + + +def test_dossier_record_creation( + dossier_record: DossierRecord, +) -> None: + assert dossier_record.id is not None + assert isinstance(dossier_record.description, (str, type(None))) + assert isinstance(dossier_record.is_approximate_date, (bool, type(None))) + assert isinstance(dossier_record.year_range_from, (float, int, type(None))) + assert isinstance(dossier_record.year_range_to, (float, int, type(None))) + assert isinstance(dossier_record.related_kings, (list, type(None))) + assert isinstance(dossier_record.provenance, (Provenance, type(None))) + assert isinstance(dossier_record.script, (Script, type(None))) + assert isinstance(dossier_record.references, (list, type(None))) + + +def test_dossier_record_defaults() -> None: + blank_dossier_record = DossierRecord("test id") + + assert blank_dossier_record.id == "test id" + assert blank_dossier_record.description is None + assert blank_dossier_record.is_approximate_date is False + assert blank_dossier_record.year_range_from is None + assert blank_dossier_record.year_range_to is None + assert blank_dossier_record.related_kings == [] + assert blank_dossier_record.provenance is None + assert blank_dossier_record.script is None + assert blank_dossier_record.references == [] + + +def test_dossier_record_to_dict( + dossier_record: DossierRecord, +) -> None: + assert DossierRecordSchema().dump(dossier_record) == { + "_id": dossier_record.id, + "description": dossier_record.description, + "isApproximateDate": dossier_record.is_approximate_date, + "yearRangeFrom": dossier_record.year_range_from, + "yearRangeTo": dossier_record.year_range_to, + "relatedKings": dossier_record.related_kings, + "provenance": dossier_record.provenance.long_name, + "script": ScriptSchema().dump(dossier_record.script), + "references": [ + str(reference).replace("ReferenceType.", "") + for reference in dossier_record.references + ], + } + + +def test_dossier_record_from_dict( + dossier_record: DossierRecord, +) -> None: + serialized_data = DossierRecordSchema().dump(dossier_record) + deserialized_object = DossierRecordSchema().load(serialized_data) + + assert deserialized_object.id == dossier_record.id + assert deserialized_object.description == dossier_record.description + assert deserialized_object.is_approximate_date == dossier_record.is_approximate_date + assert deserialized_object.year_range_from == dossier_record.year_range_from + assert deserialized_object.year_range_to == dossier_record.year_range_to + assert deserialized_object.related_kings == dossier_record.related_kings + assert deserialized_object.provenance == dossier_record.provenance + assert deserialized_object.script == dossier_record.script + assert deserialized_object.references == dossier_record.references diff --git a/ebl/tests/dossier/test_dossier_repository.py b/ebl/tests/dossiers/test_dossier_repository.py similarity index 82% rename from ebl/tests/dossier/test_dossier_repository.py rename to ebl/tests/dossiers/test_dossier_repository.py index 6f1bc952f..b510137cc 100644 --- a/ebl/tests/dossier/test_dossier_repository.py +++ b/ebl/tests/dossiers/test_dossier_repository.py @@ -7,4 +7,4 @@ def test_fetch(dossier_repository: DossierRepository): dossier_repository.create(dossier_record) dossier_repository.create(DossierRecordFactory.build()) - assert dossier_repository.fetch(dossier_record.name) == dossier_record + assert dossier_repository.fetch(dossier_record.id) == dossier_record diff --git a/ebl/tests/dossier/test_dossier_route.py b/ebl/tests/dossiers/test_dossier_route.py similarity index 100% rename from ebl/tests/dossier/test_dossier_route.py rename to ebl/tests/dossiers/test_dossier_route.py diff --git a/ebl/tests/factories/dossier.py b/ebl/tests/factories/dossier.py index 45615afee..c159deb27 100644 --- a/ebl/tests/factories/dossier.py +++ b/ebl/tests/factories/dossier.py @@ -13,7 +13,7 @@ class DossierRecordFactory(factory.Factory): class Meta: model = DossierRecord - name = factory.Faker("word") + id = factory.Faker("word") description = factory.Faker("sentence") is_approximate_date = factory.Faker("boolean") year_range_from = factory.Maybe("is_approximate_date", randint(-2500, -400), None) @@ -23,7 +23,7 @@ class Meta: None, ) related_kings = factory.LazyAttribute( - lambda _: [chronology.kings[i].order_global for i in range(randint(0, 100))] + lambda _: [chronology.kings[i].order_global for i in range(randint(0, 10))] ) provenance = factory.fuzzy.FuzzyChoice(set(Provenance) - {Provenance.STANDARD_TEXT}) script = factory.SubFactory(ScriptFactory) From fa677bca3777a2338ebe7b1417c460db2b485a8a Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Wed, 11 Dec 2024 01:30:51 +0000 Subject: [PATCH 7/9] Update & add test --- ebl/app.py | 6 +++ ebl/context.py | 4 +- ...r_repository.py => dossiers_repository.py} | 4 +- ...sitory.py => mongo_dossiers_repository.py} | 4 +- ebl/dossiers/web/bootstrap.py | 6 +-- ebl/dossiers/web/dossier_records.py | 14 +++---- ebl/tests/conftest.py | 12 +++--- ebl/tests/dossiers/test_dossier.py | 6 ++- ebl/tests/dossiers/test_dossier_repository.py | 10 ----- ebl/tests/dossiers/test_dossier_route.py | 42 ------------------- .../dossiers/test_dossiers_repository.py | 10 +++++ ebl/tests/dossiers/test_dossiers_route.py | 28 +++++++++++++ 12 files changed, 70 insertions(+), 76 deletions(-) rename ebl/dossiers/application/{dossier_repository.py => dossiers_repository.py} (72%) rename ebl/dossiers/infrastructure/{mongo_dossier_repository.py => mongo_dossiers_repository.py} (94%) delete mode 100644 ebl/tests/dossiers/test_dossier_repository.py delete mode 100644 ebl/tests/dossiers/test_dossier_route.py create mode 100644 ebl/tests/dossiers/test_dossiers_repository.py create mode 100644 ebl/tests/dossiers/test_dossiers_route.py diff --git a/ebl/app.py b/ebl/app.py index d90037892..849d9cc08 100644 --- a/ebl/app.py +++ b/ebl/app.py @@ -43,6 +43,7 @@ from ebl.signs.infrastructure.mongo_sign_repository import MongoSignRepository from ebl.signs.web.bootstrap import create_signs_routes from ebl.afo_register.web.bootstrap import create_afo_register_routes +from ebl.dossiers.web.bootstrap import create_dossiers_routes from ebl.transliteration.application.parallel_line_injector import ParallelLineInjector from ebl.transliteration.infrastructure.mongo_parallel_repository import ( MongoParallelRepository, @@ -50,6 +51,9 @@ from ebl.afo_register.infrastructure.mongo_afo_register_repository import ( MongoAfoRegisterRepository, ) +from ebl.dossiers.infrastructure.mongo_dossiers_repository import ( + MongoDossiersRepository, +) from ebl.users.domain.user import Guest from ebl.users.infrastructure.auth0 import Auth0Backend from ebl.fragmentarium.infrastructure.mongo_findspot_repository import ( @@ -99,6 +103,7 @@ def create_context(): annotations_repository=MongoAnnotationsRepository(database), lemma_repository=MongoLemmaRepository(database), afo_register_repository=MongoAfoRegisterRepository(database), + dossiers_repository=MongoDossiersRepository(database), findspot_repository=MongoFindspotRepository(database), custom_cache=custom_cache, cache=cache, @@ -128,6 +133,7 @@ def create_app(context: Context, issuer: str = "", audience: str = ""): create_lemmatization_routes(api, context) create_markup_routes(api, context) create_afo_register_routes(api, context) + create_dossiers_routes(api, context) return api diff --git a/ebl/context.py b/ebl/context.py index 28511dbd6..c43cc9df8 100644 --- a/ebl/context.py +++ b/ebl/context.py @@ -29,7 +29,7 @@ from ebl.fragmentarium.infrastructure.mongo_findspot_repository import ( MongoFindspotRepository, ) -from ebl.dossiers.application.dossier_repository import DossierRepository +from ebl.dossiers.application.dossiers_repository import DossiersRepository @attr.s(auto_attribs=True, frozen=True) @@ -54,7 +54,7 @@ class Context: cache: Cache parallel_line_injector: ParallelLineInjector afo_register_repository: AfoRegisterRepository - dossier_repository: DossierRepository + dossiers_repository: DossiersRepository def get_bibliography(self): return Bibliography(self.bibliography_repository, self.changelog) diff --git a/ebl/dossiers/application/dossier_repository.py b/ebl/dossiers/application/dossiers_repository.py similarity index 72% rename from ebl/dossiers/application/dossier_repository.py rename to ebl/dossiers/application/dossiers_repository.py index 3bebcb852..c70a7d41e 100644 --- a/ebl/dossiers/application/dossier_repository.py +++ b/ebl/dossiers/application/dossiers_repository.py @@ -5,9 +5,9 @@ ) -class DossierRepository(ABC): +class DossiersRepository(ABC): @abstractmethod - def fetch(self, name: str) -> DossierRecord: ... + def fetch(self, id: str) -> DossierRecord: ... @abstractmethod def create(self, dossier_record: DossierRecord) -> str: ... diff --git a/ebl/dossiers/infrastructure/mongo_dossier_repository.py b/ebl/dossiers/infrastructure/mongo_dossiers_repository.py similarity index 94% rename from ebl/dossiers/infrastructure/mongo_dossier_repository.py rename to ebl/dossiers/infrastructure/mongo_dossiers_repository.py index 78be66f5b..0f4c2ccc3 100644 --- a/ebl/dossiers/infrastructure/mongo_dossier_repository.py +++ b/ebl/dossiers/infrastructure/mongo_dossiers_repository.py @@ -4,7 +4,7 @@ from ebl.dossiers.domain.dossier_record import ( DossierRecord, ) -from ebl.dossiers.application.dossier_repository import DossierRepository +from ebl.dossiers.application.dossiers_repository import DossiersRepository from ebl.common.domain.provenance import Provenance from ebl.fragmentarium.application.fragment_schema import ScriptSchema from ebl.schemas import NameEnumField @@ -46,7 +46,7 @@ def make_record(self, data, **kwargs): return DossierRecord(**data) -class MongoDossierRepository(DossierRepository): +class MongoDossiersRepository(DossiersRepository): def __init__(self, database: Database): self._dossier = MongoCollection(database, COLLECTION) diff --git a/ebl/dossiers/web/bootstrap.py b/ebl/dossiers/web/bootstrap.py index b843ca2ac..5a4133168 100644 --- a/ebl/dossiers/web/bootstrap.py +++ b/ebl/dossiers/web/bootstrap.py @@ -2,10 +2,10 @@ from ebl.context import Context from ebl.dossiers.web.dossier_records import ( - DossierResource, + DossiersResource, ) -def create_afo_register_routes(api: falcon.App, context: Context): - dossier_resourse = DossierResource(context.dossier_repository) +def create_dossiers_routes(api: falcon.App, context: Context): + dossier_resourse = DossiersResource(context.dossiers_repository) api.add_route("/dossiers", dossier_resourse) diff --git a/ebl/dossiers/web/dossier_records.py b/ebl/dossiers/web/dossier_records.py index c3a71a39f..402092e37 100644 --- a/ebl/dossiers/web/dossier_records.py +++ b/ebl/dossiers/web/dossier_records.py @@ -1,21 +1,21 @@ from falcon import Request, Response from ebl.errors import NotFoundError -from ebl.dossiers.application.dossier_repository import DossierRepository -from ebl.dossiers.infrastructure.mongo_dossier_repository import ( +from ebl.dossiers.application.dossiers_repository import DossiersRepository +from ebl.dossiers.infrastructure.mongo_dossiers_repository import ( DossierRecordSchema, ) -class DossierResource: - def __init__(self, _dossierRepository: DossierRepository): - self._dossierRepository = _dossierRepository +class DossiersResource: + def __init__(self, _dossiersRepository: DossiersRepository): + self._dossiersRepository = _dossiersRepository def on_get(self, req: Request, resp: Response) -> None: try: - response = self._dossierRepository.fetch(req.params) + dossiers = self._dossiersRepository.fetch(req.params["id"]) except ValueError as error: raise NotFoundError( f"No dossier records matching {str(req.params)} found." ) from error - resp.media = DossierRecordSchema().dump(response, many=True) + resp.media = DossierRecordSchema().dump([dossiers], many=True) diff --git a/ebl/tests/conftest.py b/ebl/tests/conftest.py index af5d5dde6..6b809a59c 100644 --- a/ebl/tests/conftest.py +++ b/ebl/tests/conftest.py @@ -84,8 +84,8 @@ from ebl.afo_register.infrastructure.mongo_afo_register_repository import ( MongoAfoRegisterRepository, ) -from ebl.dossiers.infrastructure.mongo_dossier_repository import ( - MongoDossierRepository, +from ebl.dossiers.infrastructure.mongo_dossiers_repository import ( + MongoDossiersRepository, ) from ebl.users.domain.user import Guest, User from ebl.users.infrastructure.auth0 import Auth0User @@ -438,8 +438,8 @@ def user() -> User: @pytest.fixture -def dossier_repository(database): - return MongoDossierRepository(database) +def dossiers_repository(database): + return MongoDossiersRepository(database) @pytest.fixture @@ -459,7 +459,7 @@ def context( annotations_repository, lemma_repository, afo_register_repository, - dossier_repository, + dossiers_repository, findspot_repository, user, parallel_line_injector, @@ -482,7 +482,7 @@ def context( annotations_repository=annotations_repository, lemma_repository=lemma_repository, afo_register_repository=afo_register_repository, - dossier_repository=dossier_repository, + dossiers_repository=dossiers_repository, findspot_repository=findspot_repository, cache=Cache({"CACHE_TYPE": "null"}), custom_cache=ChapterCache(mongo_cache_repository), diff --git a/ebl/tests/dossiers/test_dossier.py b/ebl/tests/dossiers/test_dossier.py index 9d6f59d86..7146aaae4 100644 --- a/ebl/tests/dossiers/test_dossier.py +++ b/ebl/tests/dossiers/test_dossier.py @@ -2,7 +2,7 @@ from ebl.dossiers.domain.dossier_record import ( DossierRecord, ) -from ebl.dossiers.infrastructure.mongo_dossier_repository import ( +from ebl.dossiers.infrastructure.mongo_dossiers_repository import ( DossierRecordSchema, ) from ebl.tests.factories.dossier import DossierRecordFactory @@ -54,7 +54,9 @@ def test_dossier_record_to_dict( "yearRangeFrom": dossier_record.year_range_from, "yearRangeTo": dossier_record.year_range_to, "relatedKings": dossier_record.related_kings, - "provenance": dossier_record.provenance.long_name, + "provenance": dossier_record.provenance.long_name + if dossier_record.provenance + else None, "script": ScriptSchema().dump(dossier_record.script), "references": [ str(reference).replace("ReferenceType.", "") diff --git a/ebl/tests/dossiers/test_dossier_repository.py b/ebl/tests/dossiers/test_dossier_repository.py deleted file mode 100644 index b510137cc..000000000 --- a/ebl/tests/dossiers/test_dossier_repository.py +++ /dev/null @@ -1,10 +0,0 @@ -from ebl.tests.factories.dossier import DossierRecordFactory -from ebl.dossiers.application.dossier_repository import DossierRepository - - -def test_fetch(dossier_repository: DossierRepository): - dossier_record = DossierRecordFactory.build() - dossier_repository.create(dossier_record) - dossier_repository.create(DossierRecordFactory.build()) - - assert dossier_repository.fetch(dossier_record.id) == dossier_record diff --git a/ebl/tests/dossiers/test_dossier_route.py b/ebl/tests/dossiers/test_dossier_route.py deleted file mode 100644 index 0ec79acd0..000000000 --- a/ebl/tests/dossiers/test_dossier_route.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -import falcon -import pytest -import json - -from ebl.afo_register.domain.afo_register_record import AfoRegisterRecord -from ebl.tests.factories.afo_register import ( - AfoRegisterRecordFactory, - AfoRegisterRecordSuggestionFactory, -) -from ebl.afo_register.application.afo_register_repository import ( - AfoRegisterRepository, -) -from ebl.afo_register.infrastructure.mongo_afo_register_repository import ( - AfoRegisterRecordSchema, - AfoRegisterRecordSuggestionSchema, -) - - -@pytest.fixture -def afo_register_record() -> AfoRegisterRecord: - return AfoRegisterRecordFactory.build() - - -def test_search_afo_register_record_route( - afo_register_record, afo_register_repository: AfoRegisterRepository, client -) -> None: - params = { - "afoNumber": afo_register_record.afo_number, - "page": afo_register_record.page, - "text": afo_register_record.text, - "textNumber": afo_register_record.text_number, - "linesDiscussed": afo_register_record.lines_discussed, - "discussedBy": afo_register_record.discussed_by, - "discussedByNotes": afo_register_record.discussed_by_notes, - } - afo_register_repository.create(afo_register_record) - get_result = client.simulate_get("/afo-register", params=params) - - assert get_result.status == falcon.HTTP_OK - assert get_result.json == [AfoRegisterRecordSchema().dump(afo_register_record)] -""" diff --git a/ebl/tests/dossiers/test_dossiers_repository.py b/ebl/tests/dossiers/test_dossiers_repository.py new file mode 100644 index 000000000..da66f9589 --- /dev/null +++ b/ebl/tests/dossiers/test_dossiers_repository.py @@ -0,0 +1,10 @@ +from ebl.tests.factories.dossier import DossierRecordFactory +from ebl.dossiers.application.dossiers_repository import DossiersRepository + + +def test_fetch(dossiers_repository: DossiersRepository): + dossier_record = DossierRecordFactory.build() + dossiers_repository.create(dossier_record) + dossiers_repository.create(DossierRecordFactory.build()) + + assert dossiers_repository.fetch(dossier_record.id) == dossier_record diff --git a/ebl/tests/dossiers/test_dossiers_route.py b/ebl/tests/dossiers/test_dossiers_route.py new file mode 100644 index 000000000..dea6aaa19 --- /dev/null +++ b/ebl/tests/dossiers/test_dossiers_route.py @@ -0,0 +1,28 @@ +import falcon +import pytest + +from ebl.dossiers.domain.dossier_record import DossierRecord +from ebl.tests.factories.dossier import ( + DossierRecordFactory, +) +from ebl.dossiers.application.dossiers_repository import ( + DossiersRepository, +) +from ebl.dossiers.infrastructure.mongo_dossiers_repository import ( + DossierRecordSchema, +) + + +@pytest.fixture +def dossier_record() -> DossierRecord: + return DossierRecordFactory.build() + + +def test_fetch_dossier_record_route( + dossier_record, dossiers_repository: DossiersRepository, client +) -> None: + dossiers_repository.create(dossier_record) + get_result = client.simulate_get("/dossiers", params={"id": dossier_record.id}) + + assert get_result.status == falcon.HTTP_OK + assert get_result.json[0] == DossierRecordSchema().dump(dossier_record) From 8d636a4c3260eb9a7f33a028fa989111858ef6a4 Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Wed, 11 Dec 2024 16:46:50 +0000 Subject: [PATCH 8/9] Implement batch queries & update --- .../application/dossiers_repository.py | 3 +- .../mongo_dossiers_repository.py | 14 ++++------ ebl/dossiers/web/dossier_records.py | 5 ++-- ebl/tests/dossiers/test_dossiers_route.py | 28 ++++++++++++++++--- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/ebl/dossiers/application/dossiers_repository.py b/ebl/dossiers/application/dossiers_repository.py index c70a7d41e..33648bd0d 100644 --- a/ebl/dossiers/application/dossiers_repository.py +++ b/ebl/dossiers/application/dossiers_repository.py @@ -1,3 +1,4 @@ +from typing import Sequence from abc import ABC, abstractmethod from ebl.dossiers.domain.dossier_record import ( @@ -7,7 +8,7 @@ class DossiersRepository(ABC): @abstractmethod - def fetch(self, id: str) -> DossierRecord: ... + def query_by_ids(self, ids: Sequence[str]) -> DossierRecord: ... @abstractmethod def create(self, dossier_record: DossierRecord) -> str: ... diff --git a/ebl/dossiers/infrastructure/mongo_dossiers_repository.py b/ebl/dossiers/infrastructure/mongo_dossiers_repository.py index 0f4c2ccc3..5a943a266 100644 --- a/ebl/dossiers/infrastructure/mongo_dossiers_repository.py +++ b/ebl/dossiers/infrastructure/mongo_dossiers_repository.py @@ -1,3 +1,4 @@ +from typing import Sequence from marshmallow import Schema, fields, post_load, EXCLUDE from pymongo.database import Database from ebl.mongo_collection import MongoCollection @@ -48,14 +49,11 @@ def make_record(self, data, **kwargs): class MongoDossiersRepository(DossiersRepository): def __init__(self, database: Database): - self._dossier = MongoCollection(database, COLLECTION) + self._collection = MongoCollection(database, COLLECTION) - def fetch(self, id: str) -> DossierRecord: - query = {"_id": id} - record_data = self._dossier.find_one(query) - if not record_data: - raise ValueError(f"No dossier record found for the id: {id}") - return DossierRecordSchema().load(record_data) + def query_by_ids(self, ids: Sequence[str]) -> Sequence[DossierRecord]: + cursor = self._collection.find_many({"_id": {"$in": ids}}) + return DossierRecordSchema(many=True).load(cursor) def create(self, dossier_record: DossierRecord) -> str: - return self._dossier.insert_one(DossierRecordSchema().dump(dossier_record)) + return self._collection.insert_one(DossierRecordSchema().dump(dossier_record)) diff --git a/ebl/dossiers/web/dossier_records.py b/ebl/dossiers/web/dossier_records.py index 402092e37..4f72ccea5 100644 --- a/ebl/dossiers/web/dossier_records.py +++ b/ebl/dossiers/web/dossier_records.py @@ -1,5 +1,6 @@ from falcon import Request, Response from ebl.errors import NotFoundError +from marshmallow import EXCLUDE from ebl.dossiers.application.dossiers_repository import DossiersRepository from ebl.dossiers.infrastructure.mongo_dossiers_repository import ( @@ -13,9 +14,9 @@ def __init__(self, _dossiersRepository: DossiersRepository): def on_get(self, req: Request, resp: Response) -> None: try: - dossiers = self._dossiersRepository.fetch(req.params["id"]) + dossiers = self._dossiersRepository.query_by_ids(req.params["ids"].split(",")) except ValueError as error: raise NotFoundError( f"No dossier records matching {str(req.params)} found." ) from error - resp.media = DossierRecordSchema().dump([dossiers], many=True) + resp.media = DossierRecordSchema(unknown=EXCLUDE, many=True).dump(dossiers) diff --git a/ebl/tests/dossiers/test_dossiers_route.py b/ebl/tests/dossiers/test_dossiers_route.py index dea6aaa19..c20342f77 100644 --- a/ebl/tests/dossiers/test_dossiers_route.py +++ b/ebl/tests/dossiers/test_dossiers_route.py @@ -1,6 +1,5 @@ import falcon import pytest - from ebl.dossiers.domain.dossier_record import DossierRecord from ebl.tests.factories.dossier import ( DossierRecordFactory, @@ -18,11 +17,32 @@ def dossier_record() -> DossierRecord: return DossierRecordFactory.build() +@pytest.fixture +def another_dossier_record() -> DossierRecord: + return DossierRecordFactory.build() + + +@pytest.fixture +def unrelated_dossier_record() -> DossierRecord: + return DossierRecordFactory.build() + + def test_fetch_dossier_record_route( - dossier_record, dossiers_repository: DossiersRepository, client + dossier_record, + another_dossier_record, + unrelated_dossier_record, + dossiers_repository: DossiersRepository, + client, ) -> None: dossiers_repository.create(dossier_record) - get_result = client.simulate_get("/dossiers", params={"id": dossier_record.id}) + dossiers_repository.create(another_dossier_record) + dossiers_repository.create(unrelated_dossier_record) + get_result = client.simulate_get( + "/dossiers", + params={"ids": ",".join([dossier_record.id, another_dossier_record.id])}, + ) assert get_result.status == falcon.HTTP_OK - assert get_result.json[0] == DossierRecordSchema().dump(dossier_record) + assert sorted(get_result.json, key=lambda r: r["_id"]) == DossierRecordSchema( + many=True + ).dump(sorted([dossier_record, another_dossier_record], key=lambda r: r.id)) From c028dc0d795d77c16c82ae0d47095a96492672ac Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Wed, 11 Dec 2024 20:35:56 +0000 Subject: [PATCH 9/9] Add dossier to fragment, update & refactor --- .../application/dossiers_repository.py | 2 +- .../mongo_dossiers_repository.py | 2 +- ebl/dossiers/web/dossier_records.py | 4 +- .../application/annotations_schema.py | 2 +- .../application/fragment_fields_schemas.py | 164 ++++++++++++++++++ .../application/fragment_info_schema.py | 2 +- .../application/fragment_schema.py | 159 ++--------------- .../application/line_to_vec_ranking_schema.py | 2 +- ebl/fragmentarium/domain/fragment.py | 7 + .../mongo_fragment_repository_get_extended.py | 2 +- ebl/fragmentarium/web/fragment_script.py | 2 +- ebl/tests/dossiers/test_dossier.py | 2 +- .../dossiers/test_dossiers_repository.py | 4 +- ebl/tests/factories/fragment.py | 16 ++ ebl/tests/fragmentarium/test_dtos.py | 9 +- .../test_fragment_script_route.py | 2 +- .../test_line_to_vec_ranking_schema.py | 2 +- ebl/tests/fragmentarium/test_script_schema.py | 2 +- 18 files changed, 223 insertions(+), 162 deletions(-) create mode 100644 ebl/fragmentarium/application/fragment_fields_schemas.py diff --git a/ebl/dossiers/application/dossiers_repository.py b/ebl/dossiers/application/dossiers_repository.py index 33648bd0d..61e431429 100644 --- a/ebl/dossiers/application/dossiers_repository.py +++ b/ebl/dossiers/application/dossiers_repository.py @@ -8,7 +8,7 @@ class DossiersRepository(ABC): @abstractmethod - def query_by_ids(self, ids: Sequence[str]) -> DossierRecord: ... + def query_by_ids(self, ids: Sequence[str]) -> Sequence[DossierRecord]: ... @abstractmethod def create(self, dossier_record: DossierRecord) -> str: ... diff --git a/ebl/dossiers/infrastructure/mongo_dossiers_repository.py b/ebl/dossiers/infrastructure/mongo_dossiers_repository.py index 5a943a266..724d50b2b 100644 --- a/ebl/dossiers/infrastructure/mongo_dossiers_repository.py +++ b/ebl/dossiers/infrastructure/mongo_dossiers_repository.py @@ -7,7 +7,7 @@ ) from ebl.dossiers.application.dossiers_repository import DossiersRepository from ebl.common.domain.provenance import Provenance -from ebl.fragmentarium.application.fragment_schema import ScriptSchema +from ebl.fragmentarium.application.fragment_fields_schemas import ScriptSchema from ebl.schemas import NameEnumField from ebl.bibliography.domain.reference import ReferenceType diff --git a/ebl/dossiers/web/dossier_records.py b/ebl/dossiers/web/dossier_records.py index 4f72ccea5..6bf3fe0c5 100644 --- a/ebl/dossiers/web/dossier_records.py +++ b/ebl/dossiers/web/dossier_records.py @@ -14,7 +14,9 @@ def __init__(self, _dossiersRepository: DossiersRepository): def on_get(self, req: Request, resp: Response) -> None: try: - dossiers = self._dossiersRepository.query_by_ids(req.params["ids"].split(",")) + dossiers = self._dossiersRepository.query_by_ids( + req.params["ids"].split(",") + ) except ValueError as error: raise NotFoundError( f"No dossier records matching {str(req.params)} found." diff --git a/ebl/fragmentarium/application/annotations_schema.py b/ebl/fragmentarium/application/annotations_schema.py index 3fb29ca24..32f670bd5 100644 --- a/ebl/fragmentarium/application/annotations_schema.py +++ b/ebl/fragmentarium/application/annotations_schema.py @@ -1,7 +1,7 @@ from marshmallow import Schema, fields, post_load, post_dump import pydash from ebl.fragmentarium.application.cropped_sign_image import CroppedSignSchema -from ebl.fragmentarium.application.fragment_schema import ScriptSchema +from ebl.fragmentarium.application.fragment_fields_schemas import ScriptSchema from ebl.fragmentarium.domain.annotation import ( Geometry, AnnotationData, diff --git a/ebl/fragmentarium/application/fragment_fields_schemas.py b/ebl/fragmentarium/application/fragment_fields_schemas.py new file mode 100644 index 000000000..496b405be --- /dev/null +++ b/ebl/fragmentarium/application/fragment_fields_schemas.py @@ -0,0 +1,164 @@ +import pydash +from marshmallow import Schema, fields, post_dump, post_load, EXCLUDE +from ebl.fragmentarium.domain.folios import Folio, Folios +from ebl.fragmentarium.domain.fragment import ( + Introduction, + Notes, + Measure, + UncuratedReference, + Script, + DossierReference, +) +from ebl.fragmentarium.domain.record import Record, RecordEntry, RecordType +from ebl.schemas import ValueEnumField +from ebl.transliteration.application.note_line_part_schemas import ( + OneOfNoteLinePartSchema, +) +from ebl.common.domain.period import Period, PeriodModifier +from ebl.fragmentarium.domain.fragment_external_numbers import ExternalNumbers + + +class MeasureSchema(Schema): + value = fields.Float(load_default=None) + note = fields.String(load_default=None) + + @post_load + def make_measure(self, data, **kwargs): + return Measure(**data) + + @post_dump + def filter_none(self, data, **kwargs): + return pydash.omit_by(data, pydash.is_none) + + +class RecordEntrySchema(Schema): + user = fields.String(required=True) + type = ValueEnumField(RecordType, required=True) + date = fields.String(required=True) + + @post_load + def make_record_entry(self, data, **kwargs): + return RecordEntry(**data) + + +class RecordSchema(Schema): + entries = fields.Nested(RecordEntrySchema, many=True, required=True) + + @post_load + def make_record(self, data, **kwargs): + return Record(tuple(data["entries"])) + + +class FolioSchema(Schema): + name = fields.String(required=True) + number = fields.String(required=True) + + @post_load + def make_record_entry(self, data, **kwargs): + return Folio(**data) + + +class FoliosSchema(Schema): + entries = fields.Nested(FolioSchema, many=True, required=True) + + @post_load + def make_folio(self, data, **kwargs): + return Folios(tuple(data["entries"])) + + +class UncuratedReferenceSchema(Schema): + document = fields.String(required=True) + pages = fields.List(fields.Integer(), required=True) + + @post_load + def make_uncurated_reference(self, data, **kwargs): + data["pages"] = tuple(data["pages"]) + return UncuratedReference(**data) + + +class MarkupTextSchema(Schema): + text = fields.String(required=True) + parts = fields.List(fields.Nested(OneOfNoteLinePartSchema), required=True) + + +class IntroductionSchema(MarkupTextSchema): + @post_load + def make_introduction(self, data, **kwargs) -> Introduction: + return Introduction(data["text"], tuple(data["parts"])) + + +class NotesSchema(MarkupTextSchema): + @post_load + def make_notes(self, data, **kwargs) -> Notes: + return Notes(data["text"], tuple(data["parts"])) + + +class ScriptSchema(Schema): + class Meta: + unknown = EXCLUDE + + period = fields.Function( + lambda script: script.period.long_name, + lambda value: Period.from_name(value), + required=True, + ) + period_modifier = ValueEnumField( + PeriodModifier, required=True, data_key="periodModifier" + ) + uncertain = fields.Boolean(load_default=None) + sort_key = fields.Function( + lambda script: script.period.sort_key, data_key="sortKey", dump_only=True + ) + + @post_load + def make_script(self, data, **kwargs) -> Script: + return Script(**data) + + +class ExternalNumbersSchema(Schema): + cdli_number = fields.String(load_default="", data_key="cdliNumber") + bm_id_number = fields.String(load_default="", data_key="bmIdNumber") + archibab_number = fields.String(load_default="", data_key="archibabNumber") + bdtns_number = fields.String(load_default="", data_key="bdtnsNumber") + chicago_isac_number = fields.String(load_default="", data_key="chicagoIsacNumber") + ur_online_number = fields.String(load_default="", data_key="urOnlineNumber") + hilprecht_jena_number = fields.String( + load_default="", data_key="hilprechtJenaNumber" + ) + hilprecht_heidelberg_number = fields.String( + load_default="", data_key="hilprechtHeidelbergNumber" + ) + metropolitan_number = fields.String(load_default="", data_key="metropolitanNumber") + yale_peabody_number = fields.String(load_default="", data_key="yalePeabodyNumber") + louvre_number = fields.String(load_default="", data_key="louvreNumber") + dublin_tcd_number = fields.String(load_default="", data_key="dublinTcdNumber") + alalah_hpm_number = fields.String(load_default="", data_key="alalahHpmNumber") + philadelphia_number = fields.String(load_default="", data_key="philadelphiaNumber") + australianinstituteofarchaeology_number = fields.String( + load_default="", data_key="australianinstituteofarchaeologyNumber" + ) + achemenet_number = fields.String(load_default="", data_key="achemenetNumber") + nabucco_number = fields.String(load_default="", data_key="nabuccoNumber") + oracc_numbers = fields.List( + fields.String(), load_default=(), data_key="oraccNumbers" + ) + seal_numbers = fields.List(fields.String(), load_default=(), data_key="sealNumbers") + + @post_load + def make_external_numbers(self, data, **kwargs) -> ExternalNumbers: + data["oracc_numbers"] = tuple(data["oracc_numbers"]) + data["seal_numbers"] = tuple(data["seal_numbers"]) + return ExternalNumbers(**data) + + @post_dump + def omit_empty_numbers(self, data, **kwargs): + return pydash.omit_by(data, pydash.is_empty) + + +class DossierReferenceSchema(Schema): + dossierId = fields.String(required=True) + isUncertain = fields.Boolean(load_default=False) + + @post_load + def make_dossier_reference(self, data, **kwargs) -> DossierReference: + return DossierReference(**data) diff --git a/ebl/fragmentarium/application/fragment_info_schema.py b/ebl/fragmentarium/application/fragment_info_schema.py index 6abde4f0e..737fa1076 100644 --- a/ebl/fragmentarium/application/fragment_info_schema.py +++ b/ebl/fragmentarium/application/fragment_info_schema.py @@ -5,7 +5,7 @@ ApiReferenceSchema, ) from ebl.common.application.schemas import AccessionSchema -from ebl.fragmentarium.application.fragment_schema import ScriptSchema +from ebl.fragmentarium.application.fragment_fields_schemas import ScriptSchema from ebl.fragmentarium.application.genre_schema import GenreSchema from ebl.fragmentarium.domain.fragment_infos_pagination import FragmentInfosPagination from ebl.transliteration.application.museum_number_schema import MuseumNumberSchema diff --git a/ebl/fragmentarium/application/fragment_schema.py b/ebl/fragmentarium/application/fragment_schema.py index 57946d483..1e2ed80e2 100644 --- a/ebl/fragmentarium/application/fragment_schema.py +++ b/ebl/fragmentarium/application/fragment_schema.py @@ -1,171 +1,37 @@ import pydash from ebl.schemas import NameEnumField -from marshmallow import Schema, fields, post_dump, post_load, EXCLUDE +from marshmallow import Schema, fields, post_dump, post_load from ebl.fragmentarium.domain.museum import Museum from ebl.bibliography.application.reference_schema import ReferenceSchema from ebl.common.application.schemas import AccessionSchema -from ebl.common.domain.period import Period, PeriodModifier from ebl.fragmentarium.application.archaeology_schemas import ArchaeologySchema from ebl.fragmentarium.application.genre_schema import GenreSchema from ebl.transliteration.application.museum_number_schema import MuseumNumberSchema -from ebl.fragmentarium.domain.folios import Folio, Folios from ebl.fragmentarium.domain.fragment import ( Fragment, Introduction, Notes, - Measure, Script, - UncuratedReference, ) from ebl.fragmentarium.domain.fragment_external_numbers import ExternalNumbers from ebl.fragmentarium.domain.line_to_vec_encoding import LineToVecEncoding -from ebl.fragmentarium.domain.record import Record, RecordEntry, RecordType from ebl.schemas import ResearchProjectField, ScopeField, ValueEnumField -from ebl.transliteration.application.note_line_part_schemas import ( - OneOfNoteLinePartSchema, -) from ebl.transliteration.application.text_schema import TextSchema from ebl.fragmentarium.application.joins_schema import JoinsSchema from ebl.fragmentarium.domain.joins import Joins from ebl.fragmentarium.domain.date import DateSchema from ebl.fragmentarium.application.colophon_schema import ColophonSchema - - -class MeasureSchema(Schema): - value = fields.Float(load_default=None) - note = fields.String(load_default=None) - - @post_load - def make_measure(self, data, **kwargs): - return Measure(**data) - - @post_dump - def filter_none(self, data, **kwargs): - return pydash.omit_by(data, pydash.is_none) - - -class RecordEntrySchema(Schema): - user = fields.String(required=True) - type = ValueEnumField(RecordType, required=True) - date = fields.String(required=True) - - @post_load - def make_record_entry(self, data, **kwargs): - return RecordEntry(**data) - - -class RecordSchema(Schema): - entries = fields.Nested(RecordEntrySchema, many=True, required=True) - - @post_load - def make_record(self, data, **kwargs): - return Record(tuple(data["entries"])) - - -class FolioSchema(Schema): - name = fields.String(required=True) - number = fields.String(required=True) - - @post_load - def make_record_entry(self, data, **kwargs): - return Folio(**data) - - -class FoliosSchema(Schema): - entries = fields.Nested(FolioSchema, many=True, required=True) - - @post_load - def make_folio(self, data, **kwargs): - return Folios(tuple(data["entries"])) - - -class UncuratedReferenceSchema(Schema): - document = fields.String(required=True) - pages = fields.List(fields.Integer(), required=True) - - @post_load - def make_uncurated_reference(self, data, **kwargs): - data["pages"] = tuple(data["pages"]) - return UncuratedReference(**data) - - -class MarkupTextSchema(Schema): - text = fields.String(required=True) - parts = fields.List(fields.Nested(OneOfNoteLinePartSchema), required=True) - - -class IntroductionSchema(MarkupTextSchema): - @post_load - def make_introduction(self, data, **kwargs) -> Introduction: - return Introduction(data["text"], tuple(data["parts"])) - - -class NotesSchema(MarkupTextSchema): - @post_load - def make_notes(self, data, **kwargs) -> Notes: - return Notes(data["text"], tuple(data["parts"])) - - -class ScriptSchema(Schema): - class Meta: - unknown = EXCLUDE - - period = fields.Function( - lambda script: script.period.long_name, - lambda value: Period.from_name(value), - required=True, - ) - period_modifier = ValueEnumField( - PeriodModifier, required=True, data_key="periodModifier" - ) - uncertain = fields.Boolean(load_default=None) - sort_key = fields.Function( - lambda script: script.period.sort_key, data_key="sortKey", dump_only=True - ) - - @post_load - def make_script(self, data, **kwargs) -> Script: - return Script(**data) - - -class ExternalNumbersSchema(Schema): - cdli_number = fields.String(load_default="", data_key="cdliNumber") - bm_id_number = fields.String(load_default="", data_key="bmIdNumber") - archibab_number = fields.String(load_default="", data_key="archibabNumber") - bdtns_number = fields.String(load_default="", data_key="bdtnsNumber") - chicago_isac_number = fields.String(load_default="", data_key="chicagoIsacNumber") - ur_online_number = fields.String(load_default="", data_key="urOnlineNumber") - hilprecht_jena_number = fields.String( - load_default="", data_key="hilprechtJenaNumber" - ) - hilprecht_heidelberg_number = fields.String( - load_default="", data_key="hilprechtHeidelbergNumber" - ) - metropolitan_number = fields.String(load_default="", data_key="metropolitanNumber") - yale_peabody_number = fields.String(load_default="", data_key="yalePeabodyNumber") - louvre_number = fields.String(load_default="", data_key="louvreNumber") - dublin_tcd_number = fields.String(load_default="", data_key="dublinTcdNumber") - alalah_hpm_number = fields.String(load_default="", data_key="alalahHpmNumber") - philadelphia_number = fields.String(load_default="", data_key="philadelphiaNumber") - australianinstituteofarchaeology_number = fields.String( - load_default="", data_key="australianinstituteofarchaeologyNumber" - ) - achemenet_number = fields.String(load_default="", data_key="achemenetNumber") - nabucco_number = fields.String(load_default="", data_key="nabuccoNumber") - oracc_numbers = fields.List( - fields.String(), load_default=(), data_key="oraccNumbers" - ) - seal_numbers = fields.List(fields.String(), load_default=(), data_key="sealNumbers") - - @post_load - def make_external_numbers(self, data, **kwargs) -> ExternalNumbers: - data["oracc_numbers"] = tuple(data["oracc_numbers"]) - data["seal_numbers"] = tuple(data["seal_numbers"]) - return ExternalNumbers(**data) - - @post_dump - def omit_empty_numbers(self, data, **kwargs): - return pydash.omit_by(data, pydash.is_empty) +from ebl.fragmentarium.application.fragment_fields_schemas import ( + MeasureSchema, + RecordSchema, + FoliosSchema, + UncuratedReferenceSchema, + IntroductionSchema, + NotesSchema, + ScriptSchema, + ExternalNumbersSchema, + DossierReferenceSchema, +) class FragmentSchema(Schema): @@ -219,6 +85,7 @@ class FragmentSchema(Schema): ) archaeology = fields.Nested(ArchaeologySchema, allow_none=True, default=None) colophon = fields.Nested(ColophonSchema, allow_none=True, default=None) + dossiers = fields.Nested(DossierReferenceSchema, many=True, default=[]) @post_load def make_fragment(self, data, **kwargs): diff --git a/ebl/fragmentarium/application/line_to_vec_ranking_schema.py b/ebl/fragmentarium/application/line_to_vec_ranking_schema.py index 60c569046..0eb1be1ce 100644 --- a/ebl/fragmentarium/application/line_to_vec_ranking_schema.py +++ b/ebl/fragmentarium/application/line_to_vec_ranking_schema.py @@ -1,6 +1,6 @@ from marshmallow import Schema, fields, pre_dump from ebl.fragmentarium.application.line_to_vec import LineToVecScore -from ebl.fragmentarium.application.fragment_schema import ScriptSchema +from ebl.fragmentarium.application.fragment_fields_schemas import ScriptSchema from ebl.fragmentarium.domain.fragment import Script diff --git a/ebl/fragmentarium/domain/fragment.py b/ebl/fragmentarium/domain/fragment.py index 6ff5dd440..0a5cc3019 100644 --- a/ebl/fragmentarium/domain/fragment.py +++ b/ebl/fragmentarium/domain/fragment.py @@ -99,6 +99,12 @@ def abbreviation(self) -> str: return self.period.value[1] +@attr.s(auto_attribs=True, frozen=True) +class DossierReference: + dossierId: str + isUncertain: bool = False + + @attr.s(auto_attribs=True, frozen=True) class Fragment(FragmentExternalNumbers): number: MuseumNumber @@ -131,6 +137,7 @@ class Fragment(FragmentExternalNumbers): archaeology: Optional[Archaeology] = None colophon: Optional[Colophon] = None external_numbers: ExternalNumbers = ExternalNumbers() + dossiers: Sequence[str] = [] @property def is_lowest_join(self) -> bool: diff --git a/ebl/fragmentarium/infrastructure/mongo_fragment_repository_get_extended.py b/ebl/fragmentarium/infrastructure/mongo_fragment_repository_get_extended.py index b4c3ffd99..6084f29c3 100644 --- a/ebl/fragmentarium/infrastructure/mongo_fragment_repository_get_extended.py +++ b/ebl/fragmentarium/infrastructure/mongo_fragment_repository_get_extended.py @@ -9,7 +9,7 @@ from ebl.fragmentarium.infrastructure.mongo_fragment_repository_base import ( MongoFragmentRepositoryBase, ) -from ebl.fragmentarium.application.fragment_schema import ScriptSchema +from ebl.fragmentarium.application.fragment_fields_schemas import ScriptSchema from ebl.transliteration.domain.museum_number import MuseumNumber from ebl.transliteration.application.museum_number_schema import MuseumNumberSchema from ebl.fragmentarium.domain.date import Date, DateSchema diff --git a/ebl/fragmentarium/web/fragment_script.py b/ebl/fragmentarium/web/fragment_script.py index 12ecb29d3..3cfc18ff9 100644 --- a/ebl/fragmentarium/web/fragment_script.py +++ b/ebl/fragmentarium/web/fragment_script.py @@ -6,7 +6,7 @@ from ebl.fragmentarium.application.fragment_updater import FragmentUpdater from ebl.fragmentarium.web.dtos import create_response_dto, parse_museum_number from ebl.users.web.require_scope import require_scope -from ebl.fragmentarium.application.fragment_schema import ScriptSchema +from ebl.fragmentarium.application.fragment_fields_schemas import ScriptSchema class FragmentScriptResource: diff --git a/ebl/tests/dossiers/test_dossier.py b/ebl/tests/dossiers/test_dossier.py index 7146aaae4..f88f0d7bb 100644 --- a/ebl/tests/dossiers/test_dossier.py +++ b/ebl/tests/dossiers/test_dossier.py @@ -7,7 +7,7 @@ ) from ebl.tests.factories.dossier import DossierRecordFactory from ebl.fragmentarium.domain.fragment import Script -from ebl.fragmentarium.application.fragment_schema import ScriptSchema +from ebl.fragmentarium.application.fragment_fields_schemas import ScriptSchema from ebl.common.domain.provenance import Provenance diff --git a/ebl/tests/dossiers/test_dossiers_repository.py b/ebl/tests/dossiers/test_dossiers_repository.py index da66f9589..48d31df52 100644 --- a/ebl/tests/dossiers/test_dossiers_repository.py +++ b/ebl/tests/dossiers/test_dossiers_repository.py @@ -2,9 +2,9 @@ from ebl.dossiers.application.dossiers_repository import DossiersRepository -def test_fetch(dossiers_repository: DossiersRepository): +def test_query_by_ids(dossiers_repository: DossiersRepository): dossier_record = DossierRecordFactory.build() dossiers_repository.create(dossier_record) dossiers_repository.create(DossierRecordFactory.build()) - assert dossiers_repository.fetch(dossier_record.id) == dossier_record + assert dossiers_repository.query_by_ids([dossier_record.id]) == [dossier_record] diff --git a/ebl/tests/factories/fragment.py b/ebl/tests/factories/fragment.py index fdcdfd7de..c34da02e1 100644 --- a/ebl/tests/factories/fragment.py +++ b/ebl/tests/factories/fragment.py @@ -19,7 +19,9 @@ Notes, Script, UncuratedReference, + DossierReference, ) + from ebl.fragmentarium.domain.fragment_external_numbers import ExternalNumbers from ebl.fragmentarium.domain.line_to_vec_encoding import LineToVecEncoding from ebl.transliteration.domain.museum_number import MuseumNumber @@ -204,6 +206,14 @@ class Meta: ) +class FragmentDossierReferenceFactory(factory.Factory): + class Meta: + model = DossierReference + + dossierId = factory.Faker("word") + isUncertain = factory.Faker("boolean") + + class FragmentFactory(factory.Factory): class Meta: model = Fragment @@ -237,6 +247,12 @@ class Meta: projects = (ResearchProject.CAIC, ResearchProject.ALU_GENEVA, ResearchProject.AMPS) archaeology = factory.SubFactory(ArchaeologyFactory) colophon = factory.SubFactory(ColophonFactory) + dossiers = factory.List( + [ + factory.SubFactory(FragmentDossierReferenceFactory) + for _ in range(random.randint(0, 4)) + ] + ) class InterestingFragmentFactory(FragmentFactory): diff --git a/ebl/tests/fragmentarium/test_dtos.py b/ebl/tests/fragmentarium/test_dtos.py index 9ecb534fe..0f7ac727c 100644 --- a/ebl/tests/fragmentarium/test_dtos.py +++ b/ebl/tests/fragmentarium/test_dtos.py @@ -17,13 +17,14 @@ LemmatizedFragmentFactory, ) from ebl.transliteration.application.text_schema import TextSchema -from ebl.fragmentarium.application.fragment_schema import ( +from ebl.fragmentarium.application.fragment_fields_schemas import ( ExternalNumbersSchema, - JoinsSchema, IntroductionSchema, NotesSchema, ScriptSchema, + DossierReferenceSchema, ) +from ebl.fragmentarium.application.joins_schema import JoinsSchema from ebl.fragmentarium.application.colophon_schema import ColophonSchema from ebl.fragmentarium.domain.date import DateSchema from ebl.fragmentarium.domain.joins import Joins @@ -121,6 +122,10 @@ def expected_dto(lemmatized_fragment, has_photo): "archaeology": ArchaeologySchema().dump(lemmatized_fragment.archaeology), "colophon": ColophonSchema().dump(lemmatized_fragment.colophon), "authorizedScopes": [], + "dossiers": [ + DossierReferenceSchema().dump(dossier) + for dossier in lemmatized_fragment.dossiers + ], }, pydash.is_none, ) diff --git a/ebl/tests/fragmentarium/test_fragment_script_route.py b/ebl/tests/fragmentarium/test_fragment_script_route.py index 4017b56ac..7acde4937 100644 --- a/ebl/tests/fragmentarium/test_fragment_script_route.py +++ b/ebl/tests/fragmentarium/test_fragment_script_route.py @@ -2,7 +2,7 @@ import pytest import json from ebl.common.domain.period import Period, PeriodModifier -from ebl.fragmentarium.application.fragment_schema import ScriptSchema +from ebl.fragmentarium.application.fragment_fields_schemas import ScriptSchema from ebl.fragmentarium.web.dtos import create_response_dto from ebl.tests.factories.fragment import FragmentFactory, ScriptFactory diff --git a/ebl/tests/fragmentarium/test_line_to_vec_ranking_schema.py b/ebl/tests/fragmentarium/test_line_to_vec_ranking_schema.py index d75a71d18..8eaf636c6 100644 --- a/ebl/tests/fragmentarium/test_line_to_vec_ranking_schema.py +++ b/ebl/tests/fragmentarium/test_line_to_vec_ranking_schema.py @@ -5,7 +5,7 @@ ) from ebl.transliteration.domain.museum_number import MuseumNumber from ebl.tests.factories.fragment import ScriptFactory -from ebl.fragmentarium.application.fragment_schema import ScriptSchema +from ebl.fragmentarium.application.fragment_fields_schemas import ScriptSchema SCRIPT = ScriptFactory.build() diff --git a/ebl/tests/fragmentarium/test_script_schema.py b/ebl/tests/fragmentarium/test_script_schema.py index eace5b0d2..8c39c9e1d 100644 --- a/ebl/tests/fragmentarium/test_script_schema.py +++ b/ebl/tests/fragmentarium/test_script_schema.py @@ -1,5 +1,5 @@ import pytest -from ebl.fragmentarium.application.fragment_schema import ScriptSchema +from ebl.fragmentarium.application.fragment_fields_schemas import ScriptSchema from ebl.fragmentarium.domain.fragment import Script from ebl.common.domain.period import Period, PeriodModifier