Skip to content

Commit

Permalink
Merge pull request #736 from hubmapconsortium/Derek-Furst/constraints…
Browse files Browse the repository at this point in the history
…-allow-non-donors

Added a bypass in metadata_constraints.py for any non-donor entries i…
  • Loading branch information
yuanzhou authored Sep 6, 2024
2 parents 99afd46 + 994e9c5 commit cf9ca70
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 63 deletions.
33 changes: 1 addition & 32 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from schema.schema_constants import DataVisibilityEnum
from schema.schema_constants import MetadataScopeEnum
from schema.schema_constants import TriggerTypeEnum
from metadata_constraints import get_constraints
from metadata_constraints import get_constraints, constraints_json_is_valid
# from lib.ontology import initialize_ubkg, init_ontology, Ontology, UbkgSDK


Expand Down Expand Up @@ -2329,9 +2329,7 @@ def validate_constraints():
'name': "ok"
}

index = 0
for constraint in json_entry:
index += 1
if order == 'descendants':
result = get_constraints(constraint, 'descendants', 'ancestors', is_match)
else:
Expand Down Expand Up @@ -4599,35 +4597,6 @@ def internal_server_error(err_msg):
abort(500, description = err_msg)


"""
Validates the incoming json for the endpoint /constraints.
Returns true if the json matches the required format. If
invalid, returns a string explaining why.
"""
def constraints_json_is_valid(json_entry):
if not isinstance(json_entry, list):
return "JSON body expects a list."

for constraint in json_entry:
if not isinstance(constraint, dict):
return "Each constraint in the list must be a JSON object."

for key in constraint:
if key not in ["ancestors", "descendants"]:
return f"Invalid key '{key}'. Allowed keys are 'ancestors' and 'descendants'."

value = constraint[key]
if isinstance(value, dict):
continue
elif isinstance(value, list):
for item in value:
if not isinstance(item, dict):
return f"The value for '{key}' must be represented as a JSON object or as a list of objects"
else:
return f"The value for '{key}' must be a JSON object or a list of JSON objects."
return True


"""
Parse the token from Authorization header
Expand Down
99 changes: 68 additions & 31 deletions src/metadata_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,36 @@ def validate_exclusions(entry, constraint, key) -> bool:
return True
else:
return False


"""
Validates the incoming json for the endpoint /constraints.
Returns true if the json matches the required format. If
invalid, returns a string explaining why.
"""
def constraints_json_is_valid(json_entry):
if not isinstance(json_entry, list):
return "JSON body expects a list."

for constraint in json_entry:
if not isinstance(constraint, dict):
return "Each constraint in the list must be a JSON object."

for key in constraint:
if key not in ["ancestors", "descendants"]:
return f"Invalid key '{key}'. Allowed keys are 'ancestors' and 'descendants'."

value = constraint[key]
if isinstance(value, dict):
continue
elif isinstance(value, list):
for item in value:
if not isinstance(item, dict):
return f"The value for '{key}' must be represented as a JSON object or as a list of objects"
else:
return f"The value for '{key}' must be a JSON object or a list of JSON objects."
return True



def get_constraints(entry, key1, key2, is_match=False) -> dict:
Expand All @@ -159,37 +189,44 @@ def get_constraints(entry, key1, key2, is_match=False) -> dict:
result = {'code': 400, 'name': "Bad Request"} if is_match else {'code': 200, 'name': 'OK', 'description': 'Nothing to validate.'}

if entry_key1 is not None:
report = determine_constraint_from_entity(entry_key1)
constraints = report.get('constraints')
if report.get('error') is not None and not constraints:
result = {'code': 400, 'name': "Bad Request", 'description': report.get('error')}

for constraint in constraints:
const_key1 = get_constraint_unit(constraint.get(key1))

if DeepDiff(entry_key1, const_key1, ignore_string_case=True, exclude_types=[type(None)]) == {}: # or validate_exclusions(entry_key1, const_key1, 'sub_type_val'):
const_key2 = constraint.get(key2)

if is_match:
entry_key2 = entry.get(key2)
v = validate_constraint_units_to_entry_units(entry_key2, const_key2)
if entry_key2 is not None and v:
result = {'code': 200, 'name': 'OK', 'description': const_key2}
"""
We only are interested in validating the constraints of donors to verify that their descendants are Organ samples.
For any other entity type, we are passing automatically with the message "Nothing to valiate". For donors, validation
Occurs as normal, with the constraint requiring a descendant that is an organ.
"""
if entry_key1.get('entity_type') and entry_key1.get('entity_type').lower() == 'donor':
report = determine_constraint_from_entity(entry_key1)
constraints = report.get('constraints')
if report.get('error') is not None and not constraints:
result = {'code': 400, 'name': "Bad Request", 'description': report.get('error')}

for constraint in constraints:
const_key1 = get_constraint_unit(constraint.get(key1))

if DeepDiff(entry_key1, const_key1, ignore_string_case=True, exclude_types=[type(None)]) == {}: # or validate_exclusions(entry_key1, const_key1, 'sub_type_val'):
const_key2 = constraint.get(key2)

if is_match:
entry_key2 = entry.get(key2)
v = validate_constraint_units_to_entry_units(entry_key2, const_key2)
if entry_key2 is not None and v:
result = {'code': 200, 'name': 'OK', 'description': const_key2}
else:
entity_type = entry_key1.get('entity_type')
entity_type = entity_type.title() if entity_type is not None else entity_type
sub_type = entry_key1.get('sub_type')
sub_type = ', '.join(sub_type) if sub_type is not None else ''
msg = (
f"This `{entity_type}` `{sub_type}` cannot be associated with the provided `{key1}` due to entity constraints. "
f"Click the link to view valid entity types that can be `{key2}`"
)
result = {'code' :404, 'name': msg, 'description': const_key2}
else:
entity_type = entry_key1.get('entity_type')
entity_type = entity_type.title() if entity_type is not None else entity_type
sub_type = entry_key1.get('sub_type')
sub_type = ', '.join(sub_type) if sub_type is not None else ''
msg = (
f"This `{entity_type}` `{sub_type}` cannot be associated with the provided `{key1}` due to entity constraints. "
f"Click the link to view valid entity types that can be `{key2}`"
)
result = {'code' :404, 'name': msg, 'description': const_key2}
else:
result = {'code': 200, 'name': 'OK', 'description': const_key2}
break

else:
result = {'code' :404, 'name': f"No matching constraints on given '{key1}"}
result = {'code': 200, 'name': 'OK', 'description': const_key2}
break

else:
result = {'code' :404, 'name': f"No matching constraints on given '{key1}"}
else:
result = {'code': 200, 'name': 'OK', 'description': 'Nothing to validate.'}
return result

0 comments on commit cf9ca70

Please sign in to comment.