From 5350a36be5b652be015f4bd776f237dfc50913b5 Mon Sep 17 00:00:00 2001 From: Francois Date: Thu, 4 Apr 2024 09:26:36 +0100 Subject: [PATCH 1/7] added json-ld contexts to api response --- api/.env_template | 10 ++-- api/Readme.md | 1 + api/pyproject.toml | 1 + api/src/api/public.py | 2 +- .../models/jsonld/1.0/BiosampleContext.jsonld | 9 ++++ .../jsonld/1.0/CollectionContext.jsonld | 14 +++++ .../jsonld/1.0/ImageAcquisitionContext.jsonld | 13 +++++ api/src/models/jsonld/1.0/ImageContext.jsonld | 21 ++++++++ .../models/jsonld/1.0/SpecimenContext.jsonld | 13 +++++ api/src/models/jsonld/1.0/StudyContext.jsonld | 13 +++++ .../1.0/StudyFileReferenceContext.jsonld | 14 +++++ api/src/models/persistence.py | 7 +++ api/src/tests/test_annotations.py | 11 +++- ...st_biosample_specimen_image_acquisition.py | 14 ++++- api/src/tests/test_file_reference.py | 13 ++++- api/src/tests/test_image.py | 19 +++++-- api/src/tests/test_misc.py | 10 +++- api/src/tests/test_rdf.py | 51 +++++++++++++++++++ api/src/tests/test_study.py | 19 +++++-- api/src/tests/util.py | 16 ++++-- 20 files changed, 250 insertions(+), 21 deletions(-) create mode 100644 api/src/models/jsonld/1.0/BiosampleContext.jsonld create mode 100644 api/src/models/jsonld/1.0/CollectionContext.jsonld create mode 100644 api/src/models/jsonld/1.0/ImageAcquisitionContext.jsonld create mode 100644 api/src/models/jsonld/1.0/ImageContext.jsonld create mode 100644 api/src/models/jsonld/1.0/SpecimenContext.jsonld create mode 100644 api/src/models/jsonld/1.0/StudyContext.jsonld create mode 100644 api/src/models/jsonld/1.0/StudyFileReferenceContext.jsonld create mode 100644 api/src/tests/test_rdf.py diff --git a/api/.env_template b/api/.env_template index 956d2f44..e925e2b3 100644 --- a/api/.env_template +++ b/api/.env_template @@ -1,7 +1,9 @@ -MONGO_CONNSTRING="mongodb://user_name:user_password@mongo_host:27018/" +# note user_name and user_password should be consitent in the next 3 rows +MONGO_CONNSTRING="mongodb://user_name:user_password@mongo_host:27017/" MONGO_INITDB_ROOT_USERNAME=should_match_connstring MONGO_INITDB_ROOT_PASSWORD=should_match_connstring DB_NAME=bia_integrator -#openssl rand -hex 32 -JWT_SECRET_KEY=long_key -USER_CREATE_SECRET_TOKEN=some_long_secret_token \ No newline at end of file +# run: 'openssl rand -hex 32' to generate a secret key for testing +JWT_SECRET_KEY="long_key" +# token needs to end in == +USER_CREATE_SECRET_TOKEN="some_long_secret_token" \ No newline at end of file diff --git a/api/Readme.md b/api/Readme.md index a95657a0..9ca9732e 100644 --- a/api/Readme.md +++ b/api/Readme.md @@ -13,6 +13,7 @@ In a future version of Poetry this warning will become an error! ``` This can be ignored. + ## Running the api ```sh diff --git a/api/pyproject.toml b/api/pyproject.toml index 020439c4..07488a61 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -29,6 +29,7 @@ pyyaml = "^6.0.1" pytest = "^7" pytest-asyncio = "^0.21.1" black = "^23.12.1" +rdflib = "^7.0" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/api/src/api/public.py b/api/src/api/public.py index 1ab29196..edd186a4 100644 --- a/api/src/api/public.py +++ b/api/src/api/public.py @@ -65,7 +65,7 @@ async def get_study( ) -> db_models.BIAStudy: study = await db.find_study_by_uuid(study_uuid) annotator.annotate_if_needed(study) - + return study diff --git a/api/src/models/jsonld/1.0/BiosampleContext.jsonld b/api/src/models/jsonld/1.0/BiosampleContext.jsonld new file mode 100644 index 00000000..16656607 --- /dev/null +++ b/api/src/models/jsonld/1.0/BiosampleContext.jsonld @@ -0,0 +1,9 @@ +{ + "@vocab": "https://www.ebi.ac.uk/bioimage-archive/undefined.schema/", + "uuid": "@id", + "title": "http://purl.org/dc/terms/title", + "description": "http://purl.org/dc/terms/description", + "@base": "https://www.ebi.ac.uk/bioimage-archive/api/v1/biosamples/", + "organism_scientific_name": "https://www.wikidata.org/wiki/Q10753560", + "organism_common_name": "https://www.wikidata.org/wiki/Q502895" +} diff --git a/api/src/models/jsonld/1.0/CollectionContext.jsonld b/api/src/models/jsonld/1.0/CollectionContext.jsonld new file mode 100644 index 00000000..1dda914b --- /dev/null +++ b/api/src/models/jsonld/1.0/CollectionContext.jsonld @@ -0,0 +1,14 @@ +{ + "@vocab": "https://www.ebi.ac.uk/bioimage-archive/undefined.schema/", + "uuid": "@id", + "title": "http://purl.org/dc/terms/title", + "description": "http://purl.org/dc/terms/description", + "@base": "https://www.ebi.ac.uk/bioimage-archive/api/v1/collections/", + "study_uuids": { + "@id": "http://purl.org/dc/terms/hasPart", + "@context": { + "@base": "https://www.ebi.ac.uk/bioimage-archive/api/v1/studies/" + }, + "@type": "@id" + } +} diff --git a/api/src/models/jsonld/1.0/ImageAcquisitionContext.jsonld b/api/src/models/jsonld/1.0/ImageAcquisitionContext.jsonld new file mode 100644 index 00000000..21dbb29b --- /dev/null +++ b/api/src/models/jsonld/1.0/ImageAcquisitionContext.jsonld @@ -0,0 +1,13 @@ +{ + "@vocab": "https://www.ebi.ac.uk/bioimage-archive/undefined.schema/", + "uuid": "@id", + "title": "http://purl.org/dc/terms/title", + "description": "http://purl.org/dc/terms/description", + "@base": "https://www.ebi.ac.uk/bioimage-archive/api/v1/image_acquisitions/", + "specimen_uuid": { + "@context": { + "@base": "https://www.ebi.ac.uk/bioimage-archive/api/v1/specimens/" + }, + "@type": "@id" + } +} diff --git a/api/src/models/jsonld/1.0/ImageContext.jsonld b/api/src/models/jsonld/1.0/ImageContext.jsonld new file mode 100644 index 00000000..e04ef3bc --- /dev/null +++ b/api/src/models/jsonld/1.0/ImageContext.jsonld @@ -0,0 +1,21 @@ +{ + "@vocab": "https://www.ebi.ac.uk/bioimage-archive/undefined.schema/", + "uuid": "@id", + "title": "http://purl.org/dc/terms/title", + "description": "http://purl.org/dc/terms/description", + "@base": "https://www.ebi.ac.uk/bioimage-archive/api/v1/images/", + "study_uuid": { + "@id": "http://purl.org/dc/terms/isPartOf", + "@context": { + "@base": "https://www.ebi.ac.uk/bioimage-archive/api/v1/biosamples/" + }, + "@type": "@id" + }, + "dimensions": "https://schema.org/size", + "image_acquisition_methods_uuid": { + "@context": { + "@base": "https://www.ebi.ac.uk/bioimage-archive/api/v1/image_acquisitions/" + }, + "@type": "@id" + } +} diff --git a/api/src/models/jsonld/1.0/SpecimenContext.jsonld b/api/src/models/jsonld/1.0/SpecimenContext.jsonld new file mode 100644 index 00000000..35eebcb6 --- /dev/null +++ b/api/src/models/jsonld/1.0/SpecimenContext.jsonld @@ -0,0 +1,13 @@ +{ + "@vocab": "https://www.ebi.ac.uk/bioimage-archive/undefined.schema/", + "uuid": "@id", + "title": "http://purl.org/dc/terms/title", + "description": "http://purl.org/dc/terms/description", + "@base": "https://www.ebi.ac.uk/bioimage-archive/api/v1/specimens/", + "biosample_uuid": { + "@context": { + "@base": "https://www.ebi.ac.uk/bioimage-archive/api/v1/biosamples/" + }, + "@type": "@id" + } +} diff --git a/api/src/models/jsonld/1.0/StudyContext.jsonld b/api/src/models/jsonld/1.0/StudyContext.jsonld new file mode 100644 index 00000000..0278667c --- /dev/null +++ b/api/src/models/jsonld/1.0/StudyContext.jsonld @@ -0,0 +1,13 @@ +{ + "@vocab": "https://www.ebi.ac.uk/bioimage-archive/undefined.schema/", + "uuid": "@id", + "title": "http://purl.org/dc/terms/title", + "description": "http://purl.org/dc/terms/description", + "@base": "https://www.ebi.ac.uk/bioimage-archive/api/v1/studies/", + "authors": { + "@id": "http://purl.org/dc/terms/creator" + }, + "organism": "https://schema.org/about", + "release_date": "https://schema.org/datePublished", + "tags": "https://schema.org/keywords" +} \ No newline at end of file diff --git a/api/src/models/jsonld/1.0/StudyFileReferenceContext.jsonld b/api/src/models/jsonld/1.0/StudyFileReferenceContext.jsonld new file mode 100644 index 00000000..c98fa947 --- /dev/null +++ b/api/src/models/jsonld/1.0/StudyFileReferenceContext.jsonld @@ -0,0 +1,14 @@ +{ + "@vocab": "https://www.ebi.ac.uk/bioimage-archive/undefined.schema/", + "uuid": "@id", + "title": "http://purl.org/dc/terms/title", + "description": "http://purl.org/dc/terms/description", + "@base": "https://www.ebi.ac.uk/bioimage-archive/api/v1/file_references/", + "study_uuid": { + "@id": "http://purl.org/dc/terms/isPartOf", + "@context": { + "@base": "https://www.ebi.ac.uk/bioimage-archive/api/v1/studies" + }, + "@type": "@id" + } +} diff --git a/api/src/models/persistence.py b/api/src/models/persistence.py index 47e65a06..fbe3206e 100644 --- a/api/src/models/persistence.py +++ b/api/src/models/persistence.py @@ -148,6 +148,8 @@ class BIAStudy(BIABaseModel, DocumentMixin, AnnotatedMixin[StudyAnnotation]): file_references_count: int = Field(default=0) images_count: int = Field(default=0) + context: str = Field(alias='@context', default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/StudyContext.json") + model_config = ConfigDict(model_version_latest=1) @@ -217,6 +219,7 @@ class Biosample(BIABaseModel, DocumentMixin): intrinsic_variables: List[str] = Field( description="Intrinsic (e.g. genetic) alteration.", default=[] ) + context: str = Field(alias='@context', default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/SpecimenContext.json") model_config = ConfigDict(model_version_latest=1) @@ -229,6 +232,7 @@ class Specimen(BIABaseModel, DocumentMixin): ) # is this a ST-only concern, or does it make sense for it to be in the models? sample_preparation_protocol: str = Field() growth_protocol: str = Field() + context: str = Field(alias='@context', default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/SpecimenContext.json") model_config = ConfigDict(model_version_latest=1) @@ -248,6 +252,7 @@ class ImageAcquisition(BIABaseModel, DocumentMixin): imaging_method: str = ( Field() ) # make this an Enum / restrict some other way? Distinguishing between "somewhat close to a controlled vocabulary" vs "completely free text" might be useful + context: str = Field(alias='@context', default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/ImageAcquisitionContext.json") model_config = ConfigDict(model_version_latest=1) @@ -278,6 +283,7 @@ class BIAImage(BIABaseModel, DocumentMixin, AnnotatedMixin[ImageAnnotation]): description="Context in which the image was acquired. This list often has one item, but it can occasionally have more (e.g. for multimodal imaging)", default=[], ) + context: str = Field(alias='@context', default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/ImageContext.json") model_config = ConfigDict(model_version_latest=2) @@ -291,6 +297,7 @@ class BIACollection(BIABaseModel, DocumentMixin, AnnotatedMixin[CollectionAnnota subtitle: str = Field() description: Optional[str] = Field(default=None) study_uuids: List[str] = Field(default=[]) + context: str = Field(alias='@context', default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/CollectionContext.json") model_config = ConfigDict(model_version_latest=1) diff --git a/api/src/tests/test_annotations.py b/api/src/tests/test_annotations.py index ca3ea53a..0311e926 100644 --- a/api/src/tests/test_annotations.py +++ b/api/src/tests/test_annotations.py @@ -7,7 +7,16 @@ import pytest from fastapi.testclient import TestClient -from .util import * +from .util import ( + get_template_image, + get_template_study, + get_template_file_reference, + get_template_collection, + DBTestMixin, + api_client, + uuid, + existing_study + ) from ..models.repository import Repository import uuid as uuid_lib diff --git a/api/src/tests/test_biosample_specimen_image_acquisition.py b/api/src/tests/test_biosample_specimen_image_acquisition.py index 03bf819e..34ce6f35 100644 --- a/api/src/tests/test_biosample_specimen_image_acquisition.py +++ b/api/src/tests/test_biosample_specimen_image_acquisition.py @@ -7,8 +7,15 @@ """ from fastapi.testclient import TestClient -from .util import * - +from .util import ( + api_client, + uuid, + existing_biosample, + existing_specimen, + existing_image_acquisition, + existing_study, + existing_image + ) def test_biosample_create_retrieve_update(api_client: TestClient, uuid: str): # Note that this actually doesn't depend on any study @@ -24,6 +31,7 @@ def test_biosample_create_retrieve_update(api_client: TestClient, uuid: str): "experimental_variables": ["placeholder_experimental_variable"], "extrinsic_variables": ["placeholder_extrinsic_variable"], "intrinsic_variables": ["placeholder_intrinsic_variable"], + "@context": "placeholder_context" } rsp = api_client.post(f"private/biosamples", json=biosample) assert rsp.status_code == 201, rsp.json() @@ -54,6 +62,7 @@ def test_specimen_create_retrieve_update( "title": "placeholder_title", "sample_preparation_protocol": "placeholder_sample_preparation_protocol", "growth_protocol": "placeholder_growth_protocol", + "@context": "placeholder_context" } rsp = api_client.post(f"private/specimens", json=specimen) assert rsp.status_code == 201, rsp.json() @@ -85,6 +94,7 @@ def test_image_acquisition_create_retrieve_update( "imaging_instrument": "placeholder_imaging_instrument", "image_acquisition_parameters": "placeholder_image_acquisition_parameters", "imaging_method": "placeholder_imaging_method", + "@context": "placeholder_context" } rsp = api_client.post(f"private/image_acquisitions", json=image_acquisition) assert rsp.status_code == 201, rsp.json() diff --git a/api/src/tests/test_file_reference.py b/api/src/tests/test_file_reference.py index feb51558..41050a2c 100644 --- a/api/src/tests/test_file_reference.py +++ b/api/src/tests/test_file_reference.py @@ -1,5 +1,16 @@ from fastapi.testclient import TestClient -from .util import * +import pytest +from typing import List +from .util import ( + get_uuid, + get_template_file_reference, + make_file_references, + unorderd_lists_equality, + assert_bulk_response_items_correct, + api_client, + existing_study, + existing_file_reference, + uuid) import itertools from uuid import UUID diff --git a/api/src/tests/test_image.py b/api/src/tests/test_image.py index 0a8b1299..8f02f286 100644 --- a/api/src/tests/test_image.py +++ b/api/src/tests/test_image.py @@ -1,5 +1,18 @@ from fastapi.testclient import TestClient -from .util import * +import pytest +from typing import List +from .util import ( + get_uuid, + make_file_references, + make_images, + make_study, + get_study, + get_template_image, + unorderd_lists_equality, + assert_bulk_response_items_correct, + api_client, + existing_study, + existing_image) import itertools from uuid import UUID import os @@ -17,7 +30,7 @@ def test_create_images(api_client: TestClient, existing_study: dict): "original_relpath": f"/home/test/{uuid}", "attributes": { "image_uuid": uuid, - }, + } } for uuid in uuids ] @@ -41,7 +54,7 @@ def test_create_images_multiple_errors(api_client: TestClient, existing_study: d "original_relpath": f"/home/test/{uuid}", "attributes": { "image_uuid": uuid, - }, + } } for uuid in uuids ] diff --git a/api/src/tests/test_misc.py b/api/src/tests/test_misc.py index eac4fc70..b4a1a640 100644 --- a/api/src/tests/test_misc.py +++ b/api/src/tests/test_misc.py @@ -1,5 +1,13 @@ from fastapi.testclient import TestClient -from .util import * +from typing import List +from .util import ( + make_images, + make_study, + api_client, + uuid, + existing_study, + existing_collection, + existing_image) def test_create_collection(api_client: TestClient, uuid: str): diff --git a/api/src/tests/test_rdf.py b/api/src/tests/test_rdf.py new file mode 100644 index 00000000..6c9f0cd6 --- /dev/null +++ b/api/src/tests/test_rdf.py @@ -0,0 +1,51 @@ +from fastapi.testclient import TestClient +import pytest +import rdflib +import pathlib +import json +import uuid +from typing import List +from .util import ( + get_template_study, + get_template_biosample, + get_template_image, + get_template_file_reference, + get_template_collection, + get_template_specimen, + get_template_image_acquisition +) + +@pytest.fixture(scope="function") +def rdf_graph(): + return rdflib.Graph() + + +@pytest.mark.parametrize( + "context_file_name, json_object_dict", + [ + ( 'CollectionContext.jsonld', get_template_collection(add_uuid=True) ), + ( 'StudyContext.jsonld', get_template_study(add_uuid=True) ), + ( 'ImageContext.jsonld', get_template_image({"uuid": uuid.uuid4()}, add_uuid=True) ), + ( 'StudyFileReferenceContext.jsonld', get_template_file_reference({"uuid": uuid.uuid4()}, add_uuid=True) ), + ( 'BiosampleContext.jsonld', get_template_biosample(add_uuid=True) ), + ( 'SpecimenContext.jsonld', get_template_specimen({"uuid": uuid.uuid4()}, add_uuid=True) ), + ( 'ImageAcquisitionContext.jsonld', get_template_image_acquisition({"uuid": uuid.uuid4()}, add_uuid=True) ), + ] +) +def test_context_creates_parseable_jsonld(rdf_graph: rdflib.Graph, context_file_name: str, json_object_dict: dict): + + context_path = pathlib.Path(pathlib.Path.cwd() / 'src/models/jsonld/1.0' / context_file_name ) + context = {} + + isParseable = False + try: + with open(context_path) as context_file: + context = json.load(context_file) + json_object_dict["@context"] = context + rdf_graph.parse(data=json_object_dict, format="json-ld") + isParseable = True + except: + isParseable = False + + assert isParseable + assert len(rdf_graph)>0 \ No newline at end of file diff --git a/api/src/tests/test_study.py b/api/src/tests/test_study.py index 7f51ce9d..24fdf085 100644 --- a/api/src/tests/test_study.py +++ b/api/src/tests/test_study.py @@ -1,5 +1,17 @@ from fastapi.testclient import TestClient -from .util import * +import pytest +from typing import List +from .util import ( + get_uuid, + make_file_references, + make_images, + make_study, + get_study, + get_template_study, + unorderd_lists_equality, + api_client, + uuid, + existing_study) def test_create_study(api_client: TestClient, uuid: str): @@ -19,7 +31,7 @@ def test_create_study(api_client: TestClient, uuid: str): ], "organism": "test", "release_date": "test", - "annotations_applied": False, + "annotations_applied": False } rsp = api_client.post("private/studies", json=study) assert rsp.status_code == 201, str(rsp) @@ -35,6 +47,7 @@ def test_create_study(api_client: TestClient, uuid: str): "file_references_count": 0, "images_count": 0, "model": {"type_name": "BIAStudy", "version": 1}, + "@context": "https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/StudyContext.json" } study_created = get_study(api_client, uuid) @@ -153,7 +166,7 @@ def test_update_study_not_created(api_client: TestClient, uuid: str): }, ], "organism": "test", - "release_date": "test", + "release_date": "test" } rsp = api_client.patch("private/studies", json=study) assert rsp.status_code == 404, str(rsp) diff --git a/api/src/tests/util.py b/api/src/tests/util.py index 2ab9a6b5..e7bf314f 100644 --- a/api/src/tests/util.py +++ b/api/src/tests/util.py @@ -3,14 +3,12 @@ from .. import app import uuid as uuid_lib from typing import List -import time import pytest import pytest_asyncio from ..models.repository import repository_create, Repository from ..api.auth import create_user, get_user import asyncio -from collections import Counter # @pytest.fixture # def api_client_private() -> TestClient: @@ -20,7 +18,7 @@ # return client -@pytest.fixture +@pytest.fixture(scope="module") def api_client() -> TestClient: client = get_client(raise_server_exceptions=False) authenticate_client(client) # @TODO: DELETEME @@ -141,6 +139,7 @@ def make_collection(api_client: TestClient, collection_attributes_override={}): "subtitle": "test_collection_subtitle", "description": "test_collection_description", "study_uuids": [], + "@context": "placeholder_context" } collection |= collection_attributes_override @@ -171,6 +170,7 @@ def get_template_study(add_uuid=False): "images_count": 0, "annotations_applied": False, "annotations": [], + "@context": "placeholder_context" } @@ -188,6 +188,7 @@ def get_template_biosample(add_uuid=False): "experimental_variables": ["placeholder_experimental_variable"], "extrinsic_variables": ["placeholder_extrinsic_variable"], "intrinsic_variables": ["placeholder_intrinsic_variable"], + "@context": "placeholder_context" } @@ -200,6 +201,7 @@ def get_template_specimen(existing_biosample, add_uuid=False): "title": "placeholder_title", "sample_preparation_protocol": "placeholder_sample_preparation_protocol", "growth_protocol": "placeholder_growth_protocol", + "@context": "placeholder_context" } @@ -213,6 +215,7 @@ def get_template_image_acquisition(existing_specimen, add_uuid=False): "imaging_instrument": "placeholder_imaging_instrument", "image_acquisition_parameters": "placeholder_image_acquisition_parameters", "imaging_method": "placeholder_imaging_method", + "@context": "placeholder_context" } @@ -274,7 +277,7 @@ def get_template_file_reference(existing_study: dict, add_uuid=False): "attributes": {}, "annotations": [], "annotations_applied": False, - "type": "file", + "type": "file" } @@ -291,6 +294,7 @@ def get_template_collection(add_uuid=False): "attributes": {}, "annotations": [], "annotations_applied": False, + "@context": "placeholder_context" } @@ -311,6 +315,7 @@ def get_template_image(existing_study: dict, add_uuid=False): "alias": None, "representations": [], "image_acquisition_methods_uuid": [], + "@context": "placeholder_context" } @@ -414,7 +419,8 @@ def get_uuid() -> str: return str(generated) -def unorderd_lists_equality(list1, list2) -> bool: +def unorderd_lists_equality(list1: list[dict], list2: list[dict]) -> bool: + # verbose equality check to compare lists of dictionaries created from json objects if len(list1) == len(list2): for elem1 in list1: if list1.count(elem1) != list2.count(elem1): From 4241459e46198843c0783e33ff69f24486679a83 Mon Sep 17 00:00:00 2001 From: Liviu Anita Date: Mon, 8 Apr 2024 12:08:38 +0100 Subject: [PATCH 2/7] Don't ignore project-local .vscode (useful for defaults) Add suggested extensions --- .gitignore | 1 - api/.vscode/extensions.json | 7 +++++++ api/Readme.md | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 api/.vscode/extensions.json diff --git a/.gitignore b/.gitignore index d2f1764b..dab4d3f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ static-sitegen/.DS_Store .history -.vscode **/poetry.lock tools/.DS_Store .DS_Store diff --git a/api/.vscode/extensions.json b/api/.vscode/extensions.json new file mode 100644 index 00000000..5f0e7165 --- /dev/null +++ b/api/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "ms-python.black-formatter", + "ms-python.debugpy", + "ms-python.python" + ] +} \ No newline at end of file diff --git a/api/Readme.md b/api/Readme.md index 9ca9732e..d96b7a3c 100644 --- a/api/Readme.md +++ b/api/Readme.md @@ -13,6 +13,10 @@ In a future version of Poetry this warning will become an error! ``` This can be ignored. +### Install recommended extensions + +If doing any development on the project, consider installing the recommended extensions. + ## Running the api From e428e1187f38631526a3f5cdcae83df80f47904a Mon Sep 17 00:00:00 2001 From: Liviu Anita Date: Mon, 8 Apr 2024 14:04:46 +0100 Subject: [PATCH 3/7] Add @context as a URI in tests, to keep consistent with the field --- api/src/models/persistence.py | 35 ++++++++++++--- ...st_biosample_specimen_image_acquisition.py | 25 +++-------- api/src/tests/test_file_reference.py | 22 ++++++---- api/src/tests/test_image.py | 32 +++++++------- api/src/tests/test_rdf.py | 44 +++++++++---------- api/src/tests/util.py | 36 +++++++-------- 6 files changed, 104 insertions(+), 90 deletions(-) diff --git a/api/src/models/persistence.py b/api/src/models/persistence.py index fbe3206e..90b748d2 100644 --- a/api/src/models/persistence.py +++ b/api/src/models/persistence.py @@ -148,7 +148,10 @@ class BIAStudy(BIABaseModel, DocumentMixin, AnnotatedMixin[StudyAnnotation]): file_references_count: int = Field(default=0) images_count: int = Field(default=0) - context: str = Field(alias='@context', default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/StudyContext.json") + context: str = Field( + alias="@context", + default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/StudyContext.json", + ) model_config = ConfigDict(model_version_latest=1) @@ -164,6 +167,11 @@ class FileReference( type: str = Field() size_in_bytes: int = Field() + context: str = Field( + alias="@context", + default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/FileReferenceContext.json", + ) + model_config = ConfigDict(model_version_latest=1) @@ -219,7 +227,10 @@ class Biosample(BIABaseModel, DocumentMixin): intrinsic_variables: List[str] = Field( description="Intrinsic (e.g. genetic) alteration.", default=[] ) - context: str = Field(alias='@context', default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/SpecimenContext.json") + context: str = Field( + alias="@context", + default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/SpecimenContext.json", + ) model_config = ConfigDict(model_version_latest=1) @@ -232,7 +243,10 @@ class Specimen(BIABaseModel, DocumentMixin): ) # is this a ST-only concern, or does it make sense for it to be in the models? sample_preparation_protocol: str = Field() growth_protocol: str = Field() - context: str = Field(alias='@context', default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/SpecimenContext.json") + context: str = Field( + alias="@context", + default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/SpecimenContext.json", + ) model_config = ConfigDict(model_version_latest=1) @@ -252,7 +266,10 @@ class ImageAcquisition(BIABaseModel, DocumentMixin): imaging_method: str = ( Field() ) # make this an Enum / restrict some other way? Distinguishing between "somewhat close to a controlled vocabulary" vs "completely free text" might be useful - context: str = Field(alias='@context', default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/ImageAcquisitionContext.json") + context: str = Field( + alias="@context", + default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/ImageAcquisitionContext.json", + ) model_config = ConfigDict(model_version_latest=1) @@ -283,7 +300,10 @@ class BIAImage(BIABaseModel, DocumentMixin, AnnotatedMixin[ImageAnnotation]): description="Context in which the image was acquired. This list often has one item, but it can occasionally have more (e.g. for multimodal imaging)", default=[], ) - context: str = Field(alias='@context', default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/ImageContext.json") + context: str = Field( + alias="@context", + default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/ImageContext.json", + ) model_config = ConfigDict(model_version_latest=2) @@ -297,7 +317,10 @@ class BIACollection(BIABaseModel, DocumentMixin, AnnotatedMixin[CollectionAnnota subtitle: str = Field() description: Optional[str] = Field(default=None) study_uuids: List[str] = Field(default=[]) - context: str = Field(alias='@context', default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/CollectionContext.json") + context: str = Field( + alias="@context", + default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/CollectionContext.json", + ) model_config = ConfigDict(model_version_latest=1) diff --git a/api/src/tests/test_biosample_specimen_image_acquisition.py b/api/src/tests/test_biosample_specimen_image_acquisition.py index 34ce6f35..373a16e2 100644 --- a/api/src/tests/test_biosample_specimen_image_acquisition.py +++ b/api/src/tests/test_biosample_specimen_image_acquisition.py @@ -14,25 +14,14 @@ existing_specimen, existing_image_acquisition, existing_study, - existing_image - ) + existing_image, + get_template_biosample, +) + def test_biosample_create_retrieve_update(api_client: TestClient, uuid: str): # Note that this actually doesn't depend on any study - biosample = { - "uuid": uuid, - "version": 0, - "title": "placeholder_title", - "organism_scientific_name": "placeholder_organism_scientific_name", - "organism_common_name": "placeholder_organism_common_name", - "organism_ncbi_taxon": "placeholder_organism_ncbi_taxon", - "description": "placeholder_description", - "biological_entity": "placeholder_biological_entity", - "experimental_variables": ["placeholder_experimental_variable"], - "extrinsic_variables": ["placeholder_extrinsic_variable"], - "intrinsic_variables": ["placeholder_intrinsic_variable"], - "@context": "placeholder_context" - } + biosample = get_template_biosample() | {"uuid": uuid} rsp = api_client.post(f"private/biosamples", json=biosample) assert rsp.status_code == 201, rsp.json() @@ -62,7 +51,7 @@ def test_specimen_create_retrieve_update( "title": "placeholder_title", "sample_preparation_protocol": "placeholder_sample_preparation_protocol", "growth_protocol": "placeholder_growth_protocol", - "@context": "placeholder_context" + "@context": "placeholder_context", } rsp = api_client.post(f"private/specimens", json=specimen) assert rsp.status_code == 201, rsp.json() @@ -94,7 +83,7 @@ def test_image_acquisition_create_retrieve_update( "imaging_instrument": "placeholder_imaging_instrument", "image_acquisition_parameters": "placeholder_image_acquisition_parameters", "imaging_method": "placeholder_imaging_method", - "@context": "placeholder_context" + "@context": "placeholder_context", } rsp = api_client.post(f"private/image_acquisitions", json=image_acquisition) assert rsp.status_code == 201, rsp.json() diff --git a/api/src/tests/test_file_reference.py b/api/src/tests/test_file_reference.py index 41050a2c..b997769c 100644 --- a/api/src/tests/test_file_reference.py +++ b/api/src/tests/test_file_reference.py @@ -2,18 +2,20 @@ import pytest from typing import List from .util import ( - get_uuid, - get_template_file_reference, - make_file_references, - unorderd_lists_equality, + get_uuid, + get_template_file_reference, + make_file_references, + unorderd_lists_equality, assert_bulk_response_items_correct, api_client, existing_study, existing_file_reference, - uuid) + uuid, +) import itertools from uuid import UUID + def test_create_file_references(api_client: TestClient, existing_study: dict): uuids = [get_uuid() for _ in range(2)] @@ -47,7 +49,9 @@ def test_create_file_references_multiple_errors( uuids = [get_uuid() for _ in range(10)] file_references = [ - { + # "use template but override everything" semantics, just to avoid this test breaking every time we change the FileReference model + get_template_file_reference(existing_study) + | { "uuid": uuid, "version": 0, "type": "file", @@ -61,7 +65,6 @@ def test_create_file_references_multiple_errors( } for uuid in uuids ] - del existing_file_reference["model"] file_references.append(existing_file_reference) file_references[5]["version"] = 2 @@ -91,7 +94,6 @@ def test_create_file_references_multiple_errors( assert rsp.status_code == 200, rsp.json() doc_persisted = rsp.json() - del doc_persisted["model"] assert doc_persisted == doc_expected @@ -475,7 +477,9 @@ def test_search_size( }, ) assert rsp.status_code == 200 - assert unorderd_lists_equality([fileref_fixtures[0], fileref_fixtures[2]], rsp.json()) + assert unorderd_lists_equality( + [fileref_fixtures[0], fileref_fixtures[2]], rsp.json() + ) rsp = api_client.post( "search/file_references/exact_match", diff --git a/api/src/tests/test_image.py b/api/src/tests/test_image.py index 8f02f286..478f5457 100644 --- a/api/src/tests/test_image.py +++ b/api/src/tests/test_image.py @@ -2,17 +2,19 @@ import pytest from typing import List from .util import ( - get_uuid, - make_file_references, - make_images, - make_study, - get_study, + get_uuid, + make_file_references, + make_images, + make_study, + get_study, get_template_image, - unorderd_lists_equality, + unorderd_lists_equality, assert_bulk_response_items_correct, + tests_base, api_client, existing_study, - existing_image) + existing_image, +) import itertools from uuid import UUID import os @@ -30,7 +32,7 @@ def test_create_images(api_client: TestClient, existing_study: dict): "original_relpath": f"/home/test/{uuid}", "attributes": { "image_uuid": uuid, - } + }, } for uuid in uuids ] @@ -54,7 +56,7 @@ def test_create_images_multiple_errors(api_client: TestClient, existing_study: d "original_relpath": f"/home/test/{uuid}", "attributes": { "image_uuid": uuid, - } + }, } for uuid in uuids ] @@ -346,6 +348,7 @@ def test_study_with_images_and_filerefs_fetch_images( images_fetched = set([img["uuid"] for img in rsp.json()]) assert images_fetched == images_created + def test_image_pagination(api_client: TestClient, existing_study: dict): images = make_images(api_client, existing_study, 5) images.sort(key=lambda img: UUID(img["uuid"]).hex) @@ -400,8 +403,7 @@ def test_image_pagination_bad_limit(api_client: TestClient, existing_study: dict def test_image_ome_metadata_create_get(api_client: TestClient, existing_image: dict): - script_dir = os.path.dirname(os.path.realpath(__file__)) - with open(os.path.join(script_dir, "data/simple.ome.xml")) as f: + with open(os.path.join(tests_base(), "data/simple.ome.xml")) as f: rsp = api_client.post( f"private/images/{existing_image['uuid']}/ome_metadata", files={"ome_metadata_file": f.read()}, @@ -433,14 +435,14 @@ def test_post_invalid_ome_metadata(api_client: TestClient, existing_image: dict) def test_image_ome_metadata_update(api_client: TestClient, existing_image: dict): - script_dir = os.path.dirname(os.path.realpath(__file__)) - with open(os.path.join(script_dir, "data/simple.ome.xml")) as f: + ome_file_path = os.path.join(tests_base(), "data/simple.ome.xml") + with open(ome_file_path) as f: rsp = api_client.post( f"private/images/{existing_image['uuid']}/ome_metadata", files={"ome_metadata_file": f.read()}, ) assert rsp.status_code == 201 - with open(os.path.join(script_dir, "data/simple.ome.xml")) as f: + with open(ome_file_path) as f: rsp = api_client.post( f"private/images/{existing_image['uuid']}/ome_metadata", files={"ome_metadata_file": f.read()}, @@ -699,7 +701,7 @@ def test_search_uri_prefix( ) assert rsp.status_code == 200 assert unorderd_lists_equality(img_fixtures[:2], rsp.json()) - + def test_search_uri_prefix_not_substring( self, api_client: TestClient, img_fixtures: List[dict], existing_study: dict ): diff --git a/api/src/tests/test_rdf.py b/api/src/tests/test_rdf.py index 6c9f0cd6..3ff2418d 100644 --- a/api/src/tests/test_rdf.py +++ b/api/src/tests/test_rdf.py @@ -1,19 +1,18 @@ -from fastapi.testclient import TestClient import pytest import rdflib -import pathlib import json import uuid -from typing import List from .util import ( - get_template_study, + get_template_study, get_template_biosample, get_template_image, get_template_file_reference, get_template_collection, get_template_specimen, - get_template_image_acquisition + get_template_image_acquisition, ) +import urllib + @pytest.fixture(scope="function") def rdf_graph(): @@ -21,31 +20,30 @@ def rdf_graph(): @pytest.mark.parametrize( - "context_file_name, json_object_dict", + "json_object_dict", [ - ( 'CollectionContext.jsonld', get_template_collection(add_uuid=True) ), - ( 'StudyContext.jsonld', get_template_study(add_uuid=True) ), - ( 'ImageContext.jsonld', get_template_image({"uuid": uuid.uuid4()}, add_uuid=True) ), - ( 'StudyFileReferenceContext.jsonld', get_template_file_reference({"uuid": uuid.uuid4()}, add_uuid=True) ), - ( 'BiosampleContext.jsonld', get_template_biosample(add_uuid=True) ), - ( 'SpecimenContext.jsonld', get_template_specimen({"uuid": uuid.uuid4()}, add_uuid=True) ), - ( 'ImageAcquisitionContext.jsonld', get_template_image_acquisition({"uuid": uuid.uuid4()}, add_uuid=True) ), - ] + get_template_collection(add_uuid=True), + get_template_study(add_uuid=True), + get_template_image({"uuid": uuid.uuid4()}, add_uuid=True), + get_template_file_reference({"uuid": uuid.uuid4()}, add_uuid=True), + get_template_biosample(add_uuid=True), + get_template_specimen({"uuid": uuid.uuid4()}, add_uuid=True), + get_template_image_acquisition({"uuid": uuid.uuid4()}, add_uuid=True), + ], ) -def test_context_creates_parseable_jsonld(rdf_graph: rdflib.Graph, context_file_name: str, json_object_dict: dict): - - context_path = pathlib.Path(pathlib.Path.cwd() / 'src/models/jsonld/1.0' / context_file_name ) - context = {} - +def test_context_creates_parseable_jsonld( + rdf_graph: rdflib.Graph, json_object_dict: dict +): isParseable = False try: - with open(context_path) as context_file: - context = json.load(context_file) - json_object_dict["@context"] = context + json_object_dict["@context"] = json.loads( + urllib.request.urlopen(json_object_dict["@context"]).read().decode("utf-8") + ) + rdf_graph.parse(data=json_object_dict, format="json-ld") isParseable = True except: isParseable = False assert isParseable - assert len(rdf_graph)>0 \ No newline at end of file + assert len(rdf_graph) > 0 diff --git a/api/src/tests/util.py b/api/src/tests/util.py index e7bf314f..86437320 100644 --- a/api/src/tests/util.py +++ b/api/src/tests/util.py @@ -9,6 +9,7 @@ from ..models.repository import repository_create, Repository from ..api.auth import create_user, get_user import asyncio +import os # @pytest.fixture # def api_client_private() -> TestClient: @@ -130,23 +131,13 @@ def get_collection( def make_collection(api_client: TestClient, collection_attributes_override={}): - uuid = get_uuid() - collection = { - "uuid": uuid, - "version": 0, - "name": "test_collection_name", - "title": "test_collection_title", - "subtitle": "test_collection_subtitle", - "description": "test_collection_description", - "study_uuids": [], - "@context": "placeholder_context" - } + collection = get_template_collection(add_uuid=True) collection |= collection_attributes_override rsp = api_client.post("private/collections", json=collection) assert rsp.status_code == 201, rsp.json() - return get_collection(api_client, uuid) + return get_collection(api_client, collection["uuid"]) def get_template_study(add_uuid=False): @@ -170,7 +161,7 @@ def get_template_study(add_uuid=False): "images_count": 0, "annotations_applied": False, "annotations": [], - "@context": "placeholder_context" + "@context": f"file://{os.path.join(tests_base(), '../models/jsonld/1.0/StudyContext.jsonld')}", } @@ -188,7 +179,7 @@ def get_template_biosample(add_uuid=False): "experimental_variables": ["placeholder_experimental_variable"], "extrinsic_variables": ["placeholder_extrinsic_variable"], "intrinsic_variables": ["placeholder_intrinsic_variable"], - "@context": "placeholder_context" + "@context": f"file://{os.path.join(tests_base(), '../models/jsonld/1.0/BiosampleContext.jsonld')}", } @@ -201,7 +192,7 @@ def get_template_specimen(existing_biosample, add_uuid=False): "title": "placeholder_title", "sample_preparation_protocol": "placeholder_sample_preparation_protocol", "growth_protocol": "placeholder_growth_protocol", - "@context": "placeholder_context" + "@context": f"file://{os.path.join(tests_base(), '../models/jsonld/1.0/SpecimenContext.jsonld')}", } @@ -215,7 +206,7 @@ def get_template_image_acquisition(existing_specimen, add_uuid=False): "imaging_instrument": "placeholder_imaging_instrument", "image_acquisition_parameters": "placeholder_image_acquisition_parameters", "imaging_method": "placeholder_imaging_method", - "@context": "placeholder_context" + "@context": f"file://{os.path.join(tests_base(), '../models/jsonld/1.0/ImageAcquisitionContext.jsonld')}", } @@ -277,7 +268,8 @@ def get_template_file_reference(existing_study: dict, add_uuid=False): "attributes": {}, "annotations": [], "annotations_applied": False, - "type": "file" + "type": "file", + "@context": f"file://{os.path.join(tests_base(), '../models/jsonld/1.0/StudyFileReferenceContext.jsonld')}", } @@ -294,7 +286,7 @@ def get_template_collection(add_uuid=False): "attributes": {}, "annotations": [], "annotations_applied": False, - "@context": "placeholder_context" + "@context": f"file://{os.path.join(tests_base(), '../models/jsonld/1.0/CollectionContext.jsonld')}", } @@ -315,7 +307,7 @@ def get_template_image(existing_study: dict, add_uuid=False): "alias": None, "representations": [], "image_acquisition_methods_uuid": [], - "@context": "placeholder_context" + "@context": f"file://{os.path.join(tests_base(), '../models/jsonld/1.0/ImageContext.jsonld')}", } @@ -412,6 +404,7 @@ def generic_exception_handler(request: Request, exc: Exception): return TestClient(app.app, base_url=TEST_SERVER_BASE_URL, **kwargs) + def get_uuid() -> str: # @TODO: make this constant and require mongo to always be clean? generated = uuid_lib.uuid4() @@ -428,6 +421,7 @@ def unorderd_lists_equality(list1: list[dict], list2: list[dict]) -> bool: return True return False + def assert_bulk_response_items_correct( api_client: TestClient, bulk_create_payload: List[dict], @@ -459,3 +453,7 @@ def assert_bulk_response_items_correct( else: # if there was no clash but the insert was rejected, the object shouldn't exist at all assert rsp.status_code == 404 + + +def tests_base() -> str: + return os.path.dirname(os.path.realpath(__file__)) From 5f86d28903f87d203316054a04009b6f4478b2ab Mon Sep 17 00:00:00 2001 From: Liviu Anita Date: Mon, 8 Apr 2024 16:52:39 +0100 Subject: [PATCH 4/7] Update readme --- api/Readme.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/api/Readme.md b/api/Readme.md index d96b7a3c..18cdcf54 100644 --- a/api/Readme.md +++ b/api/Readme.md @@ -15,7 +15,11 @@ This can be ignored. ### Install recommended extensions -If doing any development on the project, consider installing the recommended extensions. +If doing any development on the project, consider installing the recommended extensions: + +* ms-python.black-formatter +* ms-python.debugpy +* ms-python.python ## Running the api From aa4e017f1f5c2b4a89cebae7feff24a242091d1e Mon Sep 17 00:00:00 2001 From: Francois Date: Mon, 8 Apr 2024 17:13:00 +0100 Subject: [PATCH 5/7] updated context to use pydantic's Url class, rather than string --- api/src/api/public.py | 2 +- api/src/models/persistence.py | 64 ++++++++++++------- api/src/tests/test_annotations.py | 4 +- ...st_biosample_specimen_image_acquisition.py | 4 +- api/src/tests/test_file_reference.py | 2 +- api/src/tests/test_misc.py | 5 +- api/src/tests/test_rdf.py | 15 +++-- api/src/tests/test_study.py | 19 +++--- api/src/tests/util.py | 20 +++--- 9 files changed, 81 insertions(+), 54 deletions(-) diff --git a/api/src/api/public.py b/api/src/api/public.py index edd186a4..1ab29196 100644 --- a/api/src/api/public.py +++ b/api/src/api/public.py @@ -65,7 +65,7 @@ async def get_study( ) -> db_models.BIAStudy: study = await db.find_study_by_uuid(study_uuid) annotator.annotate_if_needed(study) - + return study diff --git a/api/src/models/persistence.py b/api/src/models/persistence.py index 90b748d2..ddec4617 100644 --- a/api/src/models/persistence.py +++ b/api/src/models/persistence.py @@ -1,12 +1,18 @@ from enum import Enum -from pydantic import BaseModel, Field, ConfigDict +from pydantic import BaseModel, Field, ConfigDict, field_serializer from pydantic.generics import GenericModel from typing import Dict, List, Optional, TypeVar, Generic from uuid import UUID +from pydantic_core import Url + from src.api.exceptions import DocumentNotFound, InvalidRequestException +def url2str(self, val: Url) -> str: + return str(val) + + class BIABaseModel(BaseModel): def json(self, ensure_ascii=False, **kwargs): """ensure_ascii defaults to False instead of True to handle the common case of non-ascii names""" @@ -62,6 +68,15 @@ def __init__(self, *args, **data): super().__init__(*args, **data) +class JSONLDDocument(DocumentMixin, BaseModel): + context: Url = Field(alias="@context", default="") + + # custom field serializer for Urls to provide strings for mongoDB + @field_serializer("context") + def url2str(self, val) -> str: + return str(val) + + class AnnotationState(str, Enum): active = "active" deleted = "deleted" @@ -132,7 +147,10 @@ class Author(BIABaseModel): name: str -class BIAStudy(BIABaseModel, DocumentMixin, AnnotatedMixin[StudyAnnotation]): +class BIAStudy( + JSONLDDocument, + AnnotatedMixin[StudyAnnotation], +): title: str = Field() description: str = Field() authors: Optional[List[Author]] = Field(default=[]) @@ -148,17 +166,15 @@ class BIAStudy(BIABaseModel, DocumentMixin, AnnotatedMixin[StudyAnnotation]): file_references_count: int = Field(default=0) images_count: int = Field(default=0) - context: str = Field( + context: Url = Field( alias="@context", - default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/StudyContext.json", + default="https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/StudyContext.jsonld", ) model_config = ConfigDict(model_version_latest=1) -class FileReference( - BIABaseModel, DocumentMixin, AnnotatedMixin[FileReferenceAnnotation] -): +class FileReference(JSONLDDocument, AnnotatedMixin[FileReferenceAnnotation]): """A reference to an externally hosted file.""" study_uuid: UUID = Field() @@ -167,9 +183,9 @@ class FileReference( type: str = Field() size_in_bytes: int = Field() - context: str = Field( + context: Url = Field( alias="@context", - default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/FileReferenceContext.json", + default="https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/FileReferenceContext.jsonld", ) model_config = ConfigDict(model_version_latest=1) @@ -211,7 +227,7 @@ class BIAImageRepresentation(BIABaseModel): rendering: Optional[RenderingInfo] = Field(default=None) -class Biosample(BIABaseModel, DocumentMixin): +class Biosample(JSONLDDocument): title: str = ( Field() ) # is this a ST-only concern, or does it make sense for it to be in the models? @@ -227,15 +243,15 @@ class Biosample(BIABaseModel, DocumentMixin): intrinsic_variables: List[str] = Field( description="Intrinsic (e.g. genetic) alteration.", default=[] ) - context: str = Field( + context: Url = Field( alias="@context", - default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/SpecimenContext.json", + default="https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/SpecimenContext.jsonld", ) model_config = ConfigDict(model_version_latest=1) -class Specimen(BIABaseModel, DocumentMixin): +class Specimen(JSONLDDocument): biosample_uuid: UUID = Field() title: str = ( @@ -243,15 +259,15 @@ class Specimen(BIABaseModel, DocumentMixin): ) # is this a ST-only concern, or does it make sense for it to be in the models? sample_preparation_protocol: str = Field() growth_protocol: str = Field() - context: str = Field( + context: Url = Field( alias="@context", - default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/SpecimenContext.json", + default="https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/SpecimenContext.jsonld", ) model_config = ConfigDict(model_version_latest=1) -class ImageAcquisition(BIABaseModel, DocumentMixin): +class ImageAcquisition(JSONLDDocument): specimen_uuid: UUID = Field() title: str = ( @@ -266,15 +282,15 @@ class ImageAcquisition(BIABaseModel, DocumentMixin): imaging_method: str = ( Field() ) # make this an Enum / restrict some other way? Distinguishing between "somewhat close to a controlled vocabulary" vs "completely free text" might be useful - context: str = Field( + context: Url = Field( alias="@context", - default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/ImageAcquisitionContext.json", + default="https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/ImageAcquisitionContext.jsonld", ) model_config = ConfigDict(model_version_latest=1) -class BIAImage(BIABaseModel, DocumentMixin, AnnotatedMixin[ImageAnnotation]): +class BIAImage(JSONLDDocument, AnnotatedMixin[ImageAnnotation]): """This class represents the abstract concept of an image. Images are generated by acquisition by instruments. @@ -300,15 +316,15 @@ class BIAImage(BIABaseModel, DocumentMixin, AnnotatedMixin[ImageAnnotation]): description="Context in which the image was acquired. This list often has one item, but it can occasionally have more (e.g. for multimodal imaging)", default=[], ) - context: str = Field( + context: Url = Field( alias="@context", - default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/ImageContext.json", + default="https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/ImageContext.jsonld", ) model_config = ConfigDict(model_version_latest=2) -class BIACollection(BIABaseModel, DocumentMixin, AnnotatedMixin[CollectionAnnotation]): +class BIACollection(JSONLDDocument, AnnotatedMixin[CollectionAnnotation]): """A collection of studies with a coherent purpose. Studies can be in multiple collections.""" @@ -317,9 +333,9 @@ class BIACollection(BIABaseModel, DocumentMixin, AnnotatedMixin[CollectionAnnota subtitle: str = Field() description: Optional[str] = Field(default=None) study_uuids: List[str] = Field(default=[]) - context: str = Field( + context: Url = Field( alias="@context", - default="https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/CollectionContext.json", + default="https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/CollectionContext.jsonld", ) model_config = ConfigDict(model_version_latest=1) diff --git a/api/src/tests/test_annotations.py b/api/src/tests/test_annotations.py index 0311e926..ee53c759 100644 --- a/api/src/tests/test_annotations.py +++ b/api/src/tests/test_annotations.py @@ -15,8 +15,8 @@ DBTestMixin, api_client, uuid, - existing_study - ) + existing_study, +) from ..models.repository import Repository import uuid as uuid_lib diff --git a/api/src/tests/test_biosample_specimen_image_acquisition.py b/api/src/tests/test_biosample_specimen_image_acquisition.py index 373a16e2..ff953819 100644 --- a/api/src/tests/test_biosample_specimen_image_acquisition.py +++ b/api/src/tests/test_biosample_specimen_image_acquisition.py @@ -51,7 +51,7 @@ def test_specimen_create_retrieve_update( "title": "placeholder_title", "sample_preparation_protocol": "placeholder_sample_preparation_protocol", "growth_protocol": "placeholder_growth_protocol", - "@context": "placeholder_context", + "@context": "https://placeholder/context", } rsp = api_client.post(f"private/specimens", json=specimen) assert rsp.status_code == 201, rsp.json() @@ -83,7 +83,7 @@ def test_image_acquisition_create_retrieve_update( "imaging_instrument": "placeholder_imaging_instrument", "image_acquisition_parameters": "placeholder_image_acquisition_parameters", "imaging_method": "placeholder_imaging_method", - "@context": "placeholder_context", + "@context": "https://placeholder/context", } rsp = api_client.post(f"private/image_acquisitions", json=image_acquisition) assert rsp.status_code == 201, rsp.json() diff --git a/api/src/tests/test_file_reference.py b/api/src/tests/test_file_reference.py index b997769c..0c9ac011 100644 --- a/api/src/tests/test_file_reference.py +++ b/api/src/tests/test_file_reference.py @@ -49,7 +49,7 @@ def test_create_file_references_multiple_errors( uuids = [get_uuid() for _ in range(10)] file_references = [ - # "use template but override everything" semantics, just to avoid this test breaking every time we change the FileReference model + # "use template but override everything" semantics, just to avoid this test breaking every time we change the FileReference model get_template_file_reference(existing_study) | { "uuid": uuid, diff --git a/api/src/tests/test_misc.py b/api/src/tests/test_misc.py index b4a1a640..97619c53 100644 --- a/api/src/tests/test_misc.py +++ b/api/src/tests/test_misc.py @@ -1,13 +1,14 @@ from fastapi.testclient import TestClient from typing import List from .util import ( - make_images, + make_images, make_study, api_client, uuid, existing_study, existing_collection, - existing_image) + existing_image, +) def test_create_collection(api_client: TestClient, uuid: str): diff --git a/api/src/tests/test_rdf.py b/api/src/tests/test_rdf.py index 3ff2418d..76dd5f3a 100644 --- a/api/src/tests/test_rdf.py +++ b/api/src/tests/test_rdf.py @@ -24,11 +24,18 @@ def rdf_graph(): [ get_template_collection(add_uuid=True), get_template_study(add_uuid=True), - get_template_image({"uuid": uuid.uuid4()}, add_uuid=True), - get_template_file_reference({"uuid": uuid.uuid4()}, add_uuid=True), + # Passing in UUID in dicitionary as we don't need a study from the test db & Pytest's parametrised tests use fixture values + # rather than the function (see https://github.com/pytest-dev/pytest/issues/349). We could use https://github.com/tvorog/pytest-lazy-fixture + # if we need this feature. + get_template_image(existing_study={"uuid": uuid.uuid4()}, add_uuid=True), + get_template_file_reference( + existing_study={"uuid": uuid.uuid4()}, add_uuid=True + ), get_template_biosample(add_uuid=True), - get_template_specimen({"uuid": uuid.uuid4()}, add_uuid=True), - get_template_image_acquisition({"uuid": uuid.uuid4()}, add_uuid=True), + get_template_specimen(existing_biosample={"uuid": uuid.uuid4()}, add_uuid=True), + get_template_image_acquisition( + existing_specimen={"uuid": uuid.uuid4()}, add_uuid=True + ), ], ) def test_context_creates_parseable_jsonld( diff --git a/api/src/tests/test_study.py b/api/src/tests/test_study.py index 24fdf085..a7631cf5 100644 --- a/api/src/tests/test_study.py +++ b/api/src/tests/test_study.py @@ -2,16 +2,17 @@ import pytest from typing import List from .util import ( - get_uuid, - make_file_references, - make_images, - make_study, + get_uuid, + make_file_references, + make_images, + make_study, get_study, - get_template_study, + get_template_study, unorderd_lists_equality, api_client, uuid, - existing_study) + existing_study, +) def test_create_study(api_client: TestClient, uuid: str): @@ -31,7 +32,7 @@ def test_create_study(api_client: TestClient, uuid: str): ], "organism": "test", "release_date": "test", - "annotations_applied": False + "annotations_applied": False, } rsp = api_client.post("private/studies", json=study) assert rsp.status_code == 201, str(rsp) @@ -47,7 +48,7 @@ def test_create_study(api_client: TestClient, uuid: str): "file_references_count": 0, "images_count": 0, "model": {"type_name": "BIAStudy", "version": 1}, - "@context": "https://github.com/BioImage-Archive/bia-integrator/tree/main/api/src/models/jsonld/1.0/StudyContext.json" + "@context": "https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/StudyContext.jsonld", } study_created = get_study(api_client, uuid) @@ -166,7 +167,7 @@ def test_update_study_not_created(api_client: TestClient, uuid: str): }, ], "organism": "test", - "release_date": "test" + "release_date": "test", } rsp = api_client.patch("private/studies", json=study) assert rsp.status_code == 404, str(rsp) diff --git a/api/src/tests/util.py b/api/src/tests/util.py index 86437320..391e4666 100644 --- a/api/src/tests/util.py +++ b/api/src/tests/util.py @@ -161,7 +161,7 @@ def get_template_study(add_uuid=False): "images_count": 0, "annotations_applied": False, "annotations": [], - "@context": f"file://{os.path.join(tests_base(), '../models/jsonld/1.0/StudyContext.jsonld')}", + "@context": f"file://{os.path.join( package_base(), 'models/jsonld/1.0/StudyContext.jsonld')}", } @@ -179,7 +179,7 @@ def get_template_biosample(add_uuid=False): "experimental_variables": ["placeholder_experimental_variable"], "extrinsic_variables": ["placeholder_extrinsic_variable"], "intrinsic_variables": ["placeholder_intrinsic_variable"], - "@context": f"file://{os.path.join(tests_base(), '../models/jsonld/1.0/BiosampleContext.jsonld')}", + "@context": f"file://{os.path.join(package_base(), 'models/jsonld/1.0/BiosampleContext.jsonld')}", } @@ -192,7 +192,7 @@ def get_template_specimen(existing_biosample, add_uuid=False): "title": "placeholder_title", "sample_preparation_protocol": "placeholder_sample_preparation_protocol", "growth_protocol": "placeholder_growth_protocol", - "@context": f"file://{os.path.join(tests_base(), '../models/jsonld/1.0/SpecimenContext.jsonld')}", + "@context": f"file://{os.path.join(package_base(), 'models/jsonld/1.0/SpecimenContext.jsonld')}", } @@ -206,7 +206,7 @@ def get_template_image_acquisition(existing_specimen, add_uuid=False): "imaging_instrument": "placeholder_imaging_instrument", "image_acquisition_parameters": "placeholder_image_acquisition_parameters", "imaging_method": "placeholder_imaging_method", - "@context": f"file://{os.path.join(tests_base(), '../models/jsonld/1.0/ImageAcquisitionContext.jsonld')}", + "@context": f"file://{os.path.join(package_base(), 'models/jsonld/1.0/ImageAcquisitionContext.jsonld')}", } @@ -269,7 +269,7 @@ def get_template_file_reference(existing_study: dict, add_uuid=False): "annotations": [], "annotations_applied": False, "type": "file", - "@context": f"file://{os.path.join(tests_base(), '../models/jsonld/1.0/StudyFileReferenceContext.jsonld')}", + "@context": f"file://{os.path.join(package_base(), 'models/jsonld/1.0/StudyFileReferenceContext.jsonld')}", } @@ -286,7 +286,7 @@ def get_template_collection(add_uuid=False): "attributes": {}, "annotations": [], "annotations_applied": False, - "@context": f"file://{os.path.join(tests_base(), '../models/jsonld/1.0/CollectionContext.jsonld')}", + "@context": f"file://{os.path.join(package_base(), 'models/jsonld/1.0/CollectionContext.jsonld')}", } @@ -307,7 +307,7 @@ def get_template_image(existing_study: dict, add_uuid=False): "alias": None, "representations": [], "image_acquisition_methods_uuid": [], - "@context": f"file://{os.path.join(tests_base(), '../models/jsonld/1.0/ImageContext.jsonld')}", + "@context": f"file://{os.path.join(package_base(), 'models/jsonld/1.0/ImageContext.jsonld')}", } @@ -455,5 +455,7 @@ def assert_bulk_response_items_correct( assert rsp.status_code == 404 -def tests_base() -> str: - return os.path.dirname(os.path.realpath(__file__)) +def package_base() -> str: + return os.path.abspath( + os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir) + ) From 492607fec8d74107f342c960bfe4a51e2234251c Mon Sep 17 00:00:00 2001 From: Francois Date: Wed, 10 Apr 2024 10:41:35 +0100 Subject: [PATCH 6/7] fixed import in tests, updated model hierarchy --- api/src/models/jsonld/1.0/ImageContext.jsonld | 3 +++ api/src/models/persistence.py | 26 ++++++++++++------- api/src/tests/test_image.py | 6 ++--- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/api/src/models/jsonld/1.0/ImageContext.jsonld b/api/src/models/jsonld/1.0/ImageContext.jsonld index e04ef3bc..bed8d3bc 100644 --- a/api/src/models/jsonld/1.0/ImageContext.jsonld +++ b/api/src/models/jsonld/1.0/ImageContext.jsonld @@ -17,5 +17,8 @@ "@base": "https://www.ebi.ac.uk/bioimage-archive/api/v1/image_acquisitions/" }, "@type": "@id" + }, + "image_representation": { + "uri": "@id" } } diff --git a/api/src/models/persistence.py b/api/src/models/persistence.py index ddec4617..33be0ff0 100644 --- a/api/src/models/persistence.py +++ b/api/src/models/persistence.py @@ -68,7 +68,7 @@ def __init__(self, *args, **data): super().__init__(*args, **data) -class JSONLDDocument(DocumentMixin, BaseModel): +class JSONLDMixin(BaseModel): context: Url = Field(alias="@context", default="") # custom field serializer for Urls to provide strings for mongoDB @@ -148,8 +148,7 @@ class Author(BIABaseModel): class BIAStudy( - JSONLDDocument, - AnnotatedMixin[StudyAnnotation], + BIABaseModel, DocumentMixin, JSONLDMixin, AnnotatedMixin[StudyAnnotation] ): title: str = Field() description: str = Field() @@ -174,7 +173,9 @@ class BIAStudy( model_config = ConfigDict(model_version_latest=1) -class FileReference(JSONLDDocument, AnnotatedMixin[FileReferenceAnnotation]): +class FileReference( + BIABaseModel, DocumentMixin, JSONLDMixin, AnnotatedMixin[FileReferenceAnnotation] +): """A reference to an externally hosted file.""" study_uuid: UUID = Field() @@ -227,7 +228,7 @@ class BIAImageRepresentation(BIABaseModel): rendering: Optional[RenderingInfo] = Field(default=None) -class Biosample(JSONLDDocument): +class Biosample(BIABaseModel, DocumentMixin, JSONLDMixin): title: str = ( Field() ) # is this a ST-only concern, or does it make sense for it to be in the models? @@ -251,7 +252,7 @@ class Biosample(JSONLDDocument): model_config = ConfigDict(model_version_latest=1) -class Specimen(JSONLDDocument): +class Specimen(BIABaseModel, DocumentMixin, JSONLDMixin): biosample_uuid: UUID = Field() title: str = ( @@ -267,7 +268,7 @@ class Specimen(JSONLDDocument): model_config = ConfigDict(model_version_latest=1) -class ImageAcquisition(JSONLDDocument): +class ImageAcquisition(BIABaseModel, DocumentMixin, JSONLDMixin): specimen_uuid: UUID = Field() title: str = ( @@ -290,7 +291,9 @@ class ImageAcquisition(JSONLDDocument): model_config = ConfigDict(model_version_latest=1) -class BIAImage(JSONLDDocument, AnnotatedMixin[ImageAnnotation]): +class BIAImage( + BIABaseModel, DocumentMixin, JSONLDMixin, AnnotatedMixin[ImageAnnotation] +): """This class represents the abstract concept of an image. Images are generated by acquisition by instruments. @@ -324,7 +327,12 @@ class BIAImage(JSONLDDocument, AnnotatedMixin[ImageAnnotation]): model_config = ConfigDict(model_version_latest=2) -class BIACollection(JSONLDDocument, AnnotatedMixin[CollectionAnnotation]): +class BIACollection( + BIABaseModel, + DocumentMixin, + JSONLDMixin, + AnnotatedMixin[CollectionAnnotation], +): """A collection of studies with a coherent purpose. Studies can be in multiple collections.""" diff --git a/api/src/tests/test_image.py b/api/src/tests/test_image.py index 478f5457..e746f997 100644 --- a/api/src/tests/test_image.py +++ b/api/src/tests/test_image.py @@ -10,7 +10,7 @@ get_template_image, unorderd_lists_equality, assert_bulk_response_items_correct, - tests_base, + package_base, api_client, existing_study, existing_image, @@ -403,7 +403,7 @@ def test_image_pagination_bad_limit(api_client: TestClient, existing_study: dict def test_image_ome_metadata_create_get(api_client: TestClient, existing_image: dict): - with open(os.path.join(tests_base(), "data/simple.ome.xml")) as f: + with open(os.path.join(package_base(), "tests/data/simple.ome.xml")) as f: rsp = api_client.post( f"private/images/{existing_image['uuid']}/ome_metadata", files={"ome_metadata_file": f.read()}, @@ -435,7 +435,7 @@ def test_post_invalid_ome_metadata(api_client: TestClient, existing_image: dict) def test_image_ome_metadata_update(api_client: TestClient, existing_image: dict): - ome_file_path = os.path.join(tests_base(), "data/simple.ome.xml") + ome_file_path = os.path.join(package_base(), "tests/data/simple.ome.xml") with open(ome_file_path) as f: rsp = api_client.post( f"private/images/{existing_image['uuid']}/ome_metadata", From 10517bedf63385bae1e21910b47fd01a50c60e0c Mon Sep 17 00:00:00 2001 From: Francois Date: Wed, 10 Apr 2024 13:43:41 +0100 Subject: [PATCH 7/7] updated client to handle json-ld context field --- clients/openapi.json | 57 +++++++++++++++++-- .../bia_integrator_api/docs/BIACollection.md | 1 + .../bia_integrator_api/docs/BIAImage.md | 1 + .../bia_integrator_api/docs/BIAStudy.md | 1 + .../bia_integrator_api/docs/Biosample.md | 1 + .../docs/ChannelRendering.md | 2 +- .../bia_integrator_api/docs/FileReference.md | 1 + .../docs/ImageAcquisition.md | 1 + .../bia_integrator_api/docs/Specimen.md | 1 + .../models/bia_collection.py | 6 +- .../bia_integrator_api/models/bia_image.py | 6 +- .../bia_integrator_api/models/bia_study.py | 6 +- .../bia_integrator_api/models/biosample.py | 6 +- .../models/channel_rendering.py | 4 +- .../models/file_reference.py | 6 +- .../models/image_acquisition.py | 6 +- .../bia_integrator_api/models/specimen.py | 6 +- 17 files changed, 91 insertions(+), 21 deletions(-) diff --git a/clients/openapi.json b/clients/openapi.json index 2cb885dc..93b9ce8b 100644 --- a/clients/openapi.json +++ b/clients/openapi.json @@ -1934,6 +1934,13 @@ "title": "Annotations", "default": [] }, + "@context": { + "type": "string", + "minLength": 1, + "format": "uri", + "title": "@Context", + "default": "https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/CollectionContext.jsonld" + }, "uuid": { "type": "string", "format": "uuid", @@ -2019,6 +2026,13 @@ "title": "Annotations", "default": [] }, + "@context": { + "type": "string", + "minLength": 1, + "format": "uri", + "title": "@Context", + "default": "https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/ImageContext.jsonld" + }, "uuid": { "type": "string", "format": "uuid", @@ -2246,6 +2260,13 @@ "title": "Annotations", "default": [] }, + "@context": { + "type": "string", + "minLength": 1, + "format": "uri", + "title": "@Context", + "default": "https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/StudyContext.jsonld" + }, "uuid": { "type": "string", "format": "uuid", @@ -2355,6 +2376,13 @@ }, "Biosample": { "properties": { + "@context": { + "type": "string", + "minLength": 1, + "format": "uri", + "title": "@Context", + "default": "https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/SpecimenContext.jsonld" + }, "uuid": { "type": "string", "format": "uuid", @@ -2620,7 +2648,7 @@ "scale_factor": { "type": "number", "title": "Scale Factor", - "default": 1 + "default": 1.0 } }, "type": "object", @@ -2680,6 +2708,13 @@ "title": "Annotations", "default": [] }, + "@context": { + "type": "string", + "minLength": 1, + "format": "uri", + "title": "@Context", + "default": "https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/FileReferenceContext.jsonld" + }, "uuid": { "type": "string", "format": "uuid", @@ -2777,6 +2812,13 @@ }, "ImageAcquisition": { "properties": { + "@context": { + "type": "string", + "minLength": 1, + "format": "uri", + "title": "@Context", + "default": "https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/ImageAcquisitionContext.jsonld" + }, "uuid": { "type": "string", "format": "uuid", @@ -3089,7 +3131,7 @@ }, "limit": { "type": "integer", - "minimum": 0, + "minimum": 0.0, "title": "Limit", "default": 10 } @@ -3202,7 +3244,7 @@ }, "limit": { "type": "integer", - "minimum": 0, + "minimum": 0.0, "title": "Limit", "default": 10 } @@ -3327,7 +3369,7 @@ }, "limit": { "type": "integer", - "minimum": 0, + "minimum": 0.0, "title": "Limit", "default": 10 } @@ -3337,6 +3379,13 @@ }, "Specimen": { "properties": { + "@context": { + "type": "string", + "minLength": 1, + "format": "uri", + "title": "@Context", + "default": "https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/SpecimenContext.jsonld" + }, "uuid": { "type": "string", "format": "uuid", diff --git a/clients/python/bia_integrator_api/docs/BIACollection.md b/clients/python/bia_integrator_api/docs/BIACollection.md index 37e32d90..c781d8aa 100644 --- a/clients/python/bia_integrator_api/docs/BIACollection.md +++ b/clients/python/bia_integrator_api/docs/BIACollection.md @@ -8,6 +8,7 @@ Name | Type | Description | Notes **attributes** | **object** | When annotations are applied, the ones that have a key different than an object attribute (so they don't overwrite it) get saved here. | [optional] **annotations_applied** | **bool** | This acts as a dirty flag, with the purpose of telling apart objects that had some fields overwritten by applying annotations (so should be rejected when writing), and those that didn't. | [optional] [default to False] **annotations** | [**List[CollectionAnnotation]**](CollectionAnnotation.md) | | [optional] [default to []] +**context** | **str** | | [optional] [default to 'https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/CollectionContext.jsonld'] **uuid** | **str** | | **version** | **int** | | **model** | [**ModelMetadata**](ModelMetadata.md) | | [optional] diff --git a/clients/python/bia_integrator_api/docs/BIAImage.md b/clients/python/bia_integrator_api/docs/BIAImage.md index 1c528d18..174faad1 100644 --- a/clients/python/bia_integrator_api/docs/BIAImage.md +++ b/clients/python/bia_integrator_api/docs/BIAImage.md @@ -8,6 +8,7 @@ Name | Type | Description | Notes **attributes** | **object** | When annotations are applied, the ones that have a key different than an object attribute (so they don't overwrite it) get saved here. | [optional] **annotations_applied** | **bool** | This acts as a dirty flag, with the purpose of telling apart objects that had some fields overwritten by applying annotations (so should be rejected when writing), and those that didn't. | [optional] [default to False] **annotations** | [**List[ImageAnnotation]**](ImageAnnotation.md) | | [optional] [default to []] +**context** | **str** | | [optional] [default to 'https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/ImageContext.jsonld'] **uuid** | **str** | | **version** | **int** | | **model** | [**ModelMetadata**](ModelMetadata.md) | | [optional] diff --git a/clients/python/bia_integrator_api/docs/BIAStudy.md b/clients/python/bia_integrator_api/docs/BIAStudy.md index 13e9deb0..290088b3 100644 --- a/clients/python/bia_integrator_api/docs/BIAStudy.md +++ b/clients/python/bia_integrator_api/docs/BIAStudy.md @@ -7,6 +7,7 @@ Name | Type | Description | Notes **attributes** | **object** | When annotations are applied, the ones that have a key different than an object attribute (so they don't overwrite it) get saved here. | [optional] **annotations_applied** | **bool** | This acts as a dirty flag, with the purpose of telling apart objects that had some fields overwritten by applying annotations (so should be rejected when writing), and those that didn't. | [optional] [default to False] **annotations** | [**List[StudyAnnotation]**](StudyAnnotation.md) | | [optional] [default to []] +**context** | **str** | | [optional] [default to 'https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/StudyContext.jsonld'] **uuid** | **str** | | **version** | **int** | | **model** | [**ModelMetadata**](ModelMetadata.md) | | [optional] diff --git a/clients/python/bia_integrator_api/docs/Biosample.md b/clients/python/bia_integrator_api/docs/Biosample.md index 2c795b82..03ec741f 100644 --- a/clients/python/bia_integrator_api/docs/Biosample.md +++ b/clients/python/bia_integrator_api/docs/Biosample.md @@ -4,6 +4,7 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**context** | **str** | | [optional] [default to 'https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/SpecimenContext.jsonld'] **uuid** | **str** | | **version** | **int** | | **model** | [**ModelMetadata**](ModelMetadata.md) | | [optional] diff --git a/clients/python/bia_integrator_api/docs/ChannelRendering.md b/clients/python/bia_integrator_api/docs/ChannelRendering.md index 3458c62e..77ccd6cc 100644 --- a/clients/python/bia_integrator_api/docs/ChannelRendering.md +++ b/clients/python/bia_integrator_api/docs/ChannelRendering.md @@ -7,7 +7,7 @@ Name | Type | Description | Notes **channel_label** | **str** | | **colormap_start** | **List[float]** | | **colormap_end** | **List[float]** | | -**scale_factor** | **float** | | [optional] [default to 1] +**scale_factor** | **float** | | [optional] [default to 1.0] ## Example diff --git a/clients/python/bia_integrator_api/docs/FileReference.md b/clients/python/bia_integrator_api/docs/FileReference.md index 2dfca613..808a2a1c 100644 --- a/clients/python/bia_integrator_api/docs/FileReference.md +++ b/clients/python/bia_integrator_api/docs/FileReference.md @@ -8,6 +8,7 @@ Name | Type | Description | Notes **attributes** | **object** | When annotations are applied, the ones that have a key different than an object attribute (so they don't overwrite it) get saved here. | [optional] **annotations_applied** | **bool** | This acts as a dirty flag, with the purpose of telling apart objects that had some fields overwritten by applying annotations (so should be rejected when writing), and those that didn't. | [optional] [default to False] **annotations** | [**List[FileReferenceAnnotation]**](FileReferenceAnnotation.md) | | [optional] [default to []] +**context** | **str** | | [optional] [default to 'https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/FileReferenceContext.jsonld'] **uuid** | **str** | | **version** | **int** | | **model** | [**ModelMetadata**](ModelMetadata.md) | | [optional] diff --git a/clients/python/bia_integrator_api/docs/ImageAcquisition.md b/clients/python/bia_integrator_api/docs/ImageAcquisition.md index 5457200b..e8ddadca 100644 --- a/clients/python/bia_integrator_api/docs/ImageAcquisition.md +++ b/clients/python/bia_integrator_api/docs/ImageAcquisition.md @@ -4,6 +4,7 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**context** | **str** | | [optional] [default to 'https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/ImageAcquisitionContext.jsonld'] **uuid** | **str** | | **version** | **int** | | **model** | [**ModelMetadata**](ModelMetadata.md) | | [optional] diff --git a/clients/python/bia_integrator_api/docs/Specimen.md b/clients/python/bia_integrator_api/docs/Specimen.md index 5d0075fb..95d3632e 100644 --- a/clients/python/bia_integrator_api/docs/Specimen.md +++ b/clients/python/bia_integrator_api/docs/Specimen.md @@ -4,6 +4,7 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**context** | **str** | | [optional] [default to 'https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/SpecimenContext.jsonld'] **uuid** | **str** | | **version** | **int** | | **model** | [**ModelMetadata**](ModelMetadata.md) | | [optional] diff --git a/clients/python/bia_integrator_api/models/bia_collection.py b/clients/python/bia_integrator_api/models/bia_collection.py index dbfd6cd1..b63d5c96 100644 --- a/clients/python/bia_integrator_api/models/bia_collection.py +++ b/clients/python/bia_integrator_api/models/bia_collection.py @@ -19,7 +19,7 @@ from typing import Any, Dict, List, Optional -from pydantic import BaseModel, Field, StrictBool, StrictInt, StrictStr, conlist +from pydantic import BaseModel, Field, StrictBool, StrictInt, StrictStr, conlist, constr from bia_integrator_api.models.collection_annotation import CollectionAnnotation from bia_integrator_api.models.model_metadata import ModelMetadata @@ -30,6 +30,7 @@ class BIACollection(BaseModel): attributes: Optional[Dict[str, Any]] = Field(None, description=" When annotations are applied, the ones that have a key different than an object attribute (so they don't overwrite it) get saved here. ") annotations_applied: Optional[StrictBool] = Field(False, description=" This acts as a dirty flag, with the purpose of telling apart objects that had some fields overwritten by applying annotations (so should be rejected when writing), and those that didn't. ") annotations: Optional[conlist(CollectionAnnotation)] = None + context: Optional[constr(strict=True, min_length=1)] = Field('https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/CollectionContext.jsonld', alias="@context") uuid: StrictStr = Field(...) version: StrictInt = Field(...) model: Optional[ModelMetadata] = None @@ -38,7 +39,7 @@ class BIACollection(BaseModel): subtitle: StrictStr = Field(...) description: Optional[StrictStr] = None study_uuids: Optional[conlist(StrictStr)] = None - __properties = ["attributes", "annotations_applied", "annotations", "uuid", "version", "model", "name", "title", "subtitle", "description", "study_uuids"] + __properties = ["attributes", "annotations_applied", "annotations", "@context", "uuid", "version", "model", "name", "title", "subtitle", "description", "study_uuids"] class Config: """Pydantic configuration""" @@ -99,6 +100,7 @@ def from_dict(cls, obj: dict) -> BIACollection: "attributes": obj.get("attributes"), "annotations_applied": obj.get("annotations_applied") if obj.get("annotations_applied") is not None else False, "annotations": [CollectionAnnotation.from_dict(_item) for _item in obj.get("annotations")] if obj.get("annotations") is not None else None, + "context": obj.get("@context") if obj.get("@context") is not None else 'https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/CollectionContext.jsonld', "uuid": obj.get("uuid"), "version": obj.get("version"), "model": ModelMetadata.from_dict(obj.get("model")) if obj.get("model") is not None else None, diff --git a/clients/python/bia_integrator_api/models/bia_image.py b/clients/python/bia_integrator_api/models/bia_image.py index 2ae13885..560952f8 100644 --- a/clients/python/bia_integrator_api/models/bia_image.py +++ b/clients/python/bia_integrator_api/models/bia_image.py @@ -19,7 +19,7 @@ from typing import Any, Dict, List, Optional -from pydantic import BaseModel, Field, StrictBool, StrictInt, StrictStr, conlist +from pydantic import BaseModel, Field, StrictBool, StrictInt, StrictStr, conlist, constr from bia_integrator_api.models.bia_image_alias import BIAImageAlias from bia_integrator_api.models.bia_image_representation import BIAImageRepresentation from bia_integrator_api.models.image_annotation import ImageAnnotation @@ -32,6 +32,7 @@ class BIAImage(BaseModel): attributes: Optional[Dict[str, Any]] = Field(None, description=" When annotations are applied, the ones that have a key different than an object attribute (so they don't overwrite it) get saved here. ") annotations_applied: Optional[StrictBool] = Field(False, description=" This acts as a dirty flag, with the purpose of telling apart objects that had some fields overwritten by applying annotations (so should be rejected when writing), and those that didn't. ") annotations: Optional[conlist(ImageAnnotation)] = None + context: Optional[constr(strict=True, min_length=1)] = Field('https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/ImageContext.jsonld', alias="@context") uuid: StrictStr = Field(...) version: StrictInt = Field(...) model: Optional[ModelMetadata] = None @@ -42,7 +43,7 @@ class BIAImage(BaseModel): representations: Optional[conlist(BIAImageRepresentation)] = None alias: Optional[BIAImageAlias] = None image_acquisition_methods_uuid: Optional[conlist(StrictStr)] = Field(None, description="Context in which the image was acquired. This list often has one item, but it can occasionally have more (e.g. for multimodal imaging)") - __properties = ["attributes", "annotations_applied", "annotations", "uuid", "version", "model", "study_uuid", "original_relpath", "name", "dimensions", "representations", "alias", "image_acquisition_methods_uuid"] + __properties = ["attributes", "annotations_applied", "annotations", "@context", "uuid", "version", "model", "study_uuid", "original_relpath", "name", "dimensions", "representations", "alias", "image_acquisition_methods_uuid"] class Config: """Pydantic configuration""" @@ -123,6 +124,7 @@ def from_dict(cls, obj: dict) -> BIAImage: "attributes": obj.get("attributes"), "annotations_applied": obj.get("annotations_applied") if obj.get("annotations_applied") is not None else False, "annotations": [ImageAnnotation.from_dict(_item) for _item in obj.get("annotations")] if obj.get("annotations") is not None else None, + "context": obj.get("@context") if obj.get("@context") is not None else 'https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/ImageContext.jsonld', "uuid": obj.get("uuid"), "version": obj.get("version"), "model": ModelMetadata.from_dict(obj.get("model")) if obj.get("model") is not None else None, diff --git a/clients/python/bia_integrator_api/models/bia_study.py b/clients/python/bia_integrator_api/models/bia_study.py index 431900bb..c2efc11c 100644 --- a/clients/python/bia_integrator_api/models/bia_study.py +++ b/clients/python/bia_integrator_api/models/bia_study.py @@ -19,7 +19,7 @@ from typing import Any, Dict, List, Optional -from pydantic import BaseModel, Field, StrictBool, StrictInt, StrictStr, conlist +from pydantic import BaseModel, Field, StrictBool, StrictInt, StrictStr, conlist, constr from bia_integrator_api.models.author import Author from bia_integrator_api.models.model_metadata import ModelMetadata from bia_integrator_api.models.study_annotation import StudyAnnotation @@ -31,6 +31,7 @@ class BIAStudy(BaseModel): attributes: Optional[Dict[str, Any]] = Field(None, description=" When annotations are applied, the ones that have a key different than an object attribute (so they don't overwrite it) get saved here. ") annotations_applied: Optional[StrictBool] = Field(False, description=" This acts as a dirty flag, with the purpose of telling apart objects that had some fields overwritten by applying annotations (so should be rejected when writing), and those that didn't. ") annotations: Optional[conlist(StudyAnnotation)] = None + context: Optional[constr(strict=True, min_length=1)] = Field('https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/StudyContext.jsonld', alias="@context") uuid: StrictStr = Field(...) version: StrictInt = Field(...) model: Optional[ModelMetadata] = None @@ -46,7 +47,7 @@ class BIAStudy(BaseModel): tags: Optional[conlist(StrictStr)] = None file_references_count: Optional[StrictInt] = 0 images_count: Optional[StrictInt] = 0 - __properties = ["attributes", "annotations_applied", "annotations", "uuid", "version", "model", "title", "description", "authors", "organism", "release_date", "accession_id", "imaging_type", "example_image_uri", "example_image_annotation_uri", "tags", "file_references_count", "images_count"] + __properties = ["attributes", "annotations_applied", "annotations", "@context", "uuid", "version", "model", "title", "description", "authors", "organism", "release_date", "accession_id", "imaging_type", "example_image_uri", "example_image_annotation_uri", "tags", "file_references_count", "images_count"] class Config: """Pydantic configuration""" @@ -119,6 +120,7 @@ def from_dict(cls, obj: dict) -> BIAStudy: "attributes": obj.get("attributes"), "annotations_applied": obj.get("annotations_applied") if obj.get("annotations_applied") is not None else False, "annotations": [StudyAnnotation.from_dict(_item) for _item in obj.get("annotations")] if obj.get("annotations") is not None else None, + "context": obj.get("@context") if obj.get("@context") is not None else 'https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/StudyContext.jsonld', "uuid": obj.get("uuid"), "version": obj.get("version"), "model": ModelMetadata.from_dict(obj.get("model")) if obj.get("model") is not None else None, diff --git a/clients/python/bia_integrator_api/models/biosample.py b/clients/python/bia_integrator_api/models/biosample.py index bad3f058..a7c65df6 100644 --- a/clients/python/bia_integrator_api/models/biosample.py +++ b/clients/python/bia_integrator_api/models/biosample.py @@ -19,13 +19,14 @@ from typing import List, Optional -from pydantic import BaseModel, Field, StrictInt, StrictStr, conlist +from pydantic import BaseModel, Field, StrictInt, StrictStr, conlist, constr from bia_integrator_api.models.model_metadata import ModelMetadata class Biosample(BaseModel): """ Biosample """ + context: Optional[constr(strict=True, min_length=1)] = Field('https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/SpecimenContext.jsonld', alias="@context") uuid: StrictStr = Field(...) version: StrictInt = Field(...) model: Optional[ModelMetadata] = None @@ -38,7 +39,7 @@ class Biosample(BaseModel): experimental_variables: Optional[conlist(StrictStr)] = None extrinsic_variables: Optional[conlist(StrictStr)] = Field(None, description="External treatment (e.g. reagent).") intrinsic_variables: Optional[conlist(StrictStr)] = Field(None, description="Intrinsic (e.g. genetic) alteration.") - __properties = ["uuid", "version", "model", "title", "organism_scientific_name", "organism_common_name", "organism_ncbi_taxon", "description", "biological_entity", "experimental_variables", "extrinsic_variables", "intrinsic_variables"] + __properties = ["@context", "uuid", "version", "model", "title", "organism_scientific_name", "organism_common_name", "organism_ncbi_taxon", "description", "biological_entity", "experimental_variables", "extrinsic_variables", "intrinsic_variables"] class Config: """Pydantic configuration""" @@ -84,6 +85,7 @@ def from_dict(cls, obj: dict) -> Biosample: return Biosample.parse_obj(obj) _obj = Biosample.parse_obj({ + "context": obj.get("@context") if obj.get("@context") is not None else 'https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/SpecimenContext.jsonld', "uuid": obj.get("uuid"), "version": obj.get("version"), "model": ModelMetadata.from_dict(obj.get("model")) if obj.get("model") is not None else None, diff --git a/clients/python/bia_integrator_api/models/channel_rendering.py b/clients/python/bia_integrator_api/models/channel_rendering.py index 179fde70..21e319bc 100644 --- a/clients/python/bia_integrator_api/models/channel_rendering.py +++ b/clients/python/bia_integrator_api/models/channel_rendering.py @@ -28,7 +28,7 @@ class ChannelRendering(BaseModel): channel_label: Optional[StrictStr] = Field(...) colormap_start: conlist(Union[StrictFloat, StrictInt]) = Field(...) colormap_end: conlist(Union[StrictFloat, StrictInt]) = Field(...) - scale_factor: Optional[Union[StrictFloat, StrictInt]] = 1 + scale_factor: Optional[Union[StrictFloat, StrictInt]] = 1.0 __properties = ["channel_label", "colormap_start", "colormap_end", "scale_factor"] class Config: @@ -75,7 +75,7 @@ def from_dict(cls, obj: dict) -> ChannelRendering: "channel_label": obj.get("channel_label"), "colormap_start": obj.get("colormap_start"), "colormap_end": obj.get("colormap_end"), - "scale_factor": obj.get("scale_factor") if obj.get("scale_factor") is not None else 1 + "scale_factor": obj.get("scale_factor") if obj.get("scale_factor") is not None else 1.0 }) return _obj diff --git a/clients/python/bia_integrator_api/models/file_reference.py b/clients/python/bia_integrator_api/models/file_reference.py index adc91105..9933fb53 100644 --- a/clients/python/bia_integrator_api/models/file_reference.py +++ b/clients/python/bia_integrator_api/models/file_reference.py @@ -19,7 +19,7 @@ from typing import Any, Dict, List, Optional -from pydantic import BaseModel, Field, StrictBool, StrictInt, StrictStr, conlist +from pydantic import BaseModel, Field, StrictBool, StrictInt, StrictStr, conlist, constr from bia_integrator_api.models.file_reference_annotation import FileReferenceAnnotation from bia_integrator_api.models.model_metadata import ModelMetadata @@ -30,6 +30,7 @@ class FileReference(BaseModel): attributes: Optional[Dict[str, Any]] = Field(None, description=" When annotations are applied, the ones that have a key different than an object attribute (so they don't overwrite it) get saved here. ") annotations_applied: Optional[StrictBool] = Field(False, description=" This acts as a dirty flag, with the purpose of telling apart objects that had some fields overwritten by applying annotations (so should be rejected when writing), and those that didn't. ") annotations: Optional[conlist(FileReferenceAnnotation)] = None + context: Optional[constr(strict=True, min_length=1)] = Field('https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/FileReferenceContext.jsonld', alias="@context") uuid: StrictStr = Field(...) version: StrictInt = Field(...) model: Optional[ModelMetadata] = None @@ -38,7 +39,7 @@ class FileReference(BaseModel): uri: StrictStr = Field(...) type: StrictStr = Field(...) size_in_bytes: StrictInt = Field(...) - __properties = ["attributes", "annotations_applied", "annotations", "uuid", "version", "model", "study_uuid", "name", "uri", "type", "size_in_bytes"] + __properties = ["attributes", "annotations_applied", "annotations", "@context", "uuid", "version", "model", "study_uuid", "name", "uri", "type", "size_in_bytes"] class Config: """Pydantic configuration""" @@ -94,6 +95,7 @@ def from_dict(cls, obj: dict) -> FileReference: "attributes": obj.get("attributes"), "annotations_applied": obj.get("annotations_applied") if obj.get("annotations_applied") is not None else False, "annotations": [FileReferenceAnnotation.from_dict(_item) for _item in obj.get("annotations")] if obj.get("annotations") is not None else None, + "context": obj.get("@context") if obj.get("@context") is not None else 'https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/FileReferenceContext.jsonld', "uuid": obj.get("uuid"), "version": obj.get("version"), "model": ModelMetadata.from_dict(obj.get("model")) if obj.get("model") is not None else None, diff --git a/clients/python/bia_integrator_api/models/image_acquisition.py b/clients/python/bia_integrator_api/models/image_acquisition.py index 73de13c4..ff83398b 100644 --- a/clients/python/bia_integrator_api/models/image_acquisition.py +++ b/clients/python/bia_integrator_api/models/image_acquisition.py @@ -19,13 +19,14 @@ from typing import Optional -from pydantic import BaseModel, Field, StrictInt, StrictStr +from pydantic import BaseModel, Field, StrictInt, StrictStr, constr from bia_integrator_api.models.model_metadata import ModelMetadata class ImageAcquisition(BaseModel): """ ImageAcquisition """ + context: Optional[constr(strict=True, min_length=1)] = Field('https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/ImageAcquisitionContext.jsonld', alias="@context") uuid: StrictStr = Field(...) version: StrictInt = Field(...) model: Optional[ModelMetadata] = None @@ -34,7 +35,7 @@ class ImageAcquisition(BaseModel): imaging_instrument: StrictStr = Field(..., description="Textual description of the instrument used to capture the images.") image_acquisition_parameters: StrictStr = Field(..., description="How the images were acquired, including instrument settings/parameters.") imaging_method: StrictStr = Field(...) - __properties = ["uuid", "version", "model", "specimen_uuid", "title", "imaging_instrument", "image_acquisition_parameters", "imaging_method"] + __properties = ["@context", "uuid", "version", "model", "specimen_uuid", "title", "imaging_instrument", "image_acquisition_parameters", "imaging_method"] class Config: """Pydantic configuration""" @@ -80,6 +81,7 @@ def from_dict(cls, obj: dict) -> ImageAcquisition: return ImageAcquisition.parse_obj(obj) _obj = ImageAcquisition.parse_obj({ + "context": obj.get("@context") if obj.get("@context") is not None else 'https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/ImageAcquisitionContext.jsonld', "uuid": obj.get("uuid"), "version": obj.get("version"), "model": ModelMetadata.from_dict(obj.get("model")) if obj.get("model") is not None else None, diff --git a/clients/python/bia_integrator_api/models/specimen.py b/clients/python/bia_integrator_api/models/specimen.py index 5b4d80a3..3dbf2452 100644 --- a/clients/python/bia_integrator_api/models/specimen.py +++ b/clients/python/bia_integrator_api/models/specimen.py @@ -19,13 +19,14 @@ from typing import Optional -from pydantic import BaseModel, Field, StrictInt, StrictStr +from pydantic import BaseModel, Field, StrictInt, StrictStr, constr from bia_integrator_api.models.model_metadata import ModelMetadata class Specimen(BaseModel): """ Specimen """ + context: Optional[constr(strict=True, min_length=1)] = Field('https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/SpecimenContext.jsonld', alias="@context") uuid: StrictStr = Field(...) version: StrictInt = Field(...) model: Optional[ModelMetadata] = None @@ -33,7 +34,7 @@ class Specimen(BaseModel): title: StrictStr = Field(...) sample_preparation_protocol: StrictStr = Field(...) growth_protocol: StrictStr = Field(...) - __properties = ["uuid", "version", "model", "biosample_uuid", "title", "sample_preparation_protocol", "growth_protocol"] + __properties = ["@context", "uuid", "version", "model", "biosample_uuid", "title", "sample_preparation_protocol", "growth_protocol"] class Config: """Pydantic configuration""" @@ -79,6 +80,7 @@ def from_dict(cls, obj: dict) -> Specimen: return Specimen.parse_obj(obj) _obj = Specimen.parse_obj({ + "context": obj.get("@context") if obj.get("@context") is not None else 'https://raw.githubusercontent.com/BioImage-Archive/bia-integrator/main/api/src/models/jsonld/1.0/SpecimenContext.jsonld', "uuid": obj.get("uuid"), "version": obj.get("version"), "model": ModelMetadata.from_dict(obj.get("model")) if obj.get("model") is not None else None,