def test_get_entry_by_name(self): """Get an entry based on its prop_name""" mapper = {"1": "user", "2": "username", "3": "phone"} input_format = [{ "prop": "1", "value": [ { "prop": "2", "value": "Edward" }, { "prop": "3", "value": "+33(0)123456789" }, ], }] c = FormatConverter(key_name="prop", value_name="value", mapper=mapper) c.add_api_format(input_format) user_entry = c.entries[0] self.assertEqual( user_entry.value.get_entry_by_name("username").value, "Edward")
def get(self, dataset_uuid, study_id=None, user=None): """ Fetch a specific dataset for a given study """ args = self._get_parser.parse_args() prop_id_to_name = get_property_map(key="id", value="name") prop_name_to_id = reverse_map(prop_id_to_name) # Used for helper route using only dataset_uuid if study_id is None: study_id = find_study_id_from_lvl1_uuid("dataset", dataset_uuid, prop_name_to_id) if study_id is None: raise Exception( f"Dataset not found in any study (uuid = {dataset_uuid})") study = Study.objects().get(id=study_id) study_json = marshal(study, study_model) # The converter is used for its get_entry_by_name() method study_converter = FormatConverter(mapper=prop_id_to_name) study_converter.add_api_format(study_json["entries"]) datasets_entry = study_converter.get_entry_by_name("datasets") dataset_nested_entry = datasets_entry.value.find_nested_entry( "uuid", dataset_uuid)[0] # The "dataset_nested_entry" entry is a NestedEntry (return list of dict) if args["entry_format"] == "api": return dataset_nested_entry.get_api_format() elif args["entry_format"] == "form": return dataset_nested_entry.get_form_format()
def test_form_to_api_format_case_3(self): mapper = {"storage": "1", "location": "2", "number_of_files": "3"} form_format = { "storage": { "location": "C:/Documents", "number_of_files": 1000 } } expected_api_format = [{ "prop": "1", "value": [ { "prop": "2", "value": "C:/Documents" }, { "prop": "3", "value": 1000 }, ], }] c = FormatConverter(key_name="prop", value_name="value", mapper=mapper) actual_api_format = c.add_form_format(form_format).get_api_format() self.assertEqual(expected_api_format, actual_api_format)
def test_api_to_form_format_case_3(self): """A nested form field. Only one form field is accepted""" mapper = {"1": "storage", "2": "location", "3": "number_of_files"} input_format = [{ "prop": "1", "value": [ { "prop": "2", "value": "C:/Documents" }, { "prop": "3", "value": 1000 }, ], }] expected_form_format = { "storage": { "location": "C:/Documents", "number_of_files": 1000 } } c = FormatConverter(key_name="prop", value_name="value", mapper=mapper) actual_form_format = c.add_api_format(input_format).get_form_format() self.assertEqual(expected_form_format, actual_form_format)
def test_remove_entries(self): mapper = { "KEEP": "0", "REMOVE_1": "1", "REMOVE_2": "2", "REMOVE_3": "3" } form_format = {"KEEP": 1, "REMOVE_1": 2, "REMOVE_2": 3, "REMOVE_3": 4} c = FormatConverter(mapper=mapper).add_form_format(form_format) assert len(c.entries) == 4 form_format_to_remove = {"REMOVE_1": 2} c_remove = FormatConverter( mapper=mapper).add_form_format(form_format_to_remove) c.remove_entries(entries=c_remove.entries) assert len(c.entries) == 3 c.remove_entries(prop_names=["REMOVE_2"]) assert len(c.entries) == 2 c.remove_entries(prop_ids=["3"]) assert len(c.entries) == 1 assert c.entries[0].prop_name == "KEEP"
def test_api_to_api_format_case_2(self): """List of simple values""" mapper = {"1": "username"} input_format = [{"prop": "1", "value": ["Edward", "Annie"]}] c = FormatConverter(key_name="prop", value_name="value", mapper=mapper) reconstructed_api_format = c.add_api_format( input_format).get_api_format() self.assertEqual(input_format, reconstructed_api_format)
def test_form_to_api_format_case_2(self): mapper = {"username": "******"} form_format = {"username": ["Edward", "Annie"]} expected_api_format = [{"prop": "1", "value": ["Edward", "Annie"]}] c = FormatConverter(key_name="prop", value_name="value", mapper=mapper) actual_api_format = c.add_form_format(form_format).get_api_format() self.assertEqual(expected_api_format, actual_api_format)
def test_api_to_form_format_case_1(self): """Simple value""" mapper = {"1": "username"} input_format = [{"prop": "1", "value": "Edward"}] expected_form_format = {"username": "******"} c = FormatConverter(key_name="prop", value_name="value", mapper=mapper) actual_form_format = c.add_api_format(input_format).get_form_format() self.assertEqual(expected_form_format, actual_form_format)
def get(self, user=None): """ Fetch a list with all entries """ # Convert query parameters args = self._get_parser.parse_args() include_deprecate = args["deprecated"] res = Study.objects() if not include_deprecate: res = res.filter(meta_information__deprecated=False) study_ids = [ u.strip() for u in re.split(r",|;", args["study_ids"]) if u.strip().lower() not in ["", "0", "none", "false"] ] if len(study_ids) > 0: res = res.filter(id__in=study_ids) if res.count() < len(study_ids): found_ids = [str(s.id) for s in res.only("id")] return { "message": f"Some study ids were not found", "missing_study_ids": list(set(study_ids) - set(found_ids)), }, 404 # Limits and Skipping applied after main filters res = res.all().skip(args["skip"]) # Issue with limit(0) that returns 0 items instead of all of them if args["limit"] != 0: res = res.limit(args["limit"]) if args["properties_id_only"] or args["entry_format"] == "form": marchal_model = study_model_prop_id else: marchal_model = study_model # Marshal studies study_json_list = marshal(list(res.select_related()), marchal_model) if args["entry_format"] == "api": return study_json_list elif args["entry_format"] == "form": prop_map = get_property_map(key="id", value="name") for study_json in study_json_list: study_converter = FormatConverter(prop_map).add_api_format( study_json["entries"] ) study_json["entries"] = study_converter.get_form_format() return study_json_list
def delete(self, pe_uuid, study_id=None, dataset_uuid=None, user=None): """ Delete a processing event given its unique identifier """ prop_id_to_name = get_property_map(key="id", value="name") prop_name_to_id = reverse_map(prop_id_to_name) # Used for helper route using only pe_uuid if dataset_uuid is None: study_id, dataset_uuid = find_study_id_and_lvl1_entity_from_lvl2_uuid( lvl1_prop="dataset", lvl2_prop="process_event", lvl2_uuid=pe_uuid, prop_name_to_id=prop_name_to_id, prop_id_to_name=prop_id_to_name, ) if study_id is None or dataset_uuid is None: raise Exception( f"Processing event not found in any dataset (uuid = {pe_uuid})" ) # Used for helper route using only dataset_uuid if study_id is None: study_id = find_study_id_from_lvl1_uuid("dataset", dataset_uuid, prop_name_to_id) if study_id is None: raise Exception( f"Dataset not found in any study (uuid = {dataset_uuid})") # 1. Get study data study = Study.objects().get(id=study_id) study_json = marshal(study, study_model) study_converter = FormatConverter(mapper=prop_id_to_name) study_converter.add_api_format(study_json["entries"]) # 2. Get dataset data datasets_entry = study_converter.get_entry_by_name("datasets") dataset_nested_entry = datasets_entry.value.find_nested_entry( "uuid", dataset_uuid)[0] # 3. Delete specific processing event pes_entry = dataset_nested_entry.get_entry_by_name("process_events") pes_entry.value.delete_nested_entry("uuid", pe_uuid) if len(pes_entry.value.value) == 0: dataset_nested_entry.remove_entries(prop_names=["process_events"]) # 4. Update study state, data and ulpoad on DB message = f"Deleted processing event" update_study(study, study_converter, api.payload, message, user) return {"message": message}
def test_clean_data(self): "Remove empty values and perform other cleaning (like stripping strings)" mapper = { "TO_KEEP": "0", "TO_KEEP_1": "1", "TO_KEEP_2": "2", "TO_KEEP_3": "3", "TO_KEEP_4": "4", "TO_REMOVE": "5", "TO_REMOVE_2": "6", } form_format = { "TO_KEEP_1": 1, "TO_REMOVE": "", "TO_KEEP_2": [1, 2, ""], "TO_KEEP_3": { "TO_KEEP": 31, "TO_REMOVE": "" }, "TO_KEEP_4": [ { "TO_KEEP": 441, "TO_REMOVE": "" }, { "TO_REMOVE": "", "TO_REMOVE_2": "" }, ], } c = FormatConverter(mapper=mapper).add_form_format(form_format) c.clean_data() actual_output = c.get_form_format() expected_output = { "TO_KEEP_1": 1, "TO_KEEP_2": [1, 2], "TO_KEEP_3": { "TO_KEEP": 31 }, "TO_KEEP_4": [{ "TO_KEEP": 441 }, {}], } self.assertEqual(actual_output, expected_output)
def test_api_to_form_format_case_4(self): """List of nested form fields. Each form field is incorporated into a list""" mapper = {"1": "contacts", "3": "name", "4": "phone"} input_format = [{ "prop": "1", "value": [ [ { "prop": "3", "value": "Edward" }, { "prop": "4", "value": "903 367 2072" }, ], [ { "prop": "3", "value": "Annie" }, { "prop": "4", "value": "731 222 8842" }, ], ], }] expected_form_format = { "contacts": [ { "name": "Edward", "phone": "903 367 2072" }, { "name": "Annie", "phone": "731 222 8842" }, ] } c = FormatConverter(key_name="prop", value_name="value", mapper=mapper) actual_form_format = c.add_api_format(input_format).get_form_format() self.assertEqual(expected_form_format, actual_form_format)
def find_study_id_and_lvl1_entity_from_lvl2_uuid(lvl1_prop, lvl2_prop, lvl2_uuid, prop_name_to_id, prop_id_to_name): """ Find parent study and lvl1 entity (ex: Dataset) given a lvl2 uuid (ex: Processing event) """ study_id = None lvl1_uuid = None aggregated_studies = get_aggregated_studies( prop_map=prop_name_to_id, level_1=lvl1_prop, level_2=lvl2_prop, ) for potential_study in aggregated_studies: if lvl2_uuid in potential_study[f"{lvl2_prop}s_uuids"]: study = potential_study study_id = study["id"] break else: return study_id, lvl1_uuid lvl1_list_entry = NestedListEntry( FormatConverter(prop_id_to_name)).add_api_format(study["datasets"]) for lvl1_nested_entry in lvl1_list_entry.value: lvl1_uuid = lvl1_nested_entry.get_entry_by_name("uuid").value lvl1_entry = lvl1_nested_entry.get_entry_by_name(f"{lvl2_prop}s") try: lvl1_entry.value.find_nested_entry("uuid", lvl2_uuid) return study_id, lvl1_uuid except: pass # Lvl 2 entity not in this lvl 1 entity return study_id, lvl1_uuid
def test_form_to_api_format_case_4(self): mapper = {"contacts": "1", "name": "3", "phone": "4"} form_format = { "contacts": [ { "name": "Edward", "phone": "903 367 2072" }, { "name": "Annie", "phone": "731 222 8842" }, ] } actual_api_format = [{ "prop": "1", "value": [ [ { "prop": "3", "value": "Edward" }, { "prop": "4", "value": "903 367 2072" }, ], [ { "prop": "3", "value": "Annie" }, { "prop": "4", "value": "731 222 8842" }, ], ], }] c = FormatConverter(key_name="prop", value_name="value", mapper=mapper) expected_api_format = c.add_form_format(form_format).get_api_format() self.assertEqual(expected_api_format, actual_api_format)
def delete(self, sample_uuid, study_id=None, user=None): """ Delete a sample from a study given its unique identifier """ prop_id_to_name = get_property_map(key="id", value="name") prop_name_to_id = reverse_map(prop_id_to_name) # Used for helper route using only sample_uuid if study_id is None: study_id = find_study_id_from_lvl1_uuid("sample", sample_uuid, prop_name_to_id) if study_id is None: raise Exception( f"Sample not found in any study (uuid = {sample_uuid})") # 1. Get study data study = Study.objects().get(id=study_id) study_json = marshal(study, study_model) study_converter = FormatConverter(mapper=prop_id_to_name) study_converter.add_api_format(study_json["entries"]) # 2. Delete specific entity samples_entry = study_converter.get_entry_by_name("samples") samples_entry.value.delete_nested_entry("uuid", sample_uuid) if len(samples_entry.value.value) == 0: study_converter.remove_entries(prop_names=["samples"]) # 3. Update study state, data and ulpoad on DB message = f"Deleted sample" update_study(study, study_converter, api.payload, message, user) return {"message": message}
def test_add_or_update_entries(self): mapper = {"username": "******", "first_name": "2", "last_name": "3"} form_format_1 = {"username": "******", "first_name": "John"} form_format_2 = {"username": "******", "last_name": "Doe"} c_1 = FormatConverter(mapper=mapper).add_form_format(form_format_1) c_2 = FormatConverter(mapper=mapper).add_form_format(form_format_2) expected_output = { "username": "******", "first_name": "John", "last_name": "Doe", } actual_output = c_1.add_or_update_entries( c_2.entries).get_form_format() self.assertEqual(actual_output, expected_output)
def post(self, study_id, user=None): """ Add a new dataset for a given study """ payload = api.payload prop_id_to_name = get_property_map(key="id", value="name") prop_name_to_id = reverse_map(prop_id_to_name) # 1. Split payload form_name = payload["form_name"] entries = payload["entries"] entry_format = payload.get("entry_format", "api") # 2. Get study data study = Study.objects().get(id=study_id) study_json = marshal(study, study_model) study_converter = FormatConverter(mapper=prop_id_to_name) study_converter.add_api_format(study_json["entries"]) # 3. Create dataset entry and append it to "datasets" # Format and clean entity dataset_converter, _ = get_entity_converter(entries, entry_format, prop_id_to_name, prop_name_to_id) # Generate UUID dataset_converter, dataset_uuid = add_uuid_entry_if_missing( dataset_converter, prop_name_to_id) study_converter = add_entity_to_study_nested_list( study_converter=study_converter, entity_converter=dataset_converter, prop_name_to_id=prop_name_to_id, study_list_prop="datasets", ) # 4. Validate dataset data against form validate_form_format_against_form(form_name, dataset_converter.get_form_format()) # 5. Update study state, data and ulpoad on DB message = "Added dataset" update_study(study, study_converter, payload, message, user) return {"message": message, "uuid": dataset_uuid}, 201
def get(self, id, user=None): """Fetch an entry given its unique identifier""" args = self._get_parser.parse_args() if args["properties_id_only"] or args["entry_format"] == "form": marchal_model = study_model_prop_id else: marchal_model = study_model study_json = marshal(Study.objects(id=id).get(), marchal_model) if args["entry_format"] == "api": return study_json elif args["entry_format"] == "form": prop_map = get_property_map(key="id", value="name") study_converter = FormatConverter(prop_map).add_api_format( study_json["entries"] ) study_json["entries"] = study_converter.get_form_format() return study_json
def test_find_nested_entry(self): """Get a specific NestedEntry based on the value of a property""" mapper = {"1": "users", "2": "username", "3": "phone"} input_format = [{ "prop": "1", "value": [ [ { "prop": "2", "value": "Edward" }, { "prop": "3", "value": "+33(0)123456789" }, ], [ { "prop": "2", "value": "John" }, { "prop": "3", "value": "+33(9)876543210" }, ], ], }] c = FormatConverter(key_name="prop", value_name="value", mapper=mapper) c.add_api_format(input_format) users_entry = c.entries[0] nested_entry, position = users_entry.value.find_nested_entry( "username", "John") self.assertEqual(nested_entry.get_form_format()["phone"], "+33(9)876543210") self.assertEqual(position, 1)
def get(self, study_id, user=None): """ Fetch a list of all datasets for a given study """ args = self._get_parser.parse_args() study = Study.objects().get(id=study_id) study_json = marshal(study, study_model) prop_map = get_property_map(key="id", value="name") # The converter is used for its get_entry_by_name() method study_converter = FormatConverter(mapper=prop_map) study_converter.add_api_format(study_json["entries"]) datasets_entry = study_converter.get_entry_by_name("datasets") if datasets_entry is not None: # The "datasets" entry is a NestedListEntry (return list of list) if args["entry_format"] == "api": return datasets_entry.get_api_format() elif args["entry_format"] == "form": return datasets_entry.get_form_format() else: return []
def validate_against_form(form_cls, form_name, entries): prop_map = get_property_map(key="id", value="name") form_data_json = FormatConverter(prop_map).add_api_format(entries).get_form_format() # 3. Validate data against form form_instance = form_cls() form_instance.process(data=form_data_json) if not form_instance.validate(): raise RequestBodyException( f"Passed data did not validate with the form {form_name}: {form_instance.errors}" ) return form_data_json
def delete(self, study_id, user=None): """ Delete all samples from a study given its unique identifier """ prop_id_to_name = get_property_map(key="id", value="name") # 1. Get study data study = Study.objects().get(id=study_id) study_json = marshal(study, study_model) study_converter = FormatConverter(mapper=prop_id_to_name) study_converter.add_api_format(study_json["entries"]) # 2. Delete samples study_converter.remove_entries(prop_names=["samples"]) # 3. Update study state, data and ulpoad on DB message = "Deleted samples" update_study(study, study_converter, api.payload, message, user) return {"message": message}
def post(self, study_id, user=None): """ Add a new sample for a given study """ payload = api.payload prop_id_to_name = get_property_map(key="id", value="name") prop_name_to_id = reverse_map(prop_id_to_name) # 1. Split payload validate_dict = payload.get("validate", None) form_names = payload.get("form_names", None) entries = payload["entries"] entry_format = payload.get("entry_format", "api") # 2. Get forms for validation forms = {} for key, validate in validate_dict.items(): if validate: forms[key] = app.form_manager.get_form_by_name( form_name=form_names[key]) # 3. Get study data study = Study.objects().get(id=study_id) study_json = marshal(study, study_model) study_converter = FormatConverter(mapper=prop_id_to_name) study_converter.add_api_format(study_json["entries"]) # 4. Unify UUIDs with existing entities (including nested ones) # Format and clean entity sample_converter, _ = get_entity_converter(entries, entry_format, prop_id_to_name, prop_name_to_id) new_sample_form_format = sample_converter.get_form_format() [new_sample_form_format] = unify_sample_entities_uuids( existing_samples=study_converter.get_form_format().get( "samples", []), new_samples=[new_sample_form_format], ) # 5. Append new samples to "samples" in study # Format and clean entity sample_converter, _ = get_entity_converter( entries=new_sample_form_format, entry_format="form", prop_id_to_name=None, prop_name_to_id=prop_name_to_id, ) # Generate UUID (redundant, UUIDs already generated by unify_sample_entities_uuids) sample_converter, sample_uuid = add_uuid_entry_if_missing( sample_converter, prop_name_to_id) study_converter = add_entity_to_study_nested_list( study_converter=study_converter, entity_converter=sample_converter, prop_name_to_id=prop_name_to_id, study_list_prop="samples", ) # 6. Validate data against form validate_sample_against_form(sample_converter.get_form_format(), validate_dict, forms) # 7. Update study state, data and ulpoad on DB message = "Added sample" update_study(study, study_converter, payload, message, user) return {"message": message, "uuid": sample_uuid}, 201
def put(self, sample_uuid, study_id=None, user=None): """ Update a sample for a given study """ prop_id_to_name = get_property_map(key="id", value="name") prop_name_to_id = reverse_map(prop_id_to_name) # Used for helper route using only sample_uuid if study_id is None: study_id = find_study_id_from_lvl1_uuid("sample", sample_uuid, prop_name_to_id) if study_id is None: raise Exception( f"Sample not found in any study (uuid = {sample_uuid})") payload = api.payload # 1. Split payload validate_dict = payload.get("validate", None) form_names = payload.get("form_names", None) entries = payload["entries"] entry_format = payload.get("entry_format", "api") # 2. Get forms for validation forms = {} for key, validate in validate_dict.items(): if validate: forms[key] = app.form_manager.get_form_by_name( form_name=form_names[key]) # 3. Get study data study = Study.objects().get(id=study_id) study_json = marshal(study, study_model) study_converter = FormatConverter(mapper=prop_id_to_name) study_converter.add_api_format(study_json["entries"]) # 3. Get current sample data samples_entry = study_converter.get_entry_by_name("samples") sample_nested_entry = samples_entry.value.find_nested_entry( "uuid", sample_uuid)[0] sample_converter = FormatConverter(mapper=prop_id_to_name) sample_converter.entries = sample_nested_entry.value # 4. Unify UUIDs with existing entities (including nested ones) # Format and clean entity new_sample_converter, _ = get_entity_converter(entries, entry_format, prop_id_to_name, prop_name_to_id) new_sample_form_format = new_sample_converter.get_form_format() [new_sample_form_format] = unify_sample_entities_uuids( existing_samples=study_converter.get_form_format().get( "samples", []), new_samples=[new_sample_form_format], ) # 5. Clean new data and get entries to remove # Format and clean entity new_sample_converter, entries_to_remove = get_entity_converter( entries=new_sample_form_format, entry_format="form", prop_id_to_name=None, prop_name_to_id=prop_name_to_id, ) # 6. Update current sample by adding, updating and deleting entries # Nested entries not present in the original form are ignored # won't be deleted if not present in the new data), it needs to be None or "" to be deleted sample_converter.add_or_update_entries(new_sample_converter.entries) sample_converter.remove_entries(entries=entries_to_remove) sample_nested_entry.value = sample_converter.entries # 7. Validate data against form validate_sample_against_form(sample_converter.get_form_format(), validate_dict, forms) # 8. Update study state, data and ulpoad on DB message = "Updated sample" update_study(study, study_converter, payload, message, user) return {"message": message}
def upload_study_related_entity(data, url, method, property_url, headers): """Send data to the API (study related entity) in form format""" entry_format = data.pop("entry_format", "form") # Format data (cleaning + conversion from "form format" to "api format") if entry_format == "form": prop_name_to_id = map_key_value(property_url, key="name", value="id") converter = FormatConverter(mapper=prop_name_to_id) converter.add_form_format(data["entries"]) elif entry_format == "api": prop_id_to_name = map_key_value(property_url, key="id", value="name") converter = FormatConverter(mapper=prop_id_to_name) converter.add_api_format(data["entries"]) converter.clean_data() data["entries"] = converter.get_api_format() data["entry_format"] = "api" # Send data to API if method == "post": res = requests.post(url=url, json=data, headers=headers) if res.status_code != 201: message = f"Failed to POST study related entity. {res.json()}" success = False else: message = "Succeded to POST study related entity" success = True elif method == "put": res = requests.put(url=url, json=data, headers=headers) if res.status_code != 200: message = f"Failed to PUT study related entity. {res.json()}" success = False else: message = "Succeded to PUT study related entity" success = True return (res.json(), message, success)
def post(self, dataset_uuid, study_id=None, user=None): """ Add a new processing event for a given dataset """ prop_id_to_name = get_property_map(key="id", value="name") prop_name_to_id = reverse_map(prop_id_to_name) # Used for helper route using only dataset_uuid if study_id is None: study_id = find_study_id_from_lvl1_uuid("dataset", dataset_uuid, prop_name_to_id) if study_id is None: raise Exception( f"Dataset not found in any study (uuid = {dataset_uuid})") payload = api.payload # 1. Split payload form_name = payload["form_name"] entries = payload["entries"] entry_format = payload.get("entry_format", "api") # 2. Get study and dataset data study = Study.objects().get(id=study_id) study_json = marshal(study, study_model) study_converter = FormatConverter(mapper=prop_id_to_name) study_converter.add_api_format(study_json["entries"]) datasets_entry = study_converter.get_entry_by_name("datasets") dataset_nested_entry, dataset_position = datasets_entry.value.find_nested_entry( "uuid", dataset_uuid) # 3. Format and clean processing event data pe_converter, _ = get_entity_converter(entries, entry_format, prop_id_to_name, prop_name_to_id) # 4. Generate UUID pe_converter, pe_uuid = add_uuid_entry_if_missing(pe_converter, prop_name_to_id, replace=False) pe_nested_entry = NestedEntry(pe_converter) pe_nested_entry.value = pe_converter.entries # 5. Check if "process_events"" entry already exist study, creates it if it doesn't pes_entry = dataset_nested_entry.get_entry_by_name("process_events") if pes_entry is not None: pes_entry.value.value.append(pe_nested_entry) else: pes_entry = Entry( FormatConverter(prop_name_to_id)).add_form_format( "process_events", [pe_nested_entry.get_form_format()]) dataset_nested_entry.value.append(pes_entry) datasets_entry.value.value[dataset_position] = dataset_nested_entry # 6. Validate processing data against form validate_form_format_against_form(form_name, pe_converter.get_form_format()) # 7. Update study state, data and ulpoad on DB message = "Added processing event" update_study(study, study_converter, payload, message, user) return {"message": message, "uuid": pe_uuid}, 201
def put(self, pe_uuid, study_id=None, dataset_uuid=None, user=None): """ Update a processing event for a given dataset """ payload = api.payload prop_id_to_name = get_property_map(key="id", value="name") prop_name_to_id = reverse_map(prop_id_to_name) # Used for helper route using only pe_uuid if dataset_uuid is None: study_id, dataset_uuid = find_study_id_and_lvl1_entity_from_lvl2_uuid( lvl1_prop="dataset", lvl2_prop="process_event", lvl2_uuid=pe_uuid, prop_name_to_id=prop_name_to_id, prop_id_to_name=prop_id_to_name, ) if study_id is None or dataset_uuid is None: raise Exception( f"Processing event not found in any dataset (uuid = {pe_uuid})" ) # Used for helper route using only dataset_uuid if study_id is None: study_id = find_study_id_from_lvl1_uuid("dataset", dataset_uuid, prop_name_to_id) if study_id is None: raise Exception( f"Dataset not found in any study (uuid = {dataset_uuid})") # 1. Split payload form_name = payload["form_name"] entries = payload["entries"] entry_format = payload.get("entry_format", "api") # 2. Get study data study = Study.objects().get(id=study_id) study_json = marshal(study, study_model) study_converter = FormatConverter(mapper=prop_id_to_name) study_converter.add_api_format(study_json["entries"]) # 3. Get dataset data datasets_entry = study_converter.get_entry_by_name("datasets") dataset_nested_entry = datasets_entry.value.find_nested_entry( "uuid", dataset_uuid)[0] # 4. Get current processing event data pes_entry = dataset_nested_entry.get_entry_by_name("process_events") pe_nested_entry = pes_entry.value.find_nested_entry("uuid", pe_uuid)[0] pe_converter = FormatConverter(mapper=prop_id_to_name) pe_converter.entries = pe_nested_entry.value # 5. Get new processing event data new_pe_converter, entries_to_remove = get_entity_converter( entries, entry_format, prop_id_to_name, prop_name_to_id) # 6. Update current processing event by adding, updating and deleting entries # Nested entries not present in the original form are ignored (example: pe["results"]) # won't be deleted if not present in the new data), it needs to be None or "" to be deleted pe_converter.add_or_update_entries(new_pe_converter.entries) pe_converter.remove_entries(entries=entries_to_remove) pe_nested_entry.value = pe_converter.entries # 7. Validate processing event data against form validate_form_format_against_form(form_name, pe_converter.get_form_format()) # 8. Update study state, data and ulpoad on DB message = "Updated processing event" update_study(study, study_converter, payload, message, user) return {"message": message}
def post(self, user=None): """ Add a new entry """ payload = api.payload # 1. Split payload form_name = payload["form_name"] initial_state = payload["initial_state"] entries = payload["entries"] entry_format = payload.get("entry_format", "api") form_cls = app.form_manager.get_form_by_name(form_name=form_name) if initial_state == "rna_sequencing_biokit": initial_state = "BiokitUploadState" app.study_state_machine.load_state(name=initial_state) prop_map = get_property_map(key="name", value="id") # 2. Make sure to have both API and form format if entry_format == "api": try: if len(entries) != len({prop["property"] for prop in entries}): raise IdenticalPropertyException( "The entries cannot have several identical property values." ) except TypeError as e: raise RequestBodyException("Entries has wrong format.") from e entries = { "api_format": entries, "form_format": validate_against_form(form_cls, form_name, entries), } else: validate_form_format_against_form(form_name, entries) entries = { "api_format": FormatConverter(prop_map) .add_form_format(entries) .get_api_format(), "form_format": entries, } # 3. Check unicity of pseudo alternate pk in entries check_alternate_pk_unicity( entries=entries["form_format"], pseudo_apks=["study_id"], prop_map=prop_map ) # 4. Evaluate new state of study by passing form data app.study_state_machine.create_study(**entries["form_format"]) state = app.study_state_machine.current_state meta_info = MetaInformation(state=str(state)) log = ChangeLog( user_id=user.id if user else None, action="Created study", timestamp=datetime.now(), manual_user=payload.get("manual_meta_information", {}).get("user", None), ) meta_info.add_log(log) study_data = { "entries": entries["api_format"], "meta_information": meta_info.to_json(), } # 5. Insert data into database study = Study(**study_data) study.save() # Index study on ES index_study_if_es(study, entries["form_format"], "add") return {"message": f"Study added", "id": str(study.id)}, 201
def put(self, id, user=None): """ Update an entry given its unique identifier """ payload = api.payload # 1. Split payload study_id = id form_name = payload["form_name"] entries = payload["entries"] entry_format = payload.get("entry_format", "api") study = Study.objects(id=study_id).first() prop_map = get_property_map(key="name", value="id") # 1. Extract form name and create form from FormManager form_cls = app.form_manager.get_form_by_name(form_name=form_name) # 2. Make sure to have both API and form format if entry_format == "api": entries = { "api_format": entries, "form_format": validate_against_form(form_cls, form_name, entries), } else: validate_form_format_against_form(form_name, entries) entries = { "api_format": FormatConverter(prop_map) .add_form_format(entries) .get_api_format(), "form_format": entries, } # 3. Check unicity of pseudo alternate pk in entries # check_alternate_pk_unicity(entries=entries["form_format"], pseudo_apks=["study_id"], prop_map=prop_map) # 4. Determine current state and evaluate next state state_name = str(study.meta_information.state) if state_name == "rna_sequencing_biokit": state_name = "BiokitUploadState" app.study_state_machine.load_state(name=state_name) app.study_state_machine.change_state(**entries["form_format"]) new_state = app.study_state_machine.current_state # 5. Create and append meta information to the study meta_info = MetaInformation( state=state_name, change_log=study.meta_information.change_log ) log = ChangeLog( action="Updated study", user_id=user.id if user else None, timestamp=datetime.now(), manual_user=payload.get("manual_meta_information", {}).get("user", None), ) meta_info.state = str(new_state) meta_info.add_log(log) study_data = { "entries": entries["api_format"], "meta_information": meta_info.to_json(), } # 6. Update data in database study.update(**study_data) # Index study on ES index_study_if_es(study, entries["form_format"], "update") return {"message": f"Update study"}