Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
a1968ed
checking for project membership before creating the report
sshugsc Jan 29, 2026
be3add7
lint
sshugsc Jan 30, 2026
ce7460a
Merge branch 'develop' into feat/DEVSU-2815-make-create-report-access…
sshugsc Feb 4, 2026
d329813
add flag field to input types
elewis2 Feb 23, 2026
4859c20
add func to create observed vars section
elewis2 Feb 24, 2026
97f4740
add func to prepare observed var section
elewis2 Feb 24, 2026
29521a6
commit to save
elewis2 Mar 4, 2026
8e86f75
Merge branch 'develop' of https://github.com/bcgsc/pori_python into f…
elewis2 Mar 9, 2026
3d54876
Merge pull request #106 from bcgsc/main
elewis2 Mar 10, 2026
caa77be
add tests, fix string input formatting
elewis2 Mar 11, 2026
31e6af5
Merge branch 'develop' of https://github.com/bcgsc/pori_python into f…
elewis2 Mar 16, 2026
6305d2f
fix transcript matching for svs
elewis2 Mar 16, 2026
36d6ab0
add test for updating sv transcript flags
elewis2 Mar 16, 2026
bbabdd9
add upload test
elewis2 Mar 16, 2026
90a51bd
add flags to test_upload
elewis2 Mar 17, 2026
dab3e65
fix spec, add tests, format
elewis2 Mar 17, 2026
7fe28de
remove unneeded test file
elewis2 Mar 17, 2026
4e585ab
fix sigv pto autogeneration
elewis2 Mar 18, 2026
50539e1
combine tests
elewis2 Mar 20, 2026
221b52e
make it more readable
sshugsc Mar 24, 2026
6522eb6
Merge branch 'develop' into feat/DEVSU-2815-make-create-report-access…
sshugsc Mar 24, 2026
cf84f96
lint
sshugsc Mar 24, 2026
e76e467
fix unsorted list, tsv header option, missing dupe transcript flags
elewis2 Apr 2, 2026
d3bd260
fix json format error
elewis2 Apr 2, 2026
588b097
expect two-col transcript csv, other minor fixes
elewis2 Apr 14, 2026
14474b9
add to docstring
elewis2 Apr 14, 2026
7766f80
minor refactor to transcript flags func
elewis2 Apr 14, 2026
1d7e84e
minor tscpt func refactor; minor dry-ing in main
elewis2 Apr 14, 2026
16ae96a
add docstring
elewis2 Apr 14, 2026
a5490d5
fix transcript_csv handling tests
elewis2 Apr 14, 2026
dca2c94
clearer docstring, formatting
elewis2 Apr 17, 2026
f102866
Merge pull request #108 from bcgsc/feat/DEVSU-2909-fix-sv-pto-autogen…
elewis2 Apr 17, 2026
3a0eec3
add seqqc section and test
elewis2 Apr 17, 2026
72d6805
commit to save
elewis2 Apr 20, 2026
4d0181b
format
elewis2 Apr 20, 2026
1e9d46d
handle existing input format
elewis2 Apr 20, 2026
59f673c
Merge branch 'develop' into feat/DEVSU-2830-load-flags
elewis2 Apr 20, 2026
7f597ac
Merge pull request #107 from bcgsc/feat/DEVSU-2830-load-flags
elewis2 Apr 20, 2026
1ca9cb8
Merge branch 'feat/DEVSU-2815-make-create-report-access-project-speci…
elewis2 Apr 20, 2026
1c8153c
typo
sshugsc Apr 20, 2026
8ab54b9
check permission first, increase mins_to_wait
elewis2 Apr 20, 2026
acb3f8e
format
elewis2 Apr 20, 2026
4304ad1
merge
elewis2 Apr 20, 2026
39469e6
fix: create missing project in check_upload_permission and add user/m…
elewis2 Apr 20, 2026
37ced1e
remove report creation option
elewis2 Apr 20, 2026
a788ee4
format
elewis2 Apr 20, 2026
0f1fccb
SDEV-5340 - output_json - use json.dump so output is formatted.
dustinbleile Apr 23, 2026
8777c66
Merge pull request #110 from bcgsc/improvements/SDEV-5340_dragen
dustinbleile Apr 23, 2026
6ab42bc
Add GraphKBConnection.version
mathieulemieux Apr 28, 2026
966cb38
Merge pull request #112 from bcgsc/feature/KBDEV-1346-get-graphkb-ver…
mathieulemieux May 1, 2026
ca406cc
Merge branch 'develop' into feat/DEVSU-2848-update-seqqc-json
elewis2 May 5, 2026
e0ee8ec
format with ruff
elewis2 May 8, 2026
dcedd63
fix issues raised in pr
elewis2 May 11, 2026
416146e
Potential fix for pull request finding
elewis2 May 11, 2026
e2c25ef
normalize before upload_json as well
elewis2 May 11, 2026
ca8d5f0
Update get_cancer_genes() to fit consensus defenition of cancer gene
mathieulemieux May 11, 2026
bca3e1d
linting
mathieulemieux May 11, 2026
d6f7c9a
Add tumourigenesis to CANCER_GENE for backward compatibility
mathieulemieux May 12, 2026
458f88f
Use Union in type hint instead of pipe
mathieulemieux May 12, 2026
aa3b884
add project_exists check and tests
elewis2 May 13, 2026
9942425
Add get_related_records() and get_related_terms() to GraphKBConnection
mathieulemieux May 13, 2026
f13cf6b
Refactor get_cancer_genes() to use get_related_terms()
mathieulemieux May 13, 2026
f3a7f53
Add get_cancer_gene_flags()
mathieulemieux May 13, 2026
27ec429
Deprecate _get_tumourigenesis_genes_list(), get_oncokb_oncogenes(), g…
mathieulemieux May 13, 2026
22f5b41
Update get_gene_information() to use get_cancer_gene_flags()
mathieulemieux May 13, 2026
2e56ea8
Add equivalent gene name caching to get_gene_information()
mathieulemieux May 13, 2026
0713931
Revert equivalent gene names caching in get_gene_information()
mathieulemieux May 14, 2026
5c3fe7a
Add test to test_cancer_gene_flags()
mathieulemieux May 14, 2026
04362e5
Merge branch 'develop' into feat/DEVSU-2815-make-create-report-access…
elewis2 May 14, 2026
0444682
do not rely on side effect
elewis2 May 15, 2026
04a31b9
Merge pull request #109 from bcgsc/feat/DEVSU-2848-update-seqqc-json
elewis2 May 15, 2026
977c1a8
Fix typo
mathieulemieux May 15, 2026
03ebef1
Merge branch 'develop' into task/KBDEV-1532-cancer-gene-traverse-chil…
mathieulemieux May 15, 2026
9c1b35c
Merge branch 'develop' into feat/DEVSU-2815-make-create-report-access…
elewis2 May 15, 2026
482ea30
Fix formatting in get_cancer_gene_flags()
mathieulemieux May 15, 2026
d8a68fa
Refactor unique gene filtering in get_cancer_gene_flags()
mathieulemieux May 15, 2026
49aaeaa
Fix _get_tumourigenesis_genes_list() signature
mathieulemieux May 15, 2026
aa2bb7f
Fix typo
mathieulemieux May 15, 2026
9a5eb03
Fix get_related_records() & get_related_terms() signatures
mathieulemieux May 15, 2026
27b8ed5
Simplify filtering in get_related_terms()
mathieulemieux May 15, 2026
ffd4999
Merge pull request #94 from bcgsc/feat/DEVSU-2815-make-create-report-…
elewis2 May 19, 2026
7dee04c
Merge branch 'develop' into task/KBDEV-1532-cancer-gene-traverse-chil…
mathieulemieux May 19, 2026
9622ddf
Merge pull request #113 from bcgsc/task/KBDEV-1532-cancer-gene-traver…
mathieulemieux May 20, 2026
e54ebfe
Bump version from 1.4.0 to 1.5.0
mathieulemieux May 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion pori_python/graphkb/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@
TSO500_SOURCE_NAME = 'tso500'
ONCOGENE = 'oncogenic'
TUMOUR_SUPPRESSIVE = 'tumour suppressive'
CANCER_GENE = 'cancer gene'
CANCER_GENE = [
'cancer gene',
'tumourigenesis',
] # KBDEV-1532. tumourigenesis for backward compatibility
FUSION_NAMES = ['structural variant', 'fusion']

