From 8df655830c426f2a618192f495a4c6103f2d95c6 Mon Sep 17 00:00:00 2001 From: DerekFurstPitt Date: Wed, 4 Sep 2024 17:37:44 -0400 Subject: [PATCH 1/3] Added a bypass in metadata_constraints.py for any non-donor entries in the constraints field. Only donors should be evaluated, everything else passes automatically with a nothing to validate 200 response --- src/metadata_constraints.py | 69 ++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/src/metadata_constraints.py b/src/metadata_constraints.py index 132625eb..5300aebe 100644 --- a/src/metadata_constraints.py +++ b/src/metadata_constraints.py @@ -159,37 +159,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['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 From c2bf51c78c30e3a3a66613fbab65629c16f6426d Mon Sep 17 00:00:00 2001 From: DerekFurstPitt Date: Thu, 5 Sep 2024 14:03:31 -0400 Subject: [PATCH 2/3] refactored constraints_json_is_valid into metadata_constraints and removed extraneous index value --- src/app.py | 36 ++---------------------------------- src/metadata_constraints.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/app.py b/src/app.py index 09138734..c20b04ea 100644 --- a/src/app.py +++ b/src/app.py @@ -2316,9 +2316,8 @@ def validate_constraints(): if not request.is_json: bad_request_error("A json body and appropriate Content-Type header are required") json_entry = request.get_json() - is_valid = constraints_json_is_valid(json_entry) - if is_valid is not True: - bad_request_error(is_valid) + if not isinstance(json_entry, list): + return "JSON body expects a list." is_match = request.values.get('match') order = request.values.get('order') @@ -2329,9 +2328,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: @@ -4599,35 +4596,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 diff --git a/src/metadata_constraints.py b/src/metadata_constraints.py index 5300aebe..4d949ca3 100644 --- a/src/metadata_constraints.py +++ b/src/metadata_constraints.py @@ -1,4 +1,8 @@ from deepdiff import DeepDiff +from flask import abort + +def bad_request_error(err_msg): + abort(400, description = err_msg) def build_constraint(ancestor: dict, descendants: list[dict]) -> dict: return { @@ -151,9 +155,37 @@ 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(entry): + if not isinstance(entry, dict): + return "Each constraint in the list must be a JSON object." + + for key in entry: + if key not in ["ancestors", "descendants"]: + return f"Invalid key '{key}'. Allowed keys are 'ancestors' and 'descendants'." + + value = entry[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: + is_valid = constraints_json_is_valid(entry) + if is_valid is not True: + bad_request_error(is_valid) entry_key1 = get_constraint_unit(entry.get(key1)) msg = f"Missing `{key1}` in request. Use orders=ancestors|descendants request param to specify. Default: ancestors" result = {'code': 400, 'name': "Bad Request"} if is_match else {'code': 200, 'name': 'OK', 'description': 'Nothing to validate.'} From 994e9c5884959edc3108364dc50628c76befe901 Mon Sep 17 00:00:00 2001 From: DerekFurstPitt Date: Thu, 5 Sep 2024 14:39:40 -0400 Subject: [PATCH 3/3] reverted earlier refactoring of constraints_json_is_valid and simply moved the function definition into metadata_constraints --- src/app.py | 7 +++--- src/metadata_constraints.py | 48 ++++++++++++++++++------------------- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/app.py b/src/app.py index c20b04ea..68306f63 100644 --- a/src/app.py +++ b/src/app.py @@ -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 @@ -2316,8 +2316,9 @@ def validate_constraints(): if not request.is_json: bad_request_error("A json body and appropriate Content-Type header are required") json_entry = request.get_json() - if not isinstance(json_entry, list): - return "JSON body expects a list." + is_valid = constraints_json_is_valid(json_entry) + if is_valid is not True: + bad_request_error(is_valid) is_match = request.values.get('match') order = request.values.get('order') diff --git a/src/metadata_constraints.py b/src/metadata_constraints.py index 4d949ca3..5686eca9 100644 --- a/src/metadata_constraints.py +++ b/src/metadata_constraints.py @@ -1,8 +1,4 @@ from deepdiff import DeepDiff -from flask import abort - -def bad_request_error(err_msg): - abort(400, description = err_msg) def build_constraint(ancestor: dict, descendants: list[dict]) -> dict: return { @@ -162,30 +158,32 @@ def validate_exclusions(entry, constraint, key) -> bool: Returns true if the json matches the required format. If invalid, returns a string explaining why. """ -def constraints_json_is_valid(entry): - if not isinstance(entry, dict): - return "Each constraint in the list must be a JSON object." - - for key in entry: - if key not in ["ancestors", "descendants"]: - return f"Invalid key '{key}'. Allowed keys are 'ancestors' and 'descendants'." - - value = entry[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." +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: - is_valid = constraints_json_is_valid(entry) - if is_valid is not True: - bad_request_error(is_valid) entry_key1 = get_constraint_unit(entry.get(key1)) msg = f"Missing `{key1}` in request. Use orders=ancestors|descendants request param to specify. Default: ancestors" result = {'code': 400, 'name': "Bad Request"} if is_match else {'code': 200, 'name': 'OK', 'description': 'Nothing to validate.'} @@ -196,7 +194,7 @@ def get_constraints(entry, key1, key2, is_match=False) -> dict: 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['entity_type'].lower() == 'donor': + 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: