Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ICAT to PaNOSC Data Model Conversion #300

Merged
merged 49 commits into from
Jan 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
f2f53c9
feat: implement basic version of `SearchAPIIncludeFilter` #261
MRichards99 Jan 4, 2022
00ee21d
refactor: move `get_icat_mapping()` into `PaNOSCMappings` #261
MRichards99 Jan 4, 2022
fc4556c
test: add tests for `SearchAPIIncludeFilter` #261
MRichards99 Jan 6, 2022
85c4bfc
conversion proof of concept #265
Jan 7, 2022
eab7f3d
refactor: make query params more efficient to create less include fil…
MRichards99 Jan 10, 2022
95319a8
Merge branch 'filter-input-conversion' into feature/search-api-includ…
MRichards99 Jan 10, 2022
3d8447e
Merge branch 'feature/search-api-include-filter-#261' into feature/ic…
VKTB Jan 12, 2022
b0983bb
refactor: address TODO in `models.py` #265
VKTB Jan 17, 2022
3cbf11b
refactor: make field types non-strict #265
VKTB Jan 17, 2022
c183a85
set non-related optional entity fields to default to `None` #265
VKTB Jan 17, 2022
ea29f47
set signular related entity fields to default to `None` #265
VKTB Jan 17, 2022
ce9e2af
set plural entity fields to default to empty list #265
VKTB Jan 17, 2022
956a902
refactor: deal with case when list of ICAT field names are found #265
VKTB Jan 17, 2022
98f9157
refactor: deal with case when list of ICAT field values are found #265
VKTB Jan 17, 2022
3ceb365
feature: deal with required related entity cases #265
VKTB Jan 17, 2022
94dcfba
disable dataset and document validator in Parameter entity #265
VKTB Jan 17, 2022
eca0a46
work out if dataset or document is public #265
VKTB Jan 17, 2022
66f0261
test: define test data #265
VKTB Jan 17, 2022
07058a0
test: unit test `from_icat` `Affiliation` entity creation #265
VKTB Jan 17, 2022
3631c61
test: unit test `from_icat` `Dataset` entity creation #265
VKTB Jan 17, 2022
8f2a6c1
test: unit test `from_icat` `Dataset` entity creation #265
VKTB Jan 17, 2022
0554bea
test: unit test `from_icat` `File` entity creation #265
VKTB Jan 17, 2022
0d0a1e4
test: unit test `from_icat` `Instrument` entity creation #265
VKTB Jan 17, 2022
f2845e3
test: unit test `from_icat` `Member` entity creation #265
VKTB Jan 17, 2022
faed594
test: unit test `from_icat` `Parameter` entity creation #265
VKTB Jan 17, 2022
c05a70d
test: unit test `from_icat` `Person` entity creation #265
VKTB Jan 17, 2022
077c279
test: unit test `from_icat` `Sample` entity creation #265
VKTB Jan 17, 2022
fb836c7
test: unit test `from_icat` `Technique` entity creation #265
VKTB Jan 17, 2022
701eb74
test: unit test `from_icat` raises `ValidationError` #265
VKTB Jan 17, 2022
e691f24
update `Parameter` mappings in example file #265
VKTB Jan 18, 2022
d606d6b
add logic for dealing with nested required related fields #265
VKTB Jan 24, 2022
e52d595
test: unit test logic for nested required related fields #265
VKTB Jan 24, 2022
7f4cd96
Merge branch 'filter-input-conversion' into feature/icat-to-panosc-da…
MRichards99 Jan 26, 2022
601c51b
refactor: update PaNOSC `Parameter` mappings in example file #265
VKTB Jan 28, 2022
1e8badb
test: update `Parameter` mappings in mappings fixture #265
VKTB Jan 28, 2022
d595ceb
test: fix tests following updates to PaNOSC `Parameter` mappings #265
VKTB Jan 28, 2022
0bbcc0d
refactor: replace `fromisoformat` with `str_to_datetime_object` #265
VKTB Jan 28, 2022
299cbcd
Apply suggestions from code review #265
VKTB Jan 28, 2022
3f463fb
Merge branch 'feature/icat-to-panosc-data-model-conversion-#265' of g…
VKTB Jan 28, 2022
3f1b1cf
docs: add new comments and fix existing #265
VKTB Jan 28, 2022
1d730d1
refactor: refactor `_get_icat_field_value` logic #265
VKTB Jan 28, 2022
5571722
test: remove code for replacing datetime timezone #265
VKTB Jan 28, 2022
71765a5
refactor: refactor `from_icat` abstract method #265
VKTB Jan 28, 2022
691a59e
fix: fix list type field checking in Python 3.6 #265
VKTB Jan 31, 2022
a253422
style: address linting warnings #265
VKTB Jan 31, 2022
3f1b2d6
style: fix typo #265
MRichards99 Jan 31, 2022
fca1a59
Merge branch 'master' into feature/icat-to-panosc-data-model-conversi…
MRichards99 Jan 31, 2022
358ac22
refactor: update PaNOSC Parameter name mapping in example file #265
VKTB Jan 31, 2022
f17038d
Merge branch 'master' into feature/icat-to-panosc-data-model-conversi…
MRichards99 Jan 31, 2022
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
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