GSC_PHARMACOGENOMIC_SOURCE_EXCLUDE_LIST = ['cancer genome interpreter', 'civic']
Expand Down
142 changes: 130 additions & 12 deletions pori_python/graphkb/genes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from typing import Any, Dict, List, Sequence, Set, Tuple, cast
from typing import Any, Dict, List, Sequence, Set, Tuple, cast, Union
from typing_extensions import deprecated

from pori_python.types import IprGene, Ontology, Record, Statement, Variant
Expand All @@ -27,8 +27,117 @@
from .vocab import get_terms_set


def get_cancer_gene_flags(
conn: GraphKBConnection,
flags: bool = False,
ignore_cache: bool = False,
) -> Union[List[Record], Dict[str, List[Record]]]:
"""
Return all cancer genes, optionally sorted by flags.

Flag definitions:
oncogenic: relevance 'oncogenic' from OncoKB
tumourSuppressive: relevance 'tumour suppressive' from OncoKB
cancerGene: relevance 'cancer gene' AND child terms ('oncogenic', 'tumour suppressive', 'other cancer gene'), from OncoKB AND TSO500

Args:
conn: the graphkb connection object
flags: if the results should be sorted by flags
ignore_cache: if cache should be ignored when querying GraphKB API

Returns (if flags=False; default): list of unique gene records
[ <record>, <record>, ... ]

Returns (if flags=True): dict of flags as keys, and list of gene records as value
{
'oncogenic': [ <record>, <record>, ... ],
'tumourSuppressive': [ <record>, <record>, ... ],
'cancerGene': [ <record>, <record>, ... ],
}
"""
# all cancer gene statements
cancer_genes = conn.get_related_terms(
terms=CANCER_GENE,
subgraphType='children',
)
statements = cast(
List[Statement],
conn.query(
{
'target': 'Statement',
'filters': {
'relevance': {'target': 'Vocabulary', 'filters': {'name': cancer_genes}}
},
'returnProperties': [
'source.name',
'relevance.name',
*[f'subject.{prop}' for prop in GENE_RETURN_PROPERTIES],
],
},
ignore_cache=ignore_cache,
),
)

