Skip to content

Commit aaab1f3

Browse files
authored
Merge pull request #203 from hubmapconsortium/test-release
v2.0.12 release
2 parents 97bf743 + c37ab07 commit aaab1f3

7 files changed

Lines changed: 124 additions & 212 deletions

File tree

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.0.11
1+
2.0.12

src/app.py

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -780,9 +780,10 @@ def create_entity(entity_type):
780780
except schema_errors.InvalidNormalizedEntityTypeException as e:
781781
bad_request_error(f"Invalid entity type provided: {entity_type}")
782782

783-
# Execute validators defined in schema yaml before entity creation
783+
# Execute entity level validator defined in schema yaml before entity creation
784+
# Currently on Dataset and Upload creation require application header
784785
try:
785-
schema_manager.execute_entity_level_validators('before_entity_create_validator', normalized_entity_type, request.headers)
786+
schema_manager.execute_entity_level_validator('before_entity_create_validator', normalized_entity_type, request.headers)
786787
except schema_errors.MissingApplicationHeaderException as e:
787788
bad_request_error(e)
788789
except schema_errors.InvalidApplicationHeaderException as e:
@@ -972,13 +973,8 @@ def update_entity(id):
972973
# Normalize user provided entity_type
973974
normalized_entity_type = schema_manager.normalize_entity_type(entity_dict['entity_type'])
974975

975-
# Execute validators defined in schema yaml before entity update
976-
try:
977-
schema_manager.execute_entity_level_validators('before_entity_update_validator', normalized_entity_type, request.headers)
978-
except schema_errors.MissingApplicationHeaderException as e:
979-
bad_request_error(e)
980-
except schema_errors.InvalidApplicationHeaderException as e:
981-
bad_request_error(e)
976+
# Note, we don't support entity level validators on entity update via PUT
977+
# Only entity create via POST is supported at the entity level
982978

983979
# Validate request json against the yaml schema
984980
# Pass in the entity_dict for missing required key check, this is different from creating new entity
@@ -988,17 +984,14 @@ def update_entity(id):
988984
# No need to log the validation errors
989985
bad_request_error(str(e))
990986

991-
# Currently only Dataset.status has such proerty level validator
992-
# Execute validators defined in schema yaml before entity property update
987+
# Execute property level validators defined in schema yaml before entity property update
993988
try:
994-
schema_manager.execute_property_level_validators('before_property_update_validators', normalized_entity_type, request.headers, json_data_dict)
995-
except schema_errors.MissingApplicationHeaderException as e:
996-
bad_request_error(e)
997-
except schema_errors.InvalidApplicationHeaderException as e:
989+
schema_manager.execute_property_level_validators('before_property_update_validators', normalized_entity_type, request.headers, entity_dict, json_data_dict)
990+
except (schema_errors.MissingApplicationHeaderException,
991+
schema_errors.InvalidApplicationHeaderException,
992+
KeyError,
993+
ValueError) as e:
998994
bad_request_error(e)
999-
except ValueError as e:
1000-
bad_request_error(e)
1001-
1002995

1003996
# Sample, Dataset, and Upload: additional validation, update entity, after_update_trigger
1004997
# Collection and Donor: update entity

src/app_neo4j_queries.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ def get_ancestor_organs(neo4j_driver, entity_uuid):
195195
# apoc.coll.toSet() reruns a set containing unique nodes
196196
f"RETURN apoc.coll.toSet(COLLECT(organ)) AS {record_field_name}")
197197

198-
logger.debug("======create_entity() query======")
198+
logger.debug("======get_ancestor_organs() query======")
199199
logger.debug(query)
200200

201201
with neo4j_driver.session() as session:

src/schema/provenance_schema.yaml

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
############################################# Rules #############################################
2-
# type: data type of the property, one of the following: string|integer|boolean|list|json_string
3-
# generated: whether the property is auto genearated or user provided, default to false
4-
# required_on_create: whether the property is required from user reqeust JSON for entity creation via POST
5-
# immutable: whether the property can NOT be updated once being created, default to false
6-
# transient: whether the property to persist in database or not, default to false
7-
# exposed: whether the property gets returned to the user or not, default to true
8-
# trigger types: before_create_trigger|after_create_trigger|before_update_trigger|after_update_trigger|on_read_trigger, one property can have none (default) or more than one triggers
2+
# Entity properties:
3+
# - type: data type of the property, one of the following: string|integer|boolean|list|json_string
4+
# - generated: whether the property is auto genearated or user provided, default to false
5+
# - required_on_create: whether the property is required from user reqeust JSON for entity creation via POST
6+
# - immutable: whether the property can NOT be updated once being created, default to false
7+
# - transient: whether the property to persist in database or not, default to false
8+
# - exposed: whether the property gets returned to the user or not, default to true
9+
# - trigger types: before_create_trigger|after_create_trigger|before_update_trigger|after_update_trigger|on_read_trigger, one property can have none (default) or more than one triggers
10+
# - updated_peripherally: a temporary measure to correctly handle any attributes which are potentially updated by multiple triggers
911

