From e7807c6410d7a1fa6f2691328e204263313f1e89 Mon Sep 17 00:00:00 2001 From: ChuckKollar Date: Fri, 2 Aug 2024 15:48:56 -0400 Subject: [PATCH 1/7] New endpoints (PUT /datasets and PUT /uploads) to handle the bulk updating of entities --- entity-api-spec.yaml | 54 ++++++++++++++ src/app.py | 156 ++++++++++++++++++++++++++++++++++++++- src/app_neo4j_queries.py | 47 ++++++++++++ 3 files changed, 254 insertions(+), 3 deletions(-) diff --git a/entity-api-spec.yaml b/entity-api-spec.yaml index 538bf712..8b8d8cd6 100644 --- a/entity-api-spec.yaml +++ b/entity-api-spec.yaml @@ -2532,6 +2532,60 @@ paths: '500': description: Internal error + '/datasets': + put: + summary: Bulk updating of entity type dataset. it's only limited to the fields:: assigned_to_group_name, ingest_task, status + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + entities: + type: array + items: + $ref: '#/components/schemas/Dataset' + responses: + '202': + description: request is being processed + content: + application/json: + schema: + type: array + properties: + uuid: + type: string + description: The uuids of the entities being processed + '500': + description: Internal error + '/uploads': + put: + summary: Bulk updating of entity type upload. it's only limited to the fields:: assigned_to_group_name, ingest_task, status + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + entities: + type: array + items: + $ref: '#/components/schemas/Upload' + responses: + '202': + description: request is being processed + content: + application/json: + schema: + type: array + properties: + uuid: + type: string + description: The uuids of the entities being processed + '500': + description: Internal error '/samples/prov-info': get: summary: 'returns all provenance information for a each sample in a json format' diff --git a/src/app.py b/src/app.py index 0e722c88..7f9881ac 100644 --- a/src/app.py +++ b/src/app.py @@ -1,6 +1,5 @@ import collections -import yaml -from typing import List +from typing import Callable, List, Optional from datetime import datetime from flask import Flask, g, jsonify, abort, request, Response, redirect, make_response from neo4j.exceptions import TransactionError @@ -8,6 +7,8 @@ import re import csv import requests +from requests.adapters import HTTPAdapter, Retry +import threading import urllib.request from io import StringIO # Don't confuse urllib (Python native library) with urllib3 (3rd-party library, requests also uses urllib3) @@ -4212,6 +4213,155 @@ def multiple_components(): return jsonify(normalized_complete_entity_list) +def bulk_update_entities( + entity_updates: dict, + token: str, + entity_api_url: str, + total_tries: int = 3, + throttle: float = 5, + after_each_callback: Optional[Callable[[int], None]] = None, +) -> dict: + """Bulk update the entities in the entity-api. + + This function supports request throttling and retries. + + Parameters + ---------- + entity_updates : dict + The dictionary of entity updates. The key is the uuid and the value is the + update dictionary. + token : str + The groups token for the request. + entity_api_url : str + The url of the entity-api. + total_tries : int, optional + The number of total requests to be made for each update, by default 3. + throttle : float, optional + The time to wait between requests and retries, by default 5. + after_each_callback : Callable[[int], None], optional + A callback function to be called after each update, by default None. The index + of the update is passed as a parameter to the callback. + + Returns + ------- + dict + The results of the bulk update. The key is the uuid of the entity. If + successful, the value is a dictionary with "success" as True and "data" as the + entity data. If failed, the value is a dictionary with "success" as False and + "data" as the error message. + """ + headers = { + "Authorization": f"Bearer {token}", + "X-Application": "entity-api", + } + # create a session with retries + session = requests.Session() + session.headers = headers + retries = Retry( + total=total_tries, + backoff_factor=throttle, + status_forcelist=[500, 502, 503, 504], + ) + session.mount(entity_api_url, HTTPAdapter(max_retries=retries)) + + results = {} + with session as s: + for idx, (uuid, payload) in enumerate(entity_updates.items()): + try: + # https://github.com/hubmapconsortium/entity-api/issues/698#issuecomment-2260799700 + # yuanzhou: When you iterate over the target uuids make individual PUT /entities/ calls. + # The main reason we use the PUT call rather than direct neo4j query is because the entity update + # needs to go through the schema trigger methods and generate corresponding values programmatically + # before sending over to neo4j. + # The PUT call returns the response immediately while the backend updating may be still going on. + res = s.put( + f"{entity_api_url}/entities/{uuid}", json=payload, timeout=15 + ) + results[uuid] = { + "success": res.ok, + "data": res.json() if res.ok else res.json().get("error"), + } + except requests.exceptions.RequestException as e: + logger.error(f"Failed to update entity {uuid}: {e}") + results[uuid] = {"success": False, "data": str(e)} + + if after_each_callback: + after_each_callback(idx) + + if idx < len(entity_updates) - 1: + time.sleep(throttle) + + return results + + +def update_datasets_uploads(entity_updates: list, token: str, entity_api_url: str) -> None: + """ + For this call to work READ_ONLY_MODE = False in the app.cfg file. + """ + update_payload = {ds.pop("uuid"): ds for ds in entity_updates} + + # send the dataset/upload updates to entity-api + update_res = bulk_update_entities(update_payload, token, entity_api_url) + + for uuid, res in update_res.items(): + if not res["success"]: + logger.error(f"Failed to update entity {uuid}: {res['data']}") + + +ENTITY_BULK_UPDATE_FIELDS_ACCEPTED = ['uuid', 'status', 'ingest_task', 'assigned_to_group_name'] + + +@app.route('/datasets', methods=['PUT']) +@app.route('/uploads', methods=['PUT']) +def entity_bulk_update(): + entity_type: str = 'dataset' + if request.path == "/uploads": + entity_type = "upload" + + validate_token_if_auth_header_exists(request) + require_json(request) + + entities = request.get_json().get('entities') + if entities is None or not isinstance(entities, list) or len(entities) == 0: + bad_request_error("Request object field 'entities' is either missing, " + "does not contain a list, or contains an empty list") + + user_token: str = get_user_token(request) + for entity in entities: + validate_user_update_privilege(entity, user_token) + + uuids = [e.get("uuid") for e in entities] + if None in uuids: + bad_request_error(f"All {entity_type}s must have a 'uuid' field") + if len(set(uuids)) != len(uuids): + bad_request_error(f"{entity_type}s must have unique 'uuid' fields") + + if not all(set(e.keys()).issubset(ENTITY_BULK_UPDATE_FIELDS_ACCEPTED) for e in entities): + bad_request_error( + f"Some {entity_type}s have invalid fields. Acceptable fields are: " + + ", ".join(ENTITY_BULK_UPDATE_FIELDS_ACCEPTED) + ) + + uuids = set([e["uuid"] for e in entities]) + try: + fields = {"uuid", "entity_type"} + db_entities = app_neo4j_queries.get_entities_by_uuid(neo4j_driver_instance, uuids, fields) + except Exception as e: + logger.error(f"Error while submitting datasets: {str(e)}") + bad_request_error(str(e)) + + diff = uuids.difference({e["uuid"] for e in db_entities if e["entity_type"].lower() == entity_type}) + if len(diff) > 0: + bad_request_error(f"No {entity_type} found with the following uuids: {', '.join(diff)}") + + entity_api_url = app.config["ENTITY_API_URL"].rstrip('/') + thread_instance =\ + threading.Thread(target=update_datasets_uploads, + args=(entities, user_token, entity_api_url)) + thread_instance.start() + + return jsonify(list(uuids)), 202 + #################################################################################################### ## Internal Functions @@ -4382,7 +4532,7 @@ def validate_user_update_privilege(entity, user_token): abort(user_write_groups) user_group_uuids = [d['uuid'] for d in user_write_groups] - if entity['group_uuid'] not in user_group_uuids and is_admin is False: + if entity.get('group_uuid') not in user_group_uuids and is_admin is False: forbidden_error(f"User does not have write privileges for this entity. " f"Reach out to the help desk (help@hubmapconsortium.org) to request access to group: {entity['group_uuid']}.") diff --git a/src/app_neo4j_queries.py b/src/app_neo4j_queries.py index 9341318f..bd116c6e 100644 --- a/src/app_neo4j_queries.py +++ b/src/app_neo4j_queries.py @@ -1,3 +1,4 @@ +from typing import Iterable, List, Optional, Union from neo4j.exceptions import TransactionError import logging import json @@ -1286,3 +1287,49 @@ def uuids_all_exist(neo4j_driver, uuids:list): if (expected_match_count == match_count): return True raise Exception(f"For {expected_match_count} uuids, only found {match_count}" f" exist as node identifiers in the Neo4j graph.") + + +def get_entities_by_uuid(neo4j_driver, + uuids: Union[str, Iterable], + fields: Union[dict, Iterable, None] = None) -> Optional[list]: + """Get the entities from the neo4j database with the given uuids. + Parameters + ---------- + uuids : Union[str, Iterable] + The uuid(s) of the entities to get. + fields : Union[dict, Iterable, None], optional + The fields to return for each entity. If None, all fields are returned. + If a dict, the keys are the database fields to return and the values are the names to return them as. + If an iterable, the fields to return. Defaults to None. + Returns + ------- + Optional[List[neo4j.Record]]: + The entity records with the given uuids, or None if no datasets were found. + The specified fields are returned for each entity. + Raises + ------ + ValueError + If fields is not a dict, an iterable, or None. + """ + if isinstance(uuids, str): + uuids = [uuids] + if not isinstance(uuids, list): + uuids = list(uuids) + + if fields is None or len(fields) == 0: + return_stmt = 'e' + elif isinstance(fields, dict): + return_stmt = ', '.join([f'e.{field} AS {name}' for field, name in fields.items()]) + elif isinstance(fields, Iterable): + return_stmt = ', '.join([f'e.{field} AS {field}' for field in fields]) + else: + raise ValueError("fields must be a dict or an iterable") + + with neo4j_driver.session() as session: + length = len(uuids) + query = "MATCH (e:Entity) WHERE e.uuid IN $uuids RETURN " + return_stmt + records = session.run(query, uuids=uuids).fetch(length) + if records is None or len(records) == 0: + return None + + return records From da1d6404269ffabfdee5020a057743e6a5353057 Mon Sep 17 00:00:00 2001 From: ChuckKollar Date: Mon, 5 Aug 2024 16:02:11 -0400 Subject: [PATCH 2/7] Fixed comments from Joe from last PR --- src/app.py | 71 ++++++++++++++++++--------------- src/schema/schema_constants.py | 1 + src/schema/schema_validators.py | 10 +++-- 3 files changed, 45 insertions(+), 37 deletions(-) diff --git a/src/app.py b/src/app.py index 7f9881ac..0b1375a6 100644 --- a/src/app.py +++ b/src/app.py @@ -4213,6 +4213,34 @@ def multiple_components(): return jsonify(normalized_complete_entity_list) +# Bulk update the entities in the entity-api. +# +# This function supports request throttling and retries. +# +# Parameters +# ---------- +# entity_updates : dict +# The dictionary of entity updates. The key is the uuid and the value is the +# update dictionary. +# token : str +# The groups token for the request. +# entity_api_url : str +# The url of the entity-api. +# total_tries : int, optional +# The number of total requests to be made for each update, by default 3. +# throttle : float, optional +# The time to wait between requests and retries, by default 5. +# after_each_callback : Callable[[int], None], optional +# A callback function to be called after each update, by default None. The index +# of the update is passed as a parameter to the callback. +# +# Returns +# ------- +# dict +# The results of the bulk update. The key is the uuid of the entity. If +# successful, the value is a dictionary with "success" as True and "data" as the +# entity data. If failed, the value is a dictionary with "success" as False and +# "data" as the error message. def bulk_update_entities( entity_updates: dict, token: str, @@ -4221,38 +4249,9 @@ def bulk_update_entities( throttle: float = 5, after_each_callback: Optional[Callable[[int], None]] = None, ) -> dict: - """Bulk update the entities in the entity-api. - - This function supports request throttling and retries. - - Parameters - ---------- - entity_updates : dict - The dictionary of entity updates. The key is the uuid and the value is the - update dictionary. - token : str - The groups token for the request. - entity_api_url : str - The url of the entity-api. - total_tries : int, optional - The number of total requests to be made for each update, by default 3. - throttle : float, optional - The time to wait between requests and retries, by default 5. - after_each_callback : Callable[[int], None], optional - A callback function to be called after each update, by default None. The index - of the update is passed as a parameter to the callback. - - Returns - ------- - dict - The results of the bulk update. The key is the uuid of the entity. If - successful, the value is a dictionary with "success" as True and "data" as the - entity data. If failed, the value is a dictionary with "success" as False and - "data" as the error message. - """ headers = { "Authorization": f"Bearer {token}", - "X-Application": "entity-api", + "X-Application": SchemaConstants.ENTITY_API_APP, } # create a session with retries session = requests.Session() @@ -4294,10 +4293,8 @@ def bulk_update_entities( return results +# For this call to work READ_ONLY_MODE = False in the app.cfg file. def update_datasets_uploads(entity_updates: list, token: str, entity_api_url: str) -> None: - """ - For this call to work READ_ONLY_MODE = False in the app.cfg file. - """ update_payload = {ds.pop("uuid"): ds for ds in entity_updates} # send the dataset/upload updates to entity-api @@ -4311,6 +4308,14 @@ def update_datasets_uploads(entity_updates: list, token: str, entity_api_url: st ENTITY_BULK_UPDATE_FIELDS_ACCEPTED = ['uuid', 'status', 'ingest_task', 'assigned_to_group_name'] +# New endpoints (PUT /datasets and PUT /uploads) to handle the bulk updating of entities see Issue: #698 +# https://github.com/hubmapconsortium/entity-api/issues/698 +# +# This is used by Data Ingest Board application for now. +# +# Shirey: With this use case we're not worried about a lot of concurrent calls to this endpoint (only one user, +# Brendan, will be ever using it). Just start a thread on request and loop through the Datasets/Uploads to change +# with a 5 second delay or so between them to allow some time for reindexing. @app.route('/datasets', methods=['PUT']) @app.route('/uploads', methods=['PUT']) def entity_bulk_update(): diff --git a/src/schema/schema_constants.py b/src/schema/schema_constants.py index 84e97c5d..7096f93d 100644 --- a/src/schema/schema_constants.py +++ b/src/schema/schema_constants.py @@ -3,6 +3,7 @@ class SchemaConstants(object): MEMCACHED_TTL = 7200 INGEST_API_APP = 'ingest-api' + ENTITY_API_APP = 'entity-api' COMPONENT_DATASET = 'component-dataset' INGEST_PIPELINE_APP = 'ingest-pipeline' HUBMAP_APP_HEADER = 'X-Hubmap-Application' diff --git a/src/schema/schema_validators.py b/src/schema/schema_validators.py index 1571b58b..adaef227 100644 --- a/src/schema/schema_validators.py +++ b/src/schema/schema_validators.py @@ -31,11 +31,13 @@ The instance of Flask request passed in from application request """ def validate_application_header_before_entity_create(normalized_entity_type, request): - # A list of applications allowed to create this new entity - # Currently only ingest-api and ingest-pipeline are allowed - # to create or update Dataset and Upload + # A list of applications allowed to create this new entity or update Dataset and Upload # Use lowercase for comparison - applications_allowed = [SchemaConstants.INGEST_API_APP, SchemaConstants.INGEST_PIPELINE_APP] + applications_allowed = [ + SchemaConstants.INGEST_API_APP, + SchemaConstants.INGEST_PIPELINE_APP, + SchemaConstants.ENTITY_API_APP + ] _validate_application_header(applications_allowed, request.headers) From 7798bf4899168592d6bc0a486c78916c23c8a5f2 Mon Sep 17 00:00:00 2001 From: ChuckKollar Date: Tue, 6 Aug 2024 14:17:38 -0400 Subject: [PATCH 3/7] Changed the Request parameters, and removed group_uuid in entity which will not exist in some circumstances. --- entity-api-spec.yaml | 28 ++++++++++------------------ src/app.py | 16 ++++++++++++++-- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/entity-api-spec.yaml b/entity-api-spec.yaml index 8b8d8cd6..c93413b8 100644 --- a/entity-api-spec.yaml +++ b/entity-api-spec.yaml @@ -2540,12 +2540,9 @@ paths: content: application/json: schema: - type: object - properties: - entities: - type: array - items: - $ref: '#/components/schemas/Dataset' + type: array + items: + $ref: '#/components/schemas/Dataset' responses: '202': description: request is being processed @@ -2553,9 +2550,8 @@ paths: application/json: schema: type: array - properties: - uuid: - type: string + items: + type: string description: The uuids of the entities being processed '500': description: Internal error @@ -2567,12 +2563,9 @@ paths: content: application/json: schema: - type: object - properties: - entities: - type: array - items: - $ref: '#/components/schemas/Upload' + type: array + items: + $ref: '#/components/schemas/Upload' responses: '202': description: request is being processed @@ -2580,9 +2573,8 @@ paths: application/json: schema: type: array - properties: - uuid: - type: string + items: + type: string description: The uuids of the entities being processed '500': description: Internal error diff --git a/src/app.py b/src/app.py index 0b1375a6..fc9b0419 100644 --- a/src/app.py +++ b/src/app.py @@ -4290,6 +4290,7 @@ def bulk_update_entities( if idx < len(entity_updates) - 1: time.sleep(throttle) + logger.info(f"bulk_update_entities() results: {results}") return results @@ -4316,6 +4317,17 @@ def update_datasets_uploads(entity_updates: list, token: str, entity_api_url: st # Shirey: With this use case we're not worried about a lot of concurrent calls to this endpoint (only one user, # Brendan, will be ever using it). Just start a thread on request and loop through the Datasets/Uploads to change # with a 5 second delay or so between them to allow some time for reindexing. +# +# Example call +# 1) pick Dataset entities to change by querying Neo4J... +# URL: http://18.205.215.12:7474/browser/ +# query: MATCH (e:Dataset {entity_type: 'Dataset'}) RETURN e.uuid, e.status, e.ingest_task, e.assigned_to_group_name LIMIT 100 +# +# curl --request PUT \ +# --url ${ENTITY_API}/datasets \ +# --header "Content-Type: application/json" \ +# --header "Authorization: Bearer ${TOKEN}" \ +# --data '[{"uuid":"6ce8d4515bc87213e787397c2b4d2f99", "assigned_to_group_name":"TMC - Cal Tech"}, {"uuid":"a44a78bfbe0e702cdc172707b6061a16", "assigned_to_group_name":"TMC - Cal Tech"}]' @app.route('/datasets', methods=['PUT']) @app.route('/uploads', methods=['PUT']) def entity_bulk_update(): @@ -4326,7 +4338,7 @@ def entity_bulk_update(): validate_token_if_auth_header_exists(request) require_json(request) - entities = request.get_json().get('entities') + entities = request.get_json() if entities is None or not isinstance(entities, list) or len(entities) == 0: bad_request_error("Request object field 'entities' is either missing, " "does not contain a list, or contains an empty list") @@ -4539,7 +4551,7 @@ def validate_user_update_privilege(entity, user_token): user_group_uuids = [d['uuid'] for d in user_write_groups] if entity.get('group_uuid') not in user_group_uuids and is_admin is False: forbidden_error(f"User does not have write privileges for this entity. " - f"Reach out to the help desk (help@hubmapconsortium.org) to request access to group: {entity['group_uuid']}.") + "Please reach out to the help desk (help@hubmapconsortium.org) to request access.") """ From dbf13b292aeb592b9e5e7f22bf04d832283f3909 Mon Sep 17 00:00:00 2001 From: ChuckKollar Date: Wed, 7 Aug 2024 09:41:08 -0400 Subject: [PATCH 4/7] X-Application -> X-Hubmap-Application --- src/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.py b/src/app.py index fc9b0419..e668ae84 100644 --- a/src/app.py +++ b/src/app.py @@ -4251,7 +4251,7 @@ def bulk_update_entities( ) -> dict: headers = { "Authorization": f"Bearer {token}", - "X-Application": SchemaConstants.ENTITY_API_APP, + "X-Hubmap-Application": SchemaConstants.ENTITY_API_APP, } # create a session with retries session = requests.Session() From 8b3e20f9e25a8542bacfd768abbdb71f0fcb86c6 Mon Sep 17 00:00:00 2001 From: ChuckKollar Date: Wed, 7 Aug 2024 14:26:54 -0400 Subject: [PATCH 5/7] Added schema_validators.validate_application_header_before_property_update to checks for entity_bulk_update() --- src/app.py | 7 ++++++- src/schema/schema_validators.py | 20 +++++++------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/app.py b/src/app.py index e668ae84..4aa1e960 100644 --- a/src/app.py +++ b/src/app.py @@ -4251,7 +4251,7 @@ def bulk_update_entities( ) -> dict: headers = { "Authorization": f"Bearer {token}", - "X-Hubmap-Application": SchemaConstants.ENTITY_API_APP, + SchemaConstants.HUBMAP_APP_HEADER: SchemaConstants.ENTITY_API_APP, } # create a session with retries session = requests.Session() @@ -4327,6 +4327,7 @@ def update_datasets_uploads(entity_updates: list, token: str, entity_api_url: st # --url ${ENTITY_API}/datasets \ # --header "Content-Type: application/json" \ # --header "Authorization: Bearer ${TOKEN}" \ +# --header "X-Hubmap-Application: entity-api" \ # --data '[{"uuid":"6ce8d4515bc87213e787397c2b4d2f99", "assigned_to_group_name":"TMC - Cal Tech"}, {"uuid":"a44a78bfbe0e702cdc172707b6061a16", "assigned_to_group_name":"TMC - Cal Tech"}]' @app.route('/datasets', methods=['PUT']) @app.route('/uploads', methods=['PUT']) @@ -4337,6 +4338,10 @@ def entity_bulk_update(): validate_token_if_auth_header_exists(request) require_json(request) + try: + schema_validators.validate_application_header_before_property_update(request) + except Exception as e: + bad_request_error(str(e)) entities = request.get_json() if entities is None or not isinstance(entities, list) or len(entities) == 0: diff --git a/src/schema/schema_validators.py b/src/schema/schema_validators.py index adaef227..4e01e14f 100644 --- a/src/schema/schema_validators.py +++ b/src/schema/schema_validators.py @@ -286,23 +286,17 @@ def collection_entities_are_existing_datasets(property_key, normalized_entity_ty Parameters ---------- -property_key : str - The target property key -normalized_type : str - Dataset request: Flask request object The instance of Flask request passed in from application request -existing_data_dict : dict - A dictionary that contains all existing entity properties -new_data_dict : dict - The json data in request body, already after the regular validations """ -def validate_application_header_before_property_update(property_key, normalized_entity_type, request, existing_data_dict, new_data_dict): - # A list of applications allowed to update this property - # Currently only ingest-api and ingest-pipeline are allowed - # to update Dataset.status or Upload.status +def validate_application_header_before_property_update(request): + # A list of applications allowed to update Dataset.status or Upload.status. # Use lowercase for comparison - applications_allowed = [SchemaConstants.INGEST_API_APP, SchemaConstants.INGEST_PIPELINE_APP] + applications_allowed = [ + SchemaConstants.INGEST_API_APP, + SchemaConstants.INGEST_PIPELINE_APP, + SchemaConstants.ENTITY_API_APP + ] _validate_application_header(applications_allowed, request.headers) From a7e0a9044d398eae3e6d64a00f35f2ec2f0618d3 Mon Sep 17 00:00:00 2001 From: ChuckKollar Date: Thu, 8 Aug 2024 11:07:02 -0400 Subject: [PATCH 6/7] Reverteded validator signature and removed explicit call in app.py --- src/app.py | 4 ---- src/schema/schema_validators.py | 12 ++++++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/app.py b/src/app.py index 4aa1e960..1431ba5e 100644 --- a/src/app.py +++ b/src/app.py @@ -4338,10 +4338,6 @@ def entity_bulk_update(): validate_token_if_auth_header_exists(request) require_json(request) - try: - schema_validators.validate_application_header_before_property_update(request) - except Exception as e: - bad_request_error(str(e)) entities = request.get_json() if entities is None or not isinstance(entities, list) or len(entities) == 0: diff --git a/src/schema/schema_validators.py b/src/schema/schema_validators.py index 4e01e14f..7e040e09 100644 --- a/src/schema/schema_validators.py +++ b/src/schema/schema_validators.py @@ -286,11 +286,19 @@ def collection_entities_are_existing_datasets(property_key, normalized_entity_ty Parameters ---------- +property_key : str + The target property key +normalized_type : str + Dataset request: Flask request object The instance of Flask request passed in from application request +existing_data_dict : dict + A dictionary that contains all existing entity properties +new_data_dict : dict + The json data in request body, already after the regular validations """ -def validate_application_header_before_property_update(request): - # A list of applications allowed to update Dataset.status or Upload.status. +def validate_application_header_before_property_update(property_key, normalized_entity_type, request, existing_data_dict, new_data_dict): + # A list of applications allowed to update Dataset.status or Upload.status # Use lowercase for comparison applications_allowed = [ SchemaConstants.INGEST_API_APP, From 26fea17c829e2836dfa8bc81d0541f0d0166744b Mon Sep 17 00:00:00 2001 From: ChuckKollar Date: Thu, 8 Aug 2024 11:24:04 -0400 Subject: [PATCH 7/7] Made the changes that Joe wanted --- src/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.py b/src/app.py index 1431ba5e..5b5acd6d 100644 --- a/src/app.py +++ b/src/app.py @@ -4328,7 +4328,7 @@ def update_datasets_uploads(entity_updates: list, token: str, entity_api_url: st # --header "Content-Type: application/json" \ # --header "Authorization: Bearer ${TOKEN}" \ # --header "X-Hubmap-Application: entity-api" \ -# --data '[{"uuid":"6ce8d4515bc87213e787397c2b4d2f99", "assigned_to_group_name":"TMC - Cal Tech"}, {"uuid":"a44a78bfbe0e702cdc172707b6061a16", "assigned_to_group_name":"TMC - Cal Tech"}]' +# --data '[{"uuid":"f22a9ba97b79eefe6b152b4315e43c76", "status":"Error", "assigned_to_group_name":"TMC - Cal Tech"}, {"uuid":"e4b371ea3ed4c3ca77791b34b829803f", "status":"Error", "assigned_to_group_name":"TMC - Cal Tech"}]' @app.route('/datasets', methods=['PUT']) @app.route('/uploads', methods=['PUT']) def entity_bulk_update():