# post-query filtering (faster)
cancerGeneStms = list(
filter(
lambda r: (
r['subject']['@class'] == 'Feature'
and r['subject']['biotype'] == 'gene'
and r['source']['name'] in [ONCOKB_SOURCE_NAME, TSO500_SOURCE_NAME]
),
statements,
)
)
oncogenicStms = list(
filter(
lambda r: (
r['relevance']['name'] == ONCOGENE and r['source']['name'] == ONCOKB_SOURCE_NAME
),
cancerGeneStms,
)
)
tumourSuppressiveStms = list(
filter(
lambda r: (
r['relevance']['name'] == TUMOUR_SUPPRESSIVE
and r['source']['name'] == ONCOKB_SOURCE_NAME
),
cancerGeneStms,
)
)

# Returning a sorted list of unique gene records, based on iProbe requirements
# Unique by name, sorted by displayName
if not flags:
seen: set = set()
unique_genes: List[Record] = []
for r in cancerGeneStms:
name = r['subject']['name']
if name not in seen:
seen.add(name)
unique_genes.append(r['subject'])

return cast(
List[Record],
sorted(unique_genes, key=lambda gene: gene['displayName']),
)

# Returning a Dict of flags, with list of associated gene records
# Duplicates are ok
return {
'cancerGene': [r['subject'] for r in cancerGeneStms],
'oncogenic': [r['subject'] for r in oncogenicStms],
'tumourSuppressive': [r['subject'] for r in tumourSuppressiveStms],
}


