Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fragment references to AfO Reg. endpoint #496

Merged
merged 28 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e13d43c
Implement AfO Regster serialiation & repos (WiP)
khoidt Oct 18, 2023
d050b45
Update resources & bind to context
khoidt Oct 18, 2023
e875e0f
Add tests & update
khoidt Oct 23, 2023
7ffd5c9
Fix test
khoidt Oct 23, 2023
5f30543
Update search, factory & tests
khoidt Oct 25, 2023
d3b9c72
Implement afo register suggestions & tests
khoidt Nov 9, 2023
21e652d
'Refactored by Sourcery' (#484)
sourcery-ai[bot] Nov 9, 2023
036d398
Merge remote-tracking branch 'origin/master' into afo-register
khoidt Nov 9, 2023
be5bc53
Update query & sorting
khoidt Nov 13, 2023
5cef900
Update tests & format
khoidt Nov 15, 2023
9b38663
Clean up
khoidt Nov 15, 2023
5c5409f
Update queries & sorting
khoidt Nov 16, 2023
df49aa1
Merge remote-tracking branch 'origin/master' into afo-register
khoidt Nov 16, 2023
4f90cf0
Update & refactor query collation, use in afo register queries
khoidt Nov 20, 2023
9e7c3f7
Refactor
khoidt Nov 20, 2023
cea4a50
Add text + textNumber queries (search by traditionalReferences)
khoidt Nov 22, 2023
e3c7206
Add route & test
khoidt Nov 22, 2023
9d25757
Use post
khoidt Nov 22, 2023
faaf8fb
Merge remote-tracking branch 'origin/master' into afo-register
khoidt Nov 22, 2023
8cc5503
Update test
khoidt Nov 22, 2023
d493fa8
Extend fragment query for `traditionalReferences`
khoidt Nov 26, 2023
ef60f9c
Merge remote-tracking branch 'origin/master' into afo-register
khoidt Nov 29, 2023
1594dca
Merge remote-tracking branch 'origin/master' into afo-register
khoidt Dec 6, 2023
c65798a
Improve
khoidt Dec 6, 2023
c626846
Implememnt fragment references to AfO Reg. endpoint
khoidt Dec 10, 2023
cc3e923
Format and clean
khoidt Dec 10, 2023
ac49486
Add tests, refactor & clean up
khoidt Dec 11, 2023
a8e8e70
'Refactored by Sourcery' (#497)
sourcery-ai[bot] Dec 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def create_search_query(query):
if text_number != text_number_stripped:
query["textNumber"] = text_number_stripped
else:
query["textNumber"] = {"$regex": f"^{text_number}.*", "$options": "i"}
query["textNumber"] = {"$regex": f"^{text_number}", "$options": "i"}
return query


Expand Down Expand Up @@ -99,9 +99,10 @@ def search_by_texts_and_numbers(
def search_suggestions(
self, text_query: str, *args, **kwargs
) -> Sequence[AfoRegisterRecordSuggestion]:
collated_query = list(make_query_params({"text": text_query}, "afo-register"))[
0
]
collated_query_iter = iter(
make_query_params({"text": text_query}, "afo-register")
)
collated_query = next(collated_query_iter)
pipeline = [
{"$match": {"text": {"$regex": collated_query.value, "$options": "i"}}},
{"$group": {"_id": "$text", "textNumbers": {"$addToSet": "$textNumber"}}},
Expand Down
15 changes: 15 additions & 0 deletions ebl/common/query/query_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ class CorpusQueryItem:
match_count: int


@attr.s(auto_attribs=True, frozen=True)
class AfORegisterToFragmentQueryItem:
traditional_reference: str
fragment_numbers: Sequence[str]


@attr.s(auto_attribs=True, frozen=True)
class CorpusQueryResult:
items: Sequence[CorpusQueryItem]
Expand All @@ -48,3 +54,12 @@ class CorpusQueryResult:
@staticmethod
def create_empty() -> "CorpusQueryResult":
return CorpusQueryResult([], 0)


@attr.s(auto_attribs=True, frozen=True)
class AfORegisterToFragmentQueryResult:
items: Sequence[AfORegisterToFragmentQueryItem]

@staticmethod
def create_empty() -> "AfORegisterToFragmentQueryResult":
return AfORegisterToFragmentQueryResult([])
26 changes: 26 additions & 0 deletions ebl/common/query/query_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
CorpusQueryResult,
QueryItem,
QueryResult,
AfORegisterToFragmentQueryResult,
AfORegisterToFragmentQueryItem,
)
from ebl.corpus.application.id_schemas import TextIdSchema
from ebl.schemas import ValueEnumField
Expand Down Expand Up @@ -42,6 +44,20 @@ def make_query_item(self, data, **kwargs) -> CorpusQueryItem:
return CorpusQueryItem(**data)


class AfORegisterToFragmentQueryItemSchema(Schema):
traditional_reference = fields.String(
required=True, data_key="traditionalReference"
)
fragment_numbers = fields.List(
fields.String(), required=True, data_key="fragmentNumbers"
)

@post_load
def make_query_item(self, data, **kwargs) -> AfORegisterToFragmentQueryItem:
data["fragment_numbers"] = tuple(data["fragment_numbers"])
return AfORegisterToFragmentQueryItem(**data)


class QueryResultSchema(Schema):
match_count_total = fields.Integer(data_key="matchCountTotal", required=True)
items = fields.Nested(QueryItemSchema, many=True, required=True)
Expand All @@ -57,3 +73,13 @@ class CorpusQueryResultSchema(QueryResultSchema):
@post_load
def make_query_result(self, data, **kwargs) -> CorpusQueryResult:
return CorpusQueryResult(**data)


class AfORegisterToFragmentQueryResultSchema(Schema):
items = fields.Nested(
AfORegisterToFragmentQueryItemSchema, many=True, required=True
)

@post_load
def make_query_result(self, data, **kwargs) -> AfORegisterToFragmentQueryResult:
return AfORegisterToFragmentQueryResult(**data)
10 changes: 9 additions & 1 deletion ebl/fragmentarium/application/fragment_repository.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from abc import ABC, abstractmethod
from typing import List, Sequence, Optional
from ebl.common.domain.scopes import Scope
from ebl.common.query.query_result import QueryResult
from ebl.common.query.query_result import QueryResult, AfORegisterToFragmentQueryResult

from ebl.fragmentarium.application.line_to_vec import LineToVecEntry
from ebl.fragmentarium.domain.fragment import Fragment
Expand Down Expand Up @@ -41,6 +41,14 @@
) -> Fragment:
...

@abstractmethod
def query_by_traditional_references(
self,
traditional_references: Sequence[str],
user_scopes: Sequence[Scope],
) -> AfORegisterToFragmentQueryResult:
...

Check notice

Code scanning / CodeQL

Statement has no effect Note

This statement has no effect.

@abstractmethod
def query_random_by_transliterated(
self, user_scopes: Sequence[Scope]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,6 @@ def _filter_by_reference(self) -> Dict:
}
return {"references": {"$elemMatch": parameters}}

def _filter_by_traditional_references(self) -> Dict:
if traditional_references := self._query.get("traditionalReferences"):
return {
"traditionalReferences": {"$elemMatch": {"$eq": traditional_references}}
}
return {}

def _prefilter(self) -> List[Dict]:
constraints = {
"$and": compact(
Expand All @@ -105,7 +98,6 @@ def _prefilter(self) -> List[Dict]:
self._filter_by_project(),
self._filter_by_script(),
self._filter_by_reference(),
self._filter_by_traditional_references(),
match_user_scopes(self._scopes),
]
),
Expand Down
24 changes: 22 additions & 2 deletions ebl/fragmentarium/infrastructure/mongo_fragment_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@

from ebl.bibliography.infrastructure.bibliography import join_reference_documents
from ebl.common.domain.scopes import Scope
from ebl.common.query.query_result import QueryResult
from ebl.common.query.query_schemas import QueryResultSchema
from ebl.common.query.query_result import QueryResult, AfORegisterToFragmentQueryResult
from ebl.common.query.query_schemas import (
QueryResultSchema,
AfORegisterToFragmentQueryResultSchema,
)
from ebl.errors import NotFoundError
from ebl.fragmentarium.application.fragment_info_schema import FragmentInfoSchema
from ebl.fragmentarium.application.fragment_repository import FragmentRepository
Expand All @@ -30,6 +33,7 @@
fragment_is,
join_joins,
join_findspots,
aggregate_by_traditional_references,
)
from ebl.fragmentarium.infrastructure.queries import match_user_scopes
from ebl.mongo_collection import MongoCollection
Expand All @@ -38,6 +42,7 @@
from ebl.transliteration.infrastructure.collections import FRAGMENTS_COLLECTION
from ebl.transliteration.infrastructure.queries import query_number_is


RETRIEVE_ALL_LIMIT = 1000


Expand Down Expand Up @@ -423,6 +428,21 @@ def query(self, query: dict, user_scopes: Sequence[Scope] = tuple()) -> QueryRes

return QueryResultSchema().load(data) if data else QueryResult.create_empty()

def query_by_traditional_references(
self,
traditional_references: Sequence[str],
user_scopes: Sequence[Scope] = tuple(),
) -> AfORegisterToFragmentQueryResult:
pipeline = aggregate_by_traditional_references(
traditional_references, user_scopes
)
data = self._fragments.aggregate(pipeline)
return (
AfORegisterToFragmentQueryResultSchema().load({"items": data})
if data
else AfORegisterToFragmentQueryResult.create_empty()
)

def list_all_fragments(
self, user_scopes: Sequence[Scope] = tuple()
) -> Sequence[str]:
Expand Down
43 changes: 43 additions & 0 deletions ebl/fragmentarium/infrastructure/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,3 +341,46 @@ def join_findspots() -> List[dict]:
{"$addFields": {"archaeology.findspot": {"$first": "$findspots"}}},
{"$project": {"findspots": False}},
]


