Skip to content

Commit

Permalink
Merge pull request #300 from ral-facilities/feature/icat-to-panosc-da…
Browse files Browse the repository at this point in the history
…ta-model-conversion-#265

ICAT to PaNOSC Data Model Conversion
  • Loading branch information
MRichards99 authored Jan 31, 2022
2 parents 63f4f17 + f17038d commit 2609aae
Show file tree
Hide file tree
Showing 9 changed files with 1,228 additions and 192 deletions.
4 changes: 2 additions & 2 deletions datagateway_api/search_api_mapping.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@
"Parameter": {
"base_icat_entity": ["InvestigationParameter", "DatasetParameter"],
"id": "id",
"name": "name",
"name": "type.name",
"value": ["numericValue", "stringValue", "dateTimeValue"],
"unit": "type.units",
"dataset": {"Dataset": "investigation.investigationInstruments.instrument.datasetInstruments.dataset"},
"dataset": {"Dataset": "dataset"},
"document": {"Document": "investigation"}
},
"Person": {
Expand Down
114 changes: 46 additions & 68 deletions datagateway_api/src/search_api/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import logging

from datagateway_api.src.common.date_handler import DateHandler
from datagateway_api.src.common.exceptions import FilterError
from datagateway_api.src.datagateway_api.icat.filters import (
PythonICATIncludeFilter,
PythonICATLimitFilter,
Expand All @@ -15,9 +14,6 @@

log = logging.getLogger()

# TODO - Implement each of these filters for Search API, inheriting from the Python ICAT
# versions


class SearchAPIWhereFilter(PythonICATWhereFilter):
def __init__(self, field, value, operation, search_api_query=None):
Expand All @@ -42,9 +38,28 @@ def apply_filter(self, query):

# Convert PaNOSC field names to ICAT field names
for field_name in panosc_field_names:
panosc_mapping_name, icat_field_name = self.get_icat_mapping(
panosc_mapping_name, icat_field_name = mappings.get_icat_mapping(
panosc_mapping_name, field_name,
)

# An edge case for ICAT has been somewhat hardcoded here, to deal with
# ICAT's different parameter value field names. The following mapping is
# assumed (where order matters):
# {"Parameter": {"value": ["numericValue", "stringValue", "dateTimeValue"]}}
if isinstance(icat_field_name, list):
if isinstance(self.value, int) or isinstance(self.value, float):
icat_field_name = icat_field_name[0]
elif isinstance(self.value, datetime):
icat_field_name = icat_field_name[2]
elif isinstance(self.value, str):
if DateHandler.is_str_a_date(self.value):
icat_field_name = icat_field_name[2]
else:
icat_field_name = icat_field_name[1]
else:
self.value = str(self.value)
icat_field_name = icat_field_name[1]

icat_field_names.append(icat_field_name)

log.debug(
Expand All @@ -60,68 +75,6 @@ def apply_filter(self, query):

return super().apply_filter(query.icat_query.query)

def get_icat_mapping(self, panosc_entity_name, field_name):
"""
This function searches the PaNOSC mappings (from `search_api_mapping.json`,
maintained/stored by :class:`PaNOSCMappings`) and retrieves the ICAT translation
from the PaNOSC input. Fields in the same entity can be found, as well as fields
from related entities (e.g. Dataset.files.path) via recursion inside this
function.
An edge case for ICAT has been somewhat hardcoded into this function to dea
with ICAT's different parameter value field names. The following mapping is
assumed (where order matters):
{"Parameter": {"value": ["numericValue", "stringValue", "dateTimeValue"]}}
:param panosc_entity_name: A PaNOSC entity name e.g. "Dataset"
:type panosc_entity_name: :class:`str`
:param field_name: PaNOSC field name to fetch the ICAT version of e.g. "name"
:type field_name: :class:`str`
:return: Tuple containing the PaNOSC entity name (which will change from the
input if a related entity is found) and the ICAT field name
mapping/translation from the PaNOSC data model
:raises FilterError: If a valid mapping cannot be found
"""

log.info(
"Searching mapping file to find ICAT translation for %s",
f"{panosc_entity_name}.{field_name}",
)

try:
icat_mapping = mappings.mappings[panosc_entity_name][field_name]
log.debug("ICAT mapping/translation found: %s", icat_mapping)
except KeyError as e:
raise FilterError(f"Bad PaNOSC to ICAT mapping: {e.args}")

if isinstance(icat_mapping, str):
# Field name
icat_field_name = icat_mapping
elif isinstance(icat_mapping, dict):
# Relation - JSON format: {PaNOSC entity name: ICAT related field name}
panosc_entity_name = list(icat_mapping.keys())[0]
icat_field_name = icat_mapping[panosc_entity_name]
elif isinstance(icat_mapping, list):
# Edge case for ICAT's different parameter value field names
if isinstance(self.value, int) or isinstance(self.value, float):
icat_field_name = icat_mapping[0]
elif isinstance(self.value, datetime):
icat_field_name = icat_mapping[2]
elif isinstance(self.value, str):
if DateHandler.is_str_a_date(self.value):
icat_field_name = icat_mapping[2]
else:
icat_field_name = icat_mapping[1]
else:
self.value = str(self.value)
icat_field_name = icat_mapping[1]

log.debug(
"Output of get_icat_mapping(): %s, %s", panosc_entity_name, icat_field_name,
)

return panosc_entity_name, icat_field_name

def __str__(self):
"""
String representation which is also used to apply WHERE filters that are inside
Expand Down Expand Up @@ -179,8 +132,33 @@ def apply_filter(self, query):


class SearchAPIIncludeFilter(PythonICATIncludeFilter):
def __init__(self, included_filters):
def __init__(self, included_filters, panosc_entity_name):
self.included_filters = included_filters
self.panosc_entity_name = panosc_entity_name
super().__init__(included_filters)

def apply_filter(self, query):
icat_field_names = []

for panosc_field_name in self.included_filters:
# Need an empty list at start of each iteration to clear field names from
# previous iterations
split_icat_field_name = []

panosc_entity_name = self.panosc_entity_name
split_panosc_fields = panosc_field_name.split(".")

for split_field in split_panosc_fields:
panosc_entity_name, icat_field_name = mappings.get_icat_mapping(
panosc_entity_name, split_field,
)
split_icat_field_name.append(icat_field_name)

icat_field_names.append(".".join(split_icat_field_name))

# All PaNOSC field names translated to ICAT, so we can overwrite the PaNOSC
# versions with the ICAT mappings so they can be used to apply the filter via
# `super().apply_filter()`
self.included_filters = icat_field_names

return super().apply_filter(query.icat_query.query)
Loading

0 comments on commit 2609aae

Please sign in to comment.