Skip to content

Commit

Permalink
Add filter support to rel and invrel endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-ball committed Nov 3, 2021
1 parent 85f697f commit 0bed6d8
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 30 deletions.
72 changes: 58 additions & 14 deletions rdamsc/api2.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,18 +320,10 @@ def unwild(word: str) -> str:
if state[-1] in [WORD, ANDWORD, NOTWORD]:
state.append(QUOTE)
continue
elif state[-1] == QUOTE:
state.pop()
word = check_type("".join(working[level]))
working[level] = list()
push_item((field, word))
if field_level == level:
field = None
continue
elif state[-1] in [RANGE, TORANGE]:
state.append(RQUOTE)
continue
elif state[-1] == RQUOTE:
elif state[-1] in [QUOTE, RQUOTE]:
state.pop()
continue

Expand Down Expand Up @@ -531,7 +523,11 @@ def extract_values(record: Mapping, fieldpath: deque) -> List:
return values


def passes_filter(record: Record, filter: Union[List, Tuple]) -> bool:
def passes_filter(
record: Record,
filter: Union[List, Tuple],
exact: bool = False,
) -> bool:
'''Determines whether the record passes the filter (True) or
is filtered out (False).
Expand Down Expand Up @@ -574,14 +570,22 @@ def passes_filter(record: Record, filter: Union[List, Tuple]) -> bool:

if test_cls == str:
for v in [d for d in values_to_test if isinstance(d, str)]:
if filter[1].casefold() in v.casefold():
return True
if exact:
if filter[1].casefold() == v.casefold():
return True
else:
if filter[1].casefold() in v.casefold():
return True
return False

if test_cls == re.Pattern:
for v in [d for d in values_to_test if isinstance(d, str)]:
if filter[1].search(v):
return True
if exact:
if filter[1].match(v):
return True
else:
if filter[1].search(v):
return True
return False

if test_cls == tuple:
Expand Down Expand Up @@ -705,6 +709,26 @@ def get_relations():
rel_records = rel.tb.all()
rel_records.sort(key=lambda k: sortval(k.get('@id')))

# Get filter parameter
filter = request.values.get('q')
if filter:
try:
parsed_filter = parse_query(filter)
except ValueError as e:
response = {
'apiVersion': api_version,
'error': {
'message': f"Bad q parameter: {e}",
}
}
return jsonify(response), 400

filtered = [
k for k in rel_records
if passes_filter(k, parsed_filter, exact=True)
]
rel_records = filtered

# Get paging parameters:
start_raw = request.values.get('start')
start = int(start_raw) if start_raw else None
Expand Down Expand Up @@ -755,6 +779,26 @@ def get_inv_relations():
rel_record.update(rel.related(mscid, direction=rel.INVERSE))
rel_records.append(rel_record)

# Get filter parameter
filter = request.values.get('q')
if filter:
try:
parsed_filter = parse_query(filter)
except ValueError as e:
response = {
'apiVersion': api_version,
'error': {
'message': f"Bad q parameter: {e}",
}
}
return jsonify(response), 400

filtered = [
k for k in rel_records
if passes_filter(k, parsed_filter, exact=True)
]
rel_records = filtered

# Get paging parameters:
start_raw = request.values.get('start')
start = int(start_raw) if start_raw else None
Expand Down
75 changes: 60 additions & 15 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,8 +476,57 @@ def get_apidataset(self, table: str):
i += 1
return apidataset

def get_apirel(self, record: str, inverse=False):
'''Returns relation in form that API would respond with.'''
rel_id = f'msc:{record}'
if inverse:
apirel = {
"@id": rel_id,
"uri": f'http://localhost/api2/invrel/{record}'}
i = 1
while hasattr(self, f'rel{i}'):
rel = getattr(self, f'rel{i}')
id = rel['@id']
for predicate, objects in rel.items():
if predicate in ['@id', 'uri']:
continue
tag = self.rv_tags[predicate].replace('_', ' ')
if '{}' in tag:
tag = tag.format(self.rc_cls.get(id[4:5]))
for object in objects:
if object != apirel['@id']:
continue
apirel.setdefault(tag, list()).append(id)
i += 1
else:
rel = dict()
if record.startswith("rel"):
rel = getattr(self, record)
rel_id = rel["@id"]
else:
i = 1
while hasattr(self, f'rel{i}'):
test_rel = getattr(self, f'rel{i}')
if test_rel['@id'] == rel_id:
rel = test_rel
break
i += 1
apirel = {
"@id": rel_id,
"uri": f'http://localhost/api2/rel/{rel_id[4:]}'}
apirel.update(rel)

table_order = {'m': 0, 't': 10, 'c': 20, 'g': 30, 'e': 40}
n = 5
for predicate in apirel.keys():
if isinstance(apirel[predicate], list):
apirel[predicate].sort(
key=lambda k: table_order[k[n - 1:n]] + int(k[n:]))
return apirel

def get_apirelset(self, inverse=False):
'''Returns table of relations in form that API would respond with.'''
table_order = {'m': 0, 't': 10, 'c': 20, 'g': 30, 'e': 40}
apidataset = list()
i = 1
n = 5
Expand All @@ -493,33 +542,29 @@ def get_apirelset(self, inverse=False):
if '{}' in tag:
tag = tag.format(self.rc_cls.get(id[4:5]))
for object in objects:
if object not in reldict:
reldict[object] = dict()
if tag not in reldict[object]:
reldict[object][tag] = list()
reldict[object][tag].append(id)
reldict.setdefault(object, dict()).setdefault(
tag, list()
).append(id)
i += 1

for id in sorted(reldict.keys(),
key=lambda k: k[:n] + k[n:].zfill(5)):
item = {
"@id": id,
"uri": f'http://localhost/api2/invrel/{id[4:]}'}
item.update(reldict[id])
apidataset.append(item)

for item in apidataset:
for predicate in item.keys():
if isinstance(item[predicate], list):
item[predicate].sort(
key=lambda k: table_order[k[n - 1:n]] + int(k[n:]))
else:
while hasattr(self, f'rel{i}'):
record = getattr(self, f'rel{i}')
record['uri'] = (
f'http://localhost/api2/rel/'f'{record["@id"][4:]}')
apidataset.append(record)
apidataset.append(self.get_apirel(f'rel{i}'))
i += 1

table_order = {'m': 0, 't': 10, 'c': 20, 'g': 30, 'e': 40}
for results in apidataset:
for predicate in results.keys():
if isinstance(results[predicate], list):
results[predicate].sort(
key=lambda k: table_order[k[n - 1:n]] + int(k[n:]))
apidataset.sort(
key=lambda k: table_order[k['@id'][n - 1:n]] + int(k['@id'][n:]))
return apidataset
Expand Down
45 changes: 44 additions & 1 deletion tests/test_api2.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,15 +585,58 @@ def mimic_output(items: t.List, query: str) -> dict:
assert json.dumps(ideal, sort_keys=True) == actual

query = '/api2/m?q=Test AND (Unmatched'
response = client.get(query, follow_redirects=True)
assert response.status_code == 400
result = response.get_json()
assert result['error']['message'] == "Bad q parameter: Unmatched parentheses."

# On rel endpoint
query = '/api2/rel?q=funders:"msc:g1"'
items = [
data_db.get_apidata("m2", with_embedded=False),
data_db.get_apirel("m1"),
data_db.get_apirel("m3"),
]
response = client.get(query, follow_redirects=True)
assert response.status_code == 200
actual = json.dumps(response.get_json(), sort_keys=True)
ideal = mimic_output(items, query)
assert json.dumps(ideal, sort_keys=True) == actual

query = '/api2/rel?q=Test AND (Unmatched'
response = client.get(query, follow_redirects=True)
assert response.status_code == 400
result = response.get_json()
assert result['error']['message'] == "Bad q parameter: Unmatched parentheses."

# On invrel endpoint
query = '/api2/invrel?q=funded\\ schemes:"msc:m1"'
items = [
data_db.get_apirel("g1", inverse=True),
]
response = client.get(query, follow_redirects=True)
assert response.status_code == 200
actual = json.dumps(response.get_json(), sort_keys=True)
ideal = mimic_output(items, query)
assert json.dumps(ideal, sort_keys=True) == actual

query = '/api2/invrel?q="funded schemes":"msc:m1"'
items = [
data_db.get_apirel("g1", inverse=True),
]
response = client.get(query, follow_redirects=True)
assert response.status_code == 200
actual = json.dumps(response.get_json(), sort_keys=True)
ideal = mimic_output(items, query)
assert json.dumps(ideal, sort_keys=True) == actual

query = '/api2/invrel?q=Test AND (Unmatched'
response = client.get(query, follow_redirects=True)
assert response.status_code == 400
result = response.get_json()
assert result['error']['message'] == "Bad q parameter: Unmatched parentheses."



def test_thesaurus(client, app, data_db):

# Test getting full scheme record
Expand Down

0 comments on commit 0bed6d8

Please sign in to comment.