10-
# Entity creation via http POST request, either standalone or derived:
12+
# Entity level validator:
13+
# - types: before_entity_create_validator, a single validation method needed for creating or updating the entity
14+
15+
# Property level validators:
16+
# - types: before_property_update_validators, a list of validation methods
17+
18+
# Entity creation via http POST request:
1119
# - Use `generated: true` to mark a property as to be auto generated by the program instead of from user input JSON
1220
# - If a property is marked as `generated: true`, either no trigger method needed (E.g. Donor.image_files) or a `before_create_trigger` can be used to generate the value
1321
# - If a property has `before_create_trigger`, it can't be specified in client request JSON
@@ -27,7 +35,6 @@
2735
# - If a property has `on_read_trigger`, it must be `transient: true`, meaning it's not stored in neo4j and only available during response
2836
# - If a property is `transient: true`, it can have `on_read_trigger` or not have one (when `exposed: false`)
2937

30-
3138
############################################# Schema #############################################
3239
# Shared properties across ACTIVITIES and ENTITIES
3340
shared_properties: &shared_properties
@@ -98,17 +105,11 @@ doi_properties: &doi_properties
98105
# So we can't define it as `immutable: true`
99106
type: string
100107
description: "The doi of the registered entity. e.g. 10.35079/hbm289.pcbm.487"
101-
# Only allowed applications can update this property via PUT
102-
before_property_update_validators:
103-
- validate_application_header_before_property_update
104108
doi_url:
105109
# This property gets set via a PUT by ingest-api after the DOI is created via DataCite
106110
# So we can't define it as `immutable: true`
107111
type: string
108112
description: "The url from the doi registry for this entity. e.g. https://doi.org/10.35079/hbm289.pcbm.487"
109-
# Only allowed applications can update this property via PUT
110-
before_property_update_validators:
111-
- validate_application_header_before_property_update
112113
# Currently cretors and contacts are only available for public Collections
113114
creators:
114115
type: list
@@ -228,7 +229,6 @@ ENTITIES:
228229
required_on_create: true # Only required for create via POST, not update via PUT
229230
description: "True if the data contains any human genetic sequence information."
230231
status:
231-
# Only allowed applications can update this property via PUT
232232
before_property_update_validators:
233233
- validate_application_header_before_property_update
234234
- validate_dataset_status_value
@@ -615,8 +615,6 @@ ENTITIES:
615615
Upload:
616616
# Only allowed applications can create new Upload via POST
617617
before_entity_create_validator: validate_application_header_before_entity_create
618-
# Only allowed applications can update an existing Upload via PUT
619-
before_entity_update_validator: validate_application_header_before_entity_update
620618
# Upload requires an ancestor of Lab, and and has no allowed decesndents
621619
derivation:
622620
source: false
@@ -633,6 +631,7 @@ ENTITIES:
633631
description: "Title of the datasets, a sentance or less"
634632
status:
635633
before_property_update_validators:
634+
- validate_application_header_before_property_update
636635
- validate_upload_status_value
637636
type: string
638637
generated: true

src/schema/schema_manager.py

