diff --git a/semantic_kraus_api/main_v1.py b/semantic_kraus_api/main_v1.py index c717dfc..7448c49 100644 --- a/semantic_kraus_api/main_v1.py +++ b/semantic_kraus_api/main_v1.py @@ -2,8 +2,8 @@ from fastapi import APIRouter, Depends, HTTPException from fastapi_versioning import versioned_api_route -from .query_parameters import Search -from .models_v1 import PaginatedResponseSubjects +from .query_parameters import Search, GetEntity +from .models_v1 import PaginatedResponseSubjects, Person from .utils import flatten_rdf_data, get_query_from_triplestore, toggle_urls_encoding @@ -27,3 +27,19 @@ async def query_entities(search: Search = Depends()): pages = math.ceil(int(res[0]["count"]) / search.limit) if len(res) > 0 else 0 count = int(res[0]["count"]) if len(res) > 0 else 0 return {"page": search.page, "count": count, "pages": pages, "results": res} + + +@router.get( + "/api/entities/person", + response_model=Person, + response_model_exclude_none=True, + tags=["Entities endpoints"], + description="Endpoint that allows to retrive a person by id.", +) +async def retrieve_person(query: GetEntity = Depends()): + query_dict = asdict(query) + res = get_query_from_triplestore_v2(query_dict, "get_person_v1.sparql") + # res = FakeList(**{"results": flatten_rdf_data(res)}) + if len(res) == 0: + raise HTTPException(status_code=404, detail="Item not found") + return {"_results": flatten_rdf_data(res)} diff --git a/semantic_kraus_api/models_v1.py b/semantic_kraus_api/models_v1.py index 9db64f6..e5bcb2b 100644 --- a/semantic_kraus_api/models_v1.py +++ b/semantic_kraus_api/models_v1.py @@ -6,6 +6,17 @@ from fastapi_versioning import version, versioned_api_route from fastapi import APIRouter, Depends, HTTPException +graph_mapping = { + "https://sk.acdh.oeaw.ac.at/project/dritte-walpurgisnacht": "Dritte Walpurgisnacht", + "https://sk.acdh.oeaw.ac.at/project/legal-kraus": "Legal Kraus Project", + "https://sk.acdh.oeaw.ac.at/project/fackel": "Die Fackel online", +} + + +def pp_source_professional_occupation(field, item, data) -> dict: + print("test") + return item + class SemanticKrausBackendBaseModel(RDFUtilsModelBaseClass): errors: typing.List[str] | None = None @@ -22,14 +33,40 @@ class InternationalizedLabel(SemanticKrausBackendBaseModel): default: str en: typing.Optional[str] de: str | None = None - fi: str | None = None - si: str | None = None - du: str | None = None def __init__(__pydantic_self__, **data: typing.Any) -> None: super().__init__(**data) +class SameAs(SemanticKrausBackendBaseModel): + """Used to provide source information""" + + id: HttpUrl = Field(..., rdfconfig=FieldConfigurationRDF(path="sameAs", anchor=True)) + object_label: str | None = Field(None, rdfconfig=FieldConfigurationRDF(path="objectLabel")) + graph: HttpUrl | None = Field(None, rdfconfig=FieldConfigurationRDF(path="graph")) + graph_label: str | None = Field(None, rdfconfig=FieldConfigurationRDF(path="graphLabel")) + + def __init__(__pydantic_self__, **data: typing.Any) -> None: + if "graph" in data: + data["graphLabel"] = graph_mapping.get(data["graph"], data["graph"]) + super().__init__(**data) + + +class ProfessionalOccupation(SemanticKrausBackendBaseModel): + """Used to provide professional occupation information""" + + id: HttpUrl = Field(..., rdfconfig=FieldConfigurationRDF(path="professionalOccupation", anchor=True)) + label: InternationalizedLabel = Field( + ..., rdfconfig=FieldConfigurationRDF(path="professionalOccupationLabel", default_dict_key="default") + ) + source: str = Field( + ..., + rdfconfig=FieldConfigurationRDF( + path="professionalOccupationSource", callback_function=pp_source_professional_occupation + ), + ) + + class Entity(SemanticKrausBackendBaseModel): id: str = Field( ..., @@ -40,6 +77,25 @@ class Entity(SemanticKrausBackendBaseModel): ) entity_class: HttpUrl = Field(..., rdfconfig=FieldConfigurationRDF(path="type")) entity_class_label: str = Field(..., rdfconfig=FieldConfigurationRDF(path="typeLabel")) + graph: HttpUrl | None = Field(None, rdfconfig=FieldConfigurationRDF(path="graph_subject")) + graph_label: str | None = Field(None, rdfconfig=FieldConfigurationRDF(path="graph_subjectLabel")) + SameAs: typing.List[SameAs] | None + + def __init__(__pydantic_self__, **data: typing.Any) -> None: + if "graph_subject" in data: + data["graph_subjectLabel"] = graph_mapping.get(data["graph_subject"], None) + super().__init__(**data) + + +class Person(SemanticKrausBackendBaseModel): + id: str = Field( + ..., + rdfconfig=FieldConfigurationRDF(path="person", anchor=True), + ) + label: InternationalizedLabel | None = Field( + None, rdfconfig=FieldConfigurationRDF(path="label", default_dict_key="default") + ) + professions: typing.List[ProfessionalOccupation] | None class PaginatedResponseBase(SemanticKrausBackendBaseModel): diff --git a/semantic_kraus_api/query_parameters.py b/semantic_kraus_api/query_parameters.py index 63d72f7..a4e3fd4 100644 --- a/semantic_kraus_api/query_parameters.py +++ b/semantic_kraus_api/query_parameters.py @@ -38,3 +38,8 @@ class Search(QueryBase): ], description="Filter by entity type. Can be multiple", ) + + +@dataclasses.dataclass(kw_only=True) +class GetEntity: + id: str = Query(..., description="The id of the entity to retrieve") diff --git a/semantic_kraus_api/sparql/entities_search_v1.sparql b/semantic_kraus_api/sparql/entities_search_v1.sparql index b54c563..21331fc 100644 --- a/semantic_kraus_api/sparql/entities_search_v1.sparql +++ b/semantic_kraus_api/sparql/entities_search_v1.sparql @@ -7,7 +7,7 @@ PREFIX rdf: PREFIX frbroo: -SELECT ?subject ?type ?typeLabel ?count ?label +SELECT ?subject ?type ?typeLabel ?count ?label ?graph ?sameAs ?objectLabel ?graph_subject WITH { SELECT (COUNT(?subject) as ?count) @@ -20,7 +20,8 @@ WITH { SELECT ?subject ?type ?score WHERE { {% include 'search_entities_sub_v1.sparql' %} - } + } + {% if q %}ORDER BY DESC(?score) ?subject {% else %} ORDER BY ?subject {% endif %} LIMIT {{limit}} {% if _offset > 0 %}OFFSET {{_offset}}{% endif %} @@ -28,8 +29,18 @@ LIMIT {{limit}} WHERE { INCLUDE %count_set - INCLUDE %query_set - ?subject rdfs:label|skos:prefLabel ?label . + INCLUDE %query_set +GRAPH ?graph_subject { + ?subject rdfs:label|skos:prefLabel ?label . } OPTIONAL {?type rdfs:label ?typeLabel . - FILTER(LANG(?typeLabel) = "en") -}} \ No newline at end of file + FILTER(LANG(?typeLabel) = "en")} + OPTIONAL { + GRAPH ?graph { + ?subject ((owl:sameAs|^owl:sameAs)*) ?sameAs . + FILTER(?subject != ?sameAs) + OPTIONAL {?sameAs rdfs:label ?objectLabel .} + + } + } + + } \ No newline at end of file diff --git a/semantic_kraus_api/sparql/get_person_v1.sparql b/semantic_kraus_api/sparql/get_person_v1.sparql new file mode 100644 index 0000000..d97d339 --- /dev/null +++ b/semantic_kraus_api/sparql/get_person_v1.sparql @@ -0,0 +1,29 @@ +PREFIX frbroo: +PREFIX rdf: +PREFIX skos: + +PREFIX rdfs: +PREFIX crm: +PREFIX owl: +SELECT ?person ?occupation ?time ?employer ?name ?graph ?type ?typeLabel WHERE { + GRAPH ?graph { + BIND(<{{id}}> as ?person) + ?person crm:P1_is_identified_by ?appellation. + ?appellation rdf:type crm:E33_E41_Linguistic_Appellation; + rdf:value ?name. + OPTIONAL { + ?appellation crm:P2_has_type ?type. + + FILTER(?type NOT IN(, ) + ) + } + + OPTIONAL{?person crm:P14i_performed ?occ. + ?occ rdf:type frbroo:F51_Pursuit; + rdfs:label ?occupation. + OPTIONAL { ?occ crm:P10_falls_within ?employer. } + OPTIONAL { ?occ (crm:P4_has_time-span/rdfs:label) ?time. } + } + } + OPTIONAL{?type rdfs:label ?typeLabel} +} \ No newline at end of file