def aggregate_by_traditional_references(
traditional_references: Sequence[str], user_scopes: Sequence[Scope] = tuple()
) -> List[dict]:
return [
{
"$match": {
"traditionalReferences": {"$in": traditional_references},
**match_user_scopes(user_scopes),
}
},
{
"$project": {
"_id": 1,
"traditionalReference": {
"$arrayElemAt": [
{
"$filter": {
"input": "$traditionalReferences",
"as": "ref",
"cond": {"$in": ["$$ref", traditional_references]},
}
},
0,
]
},
}
},
{
"$group": {
"_id": "$traditionalReference",
"fragmentNumbers": {"$addToSet": "$_id"},
}
},
{
"$project": {
"traditionalReference": "$_id",
"fragmentNumbers": 1,
"_id": 0,
}
},
]
7 changes: 7 additions & 0 deletions ebl/fragmentarium/web/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
from ebl.fragmentarium.web.introductions import IntroductionResource
from ebl.fragmentarium.web.archaeology import ArchaeologyResource
from ebl.fragmentarium.web.notes import NotesResource
from ebl.fragmentarium.web.fragments_afo_register import (
AfoRegisterFragmentsQueryResource,
)
from ebl.corpus.web.chapters import ChaptersByManuscriptResource
from ebl.corpus.application.corpus import Corpus