Lines changed: 16 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -690,18 +690,20 @@ def validate_json_data_against_schema(json_data_dict, normalized_entity_type, ex
690690

691691

692692
"""
693-
Execute the entity level validators defined in the schema yaml
693+
Execute the entity level validator of the given type defined in the schema yaml
694+
before entity creation via POST or entity update via PUT
695+
Only one validator defined per given validator type, no need to support multiple validators
694696
695697
Parameters
696698
----------
697699
validator_type : str
698-
One of the validator types: before_entity_create_validator, before_entity_update_validator
700+
One of the validator types: before_entity_create_validator
699701
normalized_entity_type : str
700702
One of the normalized entity types defined in the schema yaml: Donor, Sample, Dataset, Upload
701703
request_headers: Flask request.headers object, behaves like a dict
702704
The instance of Flask request.headers passed in from application request
703705
"""
704-
def execute_entity_level_validators(validator_type, normalized_entity_type, request_headers):
706+
def execute_entity_level_validator(validator_type, normalized_entity_type, request_headers):
705707
global _schema
706708

707709
# A bit validation
@@ -733,19 +735,22 @@ def execute_entity_level_validators(validator_type, normalized_entity_type, requ
733735

734736
"""
735737
Execute the property level validators defined in the schema yaml
738+
before property update via PUT
736739
737740
Parameters
738741
----------
739742
validator_type : str
740-
For now only: before_property_update_validators
743+
For now only: before_property_update_validators (support multiple validators)
741744
normalized_entity_type : str
742745
One of the normalized entity types defined in the schema yaml: Donor, Sample, Dataset, Upload
743746
request_headers: Flask request.headers object, behaves like a dict
744747
The instance of Flask request.headers passed in from application request
745-
request_json_data : dict
748+
existing_data_dict : dict
749+
A dictionary that contains all existing entity properties
750+
new_data_dict : dict
746751
The json data in request body, already after the regular validations
747752
"""
748-
def execute_property_level_validators(validator_type, normalized_entity_type, request_headers, request_json_data):
753+
def execute_property_level_validators(validator_type, normalized_entity_type, request_headers, existing_data_dict, new_data_dict):
749754
global _schema
750755

751756
schema_section = None
@@ -758,7 +763,7 @@ def execute_property_level_validators(validator_type, normalized_entity_type, re
758763

759764
for key in properties:
760765
# Only run the validators for keys present in the request json
761-
if (key in request_json_data) and (validator_type in properties[key]):
766+
if (key in new_data_dict) and (validator_type in properties[key]):
762767
# Get a list of defined validators on this property
763768
validators_list = properties[key][validator_type]
764769
# Run each validator defined on this property
@@ -769,7 +774,7 @@ def execute_property_level_validators(validator_type, normalized_entity_type, re
769774

770775
logger.debug(f"To run {validator_type}: {validator_method_name} defined for entity {normalized_entity_type} on property {key}")
771776

772-
validator_method_to_call(key, normalized_entity_type, request_headers, request_json_data)
777+
validator_method_to_call(key, normalized_entity_type, request_headers, existing_data_dict, new_data_dict)
773778
except schema_errors.MissingApplicationHeaderException as e:
774779
raise schema_errors.MissingApplicationHeaderException(e)
775780
except schema_errors.InvalidApplicationHeaderException as e:
@@ -782,128 +787,6 @@ def execute_property_level_validators(validator_type, normalized_entity_type, re
782787
logger.exception(msg)
783788

784789

785-
"""
786-
Get a list of application applications (other than users) that can
787-
create new entity or update the existing entity of the given type
788-
789-
Parameters
790-
----------
791-
normalized_entity_type : str
792-
One of the normalized entity types: Dataset, Upload
793-
action : str
794-
applications_allowed_on_entity_create or applications_allowed_on_entity_update
795-
796-
Returns
797-
-------
798-
list
799-
A list of applications (normlized with lowercase)
800-
"""
801-
def get_entity_level_allowed_applications(normalize_entity_type, action):
802-
global _schema
803-
804-
applications = []
805-
entity = _schema['ENTITIES'][normalize_entity_type]
806-
key = action
807-
808-
# When not specified, both users and applications can create this entity
809-
if (key in entity) and isinstance(entity[key], list):
810-
# Lowercase all applications in the list via a list comprehension
811-
applications = [application.lower() for application in entity[key]]
812-
813-
return applications
814-
815-
"""
816-
Get a list of applications (other than users) that can
817-
update the existing entity property of the given property key
818-
819-
Parameters
820-
----------
821-
normalized_entity_type : str
822-
Dataset (Dataset.status is the only property requires this for now)
823-
property_key : str
824-
The target property key that requires allowed applications to update
825-
826-
Returns
827-
-------
828-
list
829-
A list of applications (normlized with lowercase)
830-
"""
831-
def get_property_level_allowed_applications_on_update_only(normalize_entity_type, property_key):
832-
global _schema
833-
834-
applications = []
835-
properties = _schema['ENTITIES'][normalize_entity_type]['properties']
836-
action = 'applications_allowed_on_property_update'
837-
838-
# When not specified, both users and applications can update this entity
839-
if (property_key in properties) and (action in properties[property_key]) and isinstance(properties[property_key][action], list):
840-
# Lowercase all applications in the list via a list comprehension
841-
applications = [application.lower() for application in properties[property_key][action]]
842-
843-
return applications
844-
845-
"""
846-
Check if the application allowed to create or update this entity
847-
848-
Parameters
849-
----------
850-
normalized_entity_type : str
851-
One of the normalized entity types: Dataset, Upload
852-
request_headers: Flask request.headers object, behaves like a dict
853-
The instance of Flask request.headers passed in from application request
854-
action : str
855-
applications_allowed_on_entity_create or applications_allowed_on_entity_update
856-
"""
857-
def validate_entity_level_application(normalized_entity_type, request_headers, action):
858-
# Get the list of applications allowed to create or update this entity
859-
# Returns empty list if no restrictions, meaning both users and aplications can create or update
860-
applications_allowed = get_entity_level_allowed_applications(normalized_entity_type, action)
861-
862-
# When application required
863-
if applications_allowed:
864-
# HTTP header names are case-insensitive
865-
# request_headers.get('X-Hubmap-Application') returns None is the header doesn't exist
866-
if not request_headers.get('X-Hubmap-Application'):
867-
msg = "Unbale to proceed due to missing X-Hubmap-Application header from request"
868-
raise schema_errors.MissingApplicationHeaderException(msg)
869-
870-
# Use lowercase for comparing the application header value against the yaml
871-
if request_headers.get('X-Hubmap-Application').lower() not in applications_allowed:
872-
msg = f"Unable to proceed due to invalid X-Hubmap-Application header value: {request_headers.get('X-Hubmap-Application')}"
873-
raise schema_errors.InvalidApplicationHeaderException(msg)
874-
875-
876-
"""
877-
Check if the application allowed to update this entity property
878-
879-
Parameters
880-
----------
881-
normalized_entity_type : str
882-
Dataset (Dataset.status is the only property requires this for now)
883-
property_key : str
884-
The target property key that requires allowed applications to update
885-
request_headers: Flask request.headers object, behaves like a dict
886-
The instance of Flask request.headers passed in from application request
887-
"""
888-
def validate_property_level_application_on_update_only(normalized_entity_type, property_key, request_headers):
889-
# Get the list of applications allowed to update this entity property, e.g., Dataset.status
890-
# Returns empty list if no restrictions, meaning both users and aplications can update
891-
applications_allowed = get_property_level_allowed_applications_on_update_only(normalized_entity_type, property_key)
892-
893-
# When application required
894-
if applications_allowed:
895-
# HTTP header names are case-insensitive
896-
# request_headers.get('X-Hubmap-Application') returns None is the header doesn't exist
897-
if not request_headers.get('X-Hubmap-Application'):
898-
msg = f"Unable to update {property_key} due to missing X-Hubmap-Application header from request"
899-
raise schema_errors.MissingApplicationHeaderException(msg)
900-
901-
# Use lowercase for comparing the application header value against the yaml
902-
if request_headers.get('X-Hubmap-Application').lower() not in applications_allowed:
903-
msg = f"Unable to update {property_key} due to invalid application header value: {request_headers.get('X-Hubmap-Application')}"
904-
raise schema_errors.InvalidApplicationHeaderException(msg)
905-
906-
907790
"""
908791
Get a list of entity types that can be used as derivation source in the schmea yaml
909792
@@ -989,10 +872,10 @@ def validate_trigger_type(trigger_type):
989872
Parameters
990873
----------
991874
validator_type : str
992-
One of the validator types: before_create_validator, before_update_validator
875+
One of the validator types: before_entity_create_validator
993876
"""
994877
def validate_entity_level_validator_type(validator_type):
995-
accepted_validator_types = ['before_entity_create_validator', 'before_entity_update_validator']
878+
accepted_validator_types = ['before_entity_create_validator']
996879
separator = ', '
997880

998881
if validator_type.lower() not in accepted_validator_types:
@@ -1007,7 +890,7 @@ def validate_entity_level_validator_type(validator_type):
1007890
Parameters
1008891
----------
1009892
validator_type : str
1010-
One of the validator types: before_create_validator, before_update_validator
893+
One of the validator types: before_property_update_validators
1011894
"""
1012895
def validate_property_level_validator_type(validator_type):
1013896
accepted_validator_types = ['before_property_update_validators']

src/schema/schema_triggers.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -537,9 +537,6 @@ def update_file_descriptions(property_key, normalized_type, user_token, existing
537537
raise KeyError(f"Missing '{property_key}' key in 'generated_dict' during calling 'update_file_descriptions()' trigger method.")
538538
existing_files_list = generated_dict[property_key]
539539

540-
541-
542-
543540
file_info_by_uuid_dict = {}
544541

545542
for file_info in existing_files_list:

0 commit comments

Comments
 (0)