diff --git a/.static_files b/.static_files index 3b171701..3ebfa187 100644 --- a/.static_files +++ b/.static_files @@ -16,6 +16,7 @@ scripts/script_utils/__init__.py scripts/script_utils/cli.py scripts/__init__.py +scripts/update_all.py scripts/license_checker.py scripts/get_package_name.py scripts/update_config_docs.py diff --git a/README.md b/README.md index 494680eb..8e41bbe7 100644 --- a/README.md +++ b/README.md @@ -62,13 +62,13 @@ We recommend using the provided Docker container. A pre-build version is available at [docker hub](https://hub.docker.com/repository/docker/ghga/metldata): ```bash -docker pull ghga/metldata:0.3.6 +docker pull ghga/metldata:0.3.7 ``` Or you can build the container yourself from the [`./Dockerfile`](./Dockerfile): ```bash # Execute in the repo's root dir: -docker build -t ghga/metldata:0.3.6 . +docker build -t ghga/metldata:0.3.7 . ``` For production-ready deployment, we recommend using Kubernetes, however, @@ -76,7 +76,7 @@ for simple use cases, you could execute the service using docker on a single server: ```bash # The entrypoint is preconfigured: -docker run -p 8080:8080 ghga/metldata:0.3.6 --help +docker run -p 8080:8080 ghga/metldata:0.3.7 --help ``` If you prefer not to use containers, you may install the service from source: diff --git a/metldata/__init__.py b/metldata/__init__.py index 40a3918f..92bc7948 100644 --- a/metldata/__init__.py +++ b/metldata/__init__.py @@ -15,4 +15,4 @@ """Short description of package""" # Please adapt to package -__version__ = "0.3.6" +__version__ = "0.3.7" diff --git a/metldata/model_utils/essentials.py b/metldata/model_utils/essentials.py index f6fbd235..90821702 100644 --- a/metldata/model_utils/essentials.py +++ b/metldata/model_utils/essentials.py @@ -22,7 +22,6 @@ import json from contextlib import contextmanager from copy import copy, deepcopy -from functools import lru_cache from pathlib import Path from tempfile import NamedTemporaryFile from typing import Any, Generator @@ -36,16 +35,11 @@ ROOT_CLASS = "Submission" -@lru_cache -def schema_view_from_model(model: MetadataModel) -> SchemaView: - """Get a schema view instance from the metadata model.""" - - return ExportableSchemaView(model) - - class MetadataModel(SchemaDefinition): """A dataclass for describing metadata models.""" + _schema_view = None + @classmethod def init_from_path(cls, model_path: Path) -> MetadataModel: """Initialize from a model file in yaml format.""" @@ -58,13 +52,19 @@ def init_from_path(cls, model_path: Path) -> MetadataModel: @property def schema_view(self) -> ExportableSchemaView: """Get a schema view instance from the metadata model.""" + schema_view = self._schema_view + if schema_view is None: + schema_view = ExportableSchemaView(self) + self._schema_view = schema_view + return schema_view - return schema_view_from_model(self) - - def copy(self) -> MetadataModel: - """Copy the model.""" - - return deepcopy(self) + def __deepcopy__(self, memo: Any): + """Return a deep copy of the model.""" + schema_view = self._schema_view + self._schema_view = None + copied_model = deepcopy(super()) + self._schema_view = schema_view + return copied_model def __eq__(self, other: object): """For comparisons.""" @@ -141,11 +141,6 @@ def temporary_yaml_path(self) -> Generator[Path, None, None]: file.flush() yield Path(file.name) - def __hash__(self): - """Return a hash of the model.""" - - return hash(self.as_json()) - class ExportableSchemaView(SchemaView): """Extend the SchemaView by adding a method for exporting a MetadataModel.""" diff --git a/metldata/transform/handling.py b/metldata/transform/handling.py index 499f66b1..960b4e2e 100644 --- a/metldata/transform/handling.py +++ b/metldata/transform/handling.py @@ -32,7 +32,7 @@ ) -class WorkflowConfigMissmatchError(RuntimeError): +class WorkflowConfigMismatchError(RuntimeError): """Raised when the provided workflow config does not match the config class of the workflow definition. """ @@ -140,11 +140,11 @@ def check_workflow_config( definition. Raises: - WorkflowConfigMissmatchError: + WorkflowConfigMismatchError: """ if workflow_config.schema_json() == workflow_definition.schema_json(): - raise WorkflowConfigMissmatchError( + raise WorkflowConfigMismatchError( workflow_definition=workflow_definition, workflow_config=workflow_config ) diff --git a/scripts/update_all.py b/scripts/update_all.py new file mode 100755 index 00000000..78854dfe --- /dev/null +++ b/scripts/update_all.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +# Copyright 2021 - 2023 Universität Tübingen, DKFZ, EMBL, and Universität zu Köln +# for the German Human Genome-Phenome Archive (GHGA) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Run all update scripts that are present in the repository in the correct order""" + +try: + from scripts.update_template_files import main as update_template +except ImportError: + pass +else: + print("Pulling in updates from template repository") + update_template() + +try: + from scripts.update_config_docs import main as update_config +except ImportError: + pass +else: + print("Updating config docs") + update_config() + +try: + from scripts.update_openapi_docs import main as update_openapi +except ImportError: + pass +else: + print("Updating OpenAPI docs") + update_openapi() + +try: + from scripts.update_readme import main as update_readme +except ImportError: + pass +else: + print("Updating README") + update_readme() diff --git a/setup.cfg b/setup.cfg index 0d6da741..37bfd18b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,10 +35,11 @@ zip_safe = False include_package_data = True packages = find: install_requires = - hexkit[mongodb]==0.10.0 - ghga-service-commons[api,auth]==0.4.2 + hexkit[mongodb]==0.10.2 + ghga-service-commons[api,auth]==0.4.3 typer==0.7.0 - linkml-runtime==1.4.2 + linkml==1.5.6 + linkml-runtime==1.5.5 linkml-validator==0.4.5 python_requires = >= 3.9 diff --git a/tests/artifact_rest/test_api_factory.py b/tests/artifact_rest/test_api_factory.py index d4255f92..1f6cdb47 100644 --- a/tests/artifact_rest/test_api_factory.py +++ b/tests/artifact_rest/test_api_factory.py @@ -22,13 +22,15 @@ from fastapi import FastAPI from ghga_service_commons.api.testing import AsyncTestClient from hexkit.protocols.dao import DaoFactoryProtocol -from hexkit.providers.mongodb.testutils import mongodb_fixture # noqa: F401 -from hexkit.providers.mongodb.testutils import MongoDbFixture from metldata.artifacts_rest.api_factory import rest_api_factory from metldata.artifacts_rest.artifact_info import ArtifactInfo from tests.artifact_rest.test_load_artifacts import load_example_artifact_resources from tests.fixtures.artifact_info import EXAMPLE_ARTIFACT_INFOS, MINIMAL_ARTIFACT_INFO +from tests.fixtures.mongodb import ( # noqa: F401; pylint: disable=unused-import + MongoDbFixture, + mongodb_fixture, +) @pytest.mark.asyncio @@ -48,7 +50,9 @@ async def get_example_app_client( @pytest.mark.asyncio -async def test_artifacts_info_endpoint(mongodb_fixture: MongoDbFixture): # noqa: F811 +async def test_artifacts_info_endpoint( + mongodb_fixture: MongoDbFixture, # noqa: F811 +): """Test happy path of using the artifacts info endpoint.""" expected_infos = EXAMPLE_ARTIFACT_INFOS @@ -99,7 +103,8 @@ async def test_get_artifact_resource_endpoint( class_name = "File" resource_id = "test_sample_01_R1" async with await get_example_app_client( - dao_factory=mongodb_fixture.dao_factory, artifact_infos=[MINIMAL_ARTIFACT_INFO] + dao_factory=mongodb_fixture.dao_factory, + artifact_infos=[MINIMAL_ARTIFACT_INFO], ) as client: response = await client.get( f"/artifacts/{artifact_name}/classes/{class_name}/resources/{resource_id}" diff --git a/tests/artifact_rest/test_load_artifacts.py b/tests/artifact_rest/test_load_artifacts.py index acafabc4..da4339f6 100644 --- a/tests/artifact_rest/test_load_artifacts.py +++ b/tests/artifact_rest/test_load_artifacts.py @@ -18,14 +18,16 @@ import pytest from hexkit.protocols.dao import DaoFactoryProtocol -from hexkit.providers.mongodb.testutils import mongodb_fixture # noqa: F401 -from hexkit.providers.mongodb.testutils import MongoDbFixture from metldata.artifacts_rest.artifact_dao import ArtifactDaoCollection from metldata.artifacts_rest.load_resources import load_artifact_resources from metldata.artifacts_rest.models import ArtifactResource from tests.fixtures.artifact_info import MINIMAL_ARTIFACT_INFO from tests.fixtures.metadata import VALID_MINIMAL_METADATA_EXAMPLE +from tests.fixtures.mongodb import ( # noqa: F401; pylint: disable=unused-import + MongoDbFixture, + mongodb_fixture, +) async def load_example_artifact_resources( @@ -51,7 +53,9 @@ async def load_example_artifact_resources( @pytest.mark.asyncio -async def test_load_artifact_resources(mongodb_fixture: MongoDbFixture): # noqa: F811 +async def test_load_artifact_resources( + mongodb_fixture: MongoDbFixture, # noqa: F811 +): """Test happy path of using load_artifact_resources function.""" dao_collection = await load_example_artifact_resources( diff --git a/tests/artifact_rest/test_query_resources.py b/tests/artifact_rest/test_query_resources.py index 13c9f3c3..801f3472 100644 --- a/tests/artifact_rest/test_query_resources.py +++ b/tests/artifact_rest/test_query_resources.py @@ -17,16 +17,20 @@ """Test the query_resource module.""" import pytest -from hexkit.providers.mongodb.testutils import mongodb_fixture # noqa: F401 -from hexkit.providers.mongodb.testutils import MongoDbFixture from metldata.artifacts_rest.query_resources import query_artifact_resource from tests.artifact_rest.test_load_artifacts import load_example_artifact_resources from tests.fixtures.artifact_info import MINIMAL_ARTIFACT_INFO +from tests.fixtures.mongodb import ( # noqa: F401; pylint: disable=unused-import + MongoDbFixture, + mongodb_fixture, +) @pytest.mark.asyncio -async def test_query_artifact_resource(mongodb_fixture: MongoDbFixture): # noqa: F811 +async def test_query_artifact_resource( + mongodb_fixture: MongoDbFixture, # noqa: F811 +): """Test happy path of using the query_artifact_resource function.""" # load example resources and prepare client: diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..3cf1f401 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,24 @@ +# Copyright 2021 - 2023 Universität Tübingen, DKFZ, EMBL, and Universität zu Köln +# for the German Human Genome-Phenome Archive (GHGA) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""""Shared fixtures""" + +from hexkit.providers.mongodb.testutils import get_mongodb_fixture +from hexkit.providers.testing.utils import get_event_loop + +event_loop = get_event_loop(scope="session") + +mongodb_session = get_mongodb_fixture(scope="session") diff --git a/tests/fixtures/event_handling.py b/tests/fixtures/event_handling.py index e08066c3..a6500c8f 100644 --- a/tests/fixtures/event_handling.py +++ b/tests/fixtures/event_handling.py @@ -29,7 +29,7 @@ ) -class EventExpectationMissmatch(RuntimeError): +class EventExpectationMismatch(RuntimeError): """Raised when expected events where not found.""" def __init__(self, expected_events: set[str], consumed_events: set[str]): @@ -49,7 +49,7 @@ def expect_events(self, expected_events: list[Event]) -> None: """Check if the events expected to be published can be consumed. Raises: - EventExpectationMissmatch: If the expected events are not consumed. + EventExpectationMismatch: If the expected events are not consumed. """ topics = sorted({event.topic for event in expected_events}) @@ -66,7 +66,7 @@ def expect_events(self, expected_events: list[Event]) -> None: expected_event_jsons = {event.json() for event in expected_events} if expected_event_jsons != observed_event_jsons: - raise EventExpectationMissmatch( + raise EventExpectationMismatch( expected_events=observed_event_jsons, consumed_events=expected_event_jsons, ) diff --git a/tests/fixtures/mongodb.py b/tests/fixtures/mongodb.py new file mode 100644 index 00000000..8f3221f8 --- /dev/null +++ b/tests/fixtures/mongodb.py @@ -0,0 +1,29 @@ +# Copyright 2021 - 2023 Universität Tübingen, DKFZ, EMBL, and Universität zu Köln +# for the German Human Genome-Phenome Archive (GHGA) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Fixture for using the MongoDB.""" + +from hexkit.providers.mongodb.testutils import MongoDbFixture +from pytest import fixture + + +@fixture +def mongodb_fixture( # pylint: disable=redefined-outer-name + mongodb_session: MongoDbFixture, +) -> MongoDbFixture: + """Fixture that gets an empty MongoDB.""" + mongodb_session.empty_collections() + return mongodb_session diff --git a/tests/load/test_main.py b/tests/load/test_main.py index 1e31fac6..bee7e332 100644 --- a/tests/load/test_main.py +++ b/tests/load/test_main.py @@ -19,8 +19,6 @@ import pytest from ghga_service_commons.api.testing import AsyncTestClient from hexkit.protocols.dao import ResourceNotFoundError -from hexkit.providers.mongodb.testutils import mongodb_fixture # noqa: F401 -from hexkit.providers.mongodb.testutils import MongoDbFixture from metldata.artifacts_rest.artifact_dao import ArtifactDaoCollection from metldata.artifacts_rest.models import ArtifactInfo @@ -28,6 +26,10 @@ from metldata.load.config import ArtifactLoaderAPIConfig from metldata.load.main import get_app from tests.fixtures.artifact_info import EXAMPLE_ARTIFACT_INFOS +from tests.fixtures.mongodb import ( # noqa: F401; pylint: disable=unused-import + MongoDbFixture, + mongodb_fixture, +) from tests.fixtures.workflows import EXAMPLE_ARTIFACTS @@ -81,7 +83,8 @@ async def test_load_artifacts_endpoint_happy( } dao_collection = await ArtifactDaoCollection.construct( - dao_factory=mongodb_fixture.dao_factory, artifact_infos=EXAMPLE_ARTIFACT_INFOS + dao_factory=mongodb_fixture.dao_factory, + artifact_infos=EXAMPLE_ARTIFACT_INFOS, ) dao = await dao_collection.get_dao( artifact_name=expected_artifact_name, class_name=expected_resource_class