Expand Down Expand Up @@ -90,6 +93,9 @@ def create_fragmentarium_routes(api: falcon.App, context: Context):
fragment_query = FragmentsQueryResource(
context.fragment_repository, context.get_transliteration_query_factory()
)
afo_register_fragments_query = AfoRegisterFragmentsQueryResource(
context.fragment_repository, finder
)
genres = GenresResource()
periods = PeriodsResource()
lemmatization = LemmatizationResource(updater)
Expand Down Expand Up @@ -137,6 +143,7 @@ def create_fragmentarium_routes(api: falcon.App, context: Context):
("/fragments/{number}/pager/{folio_name}/{folio_number}", folio_pager),
("/folios/{name}/{number}", folios),
("/fragments/query", fragment_query),
("/fragments/query-by-traditional-references", afo_register_fragments_query),
("/fragments/all", all_fragments),
("/findspots", findspots),
]
Expand Down
26 changes: 26 additions & 0 deletions ebl/fragmentarium/web/fragments_afo_register.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from falcon import Request, Response

from ebl.errors import DataError
from ebl.fragmentarium.application.fragment_repository import FragmentRepository
from ebl.fragmentarium.application.fragment_finder import FragmentFinder

from marshmallow import ValidationError
from ebl.common.query.query_schemas import AfORegisterToFragmentQueryResultSchema


class AfoRegisterFragmentsQueryResource:
def __init__(self, fragment_repository: FragmentRepository, finder: FragmentFinder):
self._repository = fragment_repository
self._finder = finder

def on_post(self, req: Request, resp: Response) -> None:
try:
result = self._repository.query_by_traditional_references(
req.media["traditionalReferences"],
req.context.user.get_scopes(prefix="read:", suffix="-fragments"),
)
resp.media = AfORegisterToFragmentQueryResultSchema().dump(result)
except ValidationError as error:
raise DataError(
f"Invalid datesInText data: '{req.media['traditionalReferences']}'"
) from error
30 changes: 30 additions & 0 deletions ebl/tests/fragmentarium/test_fragment_afo_register_route.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import falcon
import json
from ebl.tests.factories.fragment import (
FragmentFactory,
)
from ebl.tests.factories.afo_register import AfoRegisterRecordFactory


def test_query_fragmentarium_afo_register(fragment_repository, client):
record = AfoRegisterRecordFactory.build()
record_id = f"{record.text} {record.text_number}"
fragment = FragmentFactory.build(traditional_references=[record_id])
fragment_repository.create_many([fragment, FragmentFactory.build()])

post_result = client.simulate_post(
"/fragments/query-by-traditional-references",
body=json.dumps({"traditionalReferences": [record_id]}),
)

expected_json = {
"items": [
{
"traditionalReference": record_id,
"fragmentNumbers": [str(fragment.number)],
}
]
}

assert post_result.status == falcon.HTTP_OK
assert post_result.json == expected_json
25 changes: 25 additions & 0 deletions ebl/tests/fragmentarium/test_fragment_repository_afo_register.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from ebl.common.query.query_schemas import (
AfORegisterToFragmentQueryResultSchema,
)
from ebl.tests.factories.fragment import (
FragmentFactory,
)
from ebl.tests.factories.afo_register import AfoRegisterRecordFactory


def test_query_fragmentarium_afo_register(fragment_repository):
record = AfoRegisterRecordFactory.build()
record_id = f"{record.text} {record.text_number}"
fragment = FragmentFactory.build(traditional_references=[record_id])
fragment_repository.create_many([fragment, FragmentFactory.build()])
result = fragment_repository.query_by_traditional_references([record_id])
assert result == AfORegisterToFragmentQueryResultSchema().load(
{
"items": [
{
"traditionalReference": record_id,
"fragmentNumbers": [str(fragment.number)],
}
]
}
)
Loading