From b209e2396fecebfabc97835e6f92d7f20eb3cb2f Mon Sep 17 00:00:00 2001 From: Dustin Bleile Date: Fri, 12 Dec 2025 12:19:03 -0800 Subject: [PATCH 01/11] DEVSU-2797 - main.ipr_report - allow null ipr_url if ipr_upload = False --- pori_python/ipr/main.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/pori_python/ipr/main.py b/pori_python/ipr/main.py index b7cd761..77f8620 100644 --- a/pori_python/ipr/main.py +++ b/pori_python/ipr/main.py @@ -31,8 +31,8 @@ preprocess_cosmic, preprocess_expression_variants, preprocess_hla, - preprocess_msi, preprocess_hrd, + preprocess_msi, preprocess_signature_variants, preprocess_small_mutations, preprocess_structural_variants, @@ -294,7 +294,7 @@ def ipr_report( username: str, password: str, content: Dict, - ipr_url: str, + ipr_url: str = "", log_level: str = "info", output_json_path: str = "", always_write_output_json: bool = False, @@ -324,7 +324,7 @@ def ipr_report( Args: username: the username for connecting to GraphKB and IPR password: the password for connecting to GraphKB and IPR - ipr_url: base URL to use in connecting to IPR + ipr_url: base URL to use in connecting to IPR (eg. https://ipr-api.bcgsc.ca/api) log_level: the logging level content: report content output_json_path: path to a JSON file to output the report upload body. @@ -358,17 +358,24 @@ def ipr_report( ) # IPR CONNECTION - ipr_conn = IprConnection(username, password, ipr_url) + ipr_conn = None + if ipr_url: + ipr_conn = IprConnection(username, password, ipr_url) + else: + logger.warning("No ipr_url given") if validate_json: + if not ipr_conn: + raise ValueError("ipr_url required to validate_json") ipr_result = ipr_conn.validate_json(content) return ipr_result if upload_json: + if not ipr_conn: + raise ValueError("ipr_url required to validate_json") ipr_result = ipr_conn.upload_report( content, mins_to_wait, async_upload, ignore_extra_fields ) - return ipr_result # validate the JSON content follows the specification try: @@ -495,6 +502,8 @@ def ipr_report( comments_list.append(graphkb_comments) if include_ipr_variant_text: + if not ipr_conn: + raise ValueError("ipr_url required to include_ipr_variant_text") ipr_comments = get_ipr_analyst_comments( ipr_conn, gkb_matches, @@ -550,18 +559,18 @@ def ipr_report( # if input includes hrdScore field, that is ok to pass to db # but prefer the 'hrd' field if it exists - if output.get('hrd'): - if output.get('hrd').get('score'): - output['hrdScore'] = output['hrd']['score'] - output.pop('hrd') # kbmatches have already been made + if output.get("hrd"): + if output.get("hrd").get("score"): + output["hrdScore"] = output["hrd"]["score"] + output.pop("hrd") # kbmatches have already been made - ipr_spec = ipr_conn.get_spec() - output = clean_unsupported_content(output, ipr_spec) ipr_result = {} upload_error = None # UPLOAD TO IPR if ipr_upload: + ipr_spec = ipr_conn.get_spec() + output = clean_unsupported_content(output, ipr_spec) try: logger.info(f"Uploading to IPR {ipr_conn.url}") ipr_result = ipr_conn.upload_report( From e95203c8828f93ed27dec29f519ce0e147d20ecd Mon Sep 17 00:00:00 2001 From: Dustin Bleile Date: Fri, 12 Dec 2025 12:19:55 -0800 Subject: [PATCH 02/11] DEVSU-2797 - lint isort - unsorted imports --- pori_python/ipr/inputs.py | 2 +- pori_python/ipr/ipr.py | 2 +- tests/test_graphkb/test_genes.py | 2 +- tests/test_ipr/test_inputs.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pori_python/ipr/inputs.py b/pori_python/ipr/inputs.py index 3cfe526..4feb12f 100644 --- a/pori_python/ipr/inputs.py +++ b/pori_python/ipr/inputs.py @@ -26,8 +26,8 @@ from .constants import ( COSMIC_SIGNATURE_VARIANT_TYPE, HLA_SIGNATURE_VARIANT_TYPE, - MSI_MAPPING, HRD_MAPPING, + MSI_MAPPING, TMB_SIGNATURE, TMB_SIGNATURE_VARIANT_TYPE, ) diff --git a/pori_python/ipr/ipr.py b/pori_python/ipr/ipr.py index 3b98d9a..12d7ce6 100644 --- a/pori_python/ipr/ipr.py +++ b/pori_python/ipr/ipr.py @@ -168,7 +168,7 @@ def convert_statements_to_alterations( diseases = [c for c in statement["conditions"] if c["@class"] == "Disease"] disease_match = len(diseases) == 1 and diseases[0]["@rid"] in disease_matches reference = ";".join([e["displayName"] for e in statement["evidence"]]) - if statement['relevance']['name'] == 'eligibility': + if statement["relevance"]["name"] == "eligibility": reference = ";".join([e["sourceId"] for e in statement["evidence"]]) ipr_section = gkb_statement.categorize_relevance( diff --git a/tests/test_graphkb/test_genes.py b/tests/test_graphkb/test_genes.py index 1c2862f..f598822 100644 --- a/tests/test_graphkb/test_genes.py +++ b/tests/test_graphkb/test_genes.py @@ -7,6 +7,7 @@ from pori_python.graphkb import GraphKBConnection from pori_python.graphkb.genes import ( + PREFERRED_GENE_SOURCE_NAME, get_cancer_genes, get_cancer_predisposition_info, get_gene_information, @@ -18,7 +19,6 @@ get_pharmacogenomic_info, get_preferred_gene_name, get_therapeutic_associated_genes, - PREFERRED_GENE_SOURCE_NAME, ) from pori_python.graphkb.util import get_rid diff --git a/tests/test_ipr/test_inputs.py b/tests/test_ipr/test_inputs.py index e99d170..da9b1dd 100644 --- a/tests/test_ipr/test_inputs.py +++ b/tests/test_ipr/test_inputs.py @@ -7,8 +7,8 @@ from pori_python.graphkb.match import INPUT_COPY_CATEGORIES from pori_python.ipr.constants import ( - MSI_MAPPING, HRD_MAPPING, + MSI_MAPPING, TMB_SIGNATURE, TMB_SIGNATURE_HIGH_THRESHOLD, ) @@ -21,8 +21,8 @@ preprocess_cosmic, preprocess_expression_variants, preprocess_hla, - preprocess_msi, preprocess_hrd, + preprocess_msi, preprocess_signature_variants, preprocess_small_mutations, preprocess_structural_variants, From 0f7b2d798d351798e120e0ee24c899e6a19fb5f4 Mon Sep 17 00:00:00 2001 From: Dustin Bleile Date: Fri, 12 Dec 2025 15:41:12 -0800 Subject: [PATCH 03/11] DEVSU-2797 - improve error message logging when graphkb_conn is made with graphkb_url None --- pori_python/graphkb/util.py | 5 ++++- pori_python/ipr/main.py | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pori_python/graphkb/util.py b/pori_python/graphkb/util.py index bbc6776..9d4e030 100644 --- a/pori_python/graphkb/util.py +++ b/pori_python/graphkb/util.py @@ -222,6 +222,8 @@ def login_demo(self) -> None: 1. get a first token from KeyCloak using username and password; self.login_demo() 2. get a second token from the GraphKB API using keyCloakToken; self.login() """ + if not self.url: + raise ValueError("no self.url set - cannot make a login demo") url_parts = urlsplit(self.url) base_url = f"{url_parts.scheme}://{url_parts.netloc}" @@ -251,7 +253,8 @@ def login(self, username: str, password: str, pori_demo: bool = False) -> None: read_timeout = 61 # KBDEV-1328. Alt. GraphKB login for GSC's PORI online demo - if pori_demo or "pori-demo" in self.url: + if self.url and (pori_demo or "pori-demo" in self.url): + logger.warning(f"login demo") self.login_demo() # use requests package directly to avoid recursion loop on login failure diff --git a/pori_python/ipr/main.py b/pori_python/ipr/main.py index 77f8620..0e0174d 100644 --- a/pori_python/ipr/main.py +++ b/pori_python/ipr/main.py @@ -417,14 +417,14 @@ def ipr_report( ) # GKB CONNECTION + gkb_user = graphkb_username if graphkb_username else username + gkb_pass = graphkb_password if graphkb_password else password if graphkb_url: logger.info(f"connecting to graphkb: {graphkb_url}") graphkb_conn = GraphKBConnection(graphkb_url) else: - graphkb_conn = GraphKBConnection() - - gkb_user = graphkb_username if graphkb_username else username - gkb_pass = graphkb_password if graphkb_password else password + # graphkb_conn = GraphKBConnection() # This will just error on trying to login + raise ValueError("graphkb_url is required") graphkb_conn.login(gkb_user, gkb_pass) From ecb9060c425bd1018e968a73780ba872c904748c Mon Sep 17 00:00:00 2001 From: Dustin Bleile Date: Mon, 15 Dec 2025 16:34:45 -0800 Subject: [PATCH 04/11] bugfix - ipr_report - upload_json immediately returns --- pori_python/ipr/main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pori_python/ipr/main.py b/pori_python/ipr/main.py index 0e0174d..52265e1 100644 --- a/pori_python/ipr/main.py +++ b/pori_python/ipr/main.py @@ -372,10 +372,11 @@ def ipr_report( if upload_json: if not ipr_conn: - raise ValueError("ipr_url required to validate_json") + raise ValueError("ipr_url required to upload_json") ipr_result = ipr_conn.upload_report( content, mins_to_wait, async_upload, ignore_extra_fields ) + return ipr_result # validate the JSON content follows the specification try: @@ -569,6 +570,8 @@ def ipr_report( # UPLOAD TO IPR if ipr_upload: + if not ipr_conn: + raise ValueError("ipr_url required to upload_report") ipr_spec = ipr_conn.get_spec() output = clean_unsupported_content(output, ipr_spec) try: From b572f2b09c4e269bd08887767a826c5d37fd174c Mon Sep 17 00:00:00 2001 From: Dustin Bleile Date: Mon, 15 Dec 2025 16:48:49 -0800 Subject: [PATCH 05/11] DEVSU-2797 - check os.environ for IPR_URL and GRAPHKB_URL --- pori_python/ipr/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pori_python/ipr/main.py b/pori_python/ipr/main.py index 52265e1..7fcff9d 100644 --- a/pori_python/ipr/main.py +++ b/pori_python/ipr/main.py @@ -358,6 +358,7 @@ def ipr_report( ) # IPR CONNECTION + ipr_url = ipr_url if ipr_url else os.environ.get("IPR_URL", "") ipr_conn = None if ipr_url: ipr_conn = IprConnection(username, password, ipr_url) @@ -420,6 +421,7 @@ def ipr_report( # GKB CONNECTION gkb_user = graphkb_username if graphkb_username else username gkb_pass = graphkb_password if graphkb_password else password + graphkb_url = graphkb_url if graphkb_url else os.environ.get("GRAPHKB_URL", "") if graphkb_url: logger.info(f"connecting to graphkb: {graphkb_url}") graphkb_conn = GraphKBConnection(graphkb_url) From 9cd51c3733573d85c965952b79a42b304b609292 Mon Sep 17 00:00:00 2001 From: Dustin Bleile Date: Tue, 16 Dec 2025 11:16:14 -0800 Subject: [PATCH 06/11] lint - fix fstring --- pori_python/graphkb/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pori_python/graphkb/util.py b/pori_python/graphkb/util.py index 9d4e030..9792639 100644 --- a/pori_python/graphkb/util.py +++ b/pori_python/graphkb/util.py @@ -254,7 +254,7 @@ def login(self, username: str, password: str, pori_demo: bool = False) -> None: # KBDEV-1328. Alt. GraphKB login for GSC's PORI online demo if self.url and (pori_demo or "pori-demo" in self.url): - logger.warning(f"login demo") + logger.warning("login demo") self.login_demo() # use requests package directly to avoid recursion loop on login failure From e05ec5bd186d525ef7a96213994a794d89bf4171 Mon Sep 17 00:00:00 2001 From: Dustin Bleile Date: Wed, 7 Jan 2026 15:05:01 -0800 Subject: [PATCH 07/11] minor type fix - null string instead of None --- pori_python/graphkb/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pori_python/graphkb/util.py b/pori_python/graphkb/util.py index 9792639..4a4cbff 100644 --- a/pori_python/graphkb/util.py +++ b/pori_python/graphkb/util.py @@ -98,7 +98,7 @@ def cache_key(request_body) -> str: class GraphKBConnection: def __init__( self, - url: str = os.environ.get("GRAPHKB_URL"), + url: str = os.environ.get("GRAPHKB_URL", ""), username: str = "", password: str = "", use_global_cache: bool = True, From 57f97f924298f9af523afa5aa2d49cd7fda7cce7 Mon Sep 17 00:00:00 2001 From: Dustin Bleile Date: Wed, 7 Jan 2026 15:08:30 -0800 Subject: [PATCH 08/11] minor lint - isort inputs & quotes --- pori_python/ipr/ipr.py | 10 +++++----- pori_python/ipr/main.py | 2 +- tests/test_ipr/test_ipr.py | 28 ++++++++++++++-------------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/pori_python/ipr/ipr.py b/pori_python/ipr/ipr.py index fae5244..742a5a7 100644 --- a/pori_python/ipr/ipr.py +++ b/pori_python/ipr/ipr.py @@ -295,10 +295,10 @@ def create_key_alterations( counts: Dict[str, Set] = {v: set() for v in type_mapping.values()} skipped_variant_types = [] - included_kbvariant_ids = list(set([item['kbVariantId'] for item in included_kb_matches])) + included_kbvariant_ids = list(set([item["kbVariantId"] for item in included_kb_matches])) for kb_match in kb_matches: - if kb_match['kbVariantId'] not in included_kbvariant_ids: + if kb_match["kbVariantId"] not in included_kbvariant_ids: continue variant_type = kb_match["variantType"] variant_key = kb_match["variant"] @@ -646,13 +646,13 @@ def get_kb_matches_sections( unique_kb_variant_ids = list( set( [ - item['kbVariantId'] + item["kbVariantId"] for conditionSet in kb_statement_matched_conditions - for item in conditionSet['matchedConditions'] + for item in conditionSet["matchedConditions"] ] ) ) - kb_variants = [item for item in kb_variants if item['kbVariantId'] in unique_kb_variant_ids] + kb_variants = [item for item in kb_variants if item["kbVariantId"] in unique_kb_variant_ids] return { "kbMatches": kb_variants, diff --git a/pori_python/ipr/main.py b/pori_python/ipr/main.py index 81b0d79..c7e026b 100644 --- a/pori_python/ipr/main.py +++ b/pori_python/ipr/main.py @@ -525,7 +525,7 @@ def ipr_report( # KEY ALTERATIONS key_alterations, variant_counts = create_key_alterations( - gkb_matches, all_variants, kb_matched_sections['kbMatches'] + gkb_matches, all_variants, kb_matched_sections["kbMatches"] ) # OUTPUT CONTENT diff --git a/tests/test_ipr/test_ipr.py b/tests/test_ipr/test_ipr.py index 9994cf4..e7c68df 100644 --- a/tests/test_ipr/test_ipr.py +++ b/tests/test_ipr/test_ipr.py @@ -5,13 +5,13 @@ from pori_python.graphkb import vocab as gkb_vocab from pori_python.ipr.ipr import ( convert_statements_to_alterations, + create_key_alterations, germline_kb_matches, get_kb_disease_matches, get_kb_matched_statements, + get_kb_matches_sections, get_kb_statement_matched_conditions, get_kb_variants, - get_kb_matches_sections, - create_key_alterations, ) from pori_python.types import Statement @@ -497,10 +497,10 @@ def test_germline_kb_matches(self): ] ALL_VARIANTS = [ - {"variant": "var1", "key": '1', "variantType": 'mut'}, - {"variant": "var2", "key": '2', "variantType": 'mut'}, - {"variant": "var3", "key": '3', "variantType": 'mut'}, - {"variant": "var4", "key": '4', "variantType": 'mut'}, + {"variant": "var1", "key": "1", "variantType": "mut"}, + {"variant": "var2", "key": "2", "variantType": "mut"}, + {"variant": "var3", "key": "3", "variantType": "mut"}, + {"variant": "var4", "key": "4", "variantType": "mut"}, ] BASIC_GKB_MATCH = { @@ -709,8 +709,8 @@ def test_partial_matches_omitted(self): gkb_matches = create_gkb_matches(input_fields) sections = get_kb_matches_sections(gkb_matches, allow_partial_matches=False) - stmts = sections['kbMatchedStatements'] - kbcs = sections['kbStatementMatchedConditions'] + stmts = sections["kbMatchedStatements"] + kbcs = sections["kbStatementMatchedConditions"] assert len(stmts) == 2 assert len(kbcs) == 1 # X only assert kbcs[0]["kbStatementId"] == "X" @@ -796,14 +796,14 @@ def test_kbvariants_removed_from_set_when_not_part_of_full_conditionset_match(se item["kbVariant"] = "test" gkb_matches = create_gkb_matches(input_fields) sections1 = get_kb_matches_sections(gkb_matches, allow_partial_matches=False) - kbcs1 = sections1['kbStatementMatchedConditions'] - kbvars1 = sections1['kbMatches'] + kbcs1 = sections1["kbStatementMatchedConditions"] + kbvars1 = sections1["kbMatches"] assert len(kbcs1) == 1 # only fully matched condition sets included assert len(kbvars1) == 2 # therefore, kbvars associated with stmt X are pruned sections2 = get_kb_matches_sections(gkb_matches, allow_partial_matches=True) - kbcs2 = sections2['kbStatementMatchedConditions'] - kbvars2 = sections2['kbMatches'] + kbcs2 = sections2["kbStatementMatchedConditions"] + kbvars2 = sections2["kbMatches"] assert len(kbcs2) == 2 # all condition sets included assert len(kbvars2) == 3 # therefore, no pruning @@ -844,12 +844,12 @@ def test_create_key_alterations_includes_only_pruned_kbmatches(self): sections1 = get_kb_matches_sections(gkb_matches, allow_partial_matches=False) key_alts1, counts1 = create_key_alterations( - gkb_matches, ALL_VARIANTS, sections1['kbMatches'] + gkb_matches, ALL_VARIANTS, sections1["kbMatches"] ) sections2 = get_kb_matches_sections(gkb_matches, allow_partial_matches=True) key_alts2, counts2 = create_key_alterations( - gkb_matches, ALL_VARIANTS, sections2['kbMatches'] + gkb_matches, ALL_VARIANTS, sections2["kbMatches"] ) # check partial-match-only variants are not included in key alterations when From a48b090cb235211541be16c476e1402d002243ad Mon Sep 17 00:00:00 2001 From: Dustin Bleile Date: Wed, 7 Jan 2026 15:16:04 -0800 Subject: [PATCH 09/11] test_genes - remove unused import PREFERRED_GENE_SOURCE_NAME --- tests/test_graphkb/test_genes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_graphkb/test_genes.py b/tests/test_graphkb/test_genes.py index d5abc65..fd97ffc 100644 --- a/tests/test_graphkb/test_genes.py +++ b/tests/test_graphkb/test_genes.py @@ -7,7 +7,6 @@ from pori_python.graphkb import GraphKBConnection from pori_python.graphkb.genes import ( - PREFERRED_GENE_SOURCE_NAME, get_cancer_genes, get_cancer_predisposition_info, get_gene_information, From 87cd9ff431fc55429dde6ed57487d588dd625874 Mon Sep 17 00:00:00 2001 From: Dustin Bleile Date: Wed, 7 Jan 2026 15:39:26 -0800 Subject: [PATCH 10/11] raise errors if no graphkb url has been set --- pori_python/graphkb/util.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pori_python/graphkb/util.py b/pori_python/graphkb/util.py index 4a4cbff..772cfae 100644 --- a/pori_python/graphkb/util.py +++ b/pori_python/graphkb/util.py @@ -143,6 +143,8 @@ def request(self, endpoint: str, method: str = "GET", **kwargs) -> Dict: Returns: dict: the json response as a python dict """ + if not self.url: + raise ValueError("no GraphKBConnection url set - cannot make a login demo") url = join_url(self.url, endpoint) self.request_count += 1 connect_timeout = 7 @@ -223,7 +225,7 @@ def login_demo(self) -> None: 2. get a second token from the GraphKB API using keyCloakToken; self.login() """ if not self.url: - raise ValueError("no self.url set - cannot make a login demo") + raise ValueError("no GraphKBConnection url set - cannot make a login demo") url_parts = urlsplit(self.url) base_url = f"{url_parts.scheme}://{url_parts.netloc}" @@ -252,8 +254,10 @@ def login(self, username: str, password: str, pori_demo: bool = False) -> None: connect_timeout = 7 read_timeout = 61 - # KBDEV-1328. Alt. GraphKB login for GSC's PORI online demo - if self.url and (pori_demo or "pori-demo" in self.url): + if not self.url: + raise ValueError("no GraphKBConnection url set - cannot login") + elif pori_demo or "pori-demo" in self.url: + # KBDEV-1328. Alt. GraphKB login for GSC's PORI online demo logger.warning("login demo") self.login_demo() From 686511dcf89c1a9bec78419bb0624b371cca6b3f Mon Sep 17 00:00:00 2001 From: Dustin Bleile Date: Wed, 7 Jan 2026 15:51:47 -0800 Subject: [PATCH 11/11] Error message when no IPR_URL defined --- pori_python/ipr/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pori_python/ipr/main.py b/pori_python/ipr/main.py index c7e026b..b0b0782 100644 --- a/pori_python/ipr/main.py +++ b/pori_python/ipr/main.py @@ -363,7 +363,7 @@ def ipr_report( if ipr_url: ipr_conn = IprConnection(username, password, ipr_url) else: - logger.warning("No ipr_url given") + logger.error("No ipr_url given with no IPR_URL environment variable") if validate_json: if not ipr_conn: