Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ openMINDS
modules/openminds_stimulation
modules/openminds_publications

**fairgraph** currently provides the following modules for working with KG v3:
**fairgraph** currently provides the following modules:

:doc:`modules/openminds_core`
covers general origin, location and content of research products.
Expand Down
73 changes: 5 additions & 68 deletions fairgraph/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
# limitations under the License.

from __future__ import annotations
from copy import deepcopy
import os
import logging
from typing import Any, Dict, Iterable, List, Optional, Union, TYPE_CHECKING
Expand All @@ -37,13 +36,7 @@
from openminds.registry import lookup_type

from .errors import AuthenticationError, AuthorizationError, ResourceExistsError
from .utility import (
adapt_namespaces_for_query,
adapt_namespaces_3to4,
adapt_namespaces_4to3,
adapt_type_4to3,
handle_scope_keyword,
)
from .utility import handle_scope_keyword
from .base import OPENMINDS_VERSION

if TYPE_CHECKING:
Expand Down Expand Up @@ -144,7 +137,6 @@ def __init__(
self.cache: Dict[str, JsonLdDocument] = {}
self._query_cache: Dict[str, str] = {}
self.accepted_terms_of_use = False
self._migrated = None
if allow_interactive:
self.user_info()

Expand Down Expand Up @@ -214,30 +206,8 @@ def _check_response(
else:
raise Exception(f"Error: {response.error} {error_context}")
else:
if self.migrated is False:
adapt_namespaces_3to4(response.data)
return response

@property
def migrated(self):
# This is a temporary work-around for use during the transitional period
# from openMINDS v3 to v4 (change of namespace)
if self._migrated is None:
self._migrated = True # to stop the call to _check_response() in instance_from_full_uri from recurring

# This is the released controlled term for "left handedness", which should be accessible to everyone
result = self.instance_from_full_uri(
"https://kg.ebrains.eu/api/instances/92631f2e-fc6e-4122-8015-a0731c67f66c", release_status="released"
)
_type = result["@type"]
if isinstance(_type, list):
_type = _type[0]
if "om-i.org" in _type:
self._migrated = True
else:
self._migrated = False
return self._migrated

def query(
self,
query: Dict[str, Any],
Expand Down Expand Up @@ -289,8 +259,6 @@ def _query(release_status, from_index, size):
)

else:
if self.migrated is False:
query = adapt_namespaces_for_query(query)

def _query(release_status, from_index, size):
response = self._kg_client.queries.test_query(
Expand Down Expand Up @@ -356,9 +324,6 @@ def list(
"""
release_status = handle_scope_keyword(scope, release_status)

if self.migrated is False:
target_type = adapt_type_4to3(target_type)

def _list(release_status, from_index, size):
response = self._kg_client.instances.list(
stage=STAGE_MAP[release_status],
Expand Down Expand Up @@ -419,8 +384,7 @@ def _get_instance(release_status):
error_context = f"_get_instance(release_status={release_status} uri={uri})"
# Normal KG URIs start with https://kg.ebrains.eu/api/instances/ with a UUID
# but for openMINDS controlled terms we may have the openMINDS URI
# of the form https://openminds.ebrains.eu/instances/ageCategory/juvenile (v3)
# or https://openminds.om-i.org/instances/ageCategory/juvenile (v4)
# of the form https://openminds.om-i.org/instances/ageCategory/juvenile
# We use different query methods for these different cases.
kg_namespace = self._kg_client.instances._kg_config.id_namespace
if uri.startswith(kg_namespace):
Expand All @@ -435,16 +399,8 @@ def _get_instance(release_status):
data = None
else:
data = response.data
elif uri.startswith("https://openminds.om-i.org/instances") or uri.startswith(
"https://openminds.ebrains.eu/instances"
):
elif uri.startswith("https://openminds.om-i.org/instances"):
payload = [uri]
if self.migrated:
if uri.startswith("https://openminds.ebrains.eu"):
payload = [uri.replace("ebrains.eu", "om-i.org")]
else:
if uri.startswith("https://openminds.om-i.org"):
payload = [uri.replace("om-i.org", "ebrains.eu")]
response = self._kg_client.instances.get_by_identifiers(
stage=STAGE_MAP[release_status],
payload=payload,
Expand Down Expand Up @@ -491,9 +447,6 @@ def create_new_instance(
raise ValueError("payload contains undefined ids")
if instance_id:
UUID(instance_id)
if self.migrated is False:
data = deepcopy(data)
adapt_namespaces_4to3(data)
if instance_id:
response = self._kg_client.instances.create_new_with_id(
space=space,
Expand All @@ -519,9 +472,6 @@ def update_instance(self, instance_id: str, data: JsonLdDocument) -> JsonLdDocum
data (dict): a JSON-LD document that modifies some or all of the data of the existing instance.
"""
UUID(instance_id)
if self.migrated is False:
data = deepcopy(data)
adapt_namespaces_4to3(data)
response = self._kg_client.instances.contribute_to_partial_replacement(
instance_id=instance_id,
payload=data,
Expand All @@ -546,9 +496,6 @@ def replace_instance(self, instance_id: str, data: JsonLdDocument) -> JsonLdDocu
data (dict): a JSON-LD document that will replace the existing instance.
"""
UUID(instance_id)
if self.migrated is False:
data = deepcopy(data)
adapt_namespaces_4to3(data)
response = self._kg_client.instances.contribute_to_full_replacement(
instance_id=instance_id,
payload=data,
Expand Down Expand Up @@ -746,10 +693,7 @@ def configure_space(self, space_name: Optional[str] = None, types: Optional[List
)
raise Exception(err_msg)
for cls in types:
if self.migrated:
target_type = cls.type_
else:
target_type = adapt_type_4to3(cls.type_)
target_type = cls.type_
result = self._kg_admin_client.assign_type_to_space(space=space_name, target_type=target_type)
if result: # error
raise Exception(f"Unable to assign {cls.__name__} to space {space_name}: {result}")
Expand Down Expand Up @@ -780,26 +724,19 @@ def space_info(
number of instances of each class in the given spaces.
"""
release_status = handle_scope_keyword(scope, release_status)
# todo: if not self.migrated, adapt type before lookup
result = self._kg_client.types.list(space=space_name, stage=STAGE_MAP[release_status])
if result.error:
raise Exception(result.error)
response = {}
for item in result.data:
if self.migrated:
type_iri = item.identifier
else:
type_ = {"@type": item.identifier}
adapt_namespaces_3to4(type_)
type_iri = type_["@type"]
type_iri = item.identifier
try:
cls = lookup_type(type_iri, OPENMINDS_VERSION)
except (KeyError, ValueError) as err:
ignore_list = [
"https://core.kg.ebrains.eu/vocab/type/Bookmark",
"https://core.kg.ebrains.eu/vocab/meta/type/Query",
"https://openminds.om-i.org/types/Query",
"https://openminds.ebrains.eu/core/URL",
"https://openminds.om-i.org/types/URL"
]
if ignore_errors or any(ignore in str(err) for ignore in ignore_list):
Expand Down
3 changes: 1 addition & 2 deletions fairgraph/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
as_list, # temporary for backwards compatibility (a lot of code imports it from here)
expand_uri,
normalize_data,
types_match,
)

if TYPE_CHECKING:
Expand Down Expand Up @@ -308,7 +307,7 @@ def _normalize_type(data_item):

type_from_data = _get_type_from_data(data)
# check types match
if not types_match(cls.type_, type_from_data):
if cls.type_ != type_from_data:
raise TypeError("type mismatch {} - {}".format(cls.type_, type_from_data))

# normalize data by expanding keys
Expand Down
10 changes: 5 additions & 5 deletions fairgraph/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class QueryProperty:

Example:
>>> p = QueryProperty(
... "https://openminds.ebrains.eu/vocab/fullName",
... "https://openminds.om-i.org/props/fullName",
... name="full_name",
... filter=Filter("CONTAINS", parameter="name"),
... sorted=True,
Expand Down Expand Up @@ -229,26 +229,26 @@ class Query:

Example:
>>> q = Query(
... node_type="https://openminds.ebrains.eu/core/ModelVersion",
... node_type="https://openminds.om-i.org/types/ModelVersion",
... label="fg-testing-modelversion",
... space="model",
... properties=[
... QueryProperty("@type"),
... QueryProperty(
... "https://openminds.ebrains.eu/vocab/fullName",
... "https://openminds.om-i.org/props/fullName",
... name="vocab:fullName",
... filter=Filter("CONTAINS", parameter="name"),
... sorted=True,
... required=True,
... ),
... QueryProperty(
... "https://openminds.ebrains.eu/vocab/versionIdentifier",
... "https://openminds.om-i.org/props/versionIdentifier",
... name="vocab:versionIdentifier",
... filter=Filter("EQUALS", parameter="version"),
... required=True,
... ),
... QueryProperty(
... "https://openminds.ebrains.eu/vocab/format",
... "https://openminds.om-i.org/props/format",
... name="vocab:format",
... ensure_order=True,
... properties=[
Expand Down
133 changes: 0 additions & 133 deletions fairgraph/utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,11 @@
# limitations under the License.

from __future__ import annotations
from copy import deepcopy
import hashlib
import logging
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union, TYPE_CHECKING
import warnings

from openminds.registry import lookup_type

from .base import OPENMINDS_VERSION

if TYPE_CHECKING:
from .client import KGClient
from .kgobject import KGObject
Expand Down Expand Up @@ -446,134 +441,6 @@ def accepted_terms_of_use(client: KGClient, accept_terms_of_use: bool = False) -
return False


def types_match(a, b):
# temporarily, during the openMINDS transition v3-v4, we allow different namespaces for the types
assert isinstance(a, str), a
assert isinstance(b, str), b
if a == b:
return True
elif a.split("/")[-1] == b.split("/")[-1]:
logger.warning(f"Assuming {a} matches {b} in types_match()")
return True
else:
return False


def _adapt_namespaces(data, adapt_keys, adapt_type, adapt_instance_uri):
if isinstance(data, list):
for item in data:
_adapt_namespaces(item, adapt_keys, adapt_type, adapt_instance_uri)
elif isinstance(data, dict):
# adapt property URIs
old_keys = tuple(data.keys())
new_keys = adapt_keys(old_keys)
for old_key, new_key in zip(old_keys, new_keys):
data[new_key] = data.pop(old_key)
for key, value in data.items():
if key == "@id":
data[key] = adapt_instance_uri(value)
elif isinstance(value, (list, dict)):
_adapt_namespaces(value, adapt_keys, adapt_type, adapt_instance_uri)
# adapt @type URIs
if "@type" in data:
data["@type"] = adapt_type(data["@type"])
else:
pass


def adapt_namespaces_3to4(data):

def adapt_keys_3to4(uri_list):
replacement = ("openminds.ebrains.eu/vocab", "openminds.om-i.org/props")
return (uri.replace(*replacement) for uri in uri_list)

def adapt_type_3to4(uri):
if isinstance(uri, list):
assert len(uri) == 1
uri = uri[0]
return f"https://openminds.om-i.org/types/{uri.split('/')[-1]}"

def adapt_instance_uri_3to4(uri):
if uri.startswith("https://openminds"):
return uri.replace("ebrains.eu", "om-i.org")
else:
return uri

return _adapt_namespaces(data, adapt_keys_3to4, adapt_type_3to4, adapt_instance_uri_3to4)


def adapt_type_4to3(uri):
if isinstance(uri, list):
assert len(uri) == 1
uri = uri[0]
cls = lookup_type(uri, OPENMINDS_VERSION)

if cls.__module__ == "test.test_client":
return cls.type_

module_name = cls.__module__.split(".")[2] # e.g., 'fairgraph.openminds.core.actors.person' -> "core"
module_name = {"controlled_terms": "controlledTerms", "specimen_prep": "specimenPrep"}.get(
module_name, module_name
)
return f"https://openminds.ebrains.eu/{module_name}/{cls.__name__}"


def adapt_namespaces_4to3(data):

def adapt_keys_4to3(uri_list):
replacement = ("openminds.om-i.org/props", "openminds.ebrains.eu/vocab")
return (uri.replace(*replacement) for uri in uri_list)

def adapt_instance_uri_4to3(uri):
if uri.startswith("https://openminds"):
return uri.replace("om-i.org", "ebrains.eu")
else:
return uri

return _adapt_namespaces(data, adapt_keys_4to3, adapt_type_4to3, adapt_instance_uri_4to3)


def adapt_namespaces_for_query(query):
"""Map from v4+ to v3 openMINDS namespace"""

def adapt_path(item_path, replacement):
if isinstance(item_path, str):
return item_path.replace(*replacement)
elif isinstance(item_path, list):
return [adapt_path(part, replacement) for part in item_path]
else:
assert isinstance(item_path, dict)
new_item_path = item_path.copy()
new_item_path["@id"] = item_path["@id"].replace(*replacement)
if "typeFilter" in item_path:
if isinstance(item_path["typeFilter"], list):
new_item_path["typeFilter"] = [
{"@id": adapt_type_4to3(subitem["@id"])} for subitem in item_path["typeFilter"]
]
else:
new_item_path["typeFilter"]["@id"] = adapt_type_4to3(item_path["typeFilter"]["@id"])
return new_item_path

def adapt_structure(structure, replacement):
for item in structure:
item["path"] = adapt_path(item["path"], replacement)
if "structure" in item:
adapt_structure(item["structure"], replacement)

def adapt_filters(structure, replacement):
for item in structure:
if "filter" in item and "value" in item["filter"]:
item["filter"]["value"] = item["filter"]["value"].replace(*replacement)
if "structure" in item:
adapt_filters(item["structure"], replacement)

migrated_query = deepcopy(query)
migrated_query["meta"]["type"] = adapt_type_4to3(migrated_query["meta"]["type"])
adapt_structure(migrated_query["structure"], ("openminds.om-i.org/props", "openminds.ebrains.eu/vocab"))
adapt_filters(migrated_query["structure"], ("openminds.om-i.org/instances", "openminds.ebrains.eu/instances"))
return migrated_query


def initialise_instances(class_list):
"""Cast openMINDS instances to their fairgraph subclass"""
for cls in class_list:
Expand Down
Loading