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

feature/model update v3 #187

Merged
merged 3 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changes

- upgrade mex-common and mex-model dependencies to metadata model v3
- apply additional linters in prep for `all` ruff linters

### Deprecated

### Removed
Expand Down
1 change: 1 addition & 0 deletions mex/backend/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NUMBER_OF_RULE_TYPES = 3 # Additive, Subtractive and Preventive
2 changes: 1 addition & 1 deletion mex/backend/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def handle_uncaught_exception(request: Request, exc: Exception) -> Response:
content=ErrorResponse(
message=str(exc),
debug=DebuggingInfo(
errors=[dict(type=type(exc).__name__)],
errors=[{"type": type(exc).__name__}],
scope=DebuggingScope.model_validate(request.scope),
),
).model_dump_json(),
Expand Down
5 changes: 3 additions & 2 deletions mex/backend/graph/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ def _check_connectivity_and_authentication(self) -> Result:
query_builder = QueryBuilder.get()
result = self.commit(query_builder.fetch_database_status())
if (status := result["currentStatus"]) != "online":
raise MExError(f"Database is {status}.") from None
msg = f"Database is {status}."
raise MExError(msg) from None
return result

def _seed_constraints(self) -> list[Result]:
Expand Down Expand Up @@ -494,7 +495,7 @@ def create_rule_set(
self._merge_edges(
rule,
stable_target_id,
extra_refs=dict(stableTargetId=stable_target_id),
extra_refs={"stableTargetId": stable_target_id},
)

def ingest(self, models: list[AnyExtractedModel]) -> list[Identifier]:
Expand Down
3 changes: 2 additions & 1 deletion mex/backend/graph/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from neo4j import Result as Neo4jResult

from mex.backend.graph.exceptions import MultipleResultsFoundError, NoResultFoundError
from mex.backend.logging import LOGGING_LINE_LENGTH


class Result:
Expand All @@ -31,7 +32,7 @@ def __iter__(self) -> Iterator[dict[str, Any]]:
def __repr__(self) -> str:
"""Return a human-readable representation of this result object."""
representation = f"Result({self.all()!r})"
if len(representation) > 90:
if len(representation) > LOGGING_LINE_LENGTH:
representation = f"{representation[:40]}... ...{representation[-40:]}"
return representation

Expand Down
2 changes: 1 addition & 1 deletion mex/backend/graph/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@ def __getattr__(self, name: str) -> Callable[..., str]:

def close(self) -> None:
"""Clean up the connector."""
pass # no clean-up needed
# no clean-up needed
2 changes: 1 addition & 1 deletion mex/backend/graph/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def expand_references_in_search_result(item: dict[str, Any]) -> None:
`_SearchResultReference`. Before parsing them into pydantic, we need to inline
the references back into the `item` dictionary.
"""
# XXX if we can use `apoc`, we might do this step directly in the cypher query
# TODO(ND): try to re-write directly in the cypher query, if we can use `apoc`
for ref in cast(list[_SearchResultReference], item.pop("_refs")):
target_list = item.setdefault(ref["label"], [None])
length_needed = 1 + ref["position"] - len(target_list)
Expand Down
1 change: 1 addition & 0 deletions mex/backend/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from mex.common.logging import logger

LOGGING_LINE_LENGTH = 90
UVICORN_LOGGING_CONFIG = DEFAULT_UVICORN_LOGGING_CONFIG.copy()
UVICORN_LOGGING_CONFIG["loggers"][logger.name] = {
"handlers": ["default"],
Expand Down
12 changes: 6 additions & 6 deletions mex/backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def create_openapi_schema() -> dict[str, Any]:
summary=app.summary,
description=app.description,
routes=app.routes,
servers=[dict(url=settings.backend_api_url)],
servers=[{"url": settings.backend_api_url}],
)
for identifier in chain(EXTRACTED_IDENTIFIER_CLASSES, MERGED_IDENTIFIER_CLASSES):
name = identifier.__name__
Expand Down Expand Up @@ -78,11 +78,11 @@ async def lifespan(_: FastAPI) -> AsyncIterator[None]:
"The MEx API includes endpoints for multiple use-cases, "
"e.g. for extractor pipelines, the MEx editor or inter-departmental access."
),
contact=dict(
name="RKI MEx Team",
email="[email protected]",
url="https://github.com/robert-koch-institut/mex-backend",
),
contact={
"name": "RKI MEx Team",
"email": "[email protected]",
"url": "https://github.com/robert-koch-institut/mex-backend",
},
lifespan=lifespan,
version="v0",
)
Expand Down
11 changes: 6 additions & 5 deletions mex/backend/merged/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from pydantic import Field, TypeAdapter, ValidationError

from mex.backend.constants import NUMBER_OF_RULE_TYPES
from mex.backend.fields import MERGEABLE_FIELDS_BY_CLASS_NAME
from mex.backend.graph.connector import GraphConnector
from mex.backend.graph.exceptions import InconsistentGraphError
Expand Down Expand Up @@ -109,7 +110,8 @@ def create_merged_item(
elif extracted_items:
entity_type = ensure_prefix(extracted_items[0].stemType, "Merged")
else:
raise MExError("One of rule_set or extracted_items is required.")
msg = "One of rule_set or extracted_items is required."
raise MExError(msg)
fields = MERGEABLE_FIELDS_BY_CLASS_NAME[entity_type]
cls = MERGED_MODEL_CLASSES_BY_NAME[entity_type]

Expand Down Expand Up @@ -178,14 +180,13 @@ def search_merged_items_in_graph(
for component in item["components"]
if component["entityType"] in RULE_MODEL_CLASSES_BY_NAME
]
if len(rules_raw) == 3:
if len(rules_raw) == NUMBER_OF_RULE_TYPES:
rule_set_response = transform_raw_rules_to_rule_set_response(rules_raw)
elif len(rules_raw) == 0:
rule_set_response = None
else:
raise MExError(
f"Unexpected number of rules found in graph: {len(rules_raw)}"
)
msg = f"Unexpected number of rules found in graph: {len(rules_raw)}"
raise MExError(msg)

items.append(
create_merged_item(
Expand Down
15 changes: 10 additions & 5 deletions mex/backend/rules/helpers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from collections.abc import Mapping
from typing import Any, Final

from mex.backend.constants import NUMBER_OF_RULE_TYPES
from mex.backend.graph.connector import GraphConnector
from mex.common.exceptions import MExError
from mex.common.models import (
Expand Down Expand Up @@ -31,8 +32,9 @@ def transform_raw_rules_to_rule_set_response(
response: dict[str, Any] = {}
model: type[AnyRuleModel] | None

if len(raw_rules) != 3:
raise MExError("inconsistent rule item count")
if len(raw_rules) != NUMBER_OF_RULE_TYPES:
msg = "inconsistent rule item count"
raise MExError(msg)

for rule in raw_rules:
for field_name, model_class_lookup in MODEL_CLASS_LOOKUP_BY_FIELD_NAME.items():
Expand All @@ -42,9 +44,11 @@ def transform_raw_rules_to_rule_set_response(
stable_target_ids.extend(rule.pop("stableTargetId", []))

if len(set(stem_types)) != 1:
raise MExError("inconsistent rule item stem types")
msg = "inconsistent rule item stem types"
raise MExError(msg)
if len(set(stable_target_ids)) != 1:
raise MExError("inconsistent rule item stableTargetIds")
msg = "inconsistent rule item stableTargetIds"
raise MExError(msg)

response["stableTargetId"] = stable_target_ids[0]
response_class_name = ensure_postfix(stem_types[0], "RuleSetResponse")
Expand Down Expand Up @@ -102,5 +106,6 @@ def update_and_get_rule_set(
stable_target_id,
[rule_set.stemType],
):
raise MExError("no merged item found for given identifier and type")
msg = "no merged item found for given identifier and type"
raise MExError(msg)
return create_and_get_rule_set(rule_set, stable_target_id)
4 changes: 2 additions & 2 deletions mex/backend/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ def __new__(
cls: type["DynamicStrEnum"], name: str, bases: tuple[type], dct: _EnumDict
) -> "DynamicStrEnum":
"""Create a new enum by adding an entry for each name in the source."""
for name in dct.pop("__names__"):
dct[dromedary_to_snake(name).upper()] = name
for value in dct.pop("__names__"):
dct[dromedary_to_snake(value).upper()] = value
return super().__new__(cls, name, bases, dct)


Expand Down
5 changes: 2 additions & 3 deletions mex/backend/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import contextlib
from collections.abc import Callable
from typing import ParamSpec, TypeVar

Expand All @@ -21,10 +22,8 @@ def prune_list_in_dict(dict_: dict[str, list[T]], key: str, item: list[T] | T) -
if not isinstance(item, list):
item = [item]
for removable in item:
try:
with contextlib.suppress(ValueError):
list_.remove(removable)
except ValueError:
pass


def reraising(
Expand Down
26 changes: 13 additions & 13 deletions pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ license = { file = "LICENSE" }
urls = { Repository = "https://github.com/robert-koch-institut/mex-backend" }
requires-python = ">=3.11,<3.13"
dependencies = [
"fastapi>=0.114.2,<1",
"fastapi>=0.115.4,<1",
"httpx>=0.27.2,<1",
"jinja2>=3.1.4,<4",
"mex-common @ git+https://github.com/robert-koch-institut/mex-common.git@0.38.0",
"mex-common @ git+https://github.com/robert-koch-institut/mex-common.git@0.40.0",
"neo4j>=5.24.0,<6",
"pydantic>=2.9.1,<3",
"starlette>=0.38.5,<1",
"starlette>=0.41.2,<1",
"uvicorn[standard]>=0.30.6,<1",
]
optional-dependencies.dev = [
Expand Down
5 changes: 3 additions & 2 deletions tests/auxiliary/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import requests
from pytest import MonkeyPatch
from requests import Response
from starlette import status

from mex.common.wikidata.connector import (
WikidataAPIConnector,
Expand All @@ -15,9 +16,9 @@
TEST_DATA_DIR = Path(__file__).parent / "test_data"


@pytest.fixture()
@pytest.fixture
def mocked_wikidata(monkeypatch: MonkeyPatch) -> None:
response_query = Mock(spec=Response, status_code=200)
response_query = Mock(spec=Response, status_code=status.HTTP_200_OK)

session = MagicMock(spec=requests.Session)
session.get = MagicMock(side_effect=[response_query])
Expand Down
Loading