@deprecated('functionality replaced by get_cancer_gene_flags')
def _get_tumourigenesis_genes_list(
conn: GraphKBConnection, relevance: str, sources: List[str], ignore_cache: bool = False
conn: GraphKBConnection,
relevance: Union[str, List[str]],
sources: Union[str, List[str]],
ignore_cache: bool = False,
) -> List[Ontology]:
statements = cast(
List[Statement],
Expand Down Expand Up @@ -57,6 +166,7 @@ def _get_tumourigenesis_genes_list(
return [gene for gene in genes.values()]


@deprecated('functionality replaced by get_cancer_gene_flags')
def get_oncokb_oncogenes(conn: GraphKBConnection) -> List[Ontology]:
"""Get the list of oncogenes stored in GraphKB derived from OncoKB.

Expand All @@ -66,9 +176,10 @@ def get_oncokb_oncogenes(conn: GraphKBConnection) -> List[Ontology]:
Returns:
gene (Feature) records
"""
return _get_tumourigenesis_genes_list(conn, ONCOGENE, [ONCOKB_SOURCE_NAME])
return _get_tumourigenesis_genes_list(conn, ONCOGENE, ONCOKB_SOURCE_NAME)


@deprecated('functionality replaced by get_cancer_gene_flags')
def get_oncokb_tumour_supressors(conn: GraphKBConnection) -> List[Ontology]:
"""Get the list of tumour supressor genes stored in GraphKB derived from OncoKB.

Expand All @@ -78,20 +189,27 @@ def get_oncokb_tumour_supressors(conn: GraphKBConnection) -> List[Ontology]:
Returns:
gene (Feature) records
"""
return _get_tumourigenesis_genes_list(conn, TUMOUR_SUPPRESSIVE, [ONCOKB_SOURCE_NAME])
return _get_tumourigenesis_genes_list(conn, TUMOUR_SUPPRESSIVE, ONCOKB_SOURCE_NAME)


@deprecated('functionality replaced by get_cancer_gene_flags')
def get_cancer_genes(conn: GraphKBConnection) -> List[Ontology]:
"""Get the list of cancer genes stored in GraphKB derived from OncoKB & TSO500.
"""
Get the list of cancer genes stored in GraphKB derived from OncoKB & TSO500.
Cancer genes include oncogenes, tumour supressor genes and other cancer genes.

Args:
conn: the graphkb connection object

Returns:
gene (Feature) records
"""
cancer_gene_terms = conn.get_related_terms(
terms=CANCER_GENE,
subgraphType='children',
)
return _get_tumourigenesis_genes_list(
conn, CANCER_GENE, [ONCOKB_SOURCE_NAME, TSO500_SOURCE_NAME]
conn, cancer_gene_terms, [ONCOKB_SOURCE_NAME, TSO500_SOURCE_NAME]
)


Expand Down Expand Up @@ -513,12 +631,12 @@ def get_gene_information(
# PositionalVariant without a reference2 implies a smallMutation type
gene_flags['knownSmallMutation'].add(condition['reference1']) # type: ignore

logger.info('fetching oncogenes list')
gene_flags['oncogene'] = convert_to_rid_set(get_oncokb_oncogenes(graphkb_conn))
logger.info('fetching tumour supressors list')
gene_flags['tumourSuppressor'] = convert_to_rid_set(get_oncokb_tumour_supressors(graphkb_conn))
logger.info('fetching cancerGeneListMatch list')
gene_flags['cancerGeneListMatch'] = convert_to_rid_set(get_cancer_genes(graphkb_conn))
# cancer gene flags
logger.info('fetching cancer genes')
cancer_gene_flags = get_cancer_gene_flags(graphkb_conn, flags=True)
gene_flags['oncogene'] = convert_to_rid_set(cancer_gene_flags['oncogenic'])
gene_flags['tumourSuppressor'] = convert_to_rid_set(cancer_gene_flags['tumourSuppressive'])
gene_flags['cancerGeneListMatch'] = convert_to_rid_set(cancer_gene_flags['cancerGene'])

logger.info('fetching therapeutic associated genes lists')
gene_flags['therapeuticAssociated'] = convert_to_rid_set(
Expand Down
68 changes: 68 additions & 0 deletions pori_python/graphkb/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,74 @@ def get_source(self, name: str) -> Record:
raise AssertionError(f'Unable to unqiuely identify source with name {name}')
return source[0]

@property
def version(self) -> Dict[str, str]:
"""
Retrieve GraphKB components version

Returns:
Dict[str, str]: component keys with version values

e.g. > {"api":"3.17.3","db":"production","parser":"2.1.0","schema":"4.1.1"}
"""
return self.request('version')

def get_related_records(
self,
base: Union[str, List[str]],
ontology: str,
subgraphType: str,
returnProperties: Optional[List[str]] = None,
) -> List[Record]:
"""
Given some base node RIDs, an ontology class and a subgraph type,
leverage the subgraphs route to return the list of related nodes.

Args:
base: the base node RIDs to start the graph traversal from
ontology: the ontology class to traverse
subgraphType: the type of traversal. See options in API specs
returnProperties: additional record properties to return

Returns:
list of related node record(s) traversed
"""
related = self.post(
uri=f'/subgraphs/{ontology}',
data={
'base': base if isinstance(base, list) else [base],
'subgraphType': subgraphType,
'returnProperties': returnProperties or [],
},
)
return related['result']['g']['nodes']

def get_related_terms(
self,
terms: Union[str, List[str]],
ontology: str = 'Vocabulary',
subgraphType: str = 'similar',
) -> List[str]:
"""
Given some base term name(s), an ontology class and a subgraph type,
leverage the subgraphs route to return the list of related term name(s)

Args:
terms: the base term name(s) to start the graph traversal from
ontology: the ontology class to traverse
subgraphType: the type of traversal

Returns:
list of related term name(s)
"""
rids = convert_to_rid_list(self.query({'target': ontology, 'filters': {'name': terms}}))
nodes = self.get_related_records(
base=rids,
ontology=ontology,
subgraphType=subgraphType,
)
return [x['name'] for x in nodes.values()]


def get_rid(conn: GraphKBConnection, target: str, name: str) -> str:
"""
Expand Down
4 changes: 2 additions & 2 deletions pori_python/graphkb/vocab.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from typing import Callable, Dict, Iterable, List, Set, cast
from typing import Callable, Dict, Iterable, List, Set, cast, Union

from pori_python.types import Ontology

from . import GraphKBConnection
from .util import convert_to_rid_list


def query_by_name(ontology_class: str, base_term_name: str) -> Dict:
def query_by_name(ontology_class: str, base_term_name: Union[str, list[str]]) -> Dict:
return {'target': ontology_class, 'filters': {'name': base_term_name}}


Expand Down
59 changes: 45 additions & 14 deletions pori_python/ipr/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,54 @@ def delete(self, uri: str, data: Dict = {}, **kwargs) -> Dict:
**kwargs,
)

def check_upload_permission(self, project_name: str) -> None:
"""Check that the current user has permission to upload to the given project.

Fetches all projects and the current user info (including groups and
projects) up front. Checks for admin, manager, create report access,
all projects access, and project membership.
"""
projects = self.get('project')
project_exists = any(p['name'] == project_name for p in projects)
if not project_exists:
raise Exception(
f'Project {project_name} does not exist or user does not have permission to view it'
)

user = self.get('user/me')
user_groups = user.get('groups', []) if isinstance(user, dict) else []
group_names = {
group.get('name', '').strip().lower()
if isinstance(group, dict)
else group.strip().lower()
for group in user_groups
}

is_admin = 'admin' in group_names
is_manager = 'manager' in group_names
has_create_report_access = 'create report access' in group_names
has_all_projects_access = 'all projects access' in group_names

# admins and managers can always create reports
can_create_report = is_admin or is_manager or has_create_report_access

user_projects = user.get('projects', []) if isinstance(user, dict) else []
has_project_access = (
is_admin
or has_all_projects_access
or any(isinstance(p, dict) and p.get('name') == project_name for p in user_projects)
)

if not can_create_report:
raise Exception('User does not have report creation permission')

if not has_project_access:
raise Exception(f'User has no permission to create report in project {project_name}')

def upload_report(
self,
content: Dict,
mins_to_wait: int = 5,
mins_to_wait: int = 10,
async_upload: bool = False,
ignore_extra_fields: bool = False,
) -> Dict:
Expand All @@ -105,19 +149,6 @@ def upload_report(
# or 'report'. jobStatus is no longer available once the report is successfully
# uploaded.

projects = self.get('project')
project_names = [item['name'] for item in projects]

# if project is not exist, create one
if content['project'] not in project_names:
logger.info(
f'Project not found - attempting to create project {content["project"]}'
)
try:
self.post('project', {'name': content['project']})
except Exception as err:
raise Exception(f'Project creation failed due to {err}')

if ignore_extra_fields:
initial_result = self.post('reports-async?ignore_extra_fields=true', content)
else:
Expand Down
Loading
Loading