diff --git a/bycon/beaconServer/beacon.py b/bycon/beaconServer/beacon.py index a08e4d9ac..91b7650aa 100755 --- a/bycon/beaconServer/beacon.py +++ b/bycon/beaconServer/beacon.py @@ -63,6 +63,8 @@ def beacon(): byc.update({"request_entity_path_id": e_p_id}) r_p_id = byc.get("request_entity_path_id", "info") + prdbug(byc, f'beacon.py - request_entity_path_id: {r_p_id}') + # check for rewrites if r_p_id in r_w: uri = environ.get('REQUEST_URI') diff --git a/bycon/config/authorizations.yaml b/bycon/config/authorizations.yaml new file mode 100644 index 000000000..5c538c601 --- /dev/null +++ b/bycon/config/authorizations.yaml @@ -0,0 +1,4 @@ +anonymous: + default: boolean +local: + default: record \ No newline at end of file diff --git a/bycon/config/config.yaml b/bycon/config/config.yaml index 5aec81947..960720319 100644 --- a/bycon/config/config.yaml +++ b/bycon/config/config.yaml @@ -1,36 +1,42 @@ -byc_root_pars: +args: {} +debug_mode: false +empty_query_all_count: false +error_response: {} +errors: [] +filters: [] +form_data: {} +granularity_levels: + none: 0 + boolean: 1 + count: 2 + record: 3 +pagination: + skip: 0 + limit: 0 +include_handovers: false +method: '' +output: '' +queries: {} +query_meta: {} +request_path_root: beacon +request_entity_path_id: +request_entity_path_id_value: false +response_entity_path_id: +request_entity_id: genomicVariant +response_entity_id: genomicVariant +response_entity: + is_entry_type: True + collection: variants + response_schema: beaconResultsetsResponse + beacon_schema: + entity_type: genomicVariant + schema: https://progenetix.org/services/schemas/genomicVariant/ +service_response: {} +service_config: {} +test_mode: false +test_mode_count: 5 - args: - request_path_root: beacon - request_entity_path_id: - request_entity_path_id_value: false - response_entity_path_id: - request_entity_id: genomicVariant - response_entity_id: genomicVariant - response_entity: - is_entry_type: True - collection: variants - response_schema: beaconResultsetsResponse - beacon_schema: - entity_type: genomicVariant - schema: https://progenetix.org/services/schemas/genomicVariant/ - service_response: {} - error_response: {} - service_config: {} - filters: [] - method: '' - output: '' - form_data: {} - queries: {} - query_meta: {} - include_handovers: false - empty_query_all_count: false - test_mode: false - test_mode_count: 5 - check_args: true - debug_mode: false - download_mode: false - errors: [] +filtering_terms_coll: collations genomes_path: - rsrc @@ -44,18 +50,10 @@ filter_flags: precision: exact include_descendant_terms: true -resource_urls: - ncbi_geosoft: https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?form=text&acc= - ncbi_geoweb: https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc= - europepmc_pmid: https://europepmc.org/article/MED/ - cellosaurus_web: https://web.expasy.org/cellosaurus/ - ################################################################################ # MongoDB configs ################################################################################ -filtering_terms_coll: collations - # special database, auto-generated ###### housekeeping_db: _byconHousekeepingDB beacon_info_coll: beaconinfo diff --git a/bycon/lib/args_parsing.py b/bycon/lib/args_parsing.py index ec96970ec..9f01b7fb6 100644 --- a/bycon/lib/args_parsing.py +++ b/bycon/lib/args_parsing.py @@ -3,11 +3,10 @@ from humps import decamelize - ################################################################################ def get_bycon_args(byc): - if byc.get("check_args", False) is False: + if byc.get("check_args", True) is False: return # Serves as "we've been here before" marker - before the env check. @@ -65,6 +64,7 @@ def args_update_form(byc): else: byc["form_data"].update({p_d: arg_vars[p]}) + ################################################################################ def filters_from_args(byc): @@ -99,19 +99,19 @@ def set_collation_types(byc): def set_processing_modes(byc): byc.update({"update_mode": False}) - try: - if byc["test_mode"] is True: - byc.update({"update_mode": False}) - print("¡¡¡ TEST MODE - no db update !!!") - return - except: - pass - - try: - if byc["args"].update: - byc.update({"update_mode": True}) - print("¡¡¡ UPDATE MODE - may overwrite entries !!!") - except: - pass + tm = byc.get("test_mode", False) + env = byc.get("env", "server") + + if not "local" in env: + return + + if byc["test_mode"] is True: + print("¡¡¡ TEST MODE - no db update !!!") + return + + if byc["args"].update: + byc.update({"update_mode": True}) + print("¡¡¡ UPDATE MODE - may overwrite entries !!!") + + -################################################################################ diff --git a/bycon/lib/beacon_response_generation.py b/bycon/lib/beacon_response_generation.py index 99b625604..3f117420e 100644 --- a/bycon/lib/beacon_response_generation.py +++ b/bycon/lib/beacon_response_generation.py @@ -65,11 +65,13 @@ def __init__(self, byc: dict): self.byc = byc self.test_mode = byc.get("test_mode", False) self.beacon_defaults = byc.get("beacon_defaults", {}) + self.authorized_granularities = byc.get("authorized_granularities", {}) + self.user_name = byc.get("user_name", "anonymous") self.entity_defaults = self.beacon_defaults.get("entity_defaults", {"info":{}}) self.form_data = byc.get("form_data", {}) self.service_config = self.byc.get("service_config", {}) self.response_schema = byc["response_schema"] - self.requested_granularity = self.form_data.get("requested_granularity", "record") + self.returned_granularity = self.form_data.get("returned_granularity", "record") self.include_handovers = self.form_data.get("include_handovers", False) self.beacon_schema = self.byc["response_entity"].get("beacon_schema", "___none___") self.record_queries = {} @@ -86,6 +88,7 @@ def __init__(self, byc: dict): # -------------------------------------------------------------------------# def resultsetResponse(self): + prdbug(self.byc, f'... resultsetResponse start, schema {self.response_schema}') if not "beaconResultsetsResponse" in self.response_schema: return @@ -94,16 +97,7 @@ def resultsetResponse(self): self.data_response["response"].update({"result_sets": self.result_sets}) self.__resultset_response_update_summaries() - if not "record" in self.requested_granularity: - # TODO /CUSTOM: This non-standard modification removes the results - # but keeps the resultSets nstructure (handovers ...) - if self.include_handovers is True: - for rs in self.data_response["response"]["result_sets"]: - rs.pop("results", None) - else: - self.data_response.pop("response", None) - if "boolean" in self.requested_granularity: - self.data_response["response_summary"].pop("num_total_results", None) + self.__resultSetResponse_force_granularities() b_h_o = self.data_response.get("beacon_handovers", []) if len(b_h_o) < 1: @@ -118,6 +112,7 @@ def resultsetResponse(self): self.__meta_add_received_request_summary_parameters() self.__meta_add_parameters() self.__meta_clean_parameters() + self.__response_clean_parameters() self.result_sets_end = datetime.datetime.now() self.result_sets_duration = self.result_sets_end - self.result_sets_start prdbug(self.byc, f'... data response duration was {self.result_sets_duration.total_seconds()} seconds') @@ -140,6 +135,7 @@ def collectionsResponse(self): self.__meta_add_received_request_summary_parameters() self.__meta_add_parameters() self.__meta_clean_parameters() + self.__response_clean_parameters() return self.data_response @@ -157,6 +153,7 @@ def filteringTermsResponse(self): self.__meta_add_received_request_summary_parameters() self.__meta_add_parameters() self.__meta_clean_parameters() + self.__response_clean_parameters() return self.data_response @@ -191,6 +188,30 @@ def __check_switch_to_error_response(self): self.data_response = self.error_response + # -------------------------------------------------------------------------# + + def __resultSetResponse_force_granularities(self): + prdbug(self.byc, f'authorized_granularities: {self.authorized_granularities}') + for rs in self.data_response["response"]["result_sets"]: + rs_granularity = self.authorized_granularities.get(rs["id"], "boolean") + prdbug(self.byc, f'rs_granularity ({rs["id"]}): {rs_granularity}') + if not "record" in rs_granularity: + # TODO /CUSTOM: This non-standard modification removes the results + # but keeps the resultSets structure (handovers ...) + rs.pop("results", None) + if "boolean" in rs_granularity: + rs.pop("results_count", None) + if "boolean" in self.returned_granularity: + self.data_response["response_summary"].pop("num_total_results", None) + + + # -------------------------------------------------------------------------# + + def __response_clean_parameters(self): + r_m = self.data_response.get("response", {}) + r_m.pop("$schema", None) + + # -------------------------------------------------------------------------# def __meta_clean_parameters(self): @@ -221,8 +242,8 @@ def __meta_add_parameters(self): form = self.form_data # TODO: this is hacky; need a separate setting of the returned granularity # since the server may decide so... - if self.requested_granularity and "returned_granularity" in r_m: - r_m.update({"returned_granularity": form.get("requested_granularity")}) + # if self.requested_granularity and "returned_granularity" in r_m: + # r_m.update({"returned_granularity": form.get("requested_granularity")}) service_meta = self.service_config.get("meta", {}) for rrs_k, rrs_v in service_meta.items(): @@ -246,7 +267,7 @@ def __meta_add_received_request_summary_parameters(self): "requested_schemas": [self.beacon_schema] }) - for name in ["dataset_ids", "test_mode"]: + for name in ["dataset_ids", "test_mode", "pagination"]: value = self.byc.get(name) if not value: continue @@ -442,6 +463,7 @@ def __return_filtering_terms(self): return + # -------------------------------------------------------------------------# def __return_filter_resources(self): @@ -596,7 +618,6 @@ def populatedResultSets(self): self.__retrieve_variants_data() self.__populate_result_sets() self.__result_sets_save_handovers() - return self.result_sets, self.record_queries @@ -640,8 +661,8 @@ def __get_handover_access_key(self): def __result_sets_save_handovers(self): ho_client = MongoClient(host=environ.get("BYCON_MONGO_HOST", "localhost")) - ho_db = ho_client[ self.byc["config"]["housekeeping_db"] ] - ho_coll = ho_db[ self.byc["config"][ "handover_coll" ] ] + ho_db = ho_client[ self.byc["housekeeping_db"] ] + ho_coll = ho_db[ self.byc["handover_coll"] ] for ds_id, d_s in self.datasets_results.items(): if not d_s: diff --git a/bycon/lib/cgi_parsing.py b/bycon/lib/cgi_parsing.py index 5d5b53b42..24c7e5abf 100644 --- a/bycon/lib/cgi_parsing.py +++ b/bycon/lib/cgi_parsing.py @@ -130,7 +130,6 @@ def parse_POST(byc): "query_meta": jbod.get("meta", {}) }) - ################################################################################ def parse_GET(byc): diff --git a/bycon/lib/dataset_parsing.py b/bycon/lib/dataset_parsing.py index df598ddba..5c722e8fb 100644 --- a/bycon/lib/dataset_parsing.py +++ b/bycon/lib/dataset_parsing.py @@ -42,8 +42,8 @@ def ds_id_from_accessid(byc): # test of existence... accessid = byc["form_data"].get("accessid", False) - ho_db = byc["config"].get("housekeeping_db", False) - ho_collname = byc["config"].get("handover_coll", False) + ho_db = byc.get("housekeeping_db", False) + ho_collname = byc.get("handover_coll", False) if any(x is False for x in [accessid, ho_db, ho_collname]): return False @@ -89,12 +89,13 @@ def ds_ids_from_args(byc): if "args" not in byc or byc["args"] is None: return False - if byc["args"].datasetIds: - ds_ids = re.split(",", byc["args"].datasetIds) - byc.update({"dataset_ids": ds_ids}) - return True + dsid_s = byc["args"].get("datasetIds") + if not dsid_s: + return False - return False + ds_ids = re.split(",", dsid_s) + byc.update({"dataset_ids": ds_ids}) + return True ################################################################################ diff --git a/bycon/lib/genome_utils.py b/bycon/lib/genome_utils.py index 726935680..f450aebd0 100644 --- a/bycon/lib/genome_utils.py +++ b/bycon/lib/genome_utils.py @@ -39,7 +39,7 @@ def set_genome_rsrc_path(byc): if genome in g_map.keys(): genome = g_map[ genome ] - byc.update({"genome_rsrc_path": path.join( pkg_path, *byc["config"]["genomes_path"], genome ) }) + byc.update({"genome_rsrc_path": path.join( pkg_path, *byc["genomes_path"], genome ) }) ################################################################################ @@ -130,11 +130,11 @@ def retrieve_gene_id_coordinates(gene_id, precision, byc): mongo_client = MongoClient(host=environ.get("BYCON_MONGO_HOST", "localhost")) db_names = list(mongo_client.list_database_names()) - services_db = byc["config"].get("services_db", "___none___") + services_db = byc.get("services_db", "___none___") if services_db not in db_names: return [], f"services db `{services_db}` does not exist" - genes_coll = byc["config"].get("genes_coll") + genes_coll = byc.get("genes_coll") if not genes_coll: return [], "no `genes_coll` parameter in `config.yaml`" diff --git a/bycon/lib/handover_generation.py b/bycon/lib/handover_generation.py index c88e88665..7f06067cc 100644 --- a/bycon/lib/handover_generation.py +++ b/bycon/lib/handover_generation.py @@ -117,8 +117,8 @@ def query_results_save_handovers(byc): def dataset_results_save_handovers(ds_id, byc): ho_client = MongoClient(host=environ.get("BYCON_MONGO_HOST", "localhost")) - ho_db = ho_client[ byc["config"]["housekeeping_db"] ] - ho_coll = ho_db[ byc["config"][ "handover_coll" ] ] + ho_db = ho_client[ byc["housekeeping_db"] ] + ho_coll = ho_db[ byc[ "handover_coll" ] ] for h_o_k in byc["dataset_results"][ds_id].keys(): diff --git a/bycon/lib/parse_filters_request.py b/bycon/lib/parse_filters_request.py index 6ce32dd51..cdd79cbfa 100644 --- a/bycon/lib/parse_filters_request.py +++ b/bycon/lib/parse_filters_request.py @@ -15,9 +15,9 @@ def parse_filters(byc): def get_global_filter_flags(byc): ff = { - "logic": byc[ "config" ][ "filter_flags" ][ "logic" ], - "precision": byc[ "config" ][ "filter_flags" ][ "precision" ], - "descendants": byc[ "config" ][ "filter_flags" ][ "include_descendant_terms" ] + "logic": byc["filter_flags"]["logic"], + "precision": byc["filter_flags"]["precision"], + "descendants": byc["filter_flags"]["include_descendant_terms"] } if "form_data" in byc: diff --git a/bycon/lib/query_execution.py b/bycon/lib/query_execution.py index ba4d26cfa..ff9ef4701 100644 --- a/bycon/lib/query_execution.py +++ b/bycon/lib/query_execution.py @@ -16,6 +16,7 @@ def execute_bycon_queries(ds_id, BQ, byc): podmd""" h_o_defs = byc["handover_definitions"]["h->o_methods"] + r_e_id = str(byc.get("response_entity_id", "___none___")) exe_queries = {} if "dataset_results" not in byc.keys(): @@ -101,9 +102,12 @@ def execute_bycon_queries(ds_id, BQ, byc): else: prefetch["biosamples.id"] = prefetch["callsets.biosample_id->biosamples.id"] - variants_query = exe_queries.get("variants", False) - if not variants_query and "genomicVariant" in byc.get("response_entity_id", "___none___"): - variants_query = {"biosample_id": {'$in': prefetch["biosamples.id"].get("target_values", [])}} + variants_query = exe_queries.get("variants") + if not variants_query: + if "genomicVariant" in r_e_id: + variants_query = {"biosample_id": {'$in': prefetch["biosamples.id"].get("target_values", [])}} + + prdbug(byc, ("variants_query", variants_query)) if variants_query: @@ -192,9 +196,8 @@ def execute_bycon_queries(ds_id, BQ, byc): prefetch.update({prevars["pref_m"]: _prefetch_data(prevars)}) # TODO: have this checked... somewhere else based on the response_entity_id - if "response_entity_id" in byc: - if "individual" in byc["response_entity_id"] or "phenopacket" in byc["response_entity_id"]: - _prefetch_add_individuals(prevars, prefetch) + if "individual" in r_e_id or "phenopacket" in r_e_id: + _prefetch_add_individuals(prevars, prefetch) ############################################################################ diff --git a/bycon/lib/query_generation.py b/bycon/lib/query_generation.py index 3731ae65b..a946f99d5 100644 --- a/bycon/lib/query_generation.py +++ b/bycon/lib/query_generation.py @@ -49,6 +49,7 @@ def __init__(self, byc: dict, dataset_id=False): self.arguments = byc.get("form_data") self.filters = byc.get("filters", []) + self.filtering_terms_coll = byc.get("filtering_terms_coll", "___none___") self.mongohost = environ.get("BYCON_MONGO_HOST", "localhost") self.test_mode = byc.get("test_mode", False) @@ -70,10 +71,10 @@ def __init__(self, byc: dict, dataset_id=False): self.filter_descendants = ff.get("descendants", True) self.filter_logic = ff.get("logic", '$and') - self.housekeeping_db = byc["config"].get("housekeeping_db", "___none___") - self.handover_coll = byc["config"].get("handover_coll", "___none___") - self.services_db = byc["config"].get("services_db", "___none___") - self.genes_coll = byc["config"].get("genes_coll", "___none___") + self.housekeeping_db = byc.get("housekeeping_db", "___none___") + self.handover_coll = byc.get("handover_coll", "___none___") + self.services_db = byc.get("services_db", "___none___") + self.genes_coll = byc.get("genes_coll", "___none___") pagination = byc.get("pagination", {"skip": 0, "limit": 0}) self.limit = pagination.get("limit", 0) @@ -465,7 +466,7 @@ def __query_from_filters(self): return data_db = MongoClient(host=self.mongohost)[self.ds_id] - coll_coll = data_db["collations"] + coll_coll = data_db[ self.filtering_terms_coll ] self.collation_ids = coll_coll.distinct("id", {}) # if self.byc["debug_mode"] is True: diff --git a/bycon/lib/read_specs.py b/bycon/lib/read_specs.py index b846fe4a7..fe802ff84 100644 --- a/bycon/lib/read_specs.py +++ b/bycon/lib/read_specs.py @@ -86,7 +86,7 @@ def update_rootpars_from_local(loc_dir, byc): byc.update({b_p: always_merger.merge(byc.get(b_p, {}), b)}) # TODO: better way to define which files are parsed from local - for p in ("dataset_definitions", "local_paths", "local_parameters", "datatable_mappings", "plot_defaults"): + for p in ("authorizations", "dataset_definitions", "local_paths", "local_parameters", "datatable_mappings", "plot_defaults"): f = path.join(loc_dir, f'{p}.yaml') d = load_yaml_empty_fallback(f) byc.update({p: always_merger.merge(byc.get(p, {}), d)}) @@ -100,8 +100,8 @@ def dbstats_return_latest(byc): # TODO: This is too hacky & should be moved to an external function # which updates the database_definitions / beacon_info yamls... - info_db = byc[ "config" ][ "housekeeping_db" ] - coll = byc[ "config" ][ "beacon_info_coll" ] + info_db = byc["housekeeping_db"] + coll = byc["beacon_info_coll"] stats = MongoClient(host=environ.get("BYCON_MONGO_HOST", "localhost"))[ info_db ][ coll ].find( { }, { "_id": 0 } ).sort( "date", -1 ).limit( 1 ) return list(stats)[0] @@ -117,8 +117,6 @@ def datasets_update_latest_stats(byc, collection_type="datasets"): stat = dbstats_return_latest(byc) - counted = byc["config"].get("beacon_count_items", {}) - for coll_id, coll in byc[ def_k ].items(): if q_k in byc: if len(byc[ q_k ]) > 0: @@ -128,11 +126,6 @@ def datasets_update_latest_stats(byc, collection_type="datasets"): if collection_type in stat: if coll_id in stat[ collection_type ].keys(): ds_vs = stat[ collection_type ][coll_id] - if "counts" in ds_vs: - for c, c_d in counted.items(): - i_k = c_d.get("info_key", "___none___") - if i_k in ds_vs["counts"]: - coll["info"].update({ c: ds_vs["counts"][ i_k ] }) if "filtering_terms" in byc["response_entity_id"]: coll.update({ "filtering_terms": stat[ collection_type ][coll_id].get("filtering_terms", []) } ) diff --git a/bycon/lib/schema_parsing.py b/bycon/lib/schema_parsing.py index 5c2f83053..1aa11354c 100644 --- a/bycon/lib/schema_parsing.py +++ b/bycon/lib/schema_parsing.py @@ -47,7 +47,7 @@ def get_schema_file_path(byc, schema_name, ext="json"): f_n = f'{schema_name}.{ext}' - p = Path(path.join( pkg_path, *byc["config"]["schemas_path"] )) + p = Path(path.join( pkg_path, *byc["schemas_path"] )) s_p_s = [ f for f in p.rglob("*") if f.is_file() ] s_p_s = [ f for f in s_p_s if f.name == f_n ] if len(s_p_s) == 1: @@ -62,7 +62,7 @@ def get_default_schema_file_path(byc, schema_path_id, file_name, ext="json"): f_n = f'{file_name}.{ext}' - p = Path(path.join( pkg_path, *byc["config"]["schemas_path"] )) + p = Path(path.join( pkg_path, *byc["schemas_path"] )) # prdbug(byc, f'==> schema files root: {p}') s_p_s = [ f for f in p.rglob("*") if f.is_file() ] s_p_s = [ f for f in s_p_s if f.name == f_n ] diff --git a/bycon/lib/service_utils.py b/bycon/lib/service_utils.py index fbd1d911e..30654a728 100644 --- a/bycon/lib/service_utils.py +++ b/bycon/lib/service_utils.py @@ -16,13 +16,9 @@ def set_byc_config_pars(byc): config = byc.get("config", {}) - b_r_p = config.get("byc_root_pars", {}) - for k, v in b_r_p.items(): + for k, v in config.items(): byc.update({k: v}) - config.pop("byc_root_pars", None) - byc.update({"config": config}) - ################################################################################ @@ -37,6 +33,10 @@ def set_beacon_defaults(byc): def run_beacon_init_stack(byc): select_dataset_ids(byc) + set_user_name(byc) + set_returned_granularities(byc) + # prdbug(byc,f'returned_granularity: {byc["returned_granularity"]}') + # # move the next to later, and deliver as Beacon error? # if len(byc["dataset_ids"]) < 1: # print_text_response("No existing dataset_id - please check dataset_definitions") @@ -56,11 +56,10 @@ def initialize_bycon_service(byc, service=False): scope = "beacon" if not service: - service = byc.get("request_entity_path_id", False) + service = byc.get("request_entity_path_id") frm = inspect.stack()[1] if not service: service = frm.function - # TODO - streamline, also for services etc. s_a_s = byc["beacon_defaults"].get("service_path_aliases", {}) @@ -87,24 +86,25 @@ def initialize_bycon_service(byc, service=False): pkg_path = path.dirname(path.abspath(mod.__file__)) if "services" in pkg_path or "byconaut" in pkg_path: scope = "services" - byc.update({ - "request_path_root": scope, - "request_entity_path_id": service - }) loc_dir = path.join( pkg_path, "local" ) conf_dir = path.join( pkg_path, "config" ) # updates `beacon_defaults`, `dataset_definitions` and `local_paths` update_rootpars_from_local(loc_dir, byc) - defaults = byc["beacon_defaults"].get("defaults", {}) for d_k, d_v in defaults.items(): byc.update({d_k: d_v}) + byc.update({ + "request_path_root": scope, + "request_entity_path_id": service + }) + # this will generate byc["service_config"] if a file with the service # name exists read_service_prefs(service, conf_dir, byc) + # prdbug(byc, f'initialize_bycon_service - request_entity_path_id: {byc.get("request_entity_path_id")}, service {service}') get_bycon_args(byc) args_update_form(byc) @@ -126,6 +126,7 @@ def initialize_bycon_service(byc, service=False): # update response_entity_id from path update_entity_ids_from_path(byc) + # prdbug(byc, f'initialize_bycon_service - response_entity_id: {byc.get("response_entity_id")}, service {service}') # update response_entity_id from form update_requested_schema_from_request(byc) @@ -162,13 +163,11 @@ def set_special_modes(byc): ################################################################################ def set_io_params(byc): - form = byc["form_data"] - - if not "pagination" in byc: - byc.update({"pagination": {"skip": 0, "limit": 0}}) + form = byc.get("form_data", {}) for sp in ["skip", "limit"]: if sp in form: + prdbug(byc, f'{sp}: {form[sp]}') if re.match(r'^\d+$', str(form[sp])): s_v = int(form[sp]) byc["pagination"].update({sp: s_v}) @@ -180,20 +179,19 @@ def set_io_params(byc): if "method_keys" in byc["service_config"]: m = form.get("method", "___none___") if m in byc["service_config"]["method_keys"].keys(): - byc["method"] = m + byc.update({"method": m}) ################################################################################ def update_entity_ids_from_path(byc): - if not byc["request_entity_path_id"]: - return + req_p_id = byc.get("request_entity_path_id") - if not byc["response_entity_path_id"]: - byc.update({"response_entity_path_id": byc["request_entity_path_id"]}) - - req_p_id = byc["request_entity_path_id"] - res_p_id = byc["response_entity_path_id"] + if not req_p_id: + return + res_p_id = byc.get("response_entity_path_id") + if not res_p_id: + res_p_id = req_p_id # TODO: in contrast to req_p_id, res_p_id hasn't been anti-aliased s_a_s = byc["beacon_defaults"].get("service_path_aliases", {}) @@ -202,9 +200,10 @@ def update_entity_ids_from_path(byc): p_e_m = byc["beacon_defaults"].get("path_entry_type_mappings", {}) + # TODO: this gets the correct entity_id w/ entity_path_id fallback byc.update({ "request_entity_id": p_e_m.get(req_p_id, req_p_id), - "response_entity_id": p_e_m.get(res_p_id, res_p_id) + "response_entity_id": p_e_m.get(res_p_id, req_p_id) }) @@ -224,6 +223,7 @@ def update_requested_schema_from_request(byc): ################################################################################ def set_response_entity(byc): + prdbug(byc, f'response_entity_id: {byc.get("response_entity_id")}') b_rt_s = byc["beacon_defaults"].get("entity_defaults", {}) r_e_id = byc.get("response_entity_id", "___none___") r_e = b_rt_s.get(r_e_id) @@ -236,7 +236,61 @@ def set_response_entity(byc): ################################################################################ def set_response_schema(byc): + prdbug(byc, byc["response_entity"]) r_s = byc["response_entity"].get("response_schema", "beaconInfoResponse") byc.update({"response_schema": r_s}) +################################################################################ + +def set_user_name(byc): + byc.update({"user_name": "anonymous"}) + # local user has full permissions + if "local" in byc["env"]: + byc.update({"user_name": "local"}) + return + + # TODO: user_name will be provided in header + un = byc["form_data"].get("user", "anonymous") + if un in byc.get("authorizations", {}): + byc.update({"user_name": un}) + + +################################################################################ + +def set_returned_granularities(byc): + + un = byc.get("user_name", "anonymous") + rg = byc["form_data"].get("requested_granularity", "record") + granularity_levels = byc.get("granularity_levels", {}) + auth = byc.get("authorizations", {}) + + g_l_s = [0] + r_g_l = granularity_levels.get(rg, 0) + + if not "authorized_granularities" in byc: + byc.update({"authorized_granularities": {}}) + + # prdbug(byc,f'user_name: {un}') + # prdbug(byc,f'authorizations: {auth}') + for d in byc["dataset_ids"]: + byc["authorized_granularities"].update({d: rg}) + ugs = auth.get(un, {}) + if d in ugs: + g_l_l = ugs[d] + elif "default" in ugs: + g_l_l = ugs["default"] + d_g_l = granularity_levels.get(g_l_l, 0) + if d_g_l <= r_g_l: + byc["authorized_granularities"].update({d: g_l_l}) + g_l_s.append(d_g_l) + + m_g_l = max(g_l_s) + + if m_g_l < r_g_l: + prdbug(byc, "Warning: Requested granularity exceeds user authorization - using a maximum of %s" % byc["returned_granularity"]) + byc.update({"returned_granularity": list(granularity_levels.keys())[m_g_l]}) + else: + byc.update({"returned_granularity": list(granularity_levels.keys())[r_g_l]}) + + diff --git a/docs/changes.md b/docs/changes.md index 67d1542f3..375d843bc 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -13,9 +13,20 @@ through the Perl based [**PGX** project](http://github.com/progenetix/PGX/). ## Changes Tracker -### ... (v1.3.7) - -* added +### 2023-12-18 (v1.3.7) + +* added handling for user specific granularity permissions + - so far `user_name` is just taken from a form parameter and then stored + as `byc` root parameter (through `set_user_name`) + - local processing (`env`) sets this to `local` (and has a default `record`) + granularity + - dataset specific, user specific maximum granularities can be set in + `authorizations.yaml` which can be extended / overwritten from settings in + `local/authorizations.yaml` (similar to `beacon_defaults.yaml` etc.) + - future updates are planned to handle proper interpretation of `user_name` + and proof of authorization... +* configuration: the basic parameters from `config.yaml` are now stored as `byc` + root parameters and not kept in a mix of root & `config` ### 2023-11-20 (v.1.3.6) diff --git a/local/authorizations.yaml b/local/authorizations.yaml new file mode 100644 index 000000000..b42551eee --- /dev/null +++ b/local/authorizations.yaml @@ -0,0 +1,9 @@ +anonymous: + default: record +mbaudis: + default: record + progenetix: record + cellz: record + examplez: record +testuser: + examplez: count diff --git a/requirements.txt b/requirements.txt index e5c8e8c51..f941fa228 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,10 @@ base36==0.1.1 deepmerge==1.1.0 +humps==0.2.2 isodate==0.6.1 json_ref_dict==0.7.1 liftover==1.1.16 -numpy==1.24.1 -Pillow==10.0.0 pyhumps==3.8.0 pymongo==4.3.3 PyYAML==6.0.1 -scipy==1.11.1 -setuptools==65.4.1 +setuptools==68.2.2 diff --git a/setup.py b/setup.py index 06f570f4d..aa2deabfe 100755 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="bycon", - version="1.3.6", + version="1.3.7", description="A Python-based environment for the Beacon v2 genomics API", long_description=long_description, long_description_content_type="text/markdown", @@ -44,5 +44,6 @@ project_urls={ # Optional "Bug Reports": "https://github.com/progenetix/bycon/issues", "Source": "https://github.com/progenetix/bycon/", + "Helpers": "https://github.com/progenetix/byconaut/", }, ) \ No newline at end of file