diff --git a/apps/iatlas/api/.dockerignore b/apps/iatlas/api/.dockerignore new file mode 100644 index 0000000000..d9a6724a1e --- /dev/null +++ b/apps/iatlas/api/.dockerignore @@ -0,0 +1,6 @@ +**/*.md +.gitignore +.git/ +Dockerfile +no_commit/ +scripts/ \ No newline at end of file diff --git a/apps/iatlas/api/.env.example b/apps/iatlas/api/.env.example new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/iatlas/api/.gitignore b/apps/iatlas/api/.gitignore new file mode 100644 index 0000000000..3bb1115d7b --- /dev/null +++ b/apps/iatlas/api/.gitignore @@ -0,0 +1,26 @@ +# git +# used by git-log to rewrite usernames +.mailmap +git-genui.config.json + +# produced vignettes +vignettes/*.html +vignettes/*.pdf + +# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 +.httr-oauth + +# Generated files +.DS_Store + +# Visual Studio Code +.vscode +*.code-workspace + +# VIM +*.swp +*.swo + +docker/ +no_commit/ +scripts/__pycache__ \ No newline at end of file diff --git a/apps/iatlas/api/.python-version b/apps/iatlas/api/.python-version new file mode 100644 index 0000000000..9919bf8c90 --- /dev/null +++ b/apps/iatlas/api/.python-version @@ -0,0 +1 @@ +3.10.13 diff --git a/apps/iatlas/api/Dockerfile b/apps/iatlas/api/Dockerfile new file mode 100644 index 0000000000..cbd4652773 --- /dev/null +++ b/apps/iatlas/api/Dockerfile @@ -0,0 +1,8 @@ +# Start with a bare Alpine Linux to keep the container image small +FROM tiangolo/uwsgi-nginx-flask:python3.8 + +WORKDIR /app +COPY . /app + +# Install the PyPI dependencies using pip +RUN pip3 install --no-cache-dir -r requirements.txt \ No newline at end of file diff --git a/apps/iatlas/api/api/__init__.py b/apps/iatlas/api/api/__init__.py new file mode 100644 index 0000000000..0589004318 --- /dev/null +++ b/apps/iatlas/api/api/__init__.py @@ -0,0 +1,55 @@ +import logging +from json import loads +from datetime import datetime as dt +from flask import Flask, request +from config import get_config +from .extensions import db, logs + + +def create_app(test=False): + config = get_config(test=test) + app = Flask(__name__) + app.config.from_object(config) + + register_extensions(app) + + # Blueprint registration here. + from .main import bp as main_bp + app.register_blueprint(main_bp) + + @app.after_request + def after_request(response): + """ Logging after every POST request only if it isn't an introspection query. """ + json_data = request.get_json() + is_introspection_query = bool(json_data and json_data.get( + 'operationName', False) == 'IntrospectionQuery') + if request.method == 'POST' and not is_introspection_query: + logger = logging.getLogger('api.access') + logger.info( + '%s [%s] %s %s %s %s %s %s %s', + request.remote_addr, + dt.utcnow().strftime('%d/%b/%Y:%H:%M:%S.%f')[:-3], + request.method, + request.path, + request.scheme, + response.status, + response.content_length, + request.referrer, + request.user_agent, + ) + return response + + @ app.teardown_appcontext + def shutdown_session(exception=None): + db.session.remove() + + return app + + +def register_extensions(app): + db.init_app(app) + logs.init_app(app) + return None + + +from api import db_models diff --git a/apps/iatlas/api/api/database/__init__.py b/apps/iatlas/api/api/database/__init__.py new file mode 100644 index 0000000000..af1912b31b --- /dev/null +++ b/apps/iatlas/api/api/database/__init__.py @@ -0,0 +1,27 @@ +from .cohort_queries import * +from .cohort_to_feature_queries import * +from .cohort_to_gene_queries import * +from .cohort_to_mutation_queries import * +from .cohort_to_sample_queries import * +from .cohort_to_tag_queries import * +from .dataset_queries import * +from .dataset_to_sample_queries import * +from .dataset_to_tag_queries import * +from .edge_queries import * +from .feature_queries import * +from .feature_to_sample_queries import * +from .gene_queries import * +from .gene_to_sample_queries import * +from .gene_to_gene_set_queries import * +from .mutation_queries import * +from .node_queries import * +from .patient_queries import * +from .publication_queries import * +from .publication_to_gene_to_gene_set_queries import * +from .result_queries import * +from .sample_to_mutation_queries import * +from .sample_to_tag_queries import * +from .snp_queries import * +from .tag_queries import * +from .tag_to_publication_queries import * +from .tag_to_tag_queries import * diff --git a/apps/iatlas/api/api/database/cohort_queries.py b/apps/iatlas/api/api/database/cohort_queries.py new file mode 100644 index 0000000000..e8aac6556e --- /dev/null +++ b/apps/iatlas/api/api/database/cohort_queries.py @@ -0,0 +1,16 @@ +from sqlalchemy import orm +from api import db +from .database_helpers import build_general_query +from api.db_models import Cohort + +cohort_related_fields = ['data_set', 'tag', + 'samples', 'features', 'mutations', 'genes'] + +cohort_core_fields = ['id', 'name', 'dataset_id', 'tag_id'] + + +def return_cohort_query(*args): + return build_general_query( + Cohort, args=args, + accepted_option_args=cohort_related_fields, + accepted_query_args=cohort_core_fields) diff --git a/apps/iatlas/api/api/database/cohort_to_feature_queries.py b/apps/iatlas/api/api/database/cohort_to_feature_queries.py new file mode 100644 index 0000000000..5f4f183f62 --- /dev/null +++ b/apps/iatlas/api/api/database/cohort_to_feature_queries.py @@ -0,0 +1,16 @@ +from sqlalchemy import orm +from api import db +from api.db_models import CohortToFeature +from .database_helpers import build_general_query + +accepted_cohort_to_feature_option_args = ['cohort', 'feature'] + +accepted_cohort_to_feature_query_args = [ + 'cohort_id', 'feature_id'] + + +def return_cohort_to_feature_query(*args): + return build_general_query( + CohortToFeature, args=args, + accepted_option_args=accepted_cohort_to_feature_option_args, + accepted_query_args=accepted_cohort_to_feature_query_args) diff --git a/apps/iatlas/api/api/database/cohort_to_gene_queries.py b/apps/iatlas/api/api/database/cohort_to_gene_queries.py new file mode 100644 index 0000000000..e65a33172d --- /dev/null +++ b/apps/iatlas/api/api/database/cohort_to_gene_queries.py @@ -0,0 +1,16 @@ +from sqlalchemy import orm +from api import db +from api.db_models import CohortToGene +from .database_helpers import build_general_query + +accepted_cohort_to_gene_option_args = ['cohort', 'gene'] + +accepted_cohort_to_gene_query_args = [ + 'cohort_id', 'gene_id'] + + +def return_cohort_to_gene_query(*args): + return build_general_query( + CohortToGene, args=args, + accepted_option_args=accepted_cohort_to_gene_option_args, + accepted_query_args=accepted_cohort_to_gene_query_args) diff --git a/apps/iatlas/api/api/database/cohort_to_mutation_queries.py b/apps/iatlas/api/api/database/cohort_to_mutation_queries.py new file mode 100644 index 0000000000..773860a4e9 --- /dev/null +++ b/apps/iatlas/api/api/database/cohort_to_mutation_queries.py @@ -0,0 +1,16 @@ +from sqlalchemy import orm +from api import db +from api.db_models import CohortToMutation +from .database_helpers import build_general_query + +accepted_cohort_to_mutation_option_args = ['cohort', 'mutation'] + +accepted_cohort_to_mutation_query_args = [ + 'cohort_id', 'mutation_id'] + + +def return_cohort_to_mutation_query(*args): + return build_general_query( + CohortToMutation, args=args, + accepted_option_args=accepted_cohort_to_mutation_option_args, + accepted_query_args=accepted_cohort_to_mutation_query_args) diff --git a/apps/iatlas/api/api/database/cohort_to_sample_queries.py b/apps/iatlas/api/api/database/cohort_to_sample_queries.py new file mode 100644 index 0000000000..456a4233e1 --- /dev/null +++ b/apps/iatlas/api/api/database/cohort_to_sample_queries.py @@ -0,0 +1,16 @@ +from sqlalchemy import orm +from api import db +from api.db_models import CohortToSample +from .database_helpers import build_general_query + +accepted_cohort_to_sample_option_args = ['cohort', 'sample'] + +accepted_cohort_to_sample_query_args = [ + 'cohort_id', 'sample_id' 'tag_id'] + + +def return_cohort_to_sample_query(*args): + return build_general_query( + CohortToSample, args=args, + accepted_option_args=accepted_cohort_to_sample_option_args, + accepted_query_args=accepted_cohort_to_sample_query_args) diff --git a/apps/iatlas/api/api/database/cohort_to_tag_queries.py b/apps/iatlas/api/api/database/cohort_to_tag_queries.py new file mode 100644 index 0000000000..ba0b409737 --- /dev/null +++ b/apps/iatlas/api/api/database/cohort_to_tag_queries.py @@ -0,0 +1,16 @@ +from sqlalchemy import orm +from api import db +from api.db_models import CohortToTag +from .database_helpers import build_general_query + +accepted_cohort_to_tag_option_args = ['cohort', 'tag'] + +accepted_cohort_to_tag_query_args = [ + 'cohort_id', 'tag_id'] + + +def return_cohort_to_tag_query(*args): + return build_general_query( + CohortToTag, args=args, + accepted_option_args=accepted_cohort_to_tag_option_args, + accepted_query_args=accepted_cohort_to_tag_query_args) diff --git a/apps/iatlas/api/api/database/database_helpers.py b/apps/iatlas/api/api/database/database_helpers.py new file mode 100644 index 0000000000..fa4d4ab9f3 --- /dev/null +++ b/apps/iatlas/api/api/database/database_helpers.py @@ -0,0 +1,68 @@ +from sqlalchemy import orm +from sqlalchemy.ext.compiler import compiles +from sqlalchemy.sql.expression import ClauseElement, Executable +from sqlalchemy.sql import Select +from sqlalchemy.dialects import postgresql + +from api import db + +general_core_fields = ['id', 'name'] + + +def build_general_query(model, args=[], accepted_option_args=[], accepted_query_args=[]): + option_args = build_option_args(*args, accepted_args=accepted_option_args) + query_args = build_query_args( + model, *args, accepted_args=accepted_query_args) + query = db.session.query(*query_args) + if option_args: + # If option args are found, the whole model must be queried. + return db.session.query(model).options(*option_args) + return db.session.query(*query_args) + + +def build_option_args(*args, accepted_args=[]): + option_args = [] + for arg in args: + if arg in accepted_args: + option_args.append(orm.joinedload(arg)) + return option_args + + +def build_query_args(model, *argv, accepted_args=[]): + query_args = [] + for arg in argv: + if arg in accepted_args: + query_args.append(getattr(model, arg)) + if not query_args: + return [model] + return query_args + +def temp_table(name, query): + e = db.session.get_bind() + c = e.connect() + trans = c.begin() + c.execute(CreateTableAs(name, query)) + trans.commit() + return c + +class CreateTableAs(Select): + def __init__(self, name, query, *arg, **kw): + super(CreateTableAs, self).__init__(None, *arg, **kw) + self.name = name + self.query = query + +@compiles(CreateTableAs) +def _create_table_as(element, compiler, **kw): + text = element.query.statement.compile(dialect=postgresql.dialect(), compile_kwargs={'literal_binds': True}) + query = "CREATE TEMP TABLE %s AS %s" % ( + element.name, + text + ) + return query + +def execute_sql(query, conn=None): + if conn: + return conn.execute(query) + engine = db.session.get_bind() + with engine.connect() as conn: + return conn.execute(query) diff --git a/apps/iatlas/api/api/database/dataset_queries.py b/apps/iatlas/api/api/database/dataset_queries.py new file mode 100644 index 0000000000..045cd83a84 --- /dev/null +++ b/apps/iatlas/api/api/database/dataset_queries.py @@ -0,0 +1,16 @@ +from sqlalchemy import orm +from api import db +from api.db_models import Dataset +from .database_helpers import general_core_fields, build_general_query + +dataset_related_fields = [ + 'dataset_sample_assoc', 'dataset_tag_assoc', 'samples', 'tags'] + +dataset_core_fields = ['id', 'name', 'display', 'dataset_type'] + + +def return_dataset_query(*args, model=Dataset): + return build_general_query( + model, args=args, + accepted_option_args=dataset_related_fields, + accepted_query_args=dataset_core_fields) diff --git a/apps/iatlas/api/api/database/dataset_to_sample_queries.py b/apps/iatlas/api/api/database/dataset_to_sample_queries.py new file mode 100644 index 0000000000..b1f572e3f8 --- /dev/null +++ b/apps/iatlas/api/api/database/dataset_to_sample_queries.py @@ -0,0 +1,15 @@ +from sqlalchemy import orm +from api import db +from api.db_models import DatasetToSample +from .database_helpers import build_general_query + +related_fields = ['data_sets', 'samples'] + +core_fields = ['dataset_id', 'sample_id'] + + +def return_dataset_to_sample_query(*args): + return build_general_query( + DatasetToSample, args=args, + accepted_option_args=related_fields, + accepted_query_args=core_fields) diff --git a/apps/iatlas/api/api/database/dataset_to_tag_queries.py b/apps/iatlas/api/api/database/dataset_to_tag_queries.py new file mode 100644 index 0000000000..23c1caebb3 --- /dev/null +++ b/apps/iatlas/api/api/database/dataset_to_tag_queries.py @@ -0,0 +1,15 @@ +from sqlalchemy import orm +from api import db +from api.db_models import DatasetToTag +from .database_helpers import build_general_query + +related_fields = ['data_sets', 'tags'] + +core_fields = ['dataset_id', 'tag_id'] + + +def return_dataset_to_tag_query(*args): + return build_general_query( + DatasetToTag, args=args, + accepted_option_args=related_fields, + accepted_query_args=core_fields) diff --git a/apps/iatlas/api/api/database/edge_queries.py b/apps/iatlas/api/api/database/edge_queries.py new file mode 100644 index 0000000000..a3f2a8795e --- /dev/null +++ b/apps/iatlas/api/api/database/edge_queries.py @@ -0,0 +1,15 @@ +from sqlalchemy import orm +from api import db +from api.db_models import Edge +from .database_helpers import build_general_query + +accepted_option_args = ['node_1', 'node_2'] + +accepted_query_args = ['id', 'node_1_id', + 'node_2_id', 'name', 'label', 'score'] + + +def return_edge_query(*args): + return build_general_query( + Edge, args=args, accepted_option_args=accepted_option_args, + accepted_query_args=accepted_query_args) diff --git a/apps/iatlas/api/api/database/feature_queries.py b/apps/iatlas/api/api/database/feature_queries.py new file mode 100644 index 0000000000..afde21eaec --- /dev/null +++ b/apps/iatlas/api/api/database/feature_queries.py @@ -0,0 +1,17 @@ +from sqlalchemy import orm +from api import db +from api.db_models import Feature +from .database_helpers import general_core_fields, build_general_query + +feature_related_fields = [ + 'copy_number_results', 'driver_results', + 'feature_sample_assoc', 'samples'] + +feature_core_fields = [ + 'id', 'name', 'display', 'order', 'unit', 'feature_class', 'method_tag', 'germline_category', 'germline_module'] + +def return_feature_query(*args): + return build_general_query( + Feature, args=args, + accepted_option_args=feature_related_fields, + accepted_query_args=feature_core_fields) diff --git a/apps/iatlas/api/api/database/feature_to_sample_queries.py b/apps/iatlas/api/api/database/feature_to_sample_queries.py new file mode 100644 index 0000000000..bb2dbb1ff1 --- /dev/null +++ b/apps/iatlas/api/api/database/feature_to_sample_queries.py @@ -0,0 +1,15 @@ +from sqlalchemy import orm +from api import db +from api.db_models import FeatureToSample +from .database_helpers import build_general_query + +related_fields = ['features', 'samples'] + +core_fields = ['feature_id', 'sample_id', 'value'] + + +def return_feature_to_sample_query(*args): + return build_general_query( + FeatureToSample, args=args, + accepted_option_args=related_fields, + accepted_query_args=core_fields) diff --git a/apps/iatlas/api/api/database/gene_queries.py b/apps/iatlas/api/api/database/gene_queries.py new file mode 100644 index 0000000000..fe010bad19 --- /dev/null +++ b/apps/iatlas/api/api/database/gene_queries.py @@ -0,0 +1,43 @@ +from api import db +from api.db_models import Gene, GeneSet +from .database_helpers import general_core_fields, build_general_query + +gene_related_fields = ['copy_number_results', + 'driver_results', + 'gene_sample_assoc', + 'gene_set_assoc', + 'gene_sets', + 'publications', + 'publication_gene_gene_set_assoc', + 'samples'] + +gene_core_fields = ['id', + 'entrez_id', + 'hgnc_id', + 'description', + 'friendly_name', + 'io_landscape_name', + 'gene_family', + 'gene_function', + 'immune_checkpoint', + 'gene_pathway', + 'super_category', + 'therapy_type'] + +gene_set_related_fields = [ + 'genes', 'gene_set_assoc', 'publications', 'publication_gene_gene_set_assoc'] + +sub_related_fields = ['genes'] + + +def return_gene_query(*args, model=Gene): + return build_general_query( + model, args=args, + accepted_option_args=gene_related_fields, + accepted_query_args=gene_core_fields) + +def return_gene_set_query(*args): + return build_general_query( + GeneSet, args=args, + accepted_option_args=gene_set_related_fields, + accepted_query_args=general_core_fields) diff --git a/apps/iatlas/api/api/database/gene_to_gene_set_queries.py b/apps/iatlas/api/api/database/gene_to_gene_set_queries.py new file mode 100644 index 0000000000..b3e7b9913c --- /dev/null +++ b/apps/iatlas/api/api/database/gene_to_gene_set_queries.py @@ -0,0 +1,15 @@ +from sqlalchemy import orm +from api import db +from api.db_models import GeneToGeneSet +from .database_helpers import build_general_query + +related_fields = ['genes', 'gene_sets'] + +core_fields = ['gene_id', 'gene_set_id'] + + +def return_gene_to_gene_set_query(*args): + return build_general_query( + GeneToGeneSet, args=args, + accepted_option_args=related_fields, + accepted_query_args=core_fields) diff --git a/apps/iatlas/api/api/database/gene_to_sample_queries.py b/apps/iatlas/api/api/database/gene_to_sample_queries.py new file mode 100644 index 0000000000..c50a18c723 --- /dev/null +++ b/apps/iatlas/api/api/database/gene_to_sample_queries.py @@ -0,0 +1,15 @@ +from sqlalchemy import orm +from api import db +from api.db_models import GeneToSample +from .database_helpers import build_general_query + +related_fields = ['gene', 'sample'] + +core_fields = ['gene_id', 'sample_id', 'rna_seq_expression', 'nanostring_expression'] + + +def return_gene_to_sample_query(*args): + return build_general_query( + GeneToSample, args=args, + accepted_option_args=related_fields, + accepted_query_args=core_fields) diff --git a/apps/iatlas/api/api/database/mutation_queries.py b/apps/iatlas/api/api/database/mutation_queries.py new file mode 100644 index 0000000000..1a4fd2637b --- /dev/null +++ b/apps/iatlas/api/api/database/mutation_queries.py @@ -0,0 +1,31 @@ +from sqlalchemy import orm +from api import db +from api.db_models import Mutation, MutationType +from .database_helpers import build_general_query, general_core_fields + +mutation_related_fields = [ + 'gene', 'mutation_type', 'sample_mutation_assoc', 'samples'] +mutation_core_fields = [ + 'id', 'name', 'gene_id', 'mutation_code', 'mutation_type_id'] + +mutation_code_related_fields = ['driver_results', 'mutations'] +mutation_code_core_fields = ['id', 'code'] + +mutation_type_related_fields = ['mutations'] +mutation_type_core_fields = general_core_fields + ['display'] + + +def return_mutation_query(*args): + return build_general_query( + Mutation, args=args, + accepted_option_args=mutation_related_fields, + accepted_query_args=mutation_core_fields) + + +def return_mutation_type_query(*args): + return build_general_query( + MutationType, args=args, + accepted_option_args=mutation_type_related_fields, + accepted_query_args=mutation_type_core_fields) + + diff --git a/apps/iatlas/api/api/database/node_queries.py b/apps/iatlas/api/api/database/node_queries.py new file mode 100644 index 0000000000..75cea71aae --- /dev/null +++ b/apps/iatlas/api/api/database/node_queries.py @@ -0,0 +1,17 @@ +from api import db +from api.db_models import Node +from .database_helpers import build_general_query + +related_fields = [ + 'data_sets', 'edges_primary', 'edges_secondary', + 'feature', 'gene', 'tag1', 'tag2'] + +core_fields = ['id', 'dataset_id', 'feature_id', + 'gene_id', 'name', 'network', 'label', 'score', 'x', 'y'] + + +def return_node_query(*args): + return build_general_query( + Node, args=args, + accepted_option_args=related_fields, + accepted_query_args=core_fields) diff --git a/apps/iatlas/api/api/database/patient_queries.py b/apps/iatlas/api/api/database/patient_queries.py new file mode 100644 index 0000000000..1d0b47156c --- /dev/null +++ b/apps/iatlas/api/api/database/patient_queries.py @@ -0,0 +1,48 @@ +from sqlalchemy import orm +from api import db +from api.db_models import Patient, Sample, Slide +from .database_helpers import build_general_query + +patient_related_fields = ['samples', 'slides'] + +patient_core_fields = [ + 'id', 'age_at_diagnosis', 'barcode', 'ethnicity', 'gender', 'height', 'race', 'weight'] + +sample_related_fields = ['data_sets', + 'dataset_sample_assoc', + 'feature_sample_assoc', + 'features', + 'gene_sample_assoc', + 'genes', + 'mutations', + 'patient', + 'sample_mutation_assoc', + 'sample_tag_assoc', + 'tags'] + +sample_core_fields = ['id', 'name', 'patient_id'] + +slide_related_fields = ['patient'] + +slide_core_fields = ['id', 'name', 'description'] + + +def return_patient_query(*args): + return build_general_query( + Patient, args=args, + accepted_option_args=patient_related_fields, + accepted_query_args=patient_core_fields) + + +def return_sample_query(*args): + return build_general_query( + Sample, args=args, + accepted_option_args=sample_related_fields, + accepted_query_args=sample_core_fields) + + +def return_slide_query(*args): + return build_general_query( + Slide, args=args, + accepted_option_args=slide_related_fields, + accepted_query_args=slide_core_fields) diff --git a/apps/iatlas/api/api/database/publication_queries.py b/apps/iatlas/api/api/database/publication_queries.py new file mode 100644 index 0000000000..e93576ad99 --- /dev/null +++ b/apps/iatlas/api/api/database/publication_queries.py @@ -0,0 +1,19 @@ +from api import db +from api.db_models import Publication +from .database_helpers import build_general_query + +publication_related_fields = ['genes', + 'gene_sets', + 'publication_gene_gene_set_assoc', + 'tag_publication_assoc', + 'tags'] + +publication_core_fields = ['id', 'do_id', 'first_author_last_name', + 'journal', 'link', 'pubmed_id', 'title', 'year'] + + +def return_publication_query(*args): + return build_general_query( + Publication, args=args, + accepted_option_args=publication_related_fields, + accepted_query_args=publication_core_fields) diff --git a/apps/iatlas/api/api/database/publication_to_gene_to_gene_set_queries.py b/apps/iatlas/api/api/database/publication_to_gene_to_gene_set_queries.py new file mode 100644 index 0000000000..768c79c86e --- /dev/null +++ b/apps/iatlas/api/api/database/publication_to_gene_to_gene_set_queries.py @@ -0,0 +1,15 @@ +from sqlalchemy import orm +from api import db +from api.db_models import PublicationToGeneToGeneSet +from .database_helpers import build_general_query + +related_fields = ['gene_sets', 'genes', 'publications'] + +core_fields = ['gene_id', 'gene_set_id', 'publication_id'] + + +def return_publication_to_gene_to_gene_set_query(*args, model=PublicationToGeneToGeneSet): + return build_general_query( + model, args=args, + accepted_option_args=related_fields, + accepted_query_args=core_fields) diff --git a/apps/iatlas/api/api/database/result_queries.py b/apps/iatlas/api/api/database/result_queries.py new file mode 100644 index 0000000000..09aa6e19f8 --- /dev/null +++ b/apps/iatlas/api/api/database/result_queries.py @@ -0,0 +1,191 @@ +from api import db +from api.db_models import ( + Colocalization, + CopyNumberResult, + DriverResult, + Neoantigen, + HeritabilityResult, + GermlineGwasResult, + RareVariantPathwayAssociation +) +from .database_helpers import build_option_args, build_query_args + +accepted_coloc_option_args = [ + 'data_set', 'coloc_data_set', 'feature', 'gene', 'snp' +] + +accepted_coloc_query_args = [ + 'id', + 'dataset_id', + 'coloc_dataset_id', + 'feature_id', + 'gene_id', + 'snp_id', + 'qtl_type', + 'ecaviar_pp', + 'plot_type'] + +accepted_cnr_option_args = ['data_set', 'feature', 'gene', 'tag'] + +accepted_cnr_query_args = [ + 'id', + 'direction', + 'mean_normal', + 'mean_cnv', + 'p_value', + 'log10_p_value', + 't_stat', + 'dataset_id', + 'feature_id', + 'gene_id', + 'tag_id' +] + +accepted_dr_option_args = ['data_set', 'feature', 'tag', 'mutation'] + +accepted_dr_query_args = [ + 'id', + 'p_value', + 'fold_change', + 'log10_p_value', + 'log10_fold_change', + 'n_wt', + 'n_mut', + 'dataset_id', + 'feature_id', + 'mutation_id', + 'tag_id' +] + +accepted_hr_option_args = ['data_set', 'feature'] + +accepted_hr_query_args = [ + 'dataset_id', + 'id' + 'feature_id', + 'p_value', + 'cluster', + 'fdr', + 'variance', + 'se' +] + +accepted_ggr_option_args = ['data_set', 'feature', 'snp'] + +accepted_ggr_query_args = [ + 'dataset_id', + 'id' + 'feature_id', + 'snp_id', + 'p_value', + 'maf' +] + +accepted_neoantigen_option_args = ['patient', 'gene'] + +accepted_neoantigen_query_args = [ + 'id', + 'pmhc', + 'freq_pmhc', + 'tpm', + 'neoantiogen_gene_id', + 'patient_id', +] + +accepted_rvpa_option_args = ['data_set', 'feature'] + +accepted_rvpa_query_args = [ + 'id', + 'data_set_id', + 'feature_id', + 'pathway', + 'p_value', + 'min', + 'max', + 'mean', + 'q1', + 'q2', + 'q3', + 'n_mutants', + 'n_total' +] + + +def return_colocalization_query(*args): + option_args = build_option_args( + *args, accepted_args=accepted_coloc_option_args) + query_args = build_query_args( + Colocalization, * args, accepted_args=accepted_coloc_query_args) + query = db.session.query(*query_args) + if option_args: + query = db.session.query(Colocalization).options(*option_args) + return query + + +def return_copy_number_result_query(*args): + option_args = build_option_args( + *args, accepted_args=accepted_cnr_option_args) + query_args = build_query_args( + CopyNumberResult, * args, accepted_args=accepted_cnr_query_args) + query = db.session.query(*query_args) + if option_args: + query = db.session.query(CopyNumberResult).options(*option_args) + return query + + +def return_driver_result_query(*args): + option_args = build_option_args( + *args, accepted_args=accepted_dr_option_args) + query_args = build_query_args( + DriverResult, *args, accepted_args=accepted_dr_query_args) + query = db.session.query(*query_args) + if option_args: + query = db.session.query(DriverResult).options(*option_args) + return query + + +def return_heritability_result_query(*args): + option_args = build_option_args( + *args, accepted_args=accepted_hr_option_args) + query_args = build_query_args( + HeritabilityResult, *args, accepted_args=accepted_hr_query_args) + query = db.session.query(*query_args) + if option_args: + query = db.session.query(HeritabilityResult).options(*option_args) + return query + + +def return_germline_gwas_result_query(*args): + option_args = build_option_args( + *args, accepted_args=accepted_ggr_option_args) + query_args = build_query_args( + GermlineGwasResult, *args, accepted_args=accepted_ggr_query_args) + query = db.session.query(*query_args) + if option_args: + query = db.session.query(GermlineGwasResult).options(*option_args) + return query + + +def return_neoantigen_query(*args): + option_args = build_option_args( + *args, accepted_args=accepted_neoantigen_option_args + ) + query_args = build_query_args( + Neoantigen, *args, accepted_args=accepted_neoantigen_query_args + ) + query = db.session.query(*query_args) + if option_args: + query = db.session.query(Neoantigen).options(*option_args) + return query + + +def return_rare_variant_pathway_associations_query(*args): + option_args = build_option_args( + *args, accepted_args=accepted_rvpa_option_args) + query_args = build_query_args( + RareVariantPathwayAssociation, *args, accepted_args=accepted_rvpa_query_args) + query = db.session.query(*query_args) + if option_args: + query = db.session.query( + RareVariantPathwayAssociation).options(*option_args) + return query diff --git a/apps/iatlas/api/api/database/sample_queries.py b/apps/iatlas/api/api/database/sample_queries.py new file mode 100644 index 0000000000..9bce3dd064 --- /dev/null +++ b/apps/iatlas/api/api/database/sample_queries.py @@ -0,0 +1,14 @@ +from api.db_models import Sample +from .database_helpers import build_general_query + +sample_related_fields = ['features', 'genes'] + +sample_core_fields = [ + 'id', 'name', 'patient_id'] + + +def return_sample_query(*args): + return build_general_query( + Sample, args=args, + accepted_option_args=sample_related_fields, + accepted_query_args=sample_core_fields) diff --git a/apps/iatlas/api/api/database/sample_to_mutation_queries.py b/apps/iatlas/api/api/database/sample_to_mutation_queries.py new file mode 100644 index 0000000000..230665ee65 --- /dev/null +++ b/apps/iatlas/api/api/database/sample_to_mutation_queries.py @@ -0,0 +1,15 @@ +from sqlalchemy import orm +from api import db +from api.db_models import SampleToMutation +from .database_helpers import build_general_query + +related_fields = ['mutations', 'samples'] + +core_fields = ['mutation_id', 'sample_id'] + + +def return_sample_to_mutation_query(*args): + return build_general_query( + SampleToMutation, args=args, + accepted_option_args=related_fields, + accepted_query_args=core_fields) diff --git a/apps/iatlas/api/api/database/sample_to_tag_queries.py b/apps/iatlas/api/api/database/sample_to_tag_queries.py new file mode 100644 index 0000000000..13e18b1885 --- /dev/null +++ b/apps/iatlas/api/api/database/sample_to_tag_queries.py @@ -0,0 +1,15 @@ +from sqlalchemy import orm +from api import db +from api.db_models import SampleToTag +from .database_helpers import build_general_query + +related_fields = ['samples', 'tags'] + +core_fields = ['sample_id', 'tag_id'] + + +def return_sample_to_tag_query(*args): + return build_general_query( + SampleToTag, args=args, + accepted_option_args=related_fields, + accepted_query_args=core_fields) diff --git a/apps/iatlas/api/api/database/slide_queries.py b/apps/iatlas/api/api/database/slide_queries.py new file mode 100644 index 0000000000..dc1df3278a --- /dev/null +++ b/apps/iatlas/api/api/database/slide_queries.py @@ -0,0 +1,15 @@ +from flaskr.db_models import Slide +from .database_helpers import general_core_fields, build_general_query + +slide_related_fields = [ + 'patient'] + +slide_core_fields = [ + 'id', 'name', 'description', 'patient_id'] + + +def return_slide_query(*args): + return build_general_query( + Slide, args=args, + accepted_option_args=slide_related_fields, + accepted_query_args=slide_core_fields) diff --git a/apps/iatlas/api/api/database/snp_queries.py b/apps/iatlas/api/api/database/snp_queries.py new file mode 100644 index 0000000000..dd57765967 --- /dev/null +++ b/apps/iatlas/api/api/database/snp_queries.py @@ -0,0 +1,13 @@ +from sqlalchemy import orm +from api import db +from .database_helpers import general_core_fields, build_general_query +from api.db_models import Snp + +snp_core_fields = [ + 'id', 'name', 'rsid', 'chr', 'bp'] + + +def return_snp_query(*args): + return build_general_query( + Snp, args=args, + accepted_query_args=snp_core_fields) diff --git a/apps/iatlas/api/api/database/tag_queries.py b/apps/iatlas/api/api/database/tag_queries.py new file mode 100644 index 0000000000..b4cbff2fbb --- /dev/null +++ b/apps/iatlas/api/api/database/tag_queries.py @@ -0,0 +1,33 @@ +from sqlalchemy import orm +from api import db +from api.db_models import Tag +from .database_helpers import build_general_query + +related_fields = ['copy_number_results', + 'data_sets', + 'dataset_tag_assoc', + 'driver_results', + 'node_tag_assoc', + 'nodes', + 'publications', + 'related_tags', + 'sample_tag_assoc', + 'samples', + 'tag_publication_assoc', + 'tags'] + +core_fields = ['id', + 'characteristics', + 'color', + 'long_display', + 'name', + 'short_display', + 'type', + 'order'] + + +def return_tag_query(*args, model=Tag): + return build_general_query( + model, args=args, + accepted_option_args=related_fields, + accepted_query_args=core_fields) diff --git a/apps/iatlas/api/api/database/tag_to_publication_queries.py b/apps/iatlas/api/api/database/tag_to_publication_queries.py new file mode 100644 index 0000000000..489db82a80 --- /dev/null +++ b/apps/iatlas/api/api/database/tag_to_publication_queries.py @@ -0,0 +1,15 @@ +from sqlalchemy import orm +from api import db +from api.db_models import TagToPublication +from .database_helpers import build_general_query + +related_fields = ['publications', 'tags'] + +core_fields = ['publication_id', 'tag_id'] + + +def return_tag_to_publication_query(*args): + return build_general_query( + TagToPublication, args=args, + accepted_option_args=related_fields, + accepted_query_args=core_fields) diff --git a/apps/iatlas/api/api/database/tag_to_tag_queries.py b/apps/iatlas/api/api/database/tag_to_tag_queries.py new file mode 100644 index 0000000000..8964cb9861 --- /dev/null +++ b/apps/iatlas/api/api/database/tag_to_tag_queries.py @@ -0,0 +1,15 @@ +from sqlalchemy import orm +from api import db +from api.db_models import TagToTag +from .database_helpers import build_general_query + +related_fields = ['related_tags', 'tags'] + +core_fields = ['related_tag_id', 'tag_id'] + + +def return_tag_to_tag_query(*args, model=TagToTag): + return build_general_query( + model, args=args, + accepted_option_args=related_fields, + accepted_query_args=core_fields) diff --git a/apps/iatlas/api/api/db_models/__init__.py b/apps/iatlas/api/api/db_models/__init__.py new file mode 100644 index 0000000000..ddab14fc4e --- /dev/null +++ b/apps/iatlas/api/api/db_models/__init__.py @@ -0,0 +1,41 @@ +from api import db + +Base = db.Model + +from .cohort import Cohort +from .cohort_to_gene import CohortToGene +from .cohort_to_feature import CohortToFeature +from .cohort_to_mutation import CohortToMutation +from .cohort_to_sample import CohortToSample +from .cohort_to_tag import CohortToTag +from .colocalization import Colocalization +from .copy_number_result import CopyNumberResult +from .dataset import Dataset +from .dataset_to_sample import DatasetToSample +from .dataset_to_tag import DatasetToTag +from .driver_result import DriverResult +from .edge import Edge +from .feature import Feature +from .feature_to_sample import FeatureToSample +from .gene import Gene +from .gene_to_sample import GeneToSample +from .gene_to_gene_set import GeneToGeneSet +from .gene_set import GeneSet +from .germline_gwas_result import GermlineGwasResult +from .heritability_result import HeritabilityResult +from .mutation import Mutation +from .mutation_type import MutationType +from .neoantigen import Neoantigen +from .node import Node +from .patient import Patient +from .publication import Publication +from .publication_to_gene_to_gene_set import PublicationToGeneToGeneSet +from .rare_variant_pathway_associations import RareVariantPathwayAssociation +from .sample import Sample +from .sample_to_mutation import SampleToMutation +from .sample_to_tag import SampleToTag +from .slide import Slide +from .snp import Snp +from .tag import Tag +from .tag_to_publication import TagToPublication +from .tag_to_tag import TagToTag diff --git a/apps/iatlas/api/api/db_models/cohort.py b/apps/iatlas/api/api/db_models/cohort.py new file mode 100644 index 0000000000..9bbc8c045f --- /dev/null +++ b/apps/iatlas/api/api/db_models/cohort.py @@ -0,0 +1,37 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class Cohort(Base): + __tablename__ = 'cohorts' + id = db.Column(db.String, primary_key=True) + name = db.Column(db.String, nullable=False) + + dataset_id = db.Column(db.String, db.ForeignKey( + 'datasets.id'), nullable=False) + + cohort_tag_id = db.Column(db.String, db.ForeignKey('tags.id'), nullable=False) + + data_set = db.relationship( + 'Dataset', backref=orm.backref('cohorts', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + tag = db.relationship( + 'Tag', backref=orm.backref('cohorts', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + samples = db.relationship( + "Sample", secondary='cohorts_to_samples', uselist=True, lazy='noload') + + features = db.relationship( + "Feature", secondary='cohorts_to_features', uselist=True, lazy='noload') + + genes = db.relationship( + "Gene", secondary='cohorts_to_genes', uselist=True, lazy='noload') + + mutations = db.relationship( + "Mutation", secondary='cohorts_to_mutations', uselist=True, lazy='noload') + + def __repr__(self): + return '' % self.name diff --git a/apps/iatlas/api/api/db_models/cohort_to_feature.py b/apps/iatlas/api/api/db_models/cohort_to_feature.py new file mode 100644 index 0000000000..3354f6ac0d --- /dev/null +++ b/apps/iatlas/api/api/db_models/cohort_to_feature.py @@ -0,0 +1,24 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class CohortToFeature(Base): + __tablename__ = 'cohorts_to_features' + + id = db.Column(db.String, primary_key=True) + + cohort_id = db.Column(db.String, db.ForeignKey( + 'cohorts.id'), primary_key=True) + + feature_id = db.Column(db.String, db.ForeignKey( + 'features.id'), primary_key=True) + + cohort = db.relationship('Cohort', backref=orm.backref( + 'cohort_feature_assoc', uselist=True, lazy='noload'), uselist=False, lazy='noload') + + feature = db.relationship('Feature', backref=orm.backref( + 'cohort_feature_assoc', uselist=True, lazy='noload'), uselist=False, lazy='noload') + + def __repr__(self): + return '' % self.id diff --git a/apps/iatlas/api/api/db_models/cohort_to_gene.py b/apps/iatlas/api/api/db_models/cohort_to_gene.py new file mode 100644 index 0000000000..d6bfef2c2e --- /dev/null +++ b/apps/iatlas/api/api/db_models/cohort_to_gene.py @@ -0,0 +1,24 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class CohortToGene(Base): + __tablename__ = 'cohorts_to_genes' + + id = db.Column(db.String, primary_key=True) + + cohort_id = db.Column(db.String, db.ForeignKey( + 'cohorts.id'), primary_key=True) + + gene_id = db.Column(db.String, db.ForeignKey( + 'genes.id'), primary_key=True) + + cohort = db.relationship('Cohort', backref=orm.backref( + 'cohort_gene_assoc', uselist=True, lazy='noload'), uselist=False, lazy='noload') + + gene = db.relationship('Gene', backref=orm.backref( + 'cohort_gene_assoc', uselist=True, lazy='noload'), uselist=False, lazy='noload') + + def __repr__(self): + return '' % self.id diff --git a/apps/iatlas/api/api/db_models/cohort_to_mutation.py b/apps/iatlas/api/api/db_models/cohort_to_mutation.py new file mode 100644 index 0000000000..d2d7c14cf8 --- /dev/null +++ b/apps/iatlas/api/api/db_models/cohort_to_mutation.py @@ -0,0 +1,24 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class CohortToMutation(Base): + __tablename__ = 'cohorts_to_mutations' + + id = db.Column(db.String, primary_key=True) + + cohort_id = db.Column(db.String, db.ForeignKey( + 'cohorts.id'), primary_key=True) + + mutation_id = db.Column(db.String, db.ForeignKey( + 'mutations.id'), primary_key=True) + + cohort = db.relationship('Cohort', backref=orm.backref( + 'cohort_mutation_assoc', uselist=True, lazy='noload'), uselist=False, lazy='noload') + + mutation = db.relationship('Mutation', backref=orm.backref( + 'cohort_mutation_assoc', uselist=True, lazy='noload'), uselist=False, lazy='noload') + + def __repr__(self): + return '' % self.id diff --git a/apps/iatlas/api/api/db_models/cohort_to_sample.py b/apps/iatlas/api/api/db_models/cohort_to_sample.py new file mode 100644 index 0000000000..ee41240c5d --- /dev/null +++ b/apps/iatlas/api/api/db_models/cohort_to_sample.py @@ -0,0 +1,27 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class CohortToSample(Base): + __tablename__ = 'cohorts_to_samples' + + id = db.Column(db.String, primary_key=True) + + cohort_id = db.Column(db.String, db.ForeignKey( + 'cohorts.id'), primary_key=True) + + sample_id = db.Column(db.String, db.ForeignKey( + 'samples.id'), primary_key=True) + + cohorts_to_samples_tag_id = db.Column(db.Integer, db.ForeignKey( + 'tags.id'), primary_key=True) + + cohort = db.relationship('Cohort', backref=orm.backref( + 'cohort_sample_assoc', uselist=True, lazy='noload'), uselist=False, lazy='noload') + + sample = db.relationship('Sample', backref=orm.backref( + 'cohort_sample_assoc', uselist=True, lazy='noload'), uselist=False, lazy='noload') + + def __repr__(self): + return '' % self.id diff --git a/apps/iatlas/api/api/db_models/cohort_to_tag.py b/apps/iatlas/api/api/db_models/cohort_to_tag.py new file mode 100644 index 0000000000..e512442a54 --- /dev/null +++ b/apps/iatlas/api/api/db_models/cohort_to_tag.py @@ -0,0 +1,24 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class CohortToTag(Base): + __tablename__ = 'cohorts_to_tags' + + id = db.Column(db.String, primary_key=True) + + cohort_id = db.Column(db.String, db.ForeignKey( + 'cohorts.id'), primary_key=True) + + tag_id = db.Column(db.String, db.ForeignKey( + 'tags.id'), primary_key=True) + + cohort = db.relationship('Cohort', backref=orm.backref( + 'cohort_tag_assoc', uselist=True, lazy='noload'), uselist=False, lazy='noload') + + tag = db.relationship('Tag', backref=orm.backref( + 'cohort_tag_assoc', uselist=True, lazy='noload'), uselist=False, lazy='noload') + + def __repr__(self): + return '' % self.id diff --git a/apps/iatlas/api/api/db_models/colocalization.py b/apps/iatlas/api/api/db_models/colocalization.py new file mode 100644 index 0000000000..51fcf0fd33 --- /dev/null +++ b/apps/iatlas/api/api/db_models/colocalization.py @@ -0,0 +1,51 @@ +from sqlalchemy import orm +from api import db +from . import Base +from api.enums import qtl_enum, ecaviar_pp_enum, coloc_plot_type_enum + + +class Colocalization(Base): + __tablename__ = 'colocalizations' + id = db.Column(db.String, primary_key=True) + qtl_type = db.Column(qtl_enum, nullable=False) + ecaviar_pp = db.Column(ecaviar_pp_enum, nullable=True) + plot_type = db.Column(coloc_plot_type_enum, nullable=True) + tissue = db.Column(db.String, nullable=True) + splice_loc = db.Column(db.String, nullable=True) + link = db.Column(db.String, nullable=False) + + dataset_id = db.Column(db.String, db.ForeignKey( + 'datasets.id'), nullable=False) + + coloc_dataset_id = db.Column(db.String, db.ForeignKey( + 'datasets.id'), nullable=False) + + feature_id = db.Column(db.String, db.ForeignKey( + 'features.id'), nullable=False) + + gene_id = db.Column(db.String, db.ForeignKey('genes.id'), nullable=False) + + snp_id = db.Column(db.String, db.ForeignKey('snps.id'), nullable=False) + + data_set = db.relationship( + 'Dataset', backref=orm.backref('colocalizations_primary', uselist=True, lazy='noload'), + uselist=False, lazy='noload', primaryjoin='Dataset.id==Colocalization.dataset_id') + + coloc_data_set = db.relationship( + 'Dataset', backref=orm.backref('colocalizations_secondary', uselist=True, lazy='noload'), + uselist=False, lazy='noload', primaryjoin='Dataset.id==Colocalization.coloc_dataset_id') + + feature = db.relationship( + 'Feature', backref=orm.backref('colocalizations', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + gene = db.relationship( + 'Gene', backref=orm.backref('colocalizations', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + snp = db.relationship( + 'Snp', backref=orm.backref('colocalizations', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + def __repr__(self): + return '' % self.id diff --git a/apps/iatlas/api/api/db_models/copy_number_result.py b/apps/iatlas/api/api/db_models/copy_number_result.py new file mode 100644 index 0000000000..6a44bba5e0 --- /dev/null +++ b/apps/iatlas/api/api/db_models/copy_number_result.py @@ -0,0 +1,40 @@ +from sqlalchemy import orm +from api import db +from . import Base +from api.enums import direction_enum + + +class CopyNumberResult(Base): + __tablename__ = 'copy_number_results' + id = db.Column(db.String, primary_key=True) + direction = db.Column(direction_enum, nullable=False) + mean_normal = db.Column(db.Numeric, nullable=True) + mean_cnv = db.Column(db.Numeric, nullable=True) + p_value = db.Column(db.Numeric, nullable=True) + log10_p_value = db.Column(db.Numeric, nullable=True) + t_stat = db.Column(db.Numeric, nullable=True) + + dataset_id = db.Column(db.String, db.ForeignKey( + 'datasets.id'), nullable=False) + + feature_id = db.Column(db.String, db.ForeignKey( + 'features.id'), nullable=False) + + gene_id = db.Column(db.String, db.ForeignKey('genes.id'), nullable=False) + + tag_id = db.Column(db.String, db.ForeignKey('tags.id'), nullable=False) + + data_set = db.relationship('Dataset', backref=orm.backref( + 'copy_number_results', uselist=True, lazy='noload'), uselist=False, lazy='noload') + + feature = db.relationship('Feature', backref=orm.backref( + 'copy_number_results', uselist=True, lazy='noload'), uselist=False, lazy='noload') + + gene = db.relationship('Gene', backref=orm.backref( + 'copy_number_results', uselist=True, lazy='noload'), uselist=False, lazy='noload') + + tag = db.relationship('Tag', backref=orm.backref( + 'copy_number_results', uselist=True, lazy='noload'), uselist=False, lazy='noload') + + def __repr__(self): + return '' % self.id diff --git a/apps/iatlas/api/api/db_models/dataset.py b/apps/iatlas/api/api/db_models/dataset.py new file mode 100644 index 0000000000..fb8f001a47 --- /dev/null +++ b/apps/iatlas/api/api/db_models/dataset.py @@ -0,0 +1,19 @@ +from api import db +from . import Base + + +class Dataset(Base): + __tablename__ = 'datasets' + id = db.Column(db.String, primary_key=True) + name = db.Column(db.String, nullable=False) + display = db.Column(db.String, nullable=True) + dataset_type = db.Column(db.String, nullable=False) + + samples = db.relationship( + 'Sample', secondary='datasets_to_samples', uselist=True, lazy='noload') + + tags = db.relationship( + 'Tag', secondary='datasets_to_tags', uselist=True, lazy='noload') + + def __repr__(self): + return '' % self.name diff --git a/apps/iatlas/api/api/db_models/dataset_to_sample.py b/apps/iatlas/api/api/db_models/dataset_to_sample.py new file mode 100644 index 0000000000..059ffb34db --- /dev/null +++ b/apps/iatlas/api/api/db_models/dataset_to_sample.py @@ -0,0 +1,22 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class DatasetToSample(Base): + __tablename__ = 'datasets_to_samples' + + dataset_id = db.Column( + db.String, db.ForeignKey('datasets.id'), primary_key=True) + + sample_id = db.Column( + db.String, db.ForeignKey('samples.id'), nullable=False) + + data_sets = db.relationship('Dataset', backref=orm.backref( + 'dataset_sample_assoc', uselist=True, lazy='noload'), uselist=True, lazy='noload') + + samples = db.relationship('Sample', backref=orm.backref( + 'dataset_sample_assoc', uselist=True, lazy='noload'), uselist=True, lazy='noload') + + def __repr__(self): + return '' % self.dataset_id diff --git a/apps/iatlas/api/api/db_models/dataset_to_tag.py b/apps/iatlas/api/api/db_models/dataset_to_tag.py new file mode 100644 index 0000000000..a24291b67c --- /dev/null +++ b/apps/iatlas/api/api/db_models/dataset_to_tag.py @@ -0,0 +1,22 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class DatasetToTag(Base): + __tablename__ = 'datasets_to_tags' + + dataset_id = db.Column( + db.String, db.ForeignKey('datasets.id'), primary_key=True) + + tag_id = db.Column( + db.String, db.ForeignKey('tags.id'), nullable=False) + + data_sets = db.relationship('Dataset', backref=orm.backref( + 'dataset_tag_assoc', uselist=True, lazy='noload'), uselist=True, lazy='noload') + + tags = db.relationship('Tag', backref=orm.backref( + 'dataset_tag_assoc', uselist=True, lazy='noload'), uselist=True, lazy='noload') + + def __repr__(self): + return '' % self.dataset_id diff --git a/apps/iatlas/api/api/db_models/driver_result.py b/apps/iatlas/api/api/db_models/driver_result.py new file mode 100644 index 0000000000..e196a8ba1b --- /dev/null +++ b/apps/iatlas/api/api/db_models/driver_result.py @@ -0,0 +1,44 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class DriverResult(Base): + __tablename__ = 'driver_results' + id = db.Column(db.String, primary_key=True) + p_value = db.Column(db.Numeric, nullable=True) + fold_change = db.Column(db.Numeric, nullable=True) + log10_p_value = db.Column(db.Numeric, nullable=True) + log10_fold_change = db.Column(db.Numeric, nullable=True) + n_wildtype = db.Column(db.Integer, nullable=True) + n_mutants = db.Column(db.Integer, nullable=True) + + dataset_id = db.Column(db.String, db.ForeignKey( + 'datasets.id'), nullable=False) + + feature_id = db.Column(db.String, db.ForeignKey( + 'features.id'), nullable=False) + + mutation_id = db.Column(db.String, db.ForeignKey( + 'mutations.id'), nullable=False) + + tag_id = db.Column(db.String, db.ForeignKey('tags.id'), nullable=False) + + data_set = db.relationship( + 'Dataset', backref=orm.backref('driver_results', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + feature = db.relationship( + 'Feature', backref=orm.backref('driver_results', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + mutation = db.relationship( + 'Mutation', backref=orm.backref('driver_results', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + tag = db.relationship( + 'Tag', backref=orm.backref('driver_results', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + def __repr__(self): + return '' % self.id diff --git a/apps/iatlas/api/api/db_models/edge.py b/apps/iatlas/api/api/db_models/edge.py new file mode 100644 index 0000000000..e9819d5fc5 --- /dev/null +++ b/apps/iatlas/api/api/db_models/edge.py @@ -0,0 +1,29 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class Edge(Base): + __tablename__ = 'edges' + id = db.Column(db.String, primary_key=True) + + node_1_id = db.Column( + db.String, db.ForeignKey('nodes.id'), nullable=False) + + node_2_id = db.Column( + db.String, db.ForeignKey('nodes.id'), nullable=False) + + label = db.Column(db.String, nullable=True) + name = db.Column(db.String, nullable=False) + score = db.Column(db.Numeric, nullable=True) + + node_1 = db.relationship( + 'Node', backref=orm.backref('edges_primary', uselist=True, lazy='noload'), + uselist=False, lazy='noload', primaryjoin='Node.id==Edge.node_1_id') + + node_2 = db.relationship( + 'Node', backref=orm.backref('edges_secondary', uselist=True, lazy='noload'), + uselist=False, lazy='noload', primaryjoin='Node.id==Edge.node_2_id') + + def __repr__(self): + return '' % self.id diff --git a/apps/iatlas/api/api/db_models/feature.py b/apps/iatlas/api/api/db_models/feature.py new file mode 100644 index 0000000000..bb10608dc3 --- /dev/null +++ b/apps/iatlas/api/api/db_models/feature.py @@ -0,0 +1,23 @@ +from sqlalchemy import orm +from api import db +from . import Base +from api.enums import unit_enum + + +class Feature(Base): + __tablename__ = 'features' + id = db.Column(db.String, primary_key=True) + name = db.Column(db.String, nullable=False) + display = db.Column(db.String, nullable=True) + order = db.Column(db.Integer, nullable=True) + unit = db.Column(unit_enum, nullable=True) + germline_category = db.Column(db.String, nullable=True) + germline_module = db.Column(db.String, nullable=True) + feature_class = db.Column(db.String, nullable=False) + method_tag = db.Column(db.String, nullable=False) + + samples = db.relationship( + "Sample", secondary='features_to_samples', uselist=True, lazy='noload') + + def __repr__(self): + return '' % self.name diff --git a/apps/iatlas/api/api/db_models/feature_to_sample.py b/apps/iatlas/api/api/db_models/feature_to_sample.py new file mode 100644 index 0000000000..35b5f33d4f --- /dev/null +++ b/apps/iatlas/api/api/db_models/feature_to_sample.py @@ -0,0 +1,25 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class FeatureToSample(Base): + __tablename__ = 'features_to_samples' + + id = db.Column(db.String, primary_key=True) + feature_to_sample_value = db.Column(db.Numeric, nullable=True) + + feature_id = db.Column(db.String, db.ForeignKey( + 'features.id'), primary_key=True) + + sample_id = db.Column(db.String, db.ForeignKey( + 'samples.id'), primary_key=True) + + features = db.relationship('Feature', backref=orm.backref( + 'feature_sample_assoc', uselist=True, lazy='noload'), uselist=True, lazy='noload') + + samples = db.relationship('Sample', backref=orm.backref( + 'feature_sample_assoc', uselist=True, lazy='noload'), uselist=True, lazy='noload') + + def __repr__(self): + return '' % self.feature_id diff --git a/apps/iatlas/api/api/db_models/gene.py b/apps/iatlas/api/api/db_models/gene.py new file mode 100644 index 0000000000..37de221543 --- /dev/null +++ b/apps/iatlas/api/api/db_models/gene.py @@ -0,0 +1,31 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class Gene(Base): + __tablename__ = 'genes' + id = db.Column(db.String, primary_key=True) + entrez_id = db.Column(db.Integer, nullable=False) + hgnc_id = db.Column(db.String, nullable=False) + description = db.Column(db.String, nullable=True) + friendly_name = db.Column(db.String, nullable=True) + io_landscape_name = db.Column(db.String, nullable=True) + gene_family = db.Column(db.String, nullable=True) + gene_function = db.Column(db.String, nullable=True) + immune_checkpoint = db.Column(db.String, nullable=True) + gene_pathway = db.Column(db.String, nullable=True) + super_category = db.Column(db.String, nullable=True) + therapy_type = db.Column(db.String, nullable=True) + + gene_sets = db.relationship( + "GeneSet", secondary='genes_to_gene_sets', uselist=True, lazy='noload') + + publications = db.relationship( + "Publication", secondary='publications_to_genes_to_gene_sets', uselist=True, lazy='noload') + + samples = db.relationship( + "Sample", secondary='genes_to_samples', uselist=True, lazy='noload') + + def __repr__(self): + return '' % self.entrez_id diff --git a/apps/iatlas/api/api/db_models/gene_set.py b/apps/iatlas/api/api/db_models/gene_set.py new file mode 100644 index 0000000000..58a843d4af --- /dev/null +++ b/apps/iatlas/api/api/db_models/gene_set.py @@ -0,0 +1,18 @@ +from api import db +from . import Base + + +class GeneSet(Base): + __tablename__ = 'gene_sets' + id = db.Column(db.String, primary_key=True) + name = db.Column(db.String, nullable=False) + display = db.Column(db.String, nullable=True) + + genes = db.relationship( + 'Gene', secondary='genes_to_gene_sets', uselist=True, lazy='noload') + + publications = db.relationship( + 'Publication', secondary='publications_to_genes_to_gene_sets', uselist=True, lazy='noload') + + def __repr__(self): + return '' % self.name diff --git a/apps/iatlas/api/api/db_models/gene_to_gene_set.py b/apps/iatlas/api/api/db_models/gene_to_gene_set.py new file mode 100644 index 0000000000..fdc0bb1895 --- /dev/null +++ b/apps/iatlas/api/api/db_models/gene_to_gene_set.py @@ -0,0 +1,23 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class GeneToGeneSet(Base): + __tablename__ = 'genes_to_gene_sets' + id = db.Column(db.String, primary_key=True) + + gene_id = db.Column( + db.String, db.ForeignKey('genes.id'), primary_key=True) + + gene_set_id = db.Column( + db.String, db.ForeignKey('gene_sets.id'), primary_key=True) + + genes = db.relationship('Gene', backref=orm.backref( + 'gene_set_assoc', uselist=True, lazy='noload'), uselist=True, lazy='noload') + + gene_sets = db.relationship('GeneSet', backref=orm.backref( + 'gene_set_assoc', uselist=True, lazy='noload'), uselist=True, lazy='noload') + + def __repr__(self): + return '' % self.gene_id diff --git a/apps/iatlas/api/api/db_models/gene_to_sample.py b/apps/iatlas/api/api/db_models/gene_to_sample.py new file mode 100644 index 0000000000..fcf8ab300c --- /dev/null +++ b/apps/iatlas/api/api/db_models/gene_to_sample.py @@ -0,0 +1,26 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class GeneToSample(Base): + __tablename__ = 'genes_to_samples' + + gene_id = db.Column(db.String, db.ForeignKey( + 'genes.id'), primary_key=True) + + sample_id = db.Column(db.String, db.ForeignKey( + 'samples.id'), primary_key=True) + + rna_seq_expression = db.Column(db.Numeric, nullable=True) + + nanostring_expression = db.Column(db.Numeric, nullable=True) + + gene = db.relationship('Gene', backref=orm.backref( + 'gene_sample_assoc', uselist=True, lazy='noload'), uselist=False, lazy='noload') + + sample = db.relationship('Sample', backref=orm.backref( + 'gene_sample_assoc', uselist=True, lazy='noload'), uselist=False, lazy='noload') + + def __repr__(self): + return '' % self.gene_id diff --git a/apps/iatlas/api/api/db_models/germline_gwas_result.py b/apps/iatlas/api/api/db_models/germline_gwas_result.py new file mode 100644 index 0000000000..44b76a81c7 --- /dev/null +++ b/apps/iatlas/api/api/db_models/germline_gwas_result.py @@ -0,0 +1,34 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class GermlineGwasResult(Base): + __tablename__ = 'germline_gwas_results' + id = db.Column(db.String, primary_key=True) + p_value = db.Column(db.Numeric, nullable=True) + maf = db.Column(db.Numeric, nullable=True) + + dataset_id = db.Column(db.String, db.ForeignKey( + 'datasets.id'), nullable=False) + + feature_id = db.Column(db.String, db.ForeignKey( + 'features.id'), nullable=False) + + snp_id = db.Column(db.String, db.ForeignKey( + 'snps.id'), nullable=False) + + data_set = db.relationship( + 'Dataset', backref=orm.backref('germline_gwas_results', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + feature = db.relationship( + 'Feature', backref=orm.backref('germline_gwas_results', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + snp = db.relationship( + 'Snp', backref=orm.backref('germline_gwas_results', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + def __repr__(self): + return '' % self.id diff --git a/apps/iatlas/api/api/db_models/heritability_result.py b/apps/iatlas/api/api/db_models/heritability_result.py new file mode 100644 index 0000000000..553b7e1089 --- /dev/null +++ b/apps/iatlas/api/api/db_models/heritability_result.py @@ -0,0 +1,30 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class HeritabilityResult(Base): + __tablename__ = 'heritability_results' + id = db.Column(db.String, primary_key=True) + p_value = db.Column(db.Numeric, nullable=True) + fdr = db.Column(db.Numeric, nullable=True) + variance = db.Column(db.Numeric, nullable=True) + se = db.Column(db.Numeric, nullable=True) + cluster = db.Column(db.String, nullable=False) + + dataset_id = db.Column(db.String, db.ForeignKey( + 'datasets.id'), nullable=False) + + feature_id = db.Column(db.String, db.ForeignKey( + 'features.id'), nullable=False) + + data_set = db.relationship( + 'Dataset', backref=orm.backref('heritability_results', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + feature = db.relationship( + 'Feature', backref=orm.backref('heritability_results', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + def __repr__(self): + return '' % self.id diff --git a/apps/iatlas/api/api/db_models/mutation.py b/apps/iatlas/api/api/db_models/mutation.py new file mode 100644 index 0000000000..67b99e5762 --- /dev/null +++ b/apps/iatlas/api/api/db_models/mutation.py @@ -0,0 +1,28 @@ +from sqlalchemy import orm +from api import db +from . import Base + +class Mutation(Base): + __tablename__ = 'mutations' + id = db.Column(db.String, primary_key=True) + name = db.Column(db.String, nullable=False) + mutation_code = db.Column(db.String, nullable=False) + + gene_id = db.Column(db.String, db.ForeignKey('genes.id'), nullable=False) + + mutation_type_id = db.Column( + db.String, db.ForeignKey('mutation_types.id'), nullable=True) + + gene = db.relationship( + "Gene", backref=orm.backref('mutations', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + mutation_type = db.relationship( + "MutationType", backref=orm.backref('mutations', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + samples = db.relationship( + "Sample", secondary='samples_to_mutations', uselist=True, lazy='noload') + + def __repr__(self): + return '' % self.id diff --git a/apps/iatlas/api/api/db_models/mutation_type.py b/apps/iatlas/api/api/db_models/mutation_type.py new file mode 100644 index 0000000000..5d823e01e4 --- /dev/null +++ b/apps/iatlas/api/api/db_models/mutation_type.py @@ -0,0 +1,12 @@ +from api import db +from . import Base + + +class MutationType(Base): + __tablename__ = 'mutation_types' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String, nullable=False) + display = db.Column(db.String, nullable=True) + + def __repr__(self): + return '' % self.name diff --git a/apps/iatlas/api/api/db_models/neoantigen.py b/apps/iatlas/api/api/db_models/neoantigen.py new file mode 100644 index 0000000000..6a687cf92d --- /dev/null +++ b/apps/iatlas/api/api/db_models/neoantigen.py @@ -0,0 +1,32 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class Neoantigen(Base): + __tablename__ = 'neoantigens' + id = db.Column(db.String, primary_key=True) + tpm = db.Column(db.Float, nullable=True) + pmhc = db.Column(db.String, nullable=False) + freq_pmhc = db.Column(db.Integer, nullable=False) + patient_id = db.Column(db.String, db.ForeignKey( + 'patients.id'), nullable=False) + neoantigen_gene_id = db.Column( + db.String, db.ForeignKey('genes.id'), nullable=True) + + gene = db.relationship( + 'Gene', + backref=orm.backref('neoantigen_assoc', uselist=True, lazy='noload'), + uselist=True, + lazy='noload' + ) + + patient = db.relationship( + 'Patient', + backref=orm.backref('neoantigen_assoc', uselist=True, lazy='noload'), + uselist=True, + lazy='noload' + ) + + def __repr__(self): + return '' % self.id diff --git a/apps/iatlas/api/api/db_models/node.py b/apps/iatlas/api/api/db_models/node.py new file mode 100644 index 0000000000..f1945b69f7 --- /dev/null +++ b/apps/iatlas/api/api/db_models/node.py @@ -0,0 +1,62 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class Node(Base): + __tablename__ = 'nodes' + id = db.Column(db.String, primary_key=True) + label = db.Column(db.String, nullable=True) + network = db.Column(db.String, nullable=False) + name = db.Column(db.String, nullable=False) + score = db.Column(db.Numeric, nullable=True) + x = db.Column(db.Numeric, nullable=True) + y = db.Column(db.Numeric, nullable=True) + + dataset_id = db.Column( + db.String, db.ForeignKey('datasets.id'), nullable=True) + + node_feature_id = db.Column( + db.String, db.ForeignKey('features.id'), nullable=True) + + node_gene_id = db.Column(db.String, db.ForeignKey('genes.id'), nullable=True) + + tag_1_id = db.Column( + db.String, db.ForeignKey('tags.id'), nullable=False) + + tag_2_id = db.Column( + db.String, db.ForeignKey('tags.id'), nullable=True) + + + data_set = db.relationship( + 'Dataset', backref=orm.backref('node', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + feature = db.relationship( + 'Feature', backref=orm.backref('node', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + gene = db.relationship( + 'Gene', backref=orm.backref('node', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + tag1 = db.relationship( + 'Tag', + backref=orm.backref('node1', uselist=True, lazy='noload'), + uselist=False, + lazy='noload', + foreign_keys=tag_1_id + ) + + tag2 = db.relationship( + 'Tag', + backref=orm.backref('node2', uselist=True, lazy='noload'), + uselist=False, + lazy='noload', + foreign_keys=tag_2_id + ) + + + + def __repr__(self): + return '' % self.id diff --git a/apps/iatlas/api/api/db_models/patient.py b/apps/iatlas/api/api/db_models/patient.py new file mode 100644 index 0000000000..4832b359a0 --- /dev/null +++ b/apps/iatlas/api/api/db_models/patient.py @@ -0,0 +1,19 @@ +from sqlalchemy import orm +from api import db +from . import Base +from api.enums import ethnicity_enum, gender_enum, race_enum + + +class Patient(Base): + __tablename__ = 'patients' + id = db.Column(db.String, primary_key=True) + age_at_diagnosis = db.Column(db.Integer, nullable=True) + name = db.Column(db.String, nullable=False) + ethnicity = db.Column(ethnicity_enum, nullable=True) + gender = db.Column(gender_enum, nullable=True) + height = db.Column(db.Integer, nullable=True) + race = db.Column(race_enum, nullable=True) + weight = db.Column(db.Integer, nullable=True) + + def __repr__(self): + return '' % self.name diff --git a/apps/iatlas/api/api/db_models/publication.py b/apps/iatlas/api/api/db_models/publication.py new file mode 100644 index 0000000000..1c55277a4b --- /dev/null +++ b/apps/iatlas/api/api/db_models/publication.py @@ -0,0 +1,26 @@ +from api import db +from . import Base + + +class Publication(Base): + __tablename__ = 'publications' + id = db.Column(db.String, primary_key=True) + do_id = db.Column(db.String, nullable=True) + first_author_last_name = db.Column(db.String, nullable=True) + journal = db.Column(db.String, nullable=True) + link = db.Column(db.String, nullable=False) + pubmed_id = db.Column(db.Integer, nullable=True) + title = db.Column(db.String, nullable=True) + year = db.Column(db.Integer, nullable=True) + + genes = db.relationship( + 'Gene', secondary='publications_to_genes_to_gene_sets', uselist=True, lazy='noload') + + gene_sets = db.relationship( + 'GeneSet', secondary='publications_to_genes_to_gene_sets', uselist=True, lazy='noload') + + tags = db.relationship( + 'Tag', secondary='tags_to_publications', uselist=True, lazy='noload') + + def __repr__(self): + return '' % self.title diff --git a/apps/iatlas/api/api/db_models/publication_to_gene_to_gene_set.py b/apps/iatlas/api/api/db_models/publication_to_gene_to_gene_set.py new file mode 100644 index 0000000000..35aa92b6e9 --- /dev/null +++ b/apps/iatlas/api/api/db_models/publication_to_gene_to_gene_set.py @@ -0,0 +1,28 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class PublicationToGeneToGeneSet(Base): + __tablename__ = 'publications_to_genes_to_gene_sets' + + gene_id = db.Column( + db.String, db.ForeignKey('genes.id'), primary_key=True) + + gene_set_id = db.Column( + db.String, db.ForeignKey('gene_sets.id'), primary_key=True) + + publication_id = db.Column( + db.String, db.ForeignKey('publications.id'), primary_key=True) + + genes = db.relationship('Gene', backref=orm.backref( + 'publication_gene_gene_set_assoc', uselist=True, lazy='noload'), uselist=True, lazy='noload') + + gene_sets = db.relationship('GeneSet', backref=orm.backref( + 'publication_gene_gene_set_assoc', uselist=True, lazy='noload'), uselist=True, lazy='noload') + + publications = db.relationship('Publication', backref=orm.backref( + 'publication_gene_gene_set_assoc', uselist=True, lazy='noload'), uselist=True, lazy='noload') + + def __repr__(self): + return '' % self.gene_id diff --git a/apps/iatlas/api/api/db_models/rare_variant_pathway_associations.py b/apps/iatlas/api/api/db_models/rare_variant_pathway_associations.py new file mode 100644 index 0000000000..88d9dbdba0 --- /dev/null +++ b/apps/iatlas/api/api/db_models/rare_variant_pathway_associations.py @@ -0,0 +1,35 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class RareVariantPathwayAssociation(Base): + __tablename__ = 'rare_variant_pathway_associations' + id = db.Column(db.String, primary_key=True) + pathway = db.Column(db.String) + p_value = db.Column(db.Numeric, nullable=True) + min = db.Column(db.Numeric, nullable=True) + max = db.Column(db.Numeric, nullable=True) + mean = db.Column(db.Numeric, nullable=True) + q1 = db.Column(db.Numeric, nullable=True) + q2 = db.Column(db.Numeric, nullable=True) + q3 = db.Column(db.Numeric, nullable=True) + n_mutants = db.Column(db.Integer, nullable=True) + n_total = db.Column(db.Integer, nullable=True) + + dataset_id = db.Column(db.String, db.ForeignKey( + 'datasets.id'), nullable=False) + + feature_id = db.Column(db.String, db.ForeignKey( + 'features.id'), nullable=False) + + data_set = db.relationship( + 'Dataset', backref=orm.backref('rare_variant_pathway_associations', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + feature = db.relationship( + 'Feature', backref=orm.backref('rare_variant_pathway_associations', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + def __repr__(self): + return '' % self.id diff --git a/apps/iatlas/api/api/db_models/sample.py b/apps/iatlas/api/api/db_models/sample.py new file mode 100644 index 0000000000..4ce9bd4e31 --- /dev/null +++ b/apps/iatlas/api/api/db_models/sample.py @@ -0,0 +1,34 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class Sample(Base): + __tablename__ = 'samples' + id = db.Column(db.String, primary_key=True) + name = db.Column(db.String, nullable=False) + + patient_id = db.Column( + db.String, db.ForeignKey('patients.id'), nullable=True) + + data_sets = db.relationship( + "Dataset", secondary='datasets_to_samples', uselist=True, lazy='noload') + + features = db.relationship( + "Feature", secondary='features_to_samples', uselist=True, lazy='noload') + + genes = db.relationship( + "Gene", secondary='genes_to_samples', uselist=True, lazy='noload') + + mutations = db.relationship( + "Mutation", secondary='samples_to_mutations', uselist=True, lazy='noload') + + tags = db.relationship( + "Tag", secondary='samples_to_tags', uselist=True, lazy='noload') + + patient = db.relationship( + "Patient", backref=orm.backref('samples', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + def __repr__(self): + return '' % self.name diff --git a/apps/iatlas/api/api/db_models/sample_to_mutation.py b/apps/iatlas/api/api/db_models/sample_to_mutation.py new file mode 100644 index 0000000000..bd90eba3e3 --- /dev/null +++ b/apps/iatlas/api/api/db_models/sample_to_mutation.py @@ -0,0 +1,25 @@ +from sqlalchemy import orm +from api import db +from . import Base +from api.enums import status_enum + + +class SampleToMutation(Base): + __tablename__ = 'samples_to_mutations' + id = db.Column(db.String, primary_key=True) + mutation_status = db.Column(status_enum, nullable=False) + + sample_id = db.Column( + db.String, db.ForeignKey('samples.id'), primary_key=True) + + mutation_id = db.Column( + db.String, db.ForeignKey('mutations.id'), primary_key=True) + + samples = db.relationship('Sample', backref=orm.backref( + 'sample_mutation_assoc', uselist=True, lazy='noload'), uselist=True, lazy='noload') + + mutations = db.relationship('Mutation', backref=orm.backref( + 'sample_mutation_assoc', uselist=True, lazy='noload'), uselist=True, lazy='noload') + + def __repr__(self): + return '' % self.sample_id diff --git a/apps/iatlas/api/api/db_models/sample_to_tag.py b/apps/iatlas/api/api/db_models/sample_to_tag.py new file mode 100644 index 0000000000..86f569a84a --- /dev/null +++ b/apps/iatlas/api/api/db_models/sample_to_tag.py @@ -0,0 +1,22 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class SampleToTag(Base): + __tablename__ = 'samples_to_tags' + + sample_id = db.Column( + db.String, db.ForeignKey('samples.id'), primary_key=True) + + tag_id = db.Column( + db.String, db.ForeignKey('tags.id'), primary_key=True) + + samples = db.relationship('Sample', backref=orm.backref( + 'sample_tag_assoc', uselist=True, lazy='noload'), uselist=True, lazy='noload') + + tags = db.relationship('Tag', backref=orm.backref( + 'sample_tag_assoc', uselist=True, lazy='noload'), uselist=True, lazy='noload') + + def __repr__(self): + return '' % self.sample_id diff --git a/apps/iatlas/api/api/db_models/slide.py b/apps/iatlas/api/api/db_models/slide.py new file mode 100644 index 0000000000..d023b2e239 --- /dev/null +++ b/apps/iatlas/api/api/db_models/slide.py @@ -0,0 +1,20 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class Slide(Base): + __tablename__ = 'slides' + id = db.Column(db.String, primary_key=True) + name = db.Column(db.String, nullable=False) + description = db.Column(db.String, nullable=True) + + patient_id = db.Column( + db.String, db.ForeignKey('patients.id'), nullable=True) + + patient = db.relationship( + 'Patient', backref=orm.backref('slides', uselist=True, lazy='noload'), + uselist=False, lazy='noload') + + def __repr__(self): + return '' % self.name diff --git a/apps/iatlas/api/api/db_models/snp.py b/apps/iatlas/api/api/db_models/snp.py new file mode 100644 index 0000000000..65a0dd8795 --- /dev/null +++ b/apps/iatlas/api/api/db_models/snp.py @@ -0,0 +1,15 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class Snp(Base): + __tablename__ = 'snps' + id = db.Column(db.String, primary_key=True) + name = db.Column(db.String, nullable=False) + rsid = db.Column(db.String, nullable=True) + chr = db.Column(db.String, nullable=True) + bp = db.Column(db.Integer, nullable=True) + + def __repr__(self): + return '' % self.name diff --git a/apps/iatlas/api/api/db_models/tag.py b/apps/iatlas/api/api/db_models/tag.py new file mode 100644 index 0000000000..c1be189698 --- /dev/null +++ b/apps/iatlas/api/api/db_models/tag.py @@ -0,0 +1,35 @@ +from api import db +from . import Base +from api.enums import tag_type_enum + + +class Tag(Base): + __tablename__ = 'tags' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String, nullable=False) + description = db.Column(db.String, nullable=True) + color = db.Column(db.String, nullable=True) + long_display = db.Column(db.String, nullable=True) + short_display = db.Column(db.String, nullable=True) + tag_type = db.Column(tag_type_enum, nullable=False) + order = db.Column(db.Integer, nullable=True) + + data_sets = db.relationship( + 'Dataset', lazy='noload', uselist=True, secondary='datasets_to_tags') + + publications = db.relationship( + 'Publication', lazy='noload', uselist=True, secondary='tags_to_publications') + + related_tags = db.relationship( + 'Tag', foreign_keys='TagToTag.tag_id', lazy='noload', + secondary='tags_to_tags', back_populates='tags', uselist=True) + + samples = db.relationship( + 'Sample', lazy='noload', uselist=True, secondary='samples_to_tags') + + tags = db.relationship( + 'Tag', foreign_keys='TagToTag.related_tag_id', lazy='noload', + secondary='tags_to_tags', back_populates='related_tags', uselist=True) + + def __repr__(self): + return '' % self.name diff --git a/apps/iatlas/api/api/db_models/tag_to_publication.py b/apps/iatlas/api/api/db_models/tag_to_publication.py new file mode 100644 index 0000000000..71bd33d2fe --- /dev/null +++ b/apps/iatlas/api/api/db_models/tag_to_publication.py @@ -0,0 +1,22 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class TagToPublication(Base): + __tablename__ = 'tags_to_publications' + + publication_id = db.Column( + db.String, db.ForeignKey('publications.id'), primary_key=True) + + tag_id = db.Column( + db.String, db.ForeignKey('tags.id'), primary_key=True) + + publications = db.relationship('Publication', backref=orm.backref( + 'tag_publication_assoc', uselist=True, lazy='noload'), uselist=True, lazy='noload') + + tags = db.relationship('Tag', backref=orm.backref( + 'tag_publication_assoc', uselist=True, lazy='noload'), uselist=True, lazy='noload') + + def __repr__(self): + return '' % self.tag_id diff --git a/apps/iatlas/api/api/db_models/tag_to_tag.py b/apps/iatlas/api/api/db_models/tag_to_tag.py new file mode 100644 index 0000000000..b30172f52e --- /dev/null +++ b/apps/iatlas/api/api/db_models/tag_to_tag.py @@ -0,0 +1,24 @@ +from sqlalchemy import orm +from api import db +from . import Base + + +class TagToTag(Base): + __tablename__ = 'tags_to_tags' + + tag_id = db.Column( + db.String, db.ForeignKey('tags.id'), primary_key=True) + + related_tag_id = db.Column( + db.String, db.ForeignKey('tags.id'), primary_key=True) + + tags = db.relationship( + 'Tag', backref=orm.backref('tag_related_assoc', uselist=True, lazy='noload'), + uselist=True, primaryjoin='Tag.id==TagToTag.tag_id', lazy='noload') + + related_tags = db.relationship( + 'Tag', backref=orm.backref('related_tag_assoc', uselist=True, lazy='noload'), + uselist=True, primaryjoin='Tag.id==TagToTag.related_tag_id', lazy='noload') + + def __repr__(self): + return '' % self.tag_id diff --git a/apps/iatlas/api/api/enums.py b/apps/iatlas/api/api/enums.py new file mode 100644 index 0000000000..a30cb5bf33 --- /dev/null +++ b/apps/iatlas/api/api/enums.py @@ -0,0 +1,24 @@ +from sqlalchemy.dialects.postgresql import ENUM + +direction_enum = ENUM('Amp', 'Del', name='direction_enum') + +ethnicity_enum = ENUM('not hispanic or latino', + 'hispanic or latino', name='ethnicity_enum') + +gender_enum = ENUM('male', 'female', name='gender_enum') + +race_enum = ENUM('white', 'asian', 'american indian or alaska native', + 'black or african american', 'native hawaiian or other pacific islander', name='race_enum') + +status_enum = ENUM('Wt', 'Mut', name='status_enum') + +unit_enum = ENUM('Count', 'Fraction', 'Per Megabase', + 'Score', 'Year', name='unit_enum') + +qtl_enum = ENUM('sQTL', 'eQTL') + +ecaviar_pp_enum = ENUM('C1', 'C2') + +coloc_plot_type_enum = ENUM('3 Level Plot', 'Expanded Region') + +tag_type_enum = ENUM('group', 'parent_group', 'meta_group', 'network') diff --git a/apps/iatlas/api/api/extensions.py b/apps/iatlas/api/api/extensions.py new file mode 100644 index 0000000000..ddd813820b --- /dev/null +++ b/apps/iatlas/api/api/extensions.py @@ -0,0 +1,5 @@ +from flask_sqlalchemy import SQLAlchemy +from api.logger import LogSetup + +db = SQLAlchemy() +logs = LogSetup() diff --git a/apps/iatlas/api/api/logger/LOGGING.md b/apps/iatlas/api/api/logger/LOGGING.md new file mode 100644 index 0000000000..39c12b49df --- /dev/null +++ b/apps/iatlas/api/api/logger/LOGGING.md @@ -0,0 +1,31 @@ +# iAtlas API Logging + +[BACK TO MAIN README](./../../README.md) + +Logging is a great way to capture application information. + +Logs can be captured at various levels: + +- DEBUG +- WARN +- INFO +- ERROR + +The application initializes logging when it is created (see `create_app` in [`api/__init__.py`](api/__init__.py)). The formatting is determined by values in the config (see [`config.py`](./../../config.py)). + +The `development` environment is set at log level DEBUG, the `staging` environment is set at log level INFO, and the `production` environment is set to log level WARN. Tests are set to log level INFO. + +To use logging, import logging, get the logger you want to use, and create a log of the appropriate level: + +```python +import logging + +logger = logging.getLogger('logger name here') + +logger.debug('This is a debugging log') +logger.warn('This is a warning log') +logger.info('This is an info log') +logger.error('This is an error log') +``` + +Logs are saved to the `.logs/` folder in the root of the project. This folder is NOT versioned in the repository. Older logs are moved to a timestamped log file and newer logs are saved to the main `iatlas-api.log` file. diff --git a/apps/iatlas/api/api/logger/__init__.py b/apps/iatlas/api/api/logger/__init__.py new file mode 100644 index 0000000000..ac9b2a4a5b --- /dev/null +++ b/apps/iatlas/api/api/logger/__init__.py @@ -0,0 +1,132 @@ +from logging.config import dictConfig +from os import makedirs, path + +""" +We have options in python for stdout (streamhandling) and file logging +File logging has options for a Rotating file based on size or time (daily) +or a watched file, which supports logrotate style rotation +Most of the changes happen in the handlers, lets define a few standards +Borrowed HEAVILY from https://medium.com/tenable-techblog/the-boring-stuff-flask-logging-21c3a5dd0392 +""" + + +class LogSetup(object): + def __init__(self, app=None, **kwargs): + if app is not None: + self.init_app(app, **kwargs) + + def init_app(self, app): + config = app.config + log_type = config['LOG_TYPE'] + logging_level = config['LOG_LEVEL'] + log_extension = '.log' + if log_type != 'stream': + try: + log_directory = config['LOG_DIR'] + app_log_file_name = config['LOG_APP_NAME'] + log_extension + access_log_file_name = config['LOG_WWW_NAME'] + log_extension + makedirs(log_directory, exist_ok=True) + except KeyError as e: + exit(code="{} is a required parameter for log_type '{}'".format( + e, log_type)) + path_sep = path.sep + app_log = path_sep.join([log_directory, app_log_file_name]) + www_log = path_sep.join([log_directory, access_log_file_name]) + + if log_type == 'stream': + logging_policy = 'logging.StreamHandler' + elif log_type == 'watched': + logging_policy = 'logging.handlers.WatchedFileHandler' + else: + log_copies = config['LOG_COPIES'] + logging_policy = 'logging.handlers.TimedRotatingFileHandler' + log_time_interval = config['LOG_TIME_INT'] + log_interval = config['LOG_INTERVAL'] + + std_format = { + 'formatters': { + 'default': { + 'format': '[%(asctime)s.%(msecs)03d] %(levelname)s %(name)s:%(funcName)s: %(message)s', + 'datefmt': '%d/%b/%Y:%H:%M:%S', + }, + 'access': {'format': '%(message)s'}, + } + } + std_logger = { + 'loggers': { + '': {'level': logging_level, 'handlers': ['default'], 'propagate': True}, + 'app.access': { + 'level': logging_level, + 'handlers': ['access_logs'], + 'propagate': False, + }, + 'root': {'level': logging_level, 'handlers': ['default']}, + } + } + if log_type == 'stream': + logging_handler = { + 'handlers': { + 'default': { + 'level': logging_level, + 'formatter': 'default', + 'class': logging_policy, + }, + 'access_logs': { + 'level': logging_level, + 'class': logging_policy, + 'formatter': 'access', + }, + } + } + elif log_type == 'watched': + logging_handler = { + 'handlers': { + 'default': { + 'level': logging_level, + 'class': logging_policy, + 'filename': app_log, + 'formatter': 'default', + 'delay': True, + }, + 'access_logs': { + 'level': logging_level, + 'class': logging_policy, + 'filename': www_log, + 'formatter': 'access', + 'delay': True, + }, + } + } + else: + logging_handler = { + 'handlers': { + 'default': { + 'level': logging_level, + 'class': logging_policy, + 'filename': app_log, + 'backupCount': log_copies, + 'interval': log_interval, + 'formatter': 'default', + 'delay': True, + 'when': log_time_interval + }, + 'access_logs': { + 'level': logging_level, + 'class': logging_policy, + 'filename': www_log, + 'backupCount': log_copies, + 'interval': log_interval, + 'formatter': 'access', + 'delay': True, + 'when': log_time_interval + }, + } + } + + log_config = { + 'version': 1, + 'formatters': std_format['formatters'], + 'loggers': std_logger['loggers'], + 'handlers': logging_handler['handlers'], + } + dictConfig(log_config) diff --git a/apps/iatlas/api/api/main/__init__.py b/apps/iatlas/api/api/main/__init__.py new file mode 100644 index 0000000000..e26507d919 --- /dev/null +++ b/apps/iatlas/api/api/main/__init__.py @@ -0,0 +1,5 @@ +from flask import Blueprint + +bp = Blueprint('main', __name__) + +from api import routes diff --git a/apps/iatlas/api/api/resolvers/__init__.py b/apps/iatlas/api/api/resolvers/__init__.py new file mode 100644 index 0000000000..2f3fb1e01b --- /dev/null +++ b/apps/iatlas/api/api/resolvers/__init__.py @@ -0,0 +1,23 @@ +from .cohorts_resolver import resolve_cohorts +from .colocalizations_resolver import resolve_colocalizations +from .copy_number_results_resolver import resolve_copy_number_results +from .data_sets_resolver import resolve_data_sets +from .driver_results_resolver import resolve_driver_results +from .edges_resolver import resolve_edges +from .features_resolver import resolve_features +from .gene_types_resolver import resolve_gene_types +from .genes_resolver import resolve_genes +from .germline_gwas_results_resolver import resolve_germline_gwas_results +from .heritability_results_resolver import resolve_heritability_results +from .mutations_resolver import resolve_mutations +from .mutation_types_resolver import resolve_mutation_types +from .neoantigens_resolver import resolve_neoantigens +from .nodes_resolver import resolve_nodes +from .patient_resolver import resolve_patients +from .rare_variant_pathway_association_resolver import resolve_rare_variant_pathway_associations +from .samples_resolver import resolve_samples +from .slide_resolver import resolve_slides +from .snp_resolver import resolve_snps +from .tags_resolver import resolve_tags +from .test_resolver import resolve_test + diff --git a/apps/iatlas/api/api/resolvers/cohorts_resolver.py b/apps/iatlas/api/api/resolvers/cohorts_resolver.py new file mode 100644 index 0000000000..5128f9b96c --- /dev/null +++ b/apps/iatlas/api/api/resolvers/cohorts_resolver.py @@ -0,0 +1,67 @@ +from .resolver_helpers import build_cohort_graphql_response, build_cohort_request, cohort_request_fields, get_requested, get_selection_set, simple_data_set_request_fields, simple_tag_request_fields, cohort_sample_request_fields, simple_feature_request_fields, simple_gene_request_fields, mutation_request_fields +from .resolver_helpers.paging_utils import paginate, Paging, paging_fields + + +def resolve_cohorts(_obj, info, distinct=False, paging=None, cohort=None, dataSet=None, tag=None): + + selection_set = get_selection_set(info=info, child_node='items') + + requested = get_requested( + selection_set=selection_set, requested_field_mapping=cohort_request_fields) + + data_set_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_data_set_request_fields, child_node='dataSet') + + tag_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_tag_request_fields, child_node='tag') + + sample_selection_set = get_selection_set( + selection_set=selection_set, child_node='samples') + + sample_requested = get_requested( + selection_set=sample_selection_set, requested_field_mapping=cohort_sample_request_fields) + + sample_tag_selection_set = get_selection_set( + selection_set=sample_selection_set, child_node='tag') + + sample_tag_requested = get_requested( + selection_set=sample_tag_selection_set, requested_field_mapping=simple_tag_request_fields) + + feature_selection_set = get_selection_set( + selection_set=selection_set, child_node='features') + + feature_requested = get_requested( + selection_set=feature_selection_set, requested_field_mapping=simple_feature_request_fields) + + gene_selection_set = get_selection_set( + selection_set=selection_set, child_node='genes') + + gene_requested = get_requested( + selection_set=gene_selection_set, requested_field_mapping=simple_gene_request_fields) + + mutation_selection_set = get_selection_set( + selection_set=selection_set, child_node='mutations') + + mutation_requested = get_requested( + selection_set=mutation_selection_set, requested_field_mapping=mutation_request_fields) + + mutation_gene_selection_set = get_selection_set( + selection_set=mutation_selection_set, child_node='gene') + + mutation_gene_requested = get_requested( + selection_set=mutation_gene_selection_set, requested_field_mapping=simple_gene_request_fields) + + paging = paging if paging else Paging.DEFAULT + + query, count_query = build_cohort_request( + requested, data_set_requested, tag_requested, distinct=distinct, paging=paging, cohort=cohort, data_set=dataSet, tag=tag) + + pagination_requested = get_requested(info, paging_fields, 'paging') + + response_function = build_cohort_graphql_response(requested=requested, sample_requested=sample_requested, sample_tag_requested=sample_tag_requested, + feature_requested=feature_requested, gene_requested=gene_requested, mutation_requested=mutation_requested, mutation_gene_requested=mutation_gene_requested) + + res = paginate(query, count_query, paging, distinct, + response_function, pagination_requested) + + return(res) diff --git a/apps/iatlas/api/api/resolvers/colocalizations_resolver.py b/apps/iatlas/api/api/resolvers/colocalizations_resolver.py new file mode 100644 index 0000000000..6fbcaed61f --- /dev/null +++ b/apps/iatlas/api/api/resolvers/colocalizations_resolver.py @@ -0,0 +1,41 @@ +from .resolver_helpers import build_coloc_graphql_response, build_colocalization_request, colocalization_request_fields, get_requested, get_selection_set, simple_data_set_request_fields, simple_feature_request_fields, simple_gene_request_fields, snp_request_fields + +from .resolver_helpers.paging_utils import paginate, Paging, paging_fields + + +def resolve_colocalizations( + _obj, info, distinct=False, paging=None, dataSet=None, colocDataSet=None, feature=None, entrez=None, snp=None, qtlType=None, eCaviarPP=None, plotType=None): + # The selection is nested under the 'items' node. + selection_set = get_selection_set(info=info, child_node='items') + requested = get_requested( + selection_set=selection_set, requested_field_mapping=colocalization_request_fields) + + data_set_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_data_set_request_fields, child_node='dataSet') + + coloc_data_set_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_data_set_request_fields, child_node='colocDataSet') + + feature_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_feature_request_fields, child_node='feature') + + gene_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_gene_request_fields, child_node='gene') + + snp_requested = get_requested( + selection_set=selection_set, requested_field_mapping=snp_request_fields, child_node='snp') + + if distinct == False: + # Add the id as a cursor if not selecting distinct + requested.add('id') + + paging = paging if paging else Paging.DEFAULT + + query, count_query = build_colocalization_request( + requested, data_set_requested, coloc_data_set_requested, feature_requested, gene_requested, snp_requested, distinct=distinct, paging=paging, data_set=dataSet, coloc_data_set=colocDataSet, feature=feature, entrez=entrez, snp=snp, qtl_type=qtlType, ecaviar_pp=eCaviarPP, plot_type=plotType) + + pagination_requested = get_requested(info, paging_fields, 'paging') + res = paginate(query, count_query, paging, distinct, + build_coloc_graphql_response, pagination_requested) + + return(res) diff --git a/apps/iatlas/api/api/resolvers/copy_number_results_resolver.py b/apps/iatlas/api/api/resolvers/copy_number_results_resolver.py new file mode 100644 index 0000000000..f397e7fea0 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/copy_number_results_resolver.py @@ -0,0 +1,36 @@ +from .resolver_helpers import build_cnr_graphql_response, build_copy_number_result_request, cnr_request_fields, feature_request_fields, gene_request_fields, get_requested, get_selection_set, simple_data_set_request_fields, simple_tag_request_fields +from .resolver_helpers.paging_utils import paginate, Paging, paging_fields, create_paging + + +def resolve_copy_number_results(_obj, info, dataSet=None, direction=None, distinct=False, entrez=None, feature=None, maxPValue=None, + maxLog10PValue=None, minLog10PValue=None, minMeanCnv=None, minMeanNormal=None, + minPValue=None, minTStat=None, paging=None, related=None, tag=None): + + # Request fields within 'items' + selection_set = get_selection_set(info=info, child_node='items') + requested = get_requested( + selection_set=selection_set, requested_field_mapping=cnr_request_fields) + + data_set_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_data_set_request_fields, child_node='dataSet') + + feature_requested = get_requested( + selection_set=selection_set, requested_field_mapping=feature_request_fields, child_node='feature') + + gene_requested = get_requested( + selection_set=selection_set, requested_field_mapping=gene_request_fields, child_node='gene') + + tag_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_tag_request_fields, child_node='tag') + + max_items = 10000 + + paging = create_paging(paging, max_items) + + query, count_query = build_copy_number_result_request( + requested, data_set_requested, feature_requested, gene_requested, tag_requested, + data_set=dataSet, direction=direction, distinct=distinct, entrez=entrez, feature=feature, max_p_value=maxPValue, max_log10_p_value=maxLog10PValue, min_log10_p_value=minLog10PValue, min_mean_cnv=minMeanCnv, min_mean_normal=minMeanNormal, min_p_value=minPValue, min_t_stat=minTStat, paging=paging, related=related, tag=tag) + + # Request fields within 'paging' + pagination_requested = get_requested(info, paging_fields, 'paging') + return paginate(query, count_query, paging, distinct, build_cnr_graphql_response(), pagination_requested) diff --git a/apps/iatlas/api/api/resolvers/data_sets_resolver.py b/apps/iatlas/api/api/resolvers/data_sets_resolver.py new file mode 100644 index 0000000000..cce07dc8ab --- /dev/null +++ b/apps/iatlas/api/api/resolvers/data_sets_resolver.py @@ -0,0 +1,24 @@ +from .resolver_helpers import build_data_set_graphql_response, data_set_request_fields, simple_sample_request_fields, simple_tag_request_fields, get_requested, build_data_set_request, get_selection_set +from .resolver_helpers.paging_utils import paginate, Paging, paging_fields + + +def resolve_data_sets(_obj, info, dataSet=None, sample=None, dataSetType=None, paging=None, distinct=False): + + selection_set = get_selection_set(info=info, child_node='items') + + requested = get_requested( + selection_set=selection_set, requested_field_mapping=data_set_request_fields) + + sample_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_sample_request_fields, child_node='samples') + + tag_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_tag_request_fields, child_node='tags') + + paging = paging if paging else Paging.DEFAULT + + query, count_query = build_data_set_request( + requested, data_set=dataSet, sample=sample, data_set_type=dataSetType, paging=paging, distinct=distinct) + + pagination_requested = get_requested(info, paging_fields, 'paging') + return paginate(query, count_query, paging, distinct, build_data_set_graphql_response(requested=requested, sample_requested=sample_requested, tag_requested=tag_requested, sample=sample), pagination_requested) diff --git a/apps/iatlas/api/api/resolvers/driver_results_resolver.py b/apps/iatlas/api/api/resolvers/driver_results_resolver.py new file mode 100644 index 0000000000..40559a44f4 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/driver_results_resolver.py @@ -0,0 +1,42 @@ +from .resolver_helpers import build_dr_graphql_response, build_driver_result_request, driver_result_request_fields, get_requested, get_selection_set, simple_data_set_request_fields, simple_feature_request_fields, mutation_request_fields, simple_gene_request_fields, simple_tag_request_fields, mutation_type_request_fields +from .resolver_helpers.paging_utils import paginate, Paging, paging_fields + + +def resolve_driver_results(_obj, info, paging=None, distinct=False, dataSet=None, entrez=None, feature=None, mutation=None, mutationCode=None, related=None, tag=None, maxPValue=None, maxLog10PValue=None, minFoldChange=None, minLog10FoldChange=None, minLog10PValue=None, minPValue=None, minNumMutants=None, minNumWildTypes=None): + + selection_set = get_selection_set(info=info, child_node='items') + + requested = get_requested( + selection_set=selection_set, requested_field_mapping=driver_result_request_fields) + + data_set_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_data_set_request_fields, child_node='dataSet') + + feature_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_feature_request_fields, child_node='feature') + + mutation_requested = get_requested( + selection_set=selection_set, requested_field_mapping=mutation_request_fields, child_node='mutation') + + mutation_selection_set = get_selection_set( + selection_set=selection_set, child_node='mutation') + + mutation_gene_selection_set = get_selection_set( + selection_set=mutation_selection_set, child_node='gene') + + mutation_gene_requested = get_requested( + selection_set=mutation_gene_selection_set, requested_field_mapping=simple_gene_request_fields) + + mutation_type_requested = get_requested( + selection_set=mutation_selection_set, requested_field_mapping=mutation_type_request_fields, child_node='mutationType') + + tag_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_tag_request_fields, child_node='tag') + + paging = paging if paging else Paging.DEFAULT + + query, count_query = build_driver_result_request( + requested, data_set_requested, feature_requested, mutation_requested, mutation_gene_requested, mutation_type_requested, tag_requested, data_set=dataSet, distinct=distinct, entrez=entrez, feature=feature, max_p_value=maxPValue, max_log10_p_value=maxLog10PValue, min_fold_change=minFoldChange, min_log10_fold_change=minLog10FoldChange, min_log10_p_value=minLog10PValue, min_p_value=minPValue, min_n_mut=minNumMutants, min_n_wt=minNumWildTypes, mutation=mutation, mutation_code=mutationCode, paging=paging, related=related, tag=tag + ) + pagination_requested = get_requested(info, paging_fields, 'paging') + return paginate(query, count_query, paging, distinct, build_dr_graphql_response, pagination_requested) diff --git a/apps/iatlas/api/api/resolvers/edges_resolver.py b/apps/iatlas/api/api/resolvers/edges_resolver.py new file mode 100644 index 0000000000..3e6ea76cf8 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/edges_resolver.py @@ -0,0 +1,33 @@ +from .resolver_helpers import (build_edge_graphql_response, build_edge_request, edge_request_fields, + get_requested, get_selection_set, simple_node_request_fields) +from .resolver_helpers.paging_utils import paginate, Paging, paging_fields + + +def resolve_edges(_obj, info, distinct=False, maxScore=None, minScore=None, node1=None, node2=None, paging=None): + ''' + All keyword arguments are optional. Keyword arguments are: + `maxScore` - a float, a maximum score value + `minScore` - a float, a minimum score value + `node1` - a list of strings, starting node names + `node2` - a list of strings, ending node names + ''' + selection_set = get_selection_set(info=info, child_node='items') + requested = get_requested( + selection_set=selection_set, requested_field_mapping=edge_request_fields) + + node_1_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_node_request_fields, child_node='node1') + + node_2_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_node_request_fields, child_node='node2') + + if distinct == False: + requested.add('id') # Add the id as a cursor if not selecting distinct + + paging = paging if paging else Paging.DEFAULT + + query, count_query = build_edge_request( + requested, node_1_requested, node_2_requested, distinct=distinct, max_score=maxScore, min_score=minScore, node_start=node1, node_end=node2, paging=paging) + + pagination_requested = get_requested(info, paging_fields, 'paging') + return paginate(query, count_query, paging, distinct, build_edge_graphql_response, pagination_requested) diff --git a/apps/iatlas/api/api/resolvers/features_resolver.py b/apps/iatlas/api/api/resolvers/features_resolver.py new file mode 100644 index 0000000000..a614faa9fc --- /dev/null +++ b/apps/iatlas/api/api/resolvers/features_resolver.py @@ -0,0 +1,26 @@ +from .resolver_helpers import build_feature_graphql_response, feature_related_sample_request_fields, feature_request_fields, get_requested, build_features_query, get_selection_set, get_requested +from .resolver_helpers.paging_utils import paginate, paging_fields, create_paging + + +def resolve_features(_obj, info, distinct=False, paging=None, feature=None, featureClass=None, maxValue=None, minValue=None, sample=None, cohort=None): + + selection_set = get_selection_set(info=info, child_node='items') + + requested = get_requested( + selection_set=selection_set, requested_field_mapping=feature_request_fields) + + sample_requested = get_requested( + selection_set=selection_set, requested_field_mapping=feature_related_sample_request_fields, child_node='samples') + + max_items = 10 if 'samples' in requested else 100_000 + + paging = create_paging(paging, max_items) + + query, count_query = build_features_query( + requested, distinct, paging, feature=feature, feature_class=featureClass, max_value=maxValue, min_value=minValue, sample=sample, cohort=cohort) + + pagination_requested = get_requested(info, paging_fields, 'paging') + + res = paginate(query, count_query, paging, distinct, + build_feature_graphql_response(requested, sample_requested, maxValue, minValue, cohort, sample), pagination_requested) + return(res) diff --git a/apps/iatlas/api/api/resolvers/gene_types_resolver.py b/apps/iatlas/api/api/resolvers/gene_types_resolver.py new file mode 100644 index 0000000000..1e9ee750bb --- /dev/null +++ b/apps/iatlas/api/api/resolvers/gene_types_resolver.py @@ -0,0 +1,12 @@ +from .resolver_helpers import get_value, request_gene_sets +from .resolver_helpers.gene import build_gene_graphql_response + + +def resolve_gene_types(_obj, info, name=None): + gene_types = request_gene_sets(_obj, info, name=name) + + return [{ + 'display': get_value(gene_type, 'display'), + 'genes': map(build_gene_graphql_response(prefix=""), get_value(gene_type, 'genes', [])), + 'name': get_value(gene_type, 'name') + } for gene_type in gene_types] diff --git a/apps/iatlas/api/api/resolvers/genes_resolver.py b/apps/iatlas/api/api/resolvers/genes_resolver.py new file mode 100644 index 0000000000..97cf7d9e55 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/genes_resolver.py @@ -0,0 +1,32 @@ +from .resolver_helpers import build_gene_graphql_response, build_gene_request, get_selection_set, gene_related_sample_request_fields, gene_request_fields, get_requested, simple_gene_set_request_fields, simple_publication_request_fields +from .resolver_helpers.paging_utils import paginate, paging_fields, create_paging + + +def resolve_genes( + _obj, info, distinct=False, paging=None, entrez=None, geneFamily=None, geneFunction=None, geneType=None, immuneCheckpoint=None, maxRnaSeqExpr=None, minRnaSeqExpr=None, pathway=None, cohort=None, sample=None, superCategory=None, therapyType=None): + + selection_set = get_selection_set(info=info, child_node='items') + + requested = get_requested( + selection_set=selection_set, requested_field_mapping=gene_request_fields) + + gene_types_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_gene_set_request_fields, child_node='geneTypes') + + publications_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_publication_request_fields, child_node='publications') + + samples_requested = get_requested( + selection_set=selection_set, requested_field_mapping=gene_related_sample_request_fields, child_node='samples') + + max_items = 10 if 'samples' in requested else 100_000 + + paging = create_paging(paging, max_items) + + query, count_query = build_gene_request( + requested, distinct=distinct, paging=paging, entrez=entrez, gene_family=geneFamily, gene_function=geneFunction, gene_type=geneType, immune_checkpoint=immuneCheckpoint, max_rna_seq_expr=maxRnaSeqExpr, min_rna_seq_expr=minRnaSeqExpr, pathway=pathway, cohort=cohort, sample=sample, super_category=superCategory, therapy_type=therapyType) + + pagination_requested = get_requested(info, paging_fields, 'paging') + res = paginate(query, count_query, paging, distinct, + build_gene_graphql_response(requested, gene_types_requested, publications_requested, samples_requested, gene_type=geneType, max_rna_seq_expr=maxRnaSeqExpr, min_rna_seq_expr=minRnaSeqExpr, cohort=cohort, sample=sample), pagination_requested) + return(res) diff --git a/apps/iatlas/api/api/resolvers/germline_gwas_results_resolver.py b/apps/iatlas/api/api/resolvers/germline_gwas_results_resolver.py new file mode 100644 index 0000000000..ae1ee2364c --- /dev/null +++ b/apps/iatlas/api/api/resolvers/germline_gwas_results_resolver.py @@ -0,0 +1,34 @@ +from .resolver_helpers import build_ggr_graphql_response, build_germline_gwas_result_request, germline_gwas_result_request_fields, get_requested, get_selection_set, simple_data_set_request_fields, simple_feature_request_fields, snp_request_fields + +from .resolver_helpers.paging_utils import paginate, Paging, paging_fields + + +def resolve_germline_gwas_results( + _obj, info, paging=None, distinct=False, dataSet=None, feature=None, snp=None, maxPValue=None, minPValue=None): + + # The selection is nested under the 'items' node. + selection_set = get_selection_set(info=info, child_node='items') + + requested = get_requested( + selection_set=selection_set, requested_field_mapping=germline_gwas_result_request_fields) + + data_set_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_data_set_request_fields, child_node='dataSet') + + feature_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_feature_request_fields, child_node='feature') + + snp_requested = get_requested( + selection_set=selection_set, requested_field_mapping=snp_request_fields, child_node='snp') + + if distinct == False: + # Add the id as a cursor if not selecting distinct + requested.add('id') + + paging = paging if paging else Paging.DEFAULT + + query, count_query = build_germline_gwas_result_request( + requested, data_set_requested, feature_requested, snp_requested, distinct=distinct, paging=paging, data_set=dataSet, feature=feature, snp=snp, max_p_value=maxPValue, min_p_value=minPValue) + + pagination_requested = get_requested(info, paging_fields, 'paging') + return paginate(query, count_query, paging, distinct, build_ggr_graphql_response, pagination_requested) diff --git a/apps/iatlas/api/api/resolvers/heritability_results_resolver.py b/apps/iatlas/api/api/resolvers/heritability_results_resolver.py new file mode 100644 index 0000000000..b4dd508bb5 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/heritability_results_resolver.py @@ -0,0 +1,25 @@ +from .resolver_helpers import build_hr_graphql_response, build_heritability_result_request, heritability_result_request_fields, get_requested, get_selection_set, simple_data_set_request_fields, simple_feature_request_fields, simple_gene_request_fields, simple_tag_request_fields + +from .resolver_helpers.paging_utils import paginate, Paging, paging_fields + + +def resolve_heritability_results( + _obj, info, dataSet=None, distinct=False, feature=None, maxPValue=None, minFoldChange=None, minPValue=None, paging=None, cluster=None): + # The selection is nested under the 'items' node. + selection_set = get_selection_set(info=info, child_node='items') + requested = get_requested( + selection_set=selection_set, requested_field_mapping=heritability_result_request_fields) + + data_set_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_data_set_request_fields, child_node='dataSet') + + feature_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_feature_request_fields, child_node='feature') + + paging = paging if paging else Paging.DEFAULT + + query, count_query = build_heritability_result_request( + requested, data_set_requested, feature_requested, data_set=dataSet, distinct=distinct, feature=feature, max_p_value=maxPValue, min_p_value=minPValue, cluster=cluster, paging=paging) + + pagination_requested = get_requested(info, paging_fields, 'paging') + return paginate(query, count_query, paging, distinct, build_hr_graphql_response, pagination_requested) diff --git a/apps/iatlas/api/api/resolvers/mutation_types_resolver.py b/apps/iatlas/api/api/resolvers/mutation_types_resolver.py new file mode 100644 index 0000000000..c59744c85a --- /dev/null +++ b/apps/iatlas/api/api/resolvers/mutation_types_resolver.py @@ -0,0 +1,11 @@ +from sqlalchemy import orm +from api import db +from api.database import return_mutation_type_query +from api.db_models import MutationType +from .resolver_helpers import build_mutation_type_graphql_response, get_requested, get_selection_set, mutation_type_request_fields, request_mutation_types + + +def resolve_mutation_types(_obj, info): + requested = get_requested(info, mutation_type_request_fields) + + return map(build_mutation_type_graphql_response, request_mutation_types(requested)) diff --git a/apps/iatlas/api/api/resolvers/mutations_resolver.py b/apps/iatlas/api/api/resolvers/mutations_resolver.py new file mode 100644 index 0000000000..88a5a6e214 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/mutations_resolver.py @@ -0,0 +1,39 @@ +from .resolver_helpers import build_mutation_graphql_response, get_requested, get_selection_set, mutation_related_sample_request_fields, mutation_request_fields, mutation_type_request_fields, build_mutation_request, simple_gene_request_fields +from .resolver_helpers.paging_utils import create_paging, paginate, paging_fields, create_paging + + +def resolve_mutations(_obj, info, cohort=None, distinct=False, entrez=None, mutation=None, mutationCode=None, mutationType=None, paging=None, sample=None, status=None): + + selection_set = get_selection_set(info=info, child_node='items') + + requested = get_requested( + selection_set=selection_set, requested_field_mapping=mutation_request_fields) + + gene_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_gene_request_fields, child_node='gene') + + mutation_type_requested = get_requested( + selection_set=selection_set, requested_field_mapping=mutation_type_request_fields, child_node='mutationType') + + sample_selection_set = get_selection_set( + selection_set=selection_set, child_node='samples') + + sample_requested = get_requested( + selection_set=sample_selection_set, requested_field_mapping=mutation_related_sample_request_fields) + + max_items = 10 if 'samples' in requested else 100_000 + + paging = create_paging(paging, max_items) + + query, count_query = build_mutation_request( + requested, gene_requested, mutation_type_requested, cohort=cohort, distinct=distinct, entrez=entrez, mutation_code=mutationCode, mutation_type=mutationType, mutation=mutation, paging=paging, sample=sample) + + pagination_requested = get_requested(info, paging_fields, 'paging') + + response_function = build_mutation_graphql_response( + requested=requested, sample_requested=sample_requested, status=status, sample=sample, cohort=cohort) + + res = paginate(query, count_query, paging, distinct, + response_function, pagination_requested) + return(res) + diff --git a/apps/iatlas/api/api/resolvers/neoantigens_resolver.py b/apps/iatlas/api/api/resolvers/neoantigens_resolver.py new file mode 100644 index 0000000000..1432c9c1cf --- /dev/null +++ b/apps/iatlas/api/api/resolvers/neoantigens_resolver.py @@ -0,0 +1,63 @@ + + +from .resolver_helpers import ( + build_neoantigen_graphql_response, + build_neoantigen_request, + neoantigen_request_fields, + get_requested, + get_selection_set, + simple_gene_request_fields, + simple_patient_request_fields +) + +from .resolver_helpers.paging_utils import paginate, create_paging, paging_fields + + +def resolve_neoantigens( + _obj, info, distinct=False, paging=None, patient=None, entrez=None, pmhc=None +): + # The selection is nested under the 'items' node. + selection_set = get_selection_set(info=info, child_node='items') + + requested = get_requested( + selection_set=selection_set, + requested_field_mapping=neoantigen_request_fields + ) + + patient_requested = get_requested( + selection_set=selection_set, + requested_field_mapping=simple_patient_request_fields, + child_node='patient' + ) + + gene_requested = get_requested( + selection_set=selection_set, + requested_field_mapping=simple_gene_request_fields, + child_node='gene' + ) + + max_items = 10000 + + paging = create_paging(paging, max_items) + + query, count_query = build_neoantigen_request( + requested, + patient_requested, + gene_requested, + distinct=distinct, + paging=paging, + patient=patient, + entrez=entrez, + pmhc=pmhc + ) + + pagination_requested = get_requested(info, paging_fields, 'paging') + + return paginate( + query=query, + count_query=count_query, + paging=paging, + distinct=distinct, + response_builder=build_neoantigen_graphql_response, + pagination_requested=pagination_requested + ) diff --git a/apps/iatlas/api/api/resolvers/nodes_resolver.py b/apps/iatlas/api/api/resolvers/nodes_resolver.py new file mode 100644 index 0000000000..5d96dab9d5 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/nodes_resolver.py @@ -0,0 +1,72 @@ +from .resolver_helpers import build_node_graphql_response, build_node_request, feature_request_fields, get_selection_set, gene_request_fields, get_requested, node_request_fields, simple_data_set_request_fields, simple_tag_request_fields +from .resolver_helpers.paging_utils import paging_fields, create_paging, paginate, paging_fields + + +def resolve_nodes( + _obj, + info, + dataSet=None, + distinct=False, + entrez=None, + feature=None, + featureClass=None, + geneType=None, + maxScore=None, + minScore=None, + network=None, + related=None, + paging=None, + tag1=None, + tag2=None, + nTags=None + ): + + selection_set = get_selection_set(info=info, child_node='items') + + requested = get_requested( + selection_set=selection_set, requested_field_mapping=node_request_fields) + + data_set_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_data_set_request_fields, child_node='dataSet') + + feature_requested = get_requested( + selection_set=selection_set, requested_field_mapping=feature_request_fields, child_node='feature') + + gene_requested = get_requested( + selection_set=selection_set, requested_field_mapping=gene_request_fields, child_node='gene') + + tag_requested1 = get_requested( + selection_set=selection_set, requested_field_mapping=simple_tag_request_fields, child_node='tag1') + + tag_requested2 = get_requested( + selection_set=selection_set, requested_field_mapping=simple_tag_request_fields, child_node='tag2') + + max_items = 1000 + + paging = create_paging(paging, max_items) + + query, count_query = build_node_request( + requested, + data_set_requested, + feature_requested, + gene_requested, + tag_requested1, + tag_requested2, + data_set=dataSet, + distinct=distinct, + entrez=entrez, + feature=feature, + feature_class=featureClass, + gene_type=geneType, + max_score=maxScore, + min_score=minScore, + network=network, + related=related, + paging=paging, + tag1=tag1, + tag2=tag2, + n_tags=nTags + ) + + pagination_requested = get_requested(info, paging_fields, 'paging') + return paginate(query, count_query, paging, distinct, build_node_graphql_response(requested), pagination_requested) diff --git a/apps/iatlas/api/api/resolvers/patient_resolver.py b/apps/iatlas/api/api/resolvers/patient_resolver.py new file mode 100644 index 0000000000..a8f8974c40 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/patient_resolver.py @@ -0,0 +1,26 @@ +from .resolver_helpers import build_patient_graphql_response, build_patient_request, get_requested, patient_request_fields, simple_slide_request_fields, get_selection_set +from .resolver_helpers.paging_utils import paginate, Paging, paging_fields + + +def resolve_patients(_obj, info, distinct=False, paging=None, maxAgeAtDiagnosis=None, minAgeAtDiagnosis=None, barcode=None, dataSet=None, ethnicity=None, gender=None, maxHeight=None, minHeight=None, race=None, sample=None, slide=None, maxWeight=None, minWeight=None): + + selection_set = get_selection_set(info=info, child_node='items') + + requested = get_requested( + selection_set=selection_set, requested_field_mapping=patient_request_fields) + + slide_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_slide_request_fields, child_node='slides') + + paging = paging if paging else Paging.DEFAULT + + query, count_query = build_patient_request( + requested, paging=paging, distinct=distinct, max_age_at_diagnosis=maxAgeAtDiagnosis, min_age_at_diagnosis=minAgeAtDiagnosis, barcode=barcode, data_set=dataSet, ethnicity=ethnicity, gender=gender, max_height=maxHeight, min_height=minHeight, race=race, sample=sample, slide=slide, max_weight=maxWeight, min_weight=minWeight + ) + + pagination_requested = get_requested(info, paging_fields, 'paging') + + res = paginate(query, count_query, paging, distinct, + build_patient_graphql_response(requested=requested, slide_requested=slide_requested, sample=sample, slide=slide), pagination_requested) + + return(res) diff --git a/apps/iatlas/api/api/resolvers/rare_variant_pathway_association_resolver.py b/apps/iatlas/api/api/resolvers/rare_variant_pathway_association_resolver.py new file mode 100644 index 0000000000..f35a0e3828 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/rare_variant_pathway_association_resolver.py @@ -0,0 +1,31 @@ +from .resolver_helpers import build_rvpa_graphql_response, build_rare_variant_pathway_association_request, rare_variant_pathway_association_request_fields, get_requested, get_selection_set, simple_data_set_request_fields, simple_feature_request_fields + +from .resolver_helpers.paging_utils import paginate, Paging, paging_fields + +def resolve_rare_variant_pathway_associations( + _obj, info, distinct=False, paging=None, dataSet=None, feature=None, pathway=None, maxPValue=None, minPValue=None): + + # The selection is nested under the 'items' node. + selection_set = get_selection_set(info=info, child_node='items') + requested = get_requested( + selection_set=selection_set, requested_field_mapping=rare_variant_pathway_association_request_fields) + + data_set_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_data_set_request_fields, child_node='dataSet') + + feature_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_feature_request_fields, child_node='feature') + + if distinct == False: + # Add the id as a cursor if not selecting distinct + requested.add('id') + + paging = paging if paging else Paging.DEFAULT + + query, count_query = build_rare_variant_pathway_association_request( + requested, data_set_requested, feature_requested, distinct=distinct, paging=paging, data_set=dataSet, feature=feature, pathway=pathway, max_p_value=maxPValue, min_p_value=minPValue) + + pagination_requested = get_requested(info, paging_fields, 'paging') + res = paginate(query, count_query, paging, distinct, + build_rvpa_graphql_response, pagination_requested) + return(res) diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/__init__.py b/apps/iatlas/api/api/resolvers/resolver_helpers/__init__.py new file mode 100644 index 0000000000..f724a3dab4 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/__init__.py @@ -0,0 +1,23 @@ +from .cohort import build_cohort_graphql_response, build_cohort_request, cohort_request_fields +from .colocalization import colocalization_request_fields, build_coloc_graphql_response, build_colocalization_request +from .copy_number_result import build_cnr_graphql_response, build_copy_number_result_request, cnr_request_fields +from .data_set import build_data_set_graphql_response, data_set_request_fields, build_data_set_request, simple_data_set_request_fields +from .driver_result import build_dr_graphql_response, build_driver_result_request, driver_result_request_fields +from .edge import build_edge_graphql_response, build_edge_request, edge_request_fields +from .feature import build_feature_graphql_response, feature_class_request_fields, feature_request_fields, simple_feature_request_fields, simple_feature_request_fields2, build_features_query +from .gene import build_gene_graphql_response, gene_request_fields, simple_gene_request_fields, build_gene_request +from .gene_set import gene_set_request_fields, request_gene_sets, simple_gene_set_request_fields +from .general_resolvers import * +from .germline_gwas_result import germline_gwas_result_request_fields, build_ggr_graphql_response, build_germline_gwas_result_request +from .heritability_result import heritability_result_request_fields, build_hr_graphql_response, build_heritability_result_request +from .mutation import build_mutation_graphql_response, build_mutation_request, mutation_request_fields +from .mutation_type import build_mutation_type_graphql_response, mutation_type_request_fields, request_mutation_types +from .neoantigen import build_neoantigen_graphql_response, build_neoantigen_request, neoantigen_request_fields +from .node import build_node_graphql_response, build_node_request, node_request_fields, simple_node_request_fields +from .patient import build_patient_request, build_patient_graphql_response, patient_request_fields, simple_patient_request_fields +from .publication import build_publication_graphql_response, publication_request_fields, simple_publication_request_fields +from .rare_variant_pathway_association import build_rvpa_graphql_response, build_rare_variant_pathway_association_request, rare_variant_pathway_association_request_fields +from .sample import build_sample_graphql_response, feature_related_sample_request_fields, gene_related_sample_request_fields, mutation_related_sample_request_fields, build_sample_request, sample_request_fields, simple_sample_request_fields, cohort_sample_request_fields +from .slide import build_slide_graphql_response, build_slide_request, slide_request_fields, simple_slide_request_fields +from .snp import snp_request_fields, build_snp_graphql_response, build_snp_request +from .tag import build_tag_graphql_response, simple_tag_request_fields, tag_request_fields, build_tag_request, has_tag_fields diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/cohort.py b/apps/iatlas/api/api/resolvers/resolver_helpers/cohort.py new file mode 100644 index 0000000000..deb092fa84 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/cohort.py @@ -0,0 +1,252 @@ +from sqlalchemy import and_ +from sqlalchemy.orm import aliased +from api import db +from api.db_models import Cohort, Dataset, Tag, Sample, Feature, Gene, Mutation, CohortToSample, CohortToFeature, CohortToGene, CohortToMutation +from .general_resolvers import build_join_condition, get_selected, get_value +from .paging_utils import get_pagination_queries + +cohort_request_fields = {'id', 'name', + 'dataSet', 'tag', 'samples', 'features', 'genes', 'mutations'} + + +def build_cohort_graphql_response(requested=[], sample_requested=[], sample_tag_requested=[], feature_requested=[], gene_requested=[], mutation_requested=[], mutation_gene_requested=[]): + from .data_set import build_data_set_graphql_response + from .feature import build_feature_graphql_response + from .gene import build_gene_graphql_response + from .mutation import build_mutation_graphql_response + from .sample import build_sample_graphql_response + from .tag import build_tag_graphql_response + + def f(cohort): + if not cohort: + return None + else: + cohort_id = get_value(cohort, 'cohort_id') + samples = get_samples(cohort_id, requested, + sample_requested, sample_tag_requested) + features = get_features(cohort_id, requested, feature_requested) + genes = get_genes(cohort_id, requested, gene_requested) + mutations = get_mutations( + cohort_id, requested, mutation_requested, mutation_gene_requested) + dict = { + 'id': cohort_id, + 'name': get_value(cohort, 'cohort_name'), + 'dataSet': build_data_set_graphql_response()(cohort), + 'tag': build_tag_graphql_response()(cohort), + 'samples': map(build_sample_graphql_response(), samples), + 'features': map(build_feature_graphql_response(), features), + 'genes': map(build_gene_graphql_response(), genes), + 'mutations': map(build_mutation_graphql_response(), mutations) + } + return(dict) + + return f + + +def build_cohort_request(requested, data_set_requested, tag_requested, cohort=None, data_set=None, tag=None, distinct=False, paging=None): + """ + Builds a SQL request. + + All positional arguments are required. Positional arguments are: + 1st position - a set of the requested fields at the root of the graphql request + 2nd position - a set of the requested fields in the 'dataSet' node of the graphql request. If 'dataSet' is not requested, this will be an empty set. + 3rd position - a set of the requested fields in the 'tag' node of the graphql request. If 'tag' is not requested, this will be an empty set. + + All keyword arguments are optional. Keyword arguments are: + `cohort` - a list of strings, cohorts + `data_set` - a list of strings, data set names + `tag` - a list of strings, tag names + """ + from .tag import get_tag_column_labels + sess = db.session + + cohort_1 = aliased(Cohort, name='c') + data_set_1 = aliased(Dataset, name='ds') + tag_1 = aliased(Tag, name='t') + + core_field_mapping = { + 'name': cohort_1.name.label('cohort_name'), + } + + data_set_core_field_mapping = { + 'display': data_set_1.display.label('data_set_display'), + 'name': data_set_1.name.label('data_set_name'), + 'type': data_set_1.dataset_type.label('data_set_type') + } + + core = get_selected(requested, core_field_mapping) + core |= {cohort_1.id.label('cohort_id')} + core |= get_selected(data_set_requested, data_set_core_field_mapping) + core |= get_tag_column_labels(tag_requested, tag_1) + + query = sess.query(*core) + query = query.select_from(cohort_1) + + if cohort: + query = query.filter(cohort_1.name.in_(cohort)) + + if 'dataSet' in requested or data_set: + is_outer = not bool(data_set) + data_set_join_condition = build_join_condition( + data_set_1.id, cohort_1.dataset_id, filter_column=data_set_1.name, filter_list=data_set) + query = query.join(data_set_1, and_( + *data_set_join_condition), isouter=is_outer) + + if 'tag' in requested or tag: + is_outer = not bool(tag) + data_set_join_condition = build_join_condition( + tag_1.id, cohort_1.cohort_tag_id, filter_column=tag_1.name, filter_list=tag) + query = query.join(tag_1, and_( + *data_set_join_condition), isouter=is_outer) + + return get_pagination_queries(query, paging, distinct, cursor_field=cohort_1.id) + + +def get_samples(id, requested, sample_requested, tag_requested): + if 'samples' not in requested: + return([]) + else: + sess = db.session + + cohort_to_sample_1 = aliased(CohortToSample, name='cts') + sample_1 = aliased(Sample, name='s') + tag_1 = aliased(Tag, name='t2') + + sample_core_field_mapping = { + 'name': sample_1.name.label('sample_name'), + } + + tag_core_field_mapping = { + 'characteristics': tag_1.description.label('tag_characteristics'), + 'color': tag_1.color.label('tag_color'), + 'longDisplay': tag_1.long_display.label('tag_long_display'), + 'name': tag_1.name.label('tag_name'), + 'shortDisplay': tag_1.short_display.label('tag_short_display') + } + + core = get_selected(sample_requested, sample_core_field_mapping) + core |= get_selected(tag_requested, tag_core_field_mapping) + + query = sess.query(*core) + query = query.select_from(cohort_to_sample_1) + query = query.filter(cohort_to_sample_1.cohort_id == id) + + sample_join_condition = build_join_condition( + cohort_to_sample_1.sample_id, sample_1.id) + + query = query.join(sample_1, and_( + *sample_join_condition), isouter=False) + + if 'tag' in sample_requested: + sample_tag_join_condition = build_join_condition( + tag_1.id, cohort_to_sample_1.cohorts_to_samples_tag_id) + query = query.join(tag_1, and_( + *sample_tag_join_condition), isouter=True) + + samples = query.all() + return(samples) + + +def get_features(id, requested, feature_requested): + if 'features' not in requested: + return([]) + else: + sess = db.session + + cohort_to_feature_1 = aliased(CohortToFeature, name='ctf') + feature_1 = aliased(Feature, name='f') + + feature_core_field_mapping = { + 'display': feature_1.display.label('feature_display'), + 'name': feature_1.name.label('feature_name'), + } + + core = get_selected(feature_requested, feature_core_field_mapping) + + query = sess.query(*core) + query = query.select_from(cohort_to_feature_1) + query = query.filter(cohort_to_feature_1.cohort_id == id) + + feature_join_condition = build_join_condition( + cohort_to_feature_1.feature_id, feature_1.id) + + query = query.join(feature_1, and_( + *feature_join_condition), isouter=False) + + features = query.all() + return(features) + + +def get_genes(id, requested, gene_requested): + if 'genes' not in requested: + return([]) + else: + sess = db.session + + cohort_to_gene_1 = aliased(CohortToGene, name='ctg') + gene_1 = aliased(Gene, name='g') + + gene_core_field_mapping = { + 'hgnc': gene_1.hgnc_id.label('gene_hgnc'), + 'entrez': gene_1.entrez_id.label('gene_entrez'), + } + + core = get_selected(gene_requested, gene_core_field_mapping) + + query = sess.query(*core) + query = query.select_from(cohort_to_gene_1) + query = query.filter(cohort_to_gene_1.cohort_id == id) + + gene_join_condition = build_join_condition( + cohort_to_gene_1.gene_id, gene_1.id) + + query = query.join(gene_1, and_( + *gene_join_condition), isouter=False) + + genes = query.all() + return(genes) + + +def get_mutations(id, requested, mutation_requested, mutation_gene_requested): + + if 'mutations' not in requested: + return([]) + else: + sess = db.session + + cohort_to_mutation_1 = aliased(CohortToMutation, name='ctm') + mutation_1 = aliased(Mutation, name='m') + gene_1 = aliased(Gene, name='g') + + mutation_core_field_mapping = { + 'mutationCode': mutation_1.mutation_code.label('mutation_code') + } + + mutation_gene_core_field_mapping = { + 'hgnc': gene_1.hgnc_id.label('gene_hgnc'), + 'entrez': gene_1.entrez_id.label('gene_entrez'), + } + + core = get_selected(mutation_requested, mutation_core_field_mapping) + core |= get_selected(mutation_gene_requested, + mutation_gene_core_field_mapping) + + query = sess.query(*core) + query = query.select_from(cohort_to_mutation_1) + query = query.filter(cohort_to_mutation_1.cohort_id == id) + + mutation_join_condition = build_join_condition( + mutation_1.id, cohort_to_mutation_1.mutation_id) + + query = query.join(mutation_1, and_( + *mutation_join_condition), isouter=False) + + if 'gene' in mutation_requested: + mutation_gene_join_condition = build_join_condition( + mutation_1.gene_id, gene_1.id) + + query = query.join(gene_1, and_( + *mutation_gene_join_condition), isouter=False) + + mutations = query.all() + return(mutations) diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/colocalization.py b/apps/iatlas/api/api/resolvers/resolver_helpers/colocalization.py new file mode 100644 index 0000000000..c619b9d2c0 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/colocalization.py @@ -0,0 +1,172 @@ +from sqlalchemy import and_ +from sqlalchemy.orm import aliased +from api import db +from api.db_models import Dataset, Colocalization, Feature, Gene, Snp +from .general_resolvers import build_join_condition, get_selected, get_value +from .data_set import build_data_set_graphql_response +from .feature import build_feature_graphql_response +from .gene import build_gene_graphql_response +from .snp import build_snp_graphql_response +from .paging_utils import get_pagination_queries + +colocalization_request_fields = { + 'id', + 'dataSet', + 'colocDataSet', + 'feature', + 'gene', + 'snp', + 'qtlType', + 'eCaviarPP', + 'plotType', + 'tissue', + 'spliceLoc', + 'plotLink' +} + + +def build_coloc_graphql_response(colocalization): + return { + 'id': get_value(colocalization, 'id'), + 'dataSet': build_data_set_graphql_response()(colocalization), + 'colocDataSet': build_data_set_graphql_response(prefix='coloc_data_set_')(colocalization), + 'feature': build_feature_graphql_response()(colocalization), + 'gene': build_gene_graphql_response()(colocalization), + 'snp': build_snp_graphql_response(colocalization), + 'qtlType': get_value(colocalization, 'qtl_type'), + 'eCaviarPP': get_value(colocalization, 'ecaviar_pp'), + 'plotType': get_value(colocalization, 'plot_type'), + 'tissue': get_value(colocalization, 'tissue'), + 'spliceLoc': get_value(colocalization, 'splice_loc'), + 'plotLink': get_value(colocalization, 'plot_link') + } + + +def build_colocalization_request( + requested, data_set_requested, coloc_data_set_requested, feature_requested, gene_requested, snp_requested, distinct=False, paging=None, data_set=None, coloc_data_set=None, feature=None, entrez=None, snp=None, qtl_type=None, ecaviar_pp=None, plot_type=None): + """ + Builds a SQL request. + + All positional arguments are required. Positional arguments are: + 1st position - a set of the requested fields at the root of the graphql request + 2nd position - a set of the requested fields in the 'dataSet' node of the graphql request. If 'dataSet' is not requested, this will be an empty set. + 3rd position - a set of the requested fields in the 'colocDataSet' node of the graphql request. If 'colocDataSet' is not requested, this will be an empty set. + 4th position - a set of the requested fields in the 'feature' node of the graphql request. If 'feature' is not requested, this will be an empty set. + 5th position - a set of the requested fields in the 'gene' node of the graphql request. If 'gene' is not requested, this will be an empty set. + 6th position - a set of the requested fields in the 'snp' node of the graphql request. If 'snp' is not requested, this will be an empty set. + + All keyword arguments are optional. Keyword arguments are: + `distinct` - a boolean, indicates whether duplicate records should be filtered out + `paging` - a dict containing pagination metadata + `data_set` - a list of strings, data set names + `coloc_data_set` - a list of strings, data set names + `feature` - a list of strings, feature names + `entrez` - a list of ints, entrez ids + `snp` - a list of strings, snp names + `qtl_type` - a string, (either 'sQTL' or 'eQTL') + `ecaviar_pp` - a string, (either 'C1' or 'C2') + `plot_type` - a string, (either '3 Level Plot' or 'Expanded Region') + """ + sess = db.session + + colocalization_1 = aliased(Colocalization, name='coloc') + data_set_1 = aliased(Dataset, name='ds') + coloc_data_set_1 = aliased(Dataset, name='cds') + feature_1 = aliased(Feature, name='f') + gene_1 = aliased(Gene, name='g') + snp_1 = aliased(Snp, name='s') + + core_field_mapping = { + 'id': colocalization_1.id.label('id'), + 'qtlType': colocalization_1.qtl_type.label('qtl_type'), + 'eCaviarPP': colocalization_1.ecaviar_pp.label('ecaviar_pp'), + 'plotType': colocalization_1.plot_type.label('plot_type'), + 'tissue': colocalization_1.tissue.label('tissue'), + 'spliceLoc': colocalization_1.splice_loc.label('splice_loc'), + 'plotLink': colocalization_1.link.label('plot_link') + } + data_set_core_field_mapping = { + 'display': data_set_1.display.label('data_set_display'), + 'name': data_set_1.name.label('data_set_name'), + 'type': data_set_1.dataset_type.label('data_set_type') + } + coloc_data_set_core_field_mapping = { + 'display': coloc_data_set_1.display.label('coloc_data_set_display'), + 'name': coloc_data_set_1.name.label('coloc_data_set_name'), + 'type': coloc_data_set_1.dataset_type.label('coloc_data_set_type') + } + feature_core_field_mapping = { + 'display': feature_1.display.label('feature_display'), + 'name': feature_1.name.label('feature_name'), + 'order': feature_1.order.label('feature_order'), + 'unit': feature_1.unit.label('feature_unit'), + 'germlineCategory': feature_1.germline_category.label('feature_germline_category'), + 'germlineModule': feature_1.germline_module.label('feature_germline_module') + } + gene_core_field_mapping = { + 'entrez': gene_1.entrez_id.label('gene_entrez'), + 'hgnc': gene_1.hgnc_id.label('gene_hgnc') + } + snp_core_field_mapping = { + 'rsid': snp_1.rsid.label('snp_rsid'), + 'name': snp_1.name.label('snp_name'), + 'bp': snp_1.bp.label('snp_bp'), + 'chr': snp_1.chr.label('snp_chr') + } + + core = get_selected(requested, core_field_mapping) + core |= get_selected(data_set_requested, data_set_core_field_mapping) + core |= get_selected(coloc_data_set_requested, + coloc_data_set_core_field_mapping) + core |= get_selected(feature_requested, feature_core_field_mapping) + core |= get_selected(gene_requested, gene_core_field_mapping) + core |= get_selected(snp_requested, snp_core_field_mapping) + + query = sess.query(*core) + query = query.select_from(colocalization_1) + + if qtl_type: + query = query.filter(colocalization_1.qtl_type == qtl_type) + + if ecaviar_pp: + query = query.filter(colocalization_1.ecaviar_pp == ecaviar_pp) + + if plot_type: + query = query.filter(colocalization_1.plot_type == plot_type) + + if 'dataSet' in requested or data_set: + is_outer = not bool(data_set) + data_set_join_condition = build_join_condition( + data_set_1.id, colocalization_1.dataset_id, filter_column=data_set_1.name, filter_list=data_set) + query = query.join(data_set_1, and_( + *data_set_join_condition), isouter=is_outer) + + if 'colocDataSet' in requested or coloc_data_set: + is_outer = not bool(data_set) + data_set_join_condition = build_join_condition( + coloc_data_set_1.id, colocalization_1.coloc_dataset_id, filter_column=coloc_data_set_1.name, filter_list=coloc_data_set) + query = query.join(coloc_data_set_1, and_( + *data_set_join_condition), isouter=is_outer) + + if 'feature' in requested or feature: + is_outer = not bool(feature) + feature_join_condition = build_join_condition( + feature_1.id, colocalization_1.feature_id, filter_column=feature_1.name, filter_list=feature) + query = query.join(feature_1, and_( + *feature_join_condition), isouter=is_outer) + + if 'gene' in requested or entrez: + is_outer = not bool(entrez) + gene_join_condition = build_join_condition( + gene_1.id, colocalization_1.gene_id, filter_column=gene_1.entrez_id, filter_list=entrez) + query = query.join(gene_1, and_( + *gene_join_condition), isouter=is_outer) + + if 'snp' in requested or snp: + is_outer = not bool(snp) + snp_join_condition = build_join_condition( + snp_1.id, colocalization_1.snp_id, filter_column=snp_1.name, filter_list=snp) + query = query.join(snp_1, and_( + *snp_join_condition), isouter=is_outer) + + return get_pagination_queries(query, paging, distinct, cursor_field=colocalization_1.id) diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/copy_number_result.py b/apps/iatlas/api/api/resolvers/resolver_helpers/copy_number_result.py new file mode 100644 index 0000000000..d9e2184222 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/copy_number_result.py @@ -0,0 +1,194 @@ +from sqlalchemy import and_ +from sqlalchemy.orm import aliased +from api import db +from api.db_models import CopyNumberResult, Dataset, DatasetToTag, Feature, Gene, Tag +from .general_resolvers import build_join_condition, get_selected, get_value +from .paging_utils import get_pagination_queries + +cnr_request_fields = {'dataSet', + 'direction', + 'feature', + 'gene', + 'meanNormal', + 'meanCnv', + 'pValue', + 'log10PValue', + 'tag', + 'tStat'} + + +def build_cnr_graphql_response(prefix=''): + from .data_set import build_data_set_graphql_response + from .feature import build_feature_graphql_response + from .gene import build_gene_graphql_response + from .tag import build_tag_graphql_response + + def f(copy_number_result): + if not copy_number_result: + return None + else: + dict = { + 'id': get_value(copy_number_result, prefix + 'id'), + 'direction': get_value(copy_number_result, prefix + 'direction'), + 'meanNormal': get_value(copy_number_result, prefix + 'mean_normal'), + 'meanCnv': get_value(copy_number_result, prefix + 'mean_cnv'), + 'pValue': get_value(copy_number_result, prefix + 'p_value'), + 'log10PValue': get_value(copy_number_result, prefix + 'log10_p_value'), + 'tStat': get_value(copy_number_result, prefix + 't_stat'), + 'dataSet': build_data_set_graphql_response()(copy_number_result), + 'feature': build_feature_graphql_response()(copy_number_result), + 'gene': build_gene_graphql_response()(copy_number_result), + 'tag': build_tag_graphql_response()(copy_number_result) + } + return(dict) + return(f) + + +def build_copy_number_result_request( + requested, data_set_requested, feature_requested, gene_requested, tag_requested, data_set=None, direction=None, distinct=False, entrez=None, feature=None, max_p_value=None, max_log10_p_value=None, min_log10_p_value=None, min_mean_cnv=None, min_mean_normal=None, min_p_value=None, min_t_stat=None, paging=None, related=None, tag=None): + """ + Builds a SQL request. + + All positional arguments are required. Positional arguments are: + 1st position - a set of the requested fields at the root of the graphql request + 2nd position - a set of the requested fields in the 'dataSet' node of the graphql request. If 'dataSet' is not requested, this will be an empty set. + 3rd position - a set of the requested fields in the 'feature' node of the graphql request. If 'feature' is not requested, this will be an empty set. + 4th position - a set of the requested fields in the 'gene' node of the graphql request. If 'gene' is not requested, this will be an empty set. + 5th position - a set of the requested fields in the 'tag' node of the graphql request. If 'tag' is not requested, this will be an empty set. + + All keyword arguments are optional. Keyword arguments are: + `data_set` - a list of strings, data set names + `direction` - a value from the DirectionEnum. (either 'Amp' or 'Del') + `distinct` - a boolean, indicates whether duplicate records should be filtered out + `entrez` - a list of integers, gene entrez ids + `feature` - a list of strings, feature names + `max_p_value` - a float, a maximum P value + `max_log10_p_value` - a float, a minimum calculated log10 P value + `min_log10_p_value` - a float, a minimum calculated log 10 P value + `min_mean_cnv` - a float, a minimum mean cnv value + `min_mean_normal` - a float, a minimum mean normal value + `min_p_value` - a float, a minimum P value + `min_t_stat` - a float, a minimum t stat value + `paging` - a dict containing pagination metadata + `related` - a list of strings, tags related to the dataset that is associated with the result. + `tag` - a list of strings, tag names + """ + from .tag import get_tag_column_labels + sess = db.session + + copy_number_result_1 = aliased(CopyNumberResult, name='dcnr') + data_set_1 = aliased(Dataset, name='ds') + feature_1 = aliased(Feature, name='f') + gene_1 = aliased(Gene, name='g') + tag_1 = aliased(Tag, name='t') + + core_field_mapping = { + 'direction': copy_number_result_1.direction.label('direction'), + 'meanNormal': copy_number_result_1.mean_normal.label('mean_normal'), + 'meanCnv': copy_number_result_1.mean_cnv.label('mean_cnv'), + 'pValue': copy_number_result_1.p_value.label('p_value'), + 'log10PValue': copy_number_result_1.log10_p_value.label('log10_p_value'), + 'tStat': copy_number_result_1.t_stat.label('t_stat')} + + data_set_field_mapping = { + 'display': data_set_1.display.label('data_set_display'), + 'name': data_set_1.name.label('data_set_name'), + 'type': data_set_1.dataset_type.label('data_set_type') + } + + feature_field_mapping = { + 'display': feature_1.display.label('feature_display'), + 'name': feature_1.name.label('feature_name'), + 'order': feature_1.order.label('order'), + 'unit': feature_1.unit.label('unit') + } + + gene_field_mapping = { + 'entrez': gene_1.entrez_id.label('gene_entrez'), + 'hgnc': gene_1.hgnc_id.label('gene_hgnc'), + 'description': gene_1.description.label('gene_description'), + 'friendlyName': gene_1.friendly_name.label('gene_friendly_name'), + 'ioLandscapeName': gene_1.io_landscape_name.label('gene_io_landscape_name') + } + + core = get_selected(requested, core_field_mapping) + core |= get_selected(data_set_requested, data_set_field_mapping) + core |= get_selected(feature_requested, feature_field_mapping) + core |= get_selected(gene_requested, gene_field_mapping) + core |= get_tag_column_labels(tag_requested, tag_1) + + if not distinct: + # Add the id as a cursor if not selecting distinct + core.add(copy_number_result_1.id.label('id')) + + query = sess.query(*core) + query = query.select_from(copy_number_result_1) + + if direction: + query = query.filter(copy_number_result_1.direction == direction) + + if max_p_value or max_p_value == 0: + query = query.filter(copy_number_result_1.p_value <= max_p_value) + + if (max_log10_p_value or max_log10_p_value == 0) and (not max_p_value and max_p_value != 0): + query = query.filter( + copy_number_result_1.log10_p_value <= max_log10_p_value) + + if (min_log10_p_value or min_log10_p_value == 0) and (not min_p_value and min_p_value != 0): + query = query.filter( + copy_number_result_1.log10_p_value >= min_log10_p_value) + + if min_mean_cnv or min_mean_cnv == 0: + query = query.filter(copy_number_result_1.mean_cnv >= min_mean_cnv) + + if min_mean_normal or min_mean_normal == 0: + query = query.filter( + copy_number_result_1.mean_normal >= min_mean_normal) + + if min_p_value or min_p_value == 0: + query = query.filter(copy_number_result_1.p_value >= min_p_value) + + if min_t_stat or min_t_stat == 0: + query = query.filter(copy_number_result_1.t_stat >= min_t_stat) + + if data_set or 'dataSet' in requested or related: + is_outer = not bool(data_set) + data_set_join_condition = build_join_condition( + data_set_1.id, copy_number_result_1.dataset_id, filter_column=data_set_1.name, filter_list=data_set) + query = query.join(data_set_1, and_( + *data_set_join_condition), isouter=is_outer) + + if entrez or 'gene' in requested: + is_outer = not bool(entrez) + data_set_join_condition = build_join_condition( + gene_1.id, copy_number_result_1.gene_id, filter_column=gene_1.entrez_id, filter_list=entrez) + query = query.join(gene_1, and_( + *data_set_join_condition), isouter=is_outer) + + if feature or 'feature' in requested: + is_outer = not bool(feature) + data_set_join_condition = build_join_condition( + feature_1.id, copy_number_result_1.feature_id, filter_column=feature_1.name, filter_list=feature) + query = query.join(feature_1, and_( + *data_set_join_condition), isouter=is_outer) + + if tag or 'tag' in requested: + is_outer = not bool(tag) + data_set_join_condition = build_join_condition( + tag_1.id, copy_number_result_1.tag_id, filter_column=tag_1.name, filter_list=tag) + query = query.join(tag_1, and_( + *data_set_join_condition), isouter=is_outer) + + if related: + data_set_to_tag_1 = aliased(DatasetToTag, name='dtt') + related_tag_1 = aliased(Tag, name='rt') + + related_tag_sub_query = sess.query(related_tag_1.id).filter( + related_tag_1.name.in_(related)) + + data_set_tag_join_condition = build_join_condition( + data_set_to_tag_1.dataset_id, data_set_1.id, data_set_to_tag_1.tag_id, related_tag_sub_query) + query = query.join( + data_set_to_tag_1, and_(*data_set_tag_join_condition)) + + return get_pagination_queries(query, paging, distinct, cursor_field=copy_number_result_1.id) diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/data_set.py b/apps/iatlas/api/api/resolvers/resolver_helpers/data_set.py new file mode 100644 index 0000000000..3260672096 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/data_set.py @@ -0,0 +1,145 @@ +from sqlalchemy.orm import aliased +from api import db +from sqlalchemy import and_ +from api.db_models import Dataset, Sample, DatasetToSample, DatasetToTag, Tag +from .general_resolvers import get_selected, get_value, build_join_condition +from .paging_utils import get_pagination_queries + + +simple_data_set_request_fields = {'display', 'name', 'type'} + +data_set_request_fields = simple_data_set_request_fields.union({ + 'samples', 'tags'}) + + +def build_data_set_graphql_response(prefix='data_set_', requested=[], sample_requested=[], tag_requested=[], sample=None): + from .sample import build_sample_graphql_response + from .tag import build_tag_graphql_response + + def f(data_set): + if not data_set: + return None + else: + id = get_value(data_set, prefix + 'id') + samples = get_samples(id, requested, sample_requested, sample) + tags = get_tags(id, requested, tag_requested) + dict = { + 'id': id, + 'display': get_value(data_set, prefix + 'display'), + 'name': get_value(data_set, prefix + 'name'), + 'type': get_value(data_set, prefix + 'type'), + 'samples': map(build_sample_graphql_response(), samples), + 'tags': map(build_tag_graphql_response(), tags), + } + return(dict) + return(f) + + +def build_data_set_request(requested, data_set=None, sample=None, data_set_type=None, distinct=False, paging=None): + ''' + Builds a SQL query. + + All positional arguments are required. Positional arguments are: + 1st position - a set of the requested fields at the root of the graphql request. + + All keyword arguments are optional. Keyword arguments are: + `data_set` - a list of strings, data set names + `sample` - a list of strings, sample names + `data_set_type` - a list of strings, data set types + `distinct` - a boolean, indicates whether duplicate records should be filtered out + `paging` - a dict containing pagination metadata + ''' + sess = db.session + + data_set_1 = aliased(Dataset, name='d') + data_set_to_sample_1 = aliased(DatasetToSample, name='dts') + sample_1 = aliased(Sample, name='s') + + core_field_mapping = { + 'display': data_set_1.display.label('data_set_display'), + 'name': data_set_1.name.label('data_set_name'), + 'type': data_set_1.dataset_type.label('data_set_type') + } + + core = get_selected(requested, core_field_mapping) + core |= {data_set_1.id.label('data_set_id')} + + query = sess.query(*core) + query = query.select_from(data_set_1) + + if sample: + + data_set_to_sample_subquery = sess.query( + data_set_to_sample_1.dataset_id) + + sample_join_condition = build_join_condition( + data_set_to_sample_1.sample_id, sample_1.id, filter_column=sample_1.name, filter_list=sample) + + data_set_to_sample_subquery = data_set_to_sample_subquery.join(sample_1, and_( + *sample_join_condition), isouter=False) + + query = query.filter(data_set_1.id.in_(data_set_to_sample_subquery)) + + if data_set: + query = query.filter(data_set_1.name.in_(data_set)) + + if data_set_type: + query = query.filter(data_set_1.dataset_type.in_(data_set_type)) + + return get_pagination_queries(query, paging, distinct, cursor_field=data_set_1.id) + + +def get_samples(dataset_id, requested, sample_requested, sample=None): + if 'samples' in requested: + sess = db.session + + data_set_to_sample_1 = aliased(DatasetToSample, name='dts') + sample_1 = aliased(Sample, name='s') + + sample_core_field_mapping = { + 'name': sample_1.name.label('sample_name')} + + sample_core = get_selected(sample_requested, sample_core_field_mapping) + #sample_core |= {sample_1.id.label('sample_id')} + + sample_query = sess.query(*sample_core) + sample_query = sample_query.select_from(sample_1) + + if sample: + sample_query = sample_query.filter(sample_1.name.in_(sample)) + + data_set_to_sample_subquery = sess.query( + data_set_to_sample_1.sample_id) + + data_set_to_sample_subquery = data_set_to_sample_subquery.filter( + data_set_to_sample_1.dataset_id == dataset_id) + + sample_query = sample_query.filter( + sample_1.id.in_(data_set_to_sample_subquery)) + + return sample_query.distinct().all() + + return [] + + +def get_tags(dataset_id, requested, tag_requested): + if 'tags' in requested: + from .tag import get_tag_column_labels + sess = db.session + + data_set_to_tag_1 = aliased(DatasetToTag, name='dtt') + tag_1 = aliased(Tag, name='t') + + tag_core = get_tag_column_labels(tag_requested, tag_1) + query = sess.query(*tag_core) + query = query.select_from(tag_1) + + subquery = sess.query(data_set_to_tag_1.tag_id) + + subquery = subquery.filter(data_set_to_tag_1.dataset_id == dataset_id) + + query = query.filter(tag_1.id.in_(subquery)) + + return query.distinct().all() + + return [] diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/driver_result.py b/apps/iatlas/api/api/resolvers/resolver_helpers/driver_result.py new file mode 100644 index 0000000000..b35eb9aa2a --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/driver_result.py @@ -0,0 +1,198 @@ +from sqlalchemy import and_ +from sqlalchemy.orm import aliased +from api import db +from api.db_models import Dataset, DatasetToTag, DriverResult, Feature, Gene, Mutation, MutationType, Tag +from .general_resolvers import build_join_condition, get_selected, get_value +from .paging_utils import get_pagination_queries + +driver_result_request_fields = { + 'dataSet', + 'feature', + 'mutation', + 'tag', + 'pValue', + 'foldChange', + 'log10PValue', + 'log10FoldChange', + 'numWildTypes', + 'numMutants' +} + + +def build_dr_graphql_response(driver_result): + from .data_set import build_data_set_graphql_response + from .feature import build_feature_graphql_response + from .mutation import build_mutation_graphql_response + from .tag import build_tag_graphql_response + + dict = { + 'id': get_value(driver_result, 'id'), + 'pValue': get_value(driver_result, 'p_value'), + 'foldChange': get_value(driver_result, 'fold_change'), + 'log10PValue': get_value(driver_result, 'log10_p_value'), + 'log10FoldChange': get_value(driver_result, 'log10_fold_change'), + 'numWildTypes': get_value(driver_result, 'n_wt'), + 'numMutants': get_value(driver_result, 'n_mut'), + 'dataSet': build_data_set_graphql_response()(driver_result), + 'feature': build_feature_graphql_response()(driver_result), + 'mutation': build_mutation_graphql_response()(driver_result), + 'tag': build_tag_graphql_response()(driver_result) + } + return(dict) + + +def build_driver_result_request(requested, data_set_requested, feature_requested, mutation_requested, mutation_gene_requested, mutation_type_requested, tag_requested, data_set=None, distinct=False, entrez=None, feature=None, max_p_value=None, max_log10_p_value=None, min_fold_change=None, min_log10_fold_change=None, min_log10_p_value=None, min_p_value=None, min_n_mut=None, min_n_wt=None, mutation=None, mutation_code=None, paging=None, related=None, tag=None): + """ + Builds a SQL request. + + All positional arguments are required. Positional arguments are: + 1st position - a set of the requested fields at the root of the graphql request + 2nd position - a set of the requested fields in the 'dataSet' node of the graphql request. If 'dataSet' is not requested, this will be an empty set. + 3rd position - a set of the requested fields in the 'feature' node of the graphql request. If 'feature' is not requested, this will be an empty set. + 4th position - a set of the requested fields in the 'mutation' node of the graphql request. If 'mutation' is not requested, this will be an empty set. + 5th position - a set of the requested fields in the 'gene' node of the graphql request. If 'gene' is not requested, this will be an empty set. + 6th position - a set of the requested fields in the 'mutationType' node of the graphql request. If 'mutationType' is not requested, this will be an empty set. + 7th position - a set of the requested fields in the 'tag' node of the graphql request. If 'tag' is not requested, this will be an empty set. + + All keyword arguments are optional. Keyword arguments are: + `data_set` - a list of strings, data set names + `distinct` - a boolean, indicates whether duplicate records should be filtered out + `entrez` - a list of integers, gene entrez ids + `feature` - a list of strings, feature names + `max_p_value` - a float, a maximum P value + `max_log10_p_value` - a float, a minimum calculated log10 P value + `min_fold_change` - a float, a minimum fold change value + `min_log10_fold_change` - a float, a minimum calculated log 10 fold change value + `min_log10_p_value` - a float, a minimum calculated log 10 P value + `min_p_value` - a float, a minimum P value + `min_n_mut` - a float, a minimum number of mutants + `min_n_wt` - a float, a minimum number of wild types + `mutation` - a list of strings, mutations + `mutation_code` - a list of strings, mutation codes + `paging` - a dict containing pagination metadata + `related` - a list of strings, tags related to the dataset that is associated with the result. + `tag` - a list of strings, tag names + """ + from .tag import get_tag_column_labels + from .gene import get_simple_gene_column_labels + from .mutation import get_mutation_column_labels, get_mutation_type_column_labels, build_simple_mutation_request + sess = db.session + + driver_result_1 = aliased(DriverResult, name='dr') + gene_1 = aliased(Gene, name='g') + mutation_1 = aliased(Mutation, name='m') + mutation_type_1 = aliased(MutationType, name='mt') + tag_1 = aliased(Tag, name='t') + feature_1 = aliased(Feature, name='f') + data_set_1 = aliased(Dataset, name='ds') + + core_field_mapping = { + 'id': driver_result_1.id.label('id'), + 'pValue': driver_result_1.p_value.label('p_value'), + 'foldChange': driver_result_1.fold_change.label('fold_change'), + 'log10PValue': driver_result_1.log10_p_value.label('log10_p_value'), + 'log10FoldChange': driver_result_1.log10_fold_change.label('log10_fold_change'), + 'numWildTypes': driver_result_1.n_wildtype.label('n_wt'), + 'numMutants': driver_result_1.n_mutants.label('n_mut') + } + data_set_core_field_mapping = { + 'display': data_set_1.display.label('data_set_display'), + 'name': data_set_1.name.label('data_set_name'), + 'type': data_set_1.dataset_type.label('data_set_type') + } + feature_core_field_mapping = { + 'display': feature_1.display.label('feature_display'), + 'name': feature_1.name.label('feature_name'), + 'order': feature_1.order.label('feature_order'), + 'unit': feature_1.unit.label('feature_unit') + } + + core = get_selected(requested, core_field_mapping) + core |= get_selected(data_set_requested, data_set_core_field_mapping) + core |= get_selected(feature_requested, feature_core_field_mapping) + core |= get_tag_column_labels(tag_requested, tag_1) + core |= get_mutation_column_labels( + mutation_requested, mutation_1 + ) + core |= get_simple_gene_column_labels(mutation_gene_requested, gene_1) + core |= get_mutation_type_column_labels( + mutation_type_requested, mutation_type_1) + + core |= {driver_result_1.id.label('id')} + + query = sess.query(*core) + query = query.select_from(driver_result_1) + + if max_p_value or max_p_value == 0: + query = query.filter(driver_result_1.p_value <= max_p_value) + + if (max_log10_p_value or max_log10_p_value == 0) and (not max_p_value and max_p_value != 0): + query = query.filter( + driver_result_1.log10_p_value <= max_log10_p_value) + + if min_fold_change or min_fold_change == 0: + query = query.filter( + driver_result_1.fold_change >= min_fold_change) + + if (min_log10_fold_change or min_log10_fold_change == 0) and (not min_fold_change and min_fold_change != 0): + query = query.filter( + driver_result_1.log10_fold_change >= min_log10_fold_change) + + if (min_log10_p_value or min_log10_p_value == 0) and (not min_p_value and min_p_value != 0): + query = query.filter( + driver_result_1.log10_p_value >= min_log10_p_value) + + if min_p_value or min_p_value == 0: + query = query.filter(driver_result_1.p_value >= min_p_value) + + if min_n_mut or min_n_mut == 0: + query = query.filter(driver_result_1.n_mutants >= min_n_mut) + + if min_n_wt or min_n_wt == 0: + query = query.filter(driver_result_1.n_wildtype >= min_n_wt) + + if 'dataSet' in requested or data_set or related: + is_outer = not bool(data_set) + data_set_join_condition = build_join_condition( + data_set_1.id, driver_result_1.dataset_id, filter_column=data_set_1.name, filter_list=data_set) + query = query.join(data_set_1, and_( + *data_set_join_condition), isouter=is_outer) + + if 'feature' in requested or feature: + is_outer = not bool(feature) + data_set_join_condition = build_join_condition( + feature_1.id, driver_result_1.feature_id, filter_column=feature_1.name, filter_list=feature) + query = query.join(feature_1, and_( + *data_set_join_condition), isouter=is_outer) + + if 'tag' in requested or tag: + is_outer = not bool(tag) + data_set_join_condition = build_join_condition( + tag_1.id, driver_result_1.tag_id, filter_column=tag_1.name, filter_list=tag) + query = query.join(tag_1, and_( + *data_set_join_condition), isouter=is_outer) + + if related: + data_set_to_tag_1 = aliased(DatasetToTag, name='dtt') + related_tag_1 = aliased(Tag, name='rt') + + related_tag_sub_query = sess.query(related_tag_1.id).filter( + related_tag_1.name.in_(related)) + + data_set_tag_join_condition = build_join_condition( + data_set_to_tag_1.dataset_id, data_set_1.id, data_set_to_tag_1.tag_id, related_tag_sub_query) + query = query.join( + data_set_to_tag_1, and_(*data_set_tag_join_condition)) + + if 'mutation' in requested or mutation: + is_outer = not bool(mutation) + mutation_join_condition = build_join_condition( + driver_result_1.mutation_id, mutation_1.id, filter_column=mutation_1.name, filter_list=mutation) + query = query.join(mutation_1, and_( + *mutation_join_condition), isouter=is_outer) + + query = build_simple_mutation_request( + query, mutation_requested, mutation_1, gene_1, mutation_type_1, entrez=entrez, mutation_code=mutation_code + ) + + return get_pagination_queries(query, paging, distinct, cursor_field=driver_result_1.id) diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/edge.py b/apps/iatlas/api/api/resolvers/resolver_helpers/edge.py new file mode 100644 index 0000000000..d5603d69d5 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/edge.py @@ -0,0 +1,79 @@ +from sqlalchemy import and_ +from sqlalchemy.orm import aliased +from api import db +from api.db_models import Edge, Node +from .paging_utils import get_pagination_queries +from .general_resolvers import build_join_condition, get_selected, get_value + +edge_request_fields = {'label', + 'name', + 'node1', + 'node2', + 'score'} + + +def build_edge_graphql_response(edge): + from .node import build_node_graphql_response + dict = { + 'id': get_value(edge, 'id'), + 'label': get_value(edge, 'label'), + 'name': get_value(edge, 'name'), + 'score': get_value(edge, 'score'), + 'node1': build_node_graphql_response(prefix='node1_')(edge), + 'node2': build_node_graphql_response(prefix='node2_')(edge) + } + return(dict) + + +def build_edge_request(requested, node_1_requested, node_2_requested, distinct=False, max_score=None, min_score=None, node_start=None, node_end=None, paging=None): + ''' + Builds a SQL request. + + All keyword arguments are optional. Keyword arguments are: + `max_score` - a float, a maximum score value + `min_score` - a float, a minimum score value + `node_start` - a list of strings, starting node names + `node_end` - a list of strings, ending node names + ''' + from .node import get_node_column_labels + sess = db.session + + edge_1 = aliased(Edge, name='e') + node_1 = aliased(Node, name='n1') + node_2 = aliased(Node, name='n2') + + core_field_mapping = { + 'id': edge_1.id.label('id'), + 'label': edge_1.label.label('label'), + 'name': edge_1.name.label('name'), + 'score': edge_1.score.label('score')} + + core = get_selected(requested, core_field_mapping) + node_1_core = get_node_column_labels( + node_1_requested, node_1, prefix='node1_') + node_2_core = get_node_column_labels( + node_2_requested, node_2, prefix='node2_') + + query = sess.query(*[*core, *node_1_core, *node_2_core]) + query = query.select_from(edge_1) + + if max_score != None: + query = query.filter(edge_1.score <= max_score) + + if min_score != None: + query = query.filter(edge_1.score >= min_score) + + if 'node1' in requested or node_start: + node_start_join_condition = build_join_condition( + node_1.id, edge_1.node_1_id, node_1.name, node_start) + query = query.join(node_1, and_(*node_start_join_condition)) + + if 'node2' in requested or node_end: + node_start_join_condition = build_join_condition( + node_2.id, edge_1.node_2_id, node_2.name, node_end) + query = query.join(node_2, and_(*node_start_join_condition)) + + import logging + logging.warning(query) + + return get_pagination_queries(query, paging, distinct, cursor_field=edge_1.id) diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/feature.py b/apps/iatlas/api/api/resolvers/resolver_helpers/feature.py new file mode 100644 index 0000000000..c45f45e54f --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/feature.py @@ -0,0 +1,214 @@ +from sqlalchemy import and_ +from sqlalchemy.orm import aliased +from api import db +from api.db_models import Feature, FeatureToSample, Sample, Cohort, CohortToSample, CohortToFeature +from .sample import build_sample_graphql_response +from .general_resolvers import build_join_condition, get_selected, get_value +from .paging_utils import get_pagination_queries +from decimal import Decimal + +feature_class_request_fields = {'name'} + +simple_feature_request_fields = { + 'display', + 'name', + 'order', + 'unit', + 'germlineModule', + 'germlineCategory' +} + +simple_feature_request_fields2 = { + 'display', + 'name', + 'order', + 'class' +} + +feature_request_fields = simple_feature_request_fields.union({ + 'class', + 'methodTag', + 'samples', + 'valueMax', + 'valueMin' +}) + + +def build_feature_graphql_response(requested=[], sample_requested=[], max_value=None, min_value=None, cohort=None, sample=None, prefix='feature_'): + + def f(feature): + if not feature: + return None + id = get_value(feature, prefix + 'id') + samples = get_samples( + [id], requested=requested, sample_requested=sample_requested, max_value=max_value, min_value=min_value, cohort=cohort, sample=sample) + if 'valueMax' in requested or 'valueMin' in requested: + values = [get_value(sample, 'sample_feature_value') + for sample in samples] + value_min = min(values) if 'valueMin' in requested else None + value_max = max(values) if 'valueMax' in requested else None + result = { + 'id': id, + 'class': get_value(feature, prefix + 'class'), + 'display': get_value(feature, prefix + 'display'), + 'methodTag': get_value(feature, prefix + 'method_tag'), + 'name': get_value(feature, prefix + 'name'), + 'order': get_value(feature, prefix + 'order'), + 'germlineModule': get_value(feature, prefix + 'germline_module'), + 'germlineCategory': get_value(feature, prefix + 'germline_category'), + 'unit': get_value(feature, prefix + 'unit'), + 'samples': map(build_sample_graphql_response(), samples), + 'valueMin': value_min if type(value_min) is Decimal else None, + 'valueMax': value_max if type(value_max) is Decimal else None + } + return(result) + return f + + +def build_features_query(requested, distinct=False, paging=None, feature=None, feature_class=None, max_value=None, min_value=None, sample=None, cohort=None): + """ + Builds a SQL request. + """ + sess = db.session + + has_min_max = 'valueMax' in requested or 'valueMin' in requested + + feature_1 = aliased(Feature, name='f') + feature_to_sample_1 = aliased(FeatureToSample, name='fts') + sample_1 = aliased(Sample, name='s') + cohort_1 = aliased(Cohort, name='c') + cohort_to_feature_1 = aliased(CohortToFeature, name='ctf') + + core_field_mapping = { + 'id': feature_1.id.label('feature_id'), + 'class': feature_1.feature_class.label('feature_class'), + 'display': feature_1.display.label('feature_display'), + 'methodTag': feature_1.method_tag.label('feature_method_tag'), + 'name': feature_1.name.label('feature_name'), + 'order': feature_1.order.label('feature_order'), + 'germlineModule': feature_1.germline_module.label('feature_germline_module'), + 'germlineCategory': feature_1.germline_category.label('feature_germline_category'), + 'unit': feature_1.unit.label('feature_unit') + } + + core = get_selected(requested, core_field_mapping) + core |= {feature_1.id.label('feature_id')} + + query = sess.query(*core) + query = query.select_from(feature_1) + + if feature: + query = query.filter(feature_1.name.in_(feature)) + + if feature_class: + query = query.filter(feature_1.feature_class.in_(feature_class)) + + if has_min_max or sample: + feature_to_sample_subquery = sess.query(feature_to_sample_1.feature_id) + + if max_value: + feature_to_sample_subquery = feature_to_sample_subquery.filter( + feature_to_sample_1.feature_to_sample_value <= max_value) + + if min_value: + feature_to_sample_subquery = feature_to_sample_subquery.filter( + feature_to_sample_1.feature_to_sample_value >= min_value) + + if sample: + + sample_join_condition = build_join_condition( + feature_to_sample_1.sample_id, sample_1.id, filter_column=sample_1.name, filter_list=sample) + cohort_subquery = feature_to_sample_subquery.join(sample_1, and_( + *sample_join_condition), isouter=False) + + feature_to_sample_subquery = feature_to_sample_subquery.filter( + sample_1.name.in_(sample)) + + query = query.filter(feature_1.id.in_(feature_to_sample_subquery)) + + if cohort: + cohort_subquery = sess.query(cohort_to_feature_1.feature_id) + + cohort_join_condition = build_join_condition( + cohort_to_feature_1.cohort_id, cohort_1.id, filter_column=cohort_1.name, filter_list=cohort) + cohort_subquery = cohort_subquery.join(cohort_1, and_( + *cohort_join_condition), isouter=False) + + query = query.filter(feature_1.id.in_(cohort_subquery)) + + if 'samples' not in requested: + order = [] + append_to_order = order.append + if 'class' in requested: + append_to_order(feature_1.feature_class) + if 'order' in requested: + append_to_order(feature_1.order) + if 'display' in requested: + append_to_order(feature_1.display) + if 'name' in requested: + append_to_order(feature_1.name) + if not order: + append_to_order(feature_1.id) + + return get_pagination_queries(query, paging, distinct, cursor_field=feature_1.id) + + +def get_samples(feature_id, requested, sample_requested, max_value=None, min_value=None, cohort=None, sample=None): + has_samples = 'samples' in requested + has_max_min = 'valueMax' in requested or 'valueMin' in requested + + if (has_samples or has_max_min): + sess = db.session + + feature_to_sample_1 = aliased(FeatureToSample, name='fts') + sample_1 = aliased(Sample, name='s') + cohort_1 = aliased(Cohort, name='c') + cohort_to_sample_1 = aliased(CohortToSample, name='cts') + + sample_core_field_mapping = { + 'name': sample_1.name.label('sample_name')} + + sample_core = get_selected(sample_requested, sample_core_field_mapping) + # Always select the sample id and the feature id. + sample_core |= {sample_1.id.label( + 'id'), feature_to_sample_1.feature_id.label('feature_id')} + + if has_max_min or 'value' in sample_requested: + sample_core |= {feature_to_sample_1.feature_to_sample_value.label( + 'sample_feature_value')} + + sample_query = sess.query(*sample_core) + sample_query = sample_query.select_from(sample_1) + + if sample: + sample_query = sample_query.filter(sample_1.name.in_(sample)) + + feature_sample_join_condition = build_join_condition( + feature_to_sample_1.sample_id, sample_1.id, feature_to_sample_1.feature_id, feature_id) + + if max_value: + feature_sample_join_condition.append( + feature_to_sample_1.feature_to_sample_value <= max_value) + + if min_value: + feature_sample_join_condition.append( + feature_to_sample_1.feature_to_sample_value >= min_value) + + sample_query = sample_query.join( + feature_to_sample_1, and_(*feature_sample_join_condition)) + + if cohort: + cohort_subquery = sess.query(cohort_to_sample_1.sample_id) + + cohort_join_condition = build_join_condition( + cohort_to_sample_1.cohort_id, cohort_1.id, filter_column=cohort_1.name, filter_list=cohort) + cohort_subquery = cohort_subquery.join(cohort_1, and_( + *cohort_join_condition), isouter=False) + + sample_query = sample_query.filter( + sample_1.id.in_(cohort_subquery)) + + samples = sample_query.distinct().all() + return samples + + return [] diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/gene.py b/apps/iatlas/api/api/resolvers/resolver_helpers/gene.py new file mode 100644 index 0000000000..dc6b024a02 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/gene.py @@ -0,0 +1,377 @@ +from sqlalchemy import and_ +from sqlalchemy.orm import aliased +from itertools import groupby +from api import db +from api.db_models import Cohort, CohortToSample, CohortToGene, Gene, GeneToSample, GeneToGeneSet, GeneSet, Publication, PublicationToGeneToGeneSet, Sample +from .general_resolvers import build_join_condition, get_selected, get_value +from .publication import build_publication_graphql_response +from .paging_utils import get_pagination_queries, fetch_page +from .sample import build_sample_graphql_response, build_gene_expression_graphql_response + + +simple_gene_request_fields = { + 'entrez', + 'hgnc', + 'description', + 'friendlyName', + 'ioLandscapeName' +} + +gene_request_fields = simple_gene_request_fields.union({ + 'geneFamily', + 'geneFunction', + 'geneTypes', + 'immuneCheckpoint', + 'pathway', + 'publications', + 'samples', + 'superCategory', + 'therapyType' +}) + + +def get_simple_gene_column_labels(requested, gene): + mapping = { + 'entrez': gene.entrez_id.label('gene_entrez'), + 'hgnc': gene.hgnc_id.label('gene_hgnc'), + 'description': gene.description.label('gene_description'), + 'friendlyName': gene.friendly_name.label('gene_friendly_name'), + 'ioLandscapeName': gene.io_landscape_name.label('gene_io_landscape_name') + } + labels = get_selected(requested, mapping) + return(labels) + + +def build_gene_graphql_response(requested=[], gene_types_requested=[], publications_requested=[], sample_requested=[], gene_type=None, cohort=None, sample=None, max_rna_seq_expr=None, min_rna_seq_expr=None, prefix='gene_'): + def f(gene): + if not gene: + return None + + id = get_value(gene, prefix + 'id') + gene_types = get_gene_types( + id, requested, gene_types_requested, gene_type=gene_type) + publications = get_publications(id, requested, publications_requested) + samples = get_samples(id, requested, sample_requested, + cohort, sample, max_rna_seq_expr, min_rna_seq_expr) + result_dict = { + 'id': id, + 'entrez': get_value(gene, prefix + 'entrez') or get_value(gene, prefix + 'entrez_id'), + 'hgnc': get_value(gene, prefix + 'hgnc') or get_value(gene, prefix + 'hgnc_id'), + 'description': get_value(gene, prefix + 'description'), + 'friendlyName': get_value(gene, prefix + 'friendly_name'), + 'ioLandscapeName': get_value(gene, prefix + 'io_landscape_name'), + 'geneFamily': get_value(gene, prefix + 'family'), + 'geneFunction': get_value(gene, prefix + 'function'), + 'immuneCheckpoint': get_value(gene, prefix + 'immune_checkpoint'), + 'pathway': get_value(gene, prefix + 'pathway'), + 'superCategory': get_value(gene, prefix + 'super_category'), + 'therapyType': get_value(gene, prefix + 'therapy_type'), + 'geneTypes': gene_types, + 'publications': map(build_publication_graphql_response, publications) + } + result_dict['samples'] = map(build_gene_expression_graphql_response(), samples) + return result_dict + return f + + +def build_pub_gene_gene_type_join_condition(gene_ids, gene_type, pub_gene_gene_type_model, pub_model): + join_condition = build_join_condition( + pub_gene_gene_type_model.publication_id, pub_model.id, pub_gene_gene_type_model.gene_id, gene_ids) + + if gene_type: + gene_type_1 = aliased(GeneSet, name='gt') + gene_type_subquery = db.session.query(gene_type_1.id).filter( + gene_type_1.name.in_(gene_type)) + join_condition.append( + pub_gene_gene_type_model.gene_type_id.in_(gene_type_subquery)) + + return join_condition + + +def build_gene_request(requested, distinct=False, paging=None, entrez=None, gene_family=None, gene_function=None, gene_type=None, immune_checkpoint=None, pathway=None, super_category=None, therapy_type=None, cohort=None, sample=None, max_rna_seq_expr=None, min_rna_seq_expr=None): + ''' + Builds a SQL request. + + All positional arguments are required. Positional arguments are: + 1st position - a set of the requested fields at the root of the graphql request + 2nd position - a set of the requested fields in the 'tag' node of the graphql request. If 'tag' is not requested, this will be an empty set. + + All keyword arguments are optional. Keyword arguments are: + `data_set` - a list of strings, data set names + `entrez` - a list of integers, gene entrez ids + `gene_family` - a list of strings, gene family names + `gene_function` - a list of strings, gene function names + `gene_type` - a list of strings, gene type names + `immune_checkpoint` - a list of strings, immune checkpoint names + `max_rna_seq_expr` - a float, a maximum RNA Sequence Expression value + `min_rna_seq_expr` - a float, a minimum RNA Sequence Expression value + `pathway` - a list of strings, pathway names + 'paging' - an instance of PagingInput + `type` - a string, the type of pagination to perform. Must be either 'OFFSET' or 'CURSOR'." + `page` - an integer, when performing OFFSET paging, the page number requested. + `limit` - an integer, when performing OFFSET paging, the number or records requested. + `first` - an integer, when performing CURSOR paging, the number of records requested AFTER the CURSOR. + `last` - an integer, when performing CURSOR paging, the number of records requested BEFORE the CURSOR. + `before` - an integer, when performing CURSOR paging: the CURSOR to be used in tandem with 'last' + `after` - an integer, when performing CURSOR paging: the CURSOR to be used in tandem with 'first' + `related` - a list of strings, tag names related to data sets + `sample` - a list of strings, sample names + `super_category` - a list of strings, super category names + `tag` - a list of strings, tag names related to samples + `therapy_type` - a list of strings, therapy type names + ''' + sess = db.session + + gene_1 = aliased(Gene, name='g') + gene_to_sample_1 = aliased(GeneToSample, name='gts') + gene_to_type_1 = aliased(GeneToGeneSet, name='ggt') + gene_type_1 = aliased(GeneSet, name='gt') + sample_1 = aliased(Sample, name='s') + cohort_1 = aliased(Cohort, name='c') + cohort_to_gene_1 = aliased(CohortToGene, name='ctg') + + core_field_mapping = { + 'id': gene_1.id.label('gene_id'), + 'entrez': gene_1.entrez_id.label('gene_entrez'), + 'hgnc': gene_1.hgnc_id.label('gene_hgnc'), + 'description': gene_1.description.label('gene_description'), + 'friendlyName': gene_1.friendly_name.label('gene_friendly_name'), + 'ioLandscapeName': gene_1.io_landscape_name.label('gene_io_landscape_name'), + 'geneFamily': gene_1.gene_family.label('gene_family'), + 'geneFunction': gene_1.gene_function.label('gene_function'), + 'immuneCheckpoint': gene_1.immune_checkpoint.label('gene_immune_checkpoint'), + 'pathway': gene_1.gene_pathway.label('gene_pathway'), + 'superCategory': gene_1.super_category.label('gene_super_category'), + 'therapyType': gene_1.therapy_type.label('gene_therapy_type') + } + + core = get_selected(requested, core_field_mapping) + core |= {gene_1.id.label('gene_id')} + + query = sess.query(*core) + query = query.select_from(gene_1) + + if entrez: + query = query.filter(gene_1.entrez_id.in_(entrez)) + + if gene_type: + query = query.join( + gene_to_type_1, and_( + gene_to_type_1.gene_id == gene_1.id, gene_to_type_1.gene_set_id.in_( + sess.query(gene_type_1.id).filter( + gene_type_1.name.in_(gene_type)) + ) + ) + ) + + if gene_family: + query = query.filter(gene_1.gene_family.in_(gene_family)) + + if gene_function: + query = query.filter(gene_1.gene_function.in_(gene_function)) + + if immune_checkpoint: + query = query.filter(gene_1.immune_checkpoint.in_(immune_checkpoint)) + + if pathway: + query = query.filter(gene_1.pathway.in_(pathway)) + + if super_category: + query = query.filter(gene_1.super_category.in_(super_category)) + + if therapy_type: + query = query.filter(gene_1.therapy_type.in_(therapy_type)) + + if max_rna_seq_expr or min_rna_seq_expr or sample: + gene_to_sample_subquery = sess.query(gene_to_sample_1.gene_id) + + if max_rna_seq_expr: + gene_to_sample_subquery = gene_to_sample_subquery.filter( + gene_to_sample_1.rna_seq_expression <= max_rna_seq_expr) + + if min_rna_seq_expr: + gene_to_sample_subquery = gene_to_sample_subquery.filter( + gene_to_sample_1.rna_seq_expression >= min_rna_seq_expr) + + if sample: + + sample_join_condition = build_join_condition( + gene_to_sample_1.sample_id, sample_1.id, filter_column=sample_1.name, filter_list=sample) + gene_to_sample_subquery = gene_to_sample_subquery.join(sample_1, and_( + *sample_join_condition), isouter=False) + + gene_to_sample_subquery = gene_to_sample_subquery.filter( + sample_1.name.in_(sample)) + + query = query.filter(gene_1.id.in_(gene_to_sample_subquery)) + + if cohort: + cohort_subquery = sess.query(cohort_to_gene_1.gene_id) + + cohort_join_condition = build_join_condition( + cohort_to_gene_1.cohort_id, cohort_1.id, filter_column=cohort_1.name, filter_list=cohort) + cohort_subquery = cohort_subquery.join(cohort_1, and_( + *cohort_join_condition), isouter=False) + + query = query.filter(gene_1.id.in_(cohort_subquery)) + + return get_pagination_queries(query, paging, distinct, cursor_field=gene_1.id) + + +def get_samples(id, requested, sample_requested, cohort=None, sample=None, max_rna_seq_expr=None, min_rna_seq_expr=None): + + if 'samples' not in requested: + return [] + + sess = db.session + + gene_to_sample_1 = aliased(GeneToSample, name='fts') + sample_1 = aliased(Sample, name='s') + cohort_1 = aliased(Cohort, name='c') + cohort_to_sample_1 = aliased(CohortToSample, name='cts') + + core_field_mapping = { + 'name': sample_1.name.label('sample_name'), + 'rnaSeqExpr': gene_to_sample_1.rna_seq_expression.label('sample_gene_rna_seq_expr'), + 'nanostringExpr': gene_to_sample_1.nanostring_expression.label('sample_gene_nanostring_expr') + } + + core = get_selected(sample_requested, core_field_mapping) + + core |= { + sample_1.id.label('sample_id'), + gene_to_sample_1.gene_id.label('gene_id'), + } + + query = sess.query(*core) + query = query.select_from(sample_1) + + if sample: + query = query.filter(sample_1.name.in_(sample)) + + gene_sample_join_condition = build_join_condition( + gene_to_sample_1.sample_id, sample_1.id, gene_to_sample_1.gene_id, [id]) + + if max_rna_seq_expr: + query = query.filter( + gene_to_sample_1.rna_seq_expression <= max_rna_seq_expr) + + if min_rna_seq_expr: + query = query.filter( + gene_to_sample_1.rna_seq_expression >= min_rna_seq_expr) + + query = query.join( + gene_to_sample_1, and_(*gene_sample_join_condition)) + + if cohort: + cohort_subquery = sess.query(cohort_to_sample_1.sample_id) + + cohort_join_condition = build_join_condition( + cohort_to_sample_1.cohort_id, cohort_1.id, filter_column=cohort_1.name, filter_list=cohort) + cohort_subquery = cohort_subquery.join(cohort_1, and_( + *cohort_join_condition), isouter=False) + + query = query.filter( + sample_1.id.in_(cohort_subquery)) + + samples = query.distinct().all() + return samples + + +def get_gene_types(gene_id, requested, gene_types_requested, gene_type=None): + + if 'geneTypes' not in requested: + return None + + sess = db.session + + gene_type_1 = aliased(GeneSet, name='gt') + gene_to_gene_type_1 = aliased(GeneToGeneSet, name='ggt') + + core_field_mapping = { + 'name': gene_type_1.name.label('name'), + 'display': gene_type_1.display.label('display') + } + + core = get_selected(gene_types_requested, core_field_mapping) + core |= { + gene_type_1.id.label('gene_id'), + gene_to_gene_type_1.gene_id.label('gene_type_id') + } + + gene_type_query = sess.query(*core) + gene_type_query = gene_type_query.select_from(gene_type_1) + + gene_gene_type_join_condition = build_join_condition( + gene_to_gene_type_1.gene_set_id, gene_type_1.id, gene_to_gene_type_1.gene_id, [gene_id]) + + if gene_type: + gene_gene_type_join_condition.append( + gene_type_1.name.in_(gene_type)) + + gene_type_query = gene_type_query.join(gene_to_gene_type_1, and_( + *gene_gene_type_join_condition)) + + order = [] + append_to_order = order.append + if 'name' in gene_types_requested: + append_to_order(gene_type_1.name) + if 'display' in gene_types_requested: + append_to_order(gene_type_1.display) + if not order: + append_to_order(gene_type_1.id) + gene_type_query = gene_type_query.order_by(*order) + + return gene_type_query.distinct().all() + + +def get_publications(gene_id, requested, publications_requested): + + if 'publications' not in requested: + return [] + + sess = db.session + + pub_1 = aliased(Publication, name='p') + pub_gene_gene_type_1 = aliased(PublicationToGeneToGeneSet, name='pggt') + + core_field_mapping = { + 'doId': pub_1.do_id.label('do_id'), + 'firstAuthorLastName': pub_1.first_author_last_name.label('first_author_last_name'), + 'journal': pub_1.journal.label('journal'), + 'name': pub_1.title.label('name'), + 'pubmedId': pub_1.pubmed_id.label('pubmed_id'), + 'title': pub_1.title.label('title'), + 'year': pub_1.year.label('year') + } + + core = get_selected(publications_requested, core_field_mapping) + core.add(pub_gene_gene_type_1.gene_id.label('gene_id')) + + query = sess.query(*core) + query = query.select_from(pub_1) + + ptgtgt_join_condition = build_join_condition( + pub_1.id, pub_gene_gene_type_1.publication_id, filter_column=pub_gene_gene_type_1.gene_id, filter_list=[gene_id]) + query = query.join(pub_gene_gene_type_1, and_( + *ptgtgt_join_condition), isouter=False) + + order = [] + append_to_order = order.append + if 'name' in publications_requested: + append_to_order(pub_1.name) + if 'pubmedId' in publications_requested: + append_to_order(pub_1.pubmed_id) + if 'doId' in publications_requested: + append_to_order(pub_1.do_id) + if 'title' in publications_requested: + append_to_order(pub_1.title) + if 'firstAuthorLastName' in publications_requested: + append_to_order(pub_1.first_author_last_name) + if 'year' in publications_requested: + append_to_order(pub_1.year) + if 'journal' in publications_requested: + append_to_order(pub_1.journal) + query = query.order_by(*order) if order else query + + return query.distinct().all() diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/gene_set.py b/apps/iatlas/api/api/resolvers/resolver_helpers/gene_set.py new file mode 100644 index 0000000000..7268cf0aec --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/gene_set.py @@ -0,0 +1,79 @@ +from sqlalchemy import orm +from api import db +from api.db_models import Gene, GeneSet +from .general_resolvers import build_option_args, get_selection_set + +simple_gene_set_request_fields = {'display', 'name'} + +gene_set_request_fields = simple_gene_set_request_fields.union({'genes'}) + + +def build_gene_set_core_request(selection_set, name=None): + """ + Builds a SQL request with just core gene set fields. + """ + sess = db.session + + gene_set_1 = orm.aliased(GeneSet, name='g') + + core_field_mapping = {'display': gene_set_1.display.label('display'), + 'name': gene_set_1.name.label('name')} + + core = build_option_args(selection_set, core_field_mapping) + + requested = build_option_args( + selection_set, {'display': 'display', 'name': 'name'}) + + query = sess.query(*core) + + if name: + query = query.filter(gene_set_1.name.in_(name)) + + order = [] + append_to_order = order.append + if 'name' in requested: + append_to_order(gene_set_1.name) + if 'display' in requested: + append_to_order(gene_set_1.display) + query = query.order_by(*order) if order else query + + return query + + +def build_gene_set_request(_obj, info, name=None): + """ + Builds a SQL request. + """ + sess = db.session + + selection_set = get_selection_set(info=info) + + gene_1 = orm.aliased(Gene, name='g') + gene_set_1 = orm.aliased(GeneSet, name='m') + + related_field_mapping = {'genes': 'genes'} + + relations = build_option_args(selection_set, related_field_mapping) + option_args = [] + + query = sess.query(gene_set_1) + + if name: + query = query.filter(gene_set_1.name.in_(name)) + + if 'genes' in relations: + query = query.join((gene_1, gene_set_1.genes), isouter=True) + option_args.append(orm.contains_eager( + gene_set_1.genes.of_type(gene_1))) + + query = query.order_by(gene_set_1.name, gene_set_1.display) + + if option_args: + return query.options(*option_args) + + return build_gene_set_core_request(selection_set, name) + + +def request_gene_sets(_obj, info, name=None): + query = build_gene_set_request(_obj, info, name=name) + return query.distinct().all() diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/general_resolvers.py b/apps/iatlas/api/api/resolvers/resolver_helpers/general_resolvers.py new file mode 100644 index 0000000000..2bbb0d3e42 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/general_resolvers.py @@ -0,0 +1,77 @@ + +def build_join_condition(join_column, column, filter_column=None, filter_list=None): + ''' + A helper function that creates a list of conditions. + + All positional arguments are required. TheyPositional arguments are: + 1st position - a column from the joining table or value who's value is to be compared as equal to the value passed in 2nd position + 2nd position - a column or value who's value is to be compared as equal to the value passed in ist position + + ie: join_column == column + + All keyword arguments are optional. Keyword arguments are: + `filter_column` - the column that will have the `in_` filter applied to it. This will only be applied if an actual value is passed to `filter_list`. + `filter_list` - A list of values to be applied to the `in_` filter. This may be a list of values or a subquery that returns a list. + ''' + join_condition = [join_column == column] + if bool(filter_list): + join_condition.append(filter_column.in_(filter_list)) + return join_condition + + +def build_option_args(selection_set=None, valid_nodes={}): + option_args = set() + add_to_option_args = option_args.add + if selection_set: + for selection in selection_set.selections: + if selection.name.value in valid_nodes: + if isinstance(valid_nodes, set): + add_to_option_args(selection.name.value) + else: + add_to_option_args(valid_nodes.get(selection.name.value)) + return option_args + + +def get_requested(info=None, requested_field_mapping={}, child_node=None, selection_set=[]): + selection_set = get_selection_set(selection_set, child_node, info) + return build_option_args(selection_set, requested_field_mapping) + + +def get_selected(requested, selected_field_mapping): + selected_keys = set([*selected_field_mapping]).intersection(requested) + return set(map(selected_field_mapping.get, selected_keys)) + + +def get_selection_set(selection_set=[], child_node=None, info=None): + ''' + A helper function to get the selection set from a graphql request. + + All keyword arguments are optional. Keyword arguments are: + `selection_set` - an initial set to get the selection set from. Defaults to an empty list. + `child_node` - the node one level down to look in. If this has no value, the root of the set it used. Defaults to None. + `info` - the info object from a request. If this is passed, the selection set will be taken from here and any passed selection set will be ignored. Defaults to None + ''' + selection_set = info.field_nodes[0].selection_set if info else selection_set + if selection_set and child_node: + new_selection_set = [] + for selection in selection_set.selections: + if selection.name.value == child_node: + new_selection_set = selection.selection_set + break + return new_selection_set + + return selection_set + + +def get_value(obj=None, attribute='name', default=None): + ''' + A helper function to get attribute values from an object. + + All keyword arguments are optional. Keyword arguments are: + `obj` - the object to get the value from. If no object is passed or the value is None, the default will be returned. Defaults to None. + `attribute` - the attribute name to look for. Defaults to 'name'. + `default` - the default value to return if the attribute is not found. Defaults to None + ''' + if obj: + return getattr(obj, attribute, default) + return default diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/germline_gwas_result.py b/apps/iatlas/api/api/resolvers/resolver_helpers/germline_gwas_result.py new file mode 100644 index 0000000000..a09e3715d8 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/germline_gwas_result.py @@ -0,0 +1,114 @@ +from sqlalchemy import and_ +from sqlalchemy.orm import aliased +from api import db +from api.db_models import Dataset, Feature, Snp, GermlineGwasResult +from .general_resolvers import build_join_condition, get_selected, get_value +from .data_set import build_data_set_graphql_response +from .feature import build_feature_graphql_response +from .snp import build_snp_graphql_response +from .paging_utils import get_pagination_queries + +germline_gwas_result_request_fields = {'dataSet', + 'id', + 'feature', + 'snp', + 'pValue', + 'maf'} + + +def build_ggr_graphql_response(germline_gwas_result): + return { + 'id': get_value(germline_gwas_result, 'id'), + 'pValue': get_value(germline_gwas_result, 'p_value'), + 'dataSet': build_data_set_graphql_response()(germline_gwas_result), + 'feature': build_feature_graphql_response()(germline_gwas_result), + 'snp': build_snp_graphql_response(germline_gwas_result), + 'maf': get_value(germline_gwas_result, 'maf') + } + + +def build_germline_gwas_result_request( + requested, data_set_requested, feature_requested, snp_requested, data_set=None, distinct=False, feature=None, snp=None, max_p_value=None, min_p_value=None, paging=None): + """ + Builds a SQL request. + + All positional arguments are required. Positional arguments are: + 1st position - a set of the requested fields at the root of the graphql request + 2nd position - a set of the requested fields in the 'dataset' node of the graphql request. If 'dataset' is not requested, this will be an empty set. + 3rd position - a set of the requested fields in the 'feature' node of the graphql request. If 'feature' is not requested, this will be an empty set. + 4th position - a set of the requested fields in the 'snp' node of the graphql request. If 'snp' is not requested, this will be an empty set. + + + All keyword arguments are optional. Keyword arguments are: + `dat_set` - a list of strings, data set names + `distinct` - a boolean, indicates whether duplicate records should be filtered out + `feature` - a list of strings, feature names + `snp` - a list of strings + `max_p_value` - a float, a maximum P value + `min_p_value` - a float, a minimum P value + `paging` - a dict containing pagination metadata + """ + sess = db.session + + germline_gwas_result_1 = aliased(GermlineGwasResult, name='ggr') + feature_1 = aliased(Feature, name='f') + data_set_1 = aliased(Dataset, name='ds') + snp_1 = aliased(Snp, name='snp') + + core_field_mapping = { + 'id': germline_gwas_result_1.id.label('id'), + 'pValue': germline_gwas_result_1.p_value.label('p_value'), + 'maf': germline_gwas_result_1.maf.label('maf') + } + data_set_core_field_mapping = {'display': data_set_1.display.label('data_set_display'), + 'name': data_set_1.name.label('data_set_name'), + 'type': data_set_1.dataset_type.label('data_set_type')} + feature_core_field_mapping = {'display': feature_1.display.label('feature_display'), + 'name': feature_1.name.label('feature_name'), + 'order': feature_1.order.label('feature_order'), + 'unit': feature_1.unit.label('feature_unit'), + 'germlineCategory': feature_1.germline_category.label('feature_germline_category'), + 'germlineModule': feature_1.germline_module.label('feature_germline_module')} + snp_core_field_mapping = {'rsid': snp_1.rsid.label('snp_rsid'), + 'name': snp_1.name.label('snp_name'), + 'bp': snp_1.bp.label('snp_bp'), + 'chr': snp_1.chr.label('snp_chr')} + + core = get_selected(requested, core_field_mapping) + core |= get_selected(data_set_requested, data_set_core_field_mapping) + core |= get_selected(feature_requested, feature_core_field_mapping) + core |= get_selected(snp_requested, snp_core_field_mapping) + + query = sess.query(*core) + query = query.select_from(germline_gwas_result_1) + + if max_p_value or max_p_value == 0: + query = query.filter( + germline_gwas_result_1.p_value <= float(max_p_value)) + + if min_p_value or min_p_value == 0: + query = query.filter( + germline_gwas_result_1.p_value >= float(min_p_value)) + + if 'dataSet' in requested or data_set: + is_outer = not bool(data_set) + data_set_join_condition = build_join_condition( + data_set_1.id, germline_gwas_result_1.dataset_id, filter_column=data_set_1.name, filter_list=data_set) + query = query.join(data_set_1, and_( + *data_set_join_condition), isouter=is_outer) + + if 'feature' in requested or feature: + is_outer = not bool(feature) + feature_join_condition = build_join_condition( + feature_1.id, germline_gwas_result_1.feature_id, filter_column=feature_1.name, filter_list=feature) + query = query.join(feature_1, and_( + *feature_join_condition), isouter=is_outer) + + if 'snp' in requested or snp: + is_outer = not bool(snp) + snp_join_condition = build_join_condition( + snp_1.id, germline_gwas_result_1.snp_id, filter_column=snp_1.name, filter_list=snp) + query = query.join(snp_1, and_( + *snp_join_condition), isouter=is_outer) + + return get_pagination_queries(query, paging, distinct, cursor_field=germline_gwas_result_1.id) diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/heritability_result.py b/apps/iatlas/api/api/resolvers/resolver_helpers/heritability_result.py new file mode 100644 index 0000000000..01e6eae4bc --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/heritability_result.py @@ -0,0 +1,115 @@ +from sqlalchemy import and_ +from sqlalchemy.orm import aliased +from api import db +from api.db_models import Dataset, HeritabilityResult, Feature +from .general_resolvers import build_join_condition, get_selected, get_value +from .data_set import build_data_set_graphql_response +from .feature import build_feature_graphql_response +from .paging_utils import get_pagination_queries + +heritability_result_request_fields = {'dataSet', + 'id', + 'feature', + 'pValue', + 'cluster', + 'fdr', + 'variance', + 'se'} + + +def build_hr_graphql_response(heritability_result): + result_dict = { + 'id': get_value(heritability_result, 'id'), + 'pValue': get_value(heritability_result, 'p_value'), + 'dataSet': build_data_set_graphql_response()(heritability_result), + 'feature': build_feature_graphql_response()(heritability_result), + 'cluster': get_value(heritability_result, 'cluster'), + 'fdr': get_value(heritability_result, 'fdr'), + 'variance': get_value(heritability_result, 'variance'), + 'se': get_value(heritability_result, 'se') + } + return(result_dict) + + +def build_heritability_result_request( + requested, data_set_requested, feature_requested, data_set=None, distinct=False, feature=None, max_p_value=None, min_p_value=None, cluster=None, paging=None): + """ + Builds a SQL request. + + All positional arguments are required. Positional arguments are: + 1st position - a set of the requested fields at the root of the graphql request + 2nd position - a set of the requested fields in the 'dataSet' node of the graphql request. If 'dataSet' is not requested, this will be an empty set. + 3rd position - a set of the requested fields in the 'feature' node of the graphql request. If 'feature' is not requested, this will be an empty set. + + All keyword arguments are optional. Keyword arguments are: + `data_set` - a list of strings, data set names + `distinct` - a boolean, indicates whether duplicate records should be filtered out + `feature` - a list of strings, feature names + `max_p_value` - a float, a maximum P value + `min_p_value` - a float, a minimum P value + `cluster` a string + `paging` - a dict containing pagination metadata + """ + sess = db.session + + heritability_result_1 = aliased(HeritabilityResult, name='hr') + feature_1 = aliased(Feature, name='f') + data_set_1 = aliased(Dataset, name='ds') + + core_field_mapping = { + 'id': heritability_result_1.id.label('id'), + 'pValue': heritability_result_1.p_value.label('p_value'), + 'se': heritability_result_1.se.label('se'), + 'variance': heritability_result_1.variance.label('variance'), + 'fdr': heritability_result_1.fdr.label('fdr'), + 'cluster': heritability_result_1.cluster.label('cluster') + } + data_set_core_field_mapping = { + 'display': data_set_1.display.label('data_set_display'), + 'name': data_set_1.name.label('data_set_name'), + 'type': data_set_1.dataset_type.label('data_set_type') + } + feature_core_field_mapping = { + 'display': feature_1.display.label('feature_display'), + 'name': feature_1.name.label('feature_name'), + 'order': feature_1.order.label('feature_order'), + 'unit': feature_1.unit.label('feature_unit'), + 'germlineCategory': feature_1.germline_category.label('feature_germline_category'), + 'germlineModule': feature_1.germline_module.label('feature_germline_module') + } + + core = get_selected(requested, core_field_mapping) + core |= get_selected(data_set_requested, data_set_core_field_mapping) + core |= get_selected(feature_requested, feature_core_field_mapping) + + if distinct == False: + # Add the id as a cursor if not selecting distinct + core.add(heritability_result_1.id) + + query = sess.query(*core) + query = query.select_from(heritability_result_1) + + if cluster: + query = query.filter(heritability_result_1.cluster.in_(cluster)) + + if max_p_value or max_p_value == 0: + query = query.filter(heritability_result_1.p_value <= max_p_value) + + if min_p_value or min_p_value == 0: + query = query.filter(heritability_result_1.p_value >= min_p_value) + + if 'dataSet' in requested or data_set: + is_outer = not bool(data_set) + data_set_join_condition = build_join_condition( + data_set_1.id, heritability_result_1.dataset_id, filter_column=data_set_1.name, filter_list=data_set) + query = query.join(data_set_1, and_( + *data_set_join_condition), isouter=is_outer) + + if 'feature' in requested or feature: + is_outer = not bool(feature) + feature_join_condition = build_join_condition( + feature_1.id, heritability_result_1.feature_id, filter_column=feature_1.name, filter_list=feature) + query = query.join(feature_1, and_( + *feature_join_condition), isouter=is_outer) + + return get_pagination_queries(query, paging, distinct, cursor_field=heritability_result_1.id) diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/mutation.py b/apps/iatlas/api/api/resolvers/resolver_helpers/mutation.py new file mode 100644 index 0000000000..bfed01b1e1 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/mutation.py @@ -0,0 +1,225 @@ +from sqlalchemy import and_ +from sqlalchemy.orm import aliased +from itertools import groupby +from api import db +from api.db_models import Gene, Mutation, MutationType, Patient, Sample, SampleToMutation, Cohort, CohortToMutation, CohortToSample +from .general_resolvers import build_join_condition, get_selected, get_value +from .paging_utils import get_pagination_queries + +mutation_request_fields = { + 'id', + 'gene', + 'name', + 'mutationCode', + 'mutationType', + 'samples' +} + + +def get_mutation_column_labels(requested, mutation, add_id=False): + mapping = { + 'name': mutation.name.label('mutation_name'), + 'mutationCode': mutation.mutation_code.label('mutation_code') + } + labels = get_selected(requested, mapping) + + if add_id: + labels |= {mutation.id.label('mutation_id')} + + return(labels) + + +def get_mutation_type_column_labels(requested, mutation_type): + mapping = { + 'display': mutation_type.display.label('display'), + 'name': mutation_type.name.label('name') + } + labels = get_selected(requested, mapping) + return(labels) + + +def build_mutation_graphql_response(requested=[], sample_requested=[], status=None, sample=None, cohort=None, prefix='mutation_'): + from .gene import build_gene_graphql_response + from .mutation_type import build_mutation_type_graphql_response + from .sample import build_sample_graphql_response + + def f(mutation): + if not mutation: + return None + mutation_id = get_value(mutation, prefix + 'id') + samples = get_samples(mutation_id=mutation_id, requested=requested, + sample_requested=sample_requested, status=status, sample=sample, cohort=cohort) + return { + 'id': mutation_id, + 'name': get_value(mutation, prefix + 'name'), + 'mutationCode': get_value(mutation, prefix + 'code'), + 'status': get_value(mutation, prefix + 'status'), + 'gene': build_gene_graphql_response()(mutation), + 'mutationType': build_mutation_type_graphql_response(mutation), + 'samples': map(build_sample_graphql_response(), samples) + } + return f + + +def build_mutation_request(requested, gene_requested, mutation_type_requested, distinct=False, paging=None, cohort=None, entrez=None, mutation=None, mutation_code=None, mutation_type=None, sample=None): + ''' + Builds a SQL request + + All positional arguments are required. Positional arguments are: + 1st position - a set of the requested fields at the root of the graphql request + 2nd position - a set of the requested fields in the 'gene' node of the graphql request. If 'gene' is not requested, this will be an empty set. + 3rd position - a set of the requested fields in the 'mutationType' node of the graphql request. If 'mutationType' is not requested, this will be an empty set. + + All keyword arguments are optional. Keyword arguments are: + `distinct` - a boolean, indicates whether duplicate records should be filtered out + `paging` - a dict containing pagination metadata + `cohort` - a list of strings, cohort names + `entrez` - a list of integers, gene entrez ids + `mutation` - a list of strings, mutation names + `mutation_code` - a list of strings, mutation codes + `mutation_type` - a list of strings, mutation type names + `sample` - a list of strings, sample names + ''' + from .gene import get_simple_gene_column_labels + sess = db.session + + gene_1 = aliased(Gene, name='g') + mutation_1 = aliased(Mutation, name='m') + mutation_type_1 = aliased(MutationType, name='mt') + sample_1 = aliased(Sample, name='s') + sample_to_mutation_1 = aliased(SampleToMutation, name='sm') + cohort_1 = aliased(Cohort, name='c') + cohort_to_mutation_1 = aliased(CohortToMutation, name='ctm') + + mutation_core = get_mutation_column_labels( + requested, mutation_1, add_id=True) + + gene_core = get_simple_gene_column_labels(gene_requested, gene_1) + + mutation_type_core = get_mutation_type_column_labels( + mutation_type_requested, mutation_type_1) + + query = sess.query(*[*mutation_core, *gene_core, *mutation_type_core]) + query = query.select_from(mutation_1) + + if mutation: + query = query.filter(mutation_1.name.in_(mutation)) + + query = build_simple_mutation_request( + query, requested, mutation_1, gene_1, mutation_type_1, + entrez=entrez, + mutation_code=mutation_code, + mutation_type=mutation_type + ) + + if sample: + sample_subquery = sess.query( + sample_to_mutation_1.mutation_id) + + sample_join_condition = build_join_condition( + sample_to_mutation_1.sample_id, sample_1.id, filter_column=sample_1.name, filter_list=sample) + + sample_subquery = sample_subquery.join(sample_1, and_( + *sample_join_condition), isouter=False) + + query = query.filter(mutation_1.id.in_(sample_subquery)) + + if cohort: + cohort_subquery = sess.query(cohort_to_mutation_1.mutation_id) + + cohort_join_condition = build_join_condition( + cohort_to_mutation_1.cohort_id, cohort_1.id, filter_column=cohort_1.name, filter_list=cohort) + + cohort_subquery = cohort_subquery.join(cohort_1, and_( + *cohort_join_condition), isouter=False) + + query = query.filter(mutation_1.id.in_(cohort_subquery)) + + return get_pagination_queries(query, paging, distinct, cursor_field=mutation_1.id) + + +def build_simple_mutation_request(query, requested, mutation_obj, gene_obj, mutation_type_obj, entrez=None, mutation_code=None, mutation_type=None): + ''' + Adds to a SQL query + + All positional arguments are required. Positional arguments are: + 1st position - a sql query + 2nd position - a set of the requested fields at the root of the graphql request + 3rd position - a mutation table object + 4th position - a gene table object + 5th position - a mutation type table object + All keyword arguments are optional. Keyword arguments are: + `entrez` - a list of integers, gene entrez ids + `mutation` - a list of strings, mutation names + `mutation_code` - a list of strings, mutation codes + `mutation_type` - a list of strings, mutation type names + ''' + + if 'gene' in requested or entrez: + is_outer = not bool(entrez) + gene_join_condition = build_join_condition( + gene_obj.id, mutation_obj.gene_id, + filter_column=gene_obj.entrez_id, + filter_list=entrez + ) + query = query.join(gene_obj, and_( + *gene_join_condition), isouter=is_outer) + + if 'mutationType' in requested or mutation_type: + is_outer = not bool(mutation_type) + mutation_type_join_condition = build_join_condition( + mutation_type_obj.id, mutation_obj.mutation_type_id, filter_column=mutation_type_obj.name, filter_list=mutation_type) + query = query.join(mutation_type_obj, and_( + *mutation_type_join_condition), isouter=is_outer) + + if mutation_code: + query = query.filter(mutation_obj.mutation_code.in_(mutation_code)) + + return(query) + + +def get_samples(mutation_id, requested, sample_requested, status=None, sample=None, cohort=None): + + if 'samples' not in requested: + return [] + + sess = db.session + + sample_1 = aliased(Sample, name='s') + sample_to_mutation_1 = aliased(SampleToMutation, name='stm') + cohort_1 = aliased(Cohort, name='c') + cohort_to_sample_1 = aliased(CohortToSample, name='cts') + + core_field_mapping = { + 'name': sample_1.name.label('sample_name'), + 'status': sample_to_mutation_1.mutation_status.label('sample_mutation_status') + } + + core = get_selected(sample_requested, core_field_mapping) + + query = sess.query(*core) + query = query.select_from(sample_to_mutation_1) + query = query.filter(sample_to_mutation_1.mutation_id == mutation_id) + + if status: + query = query.filter(sample_to_mutation_1.mutation_status.in_(status)) + + sample_join_condition = build_join_condition( + sample_to_mutation_1.sample_id, sample_1.id, filter_column=sample_1.name, filter_list=sample) + + query = query.join( + sample_1, and_(*sample_join_condition)) + + if cohort: + cohort_subquery = sess.query(cohort_to_sample_1.sample_id) + + cohort_join_condition = build_join_condition( + cohort_to_sample_1.cohort_id, cohort_1.id, filter_column=cohort_1.name, filter_list=cohort) + + cohort_subquery = cohort_subquery.join(cohort_1, and_( + *cohort_join_condition), isouter=False) + + query = query.filter( + sample_to_mutation_1.sample_id.in_(cohort_subquery)) + + return query.all() diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/mutation_type.py b/apps/iatlas/api/api/resolvers/resolver_helpers/mutation_type.py new file mode 100644 index 0000000000..e71bdb6960 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/mutation_type.py @@ -0,0 +1,47 @@ +from sqlalchemy import and_, orm +from api import db +from api.db_models import MutationType +from .general_resolvers import get_selected, get_value + +mutation_type_request_fields = {'display', 'name'} + + +def build_mutation_type_graphql_response(mutation_type): + if not mutation_type: + return None + return { + 'display': get_value(mutation_type, 'display'), + 'name': get_value(mutation_type) + } + + +def build_mutation_type_request(requested): + """ + Builds a SQL request. + """ + sess = db.session + + mutation_type_1 = orm.aliased(MutationType, name='mt') + + core_field_mapping = {'display': mutation_type_1.display.label('display'), + 'name': mutation_type_1.name.label('name')} + core = get_selected(requested, core_field_mapping) + + query = sess.query(*core) + query = query.select_from(mutation_type_1) + + order = [] + append_to_order = order.append + if 'name' in requested: + append_to_order(mutation_type_1.name) + if 'display' in requested: + append_to_order(mutation_type_1.display) + + query = query.order_by(*order) if order else query + + return query + + +def request_mutation_types(requested): + query = build_mutation_type_request(requested) + return query.all() diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/neoantigen.py b/apps/iatlas/api/api/resolvers/resolver_helpers/neoantigen.py new file mode 100644 index 0000000000..f932a596e9 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/neoantigen.py @@ -0,0 +1,105 @@ +from sqlalchemy import and_ +from sqlalchemy.orm import aliased +from api import db +from api.db_models import Neoantigen, Gene, Patient +from .general_resolvers import build_join_condition, get_selected, get_value +from .gene import build_gene_graphql_response +from .patient import build_patient_graphql_response +from .paging_utils import get_pagination_queries + + +neoantigen_request_fields = { + "id", + "pmhc", + "freqPmhc", + "tpm", + "gene", + "patient", +} + + +def build_neoantigen_graphql_response(neoantigen): + result_dict = { + 'id': get_value(neoantigen, 'id'), + 'pmhc': get_value(neoantigen, 'pmhc'), + 'freqPmhc': get_value(neoantigen, 'freq_pmhc'), + 'tpm': get_value(neoantigen, 'tpm'), + 'gene': build_gene_graphql_response()(neoantigen), + 'patient': build_patient_graphql_response()(neoantigen), + } + return(result_dict) + + +def build_neoantigen_request( + requested, + patient_requested, + gene_requested, + distinct=False, + paging=None, + patient=None, + entrez=None, + pmhc=None, +): + sess = db.session + + neoantigen_1 = aliased(Neoantigen, name='n') + gene_1 = aliased(Gene, name='g') + patient_1 = aliased(Patient, name='p') + + core_field_mapping = { + 'id': neoantigen_1.id.label('id'), + 'tpm': neoantigen_1.tpm.label('tpm'), + 'pmhc': neoantigen_1.pmhc.label('pmhc'), + 'freqPmhc': neoantigen_1.freq_pmhc.label('freq_pmhc'), + } + gene_core_field_mapping = { + 'entrez': gene_1.entrez_id.label('gene_entrez'), + 'hgnc': gene_1.hgnc_id.label('gene_hgnc'), + } + patient_core_field_mapping = { + 'barcode': patient_1.name.label('patient_barcode'), + } + + core = get_selected(requested, core_field_mapping) + core |= get_selected(gene_requested, gene_core_field_mapping) + core |= get_selected(patient_requested, patient_core_field_mapping) + + if distinct == False: + # Add the id as a cursor if not selecting distinct + core.add(neoantigen_1.id) + + query = sess.query(*core) + query = query.select_from(neoantigen_1) + + if pmhc: + query = query.filter(neoantigen_1.pmhc.in_(pmhc)) + + if 'gene' in requested or entrez: + is_outer = not bool(entrez) + gene_join_condition = build_join_condition( + gene_1.id, + neoantigen_1.neoantigen_gene_id, + filter_column=gene_1.entrez_id, + filter_list=entrez + ) + query = query.join( + gene_1, + and_(*gene_join_condition), + isouter=is_outer + ) + + if 'patient' in requested or patient: + is_outer = not bool(patient) + patient_join_condition = build_join_condition( + patient_1.id, + neoantigen_1.patient_id, + filter_column=patient_1.name, + filter_list=patient + ) + query = query.join( + patient_1, + and_(*patient_join_condition), + isouter=is_outer + ) + + return get_pagination_queries(query, paging, distinct, cursor_field=neoantigen_1.id) diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/node.py b/apps/iatlas/api/api/resolvers/resolver_helpers/node.py new file mode 100644 index 0000000000..d2200295ce --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/node.py @@ -0,0 +1,260 @@ +from itertools import groupby +from sqlalchemy import and_, func +from sqlalchemy.orm import aliased +from api import db +from api.db_models import Dataset, DatasetToTag, Feature, Gene, GeneToGeneSet, GeneSet, Node, Tag +from .general_resolvers import build_join_condition, get_selected, get_value +from .paging_utils import get_pagination_queries + +simple_node_request_fields = { + 'label', + 'name', + 'network', + 'score', + 'x', + 'y', +} + +node_request_fields = simple_node_request_fields.union({ + 'dataSet', + 'feature', + 'gene', + 'tags', +}) + + +def get_node_column_labels(requested, node, prefix='node_', add_id=False): + mapping = { + 'label': node.label.label(prefix + 'label'), + 'name': node.name.label(prefix + 'name'), + 'network': node.network.label(prefix + 'network'), + 'score': node.score.label(prefix + 'score'), + 'x': node.x.label(prefix + 'x'), + 'y': node.y.label(prefix + 'y') + } + labels = get_selected(requested, mapping) + + if add_id: + labels |= {node.id.label('id')} + + return(labels) + + +def build_node_graphql_response(requested=[], prefix='node_'): + from .data_set import build_data_set_graphql_response + from .feature import build_feature_graphql_response + from .gene import build_gene_graphql_response + from .tag import build_tag_graphql_response + + def f(node): + if not node: + return None + else: + node_id = get_value(node, 'id') + has_tag1 = get_value(node, 'tag_1_name') + has_tag2 = get_value(node, 'tag_2_name') + has_feature = get_value(node, 'feature_name') or get_value( + node, 'feature_display') or get_value(node, 'feature_order') or get_value(node, 'feature_unit') + has_gene = get_value(node, 'gene_entrez') or get_value(node, 'gene_hgnc') or get_value( + node, 'gene_description') or get_value(node, 'gene_friendly_name') or get_value(node, 'gene_io_landscape_name') + dict = { + 'id': node_id, + 'label': get_value(node, prefix + 'label'), + 'name': get_value(node, prefix + 'name'), + 'network': get_value(node, prefix + 'network'), + 'score': get_value(node, prefix + 'score'), + 'x': get_value(node, prefix + 'x'), + 'y': get_value(node, prefix + 'y'), + 'dataSet': build_data_set_graphql_response()(node), + 'feature': build_feature_graphql_response()(node) if has_feature else None, + 'gene': build_gene_graphql_response()(node) if has_gene else None, + 'tag1': build_tag_graphql_response(prefix='tag_1_')(node) if has_tag1 else None, + 'tag2': build_tag_graphql_response(prefix='tag_2_')(node) if has_tag2 else None, + } + return(dict) + return(f) + + +def build_node_request( + requested, + data_set_requested, + feature_requested, + gene_requested, + tag_requested1, + tag_requested2, + data_set=None, + distinct=False, + entrez=None, + feature=None, + feature_class=None, + gene_type=None, + max_score=None, + min_score=None, + network=None, + paging=None, + related=None, + tag1=None, + tag2=None, + n_tags=None + ): + ''' + Builds a SQL request. + + All positional arguments are required. Positional arguments are: + 1st position - a set of the requested fields at the root of the graphql request + 2nd position - a set of the requested fields in the 'dataSet' node of the graphql request. If 'dataSet' is not requested, this will be an empty set. + 3rd position - a set of the requested fields in the 'feature' node of the graphql request. If 'feature' is not requested, this will be an empty set. + 4th position - a set of the requested fields in the 'gene' node of the graphql request. If 'gene' is not requested, this will be an empty set. + + All keyword arguments are optional. Keyword arguments are: + `data_set` - a list of strings, data set names + `distinct` - a boolean, specifies whether or not duplicates should be filtered out + `entrez` - a list of integers, gene entrez ids + `feature` - a list of strings, feature names + `feature_class` - a list of strings, feature class names + `gene_type` - a list of strings, gene type names + `max_score` - a float, a maximum score value + `min_score` - a float, a minimum score value + `network` - a list of strings + 'paging' - an instance of PagingInput + `type` - a string, the type of pagination to perform. Must be either 'OFFSET' or 'CURSOR'." + `page` - an integer, when performing OFFSET paging, the page number requested. + `limit` - an integer, when performing OFFSET paging, the number or records requested. + `first` - an integer, when performing CURSOR paging, the number of records requested AFTER the CURSOR. + `last` - an integer, when performing CURSOR paging, the number of records requested BEFORE the CURSOR. + `before` - an integer, when performing CURSOR paging: the CURSOR to be used in tandem with 'last' + `after` - an integer, when performing CURSOR paging: the CURSOR to be used in tandem with 'first' + `related` - a list of strings, tag names related to data sets + `tag1` - a list of strings, tag names + `tag2` - a list of strings, tag names + `n_tags` - the number of tags the node should have + ''' + from .tag import get_tag_column_labels + + sess = db.session + + data_set_1 = aliased(Dataset, name='d') + feature_1 = aliased(Feature, name='f') + gene_1 = aliased(Gene, name='g') + node_1 = aliased(Node, name='n') + tag_1 = aliased(Tag, name="t1") + tag_2 = aliased(Tag, name="t2") + + + data_set_field_mapping = { + 'display': data_set_1.display.label('data_set_display'), + 'name': data_set_1.name.label('data_set_name'), + 'type': data_set_1.dataset_type.label('data_set_type') + } + + feature_field_mapping = { + 'display': feature_1.display.label('feature_display'), + 'name': feature_1.name.label('feature_name'), + 'order': feature_1.order.label('feature_order'), + 'unit': feature_1.unit.label('feature_unit') + } + + gene_field_mapping = { + 'entrez': gene_1.entrez_id.label('gene_entrez'), + 'hgnc': gene_1.hgnc_id.label('gene_hgnc'), + 'description': gene_1.description.label('gene_description'), + 'friendlyName': gene_1.friendly_name.label('gene_friendly_name'), + 'ioLandscapeName': gene_1.io_landscape_name.label('gene_io_landscape_name') + } + + node_core = get_node_column_labels(requested, node_1, add_id=True) + data_set_core = get_selected(data_set_requested, data_set_field_mapping) + feature_core = get_selected(feature_requested, feature_field_mapping) + gene_core = get_selected(gene_requested, gene_field_mapping) + tag_core1 = get_tag_column_labels(tag_requested1, tag_1, prefix='tag_1_') + tag_core2 = get_tag_column_labels(tag_requested2, tag_2, prefix='tag_2_') + + query = sess.query( + *[*node_core, *data_set_core, *feature_core, *gene_core, *tag_core1, *tag_core2]) + query = query.select_from(node_1) + + if max_score: + query = query.filter(node_1.score <= max_score) + + if min_score: + query = query.filter(node_1.score >= min_score) + + if network: + query = query.filter(node_1.network.in_(network)) + + if tag1 or tag_requested1: + is_outer = not bool(tag1) + tag_join_condition = build_join_condition( + tag_1.id, + node_1.tag_1_id, + filter_column=tag_1.name, + filter_list=tag1 + ) + query = query.join(tag_1, and_( + *tag_join_condition), isouter=is_outer) + + if n_tags or tag2 or tag_requested1: + if tag2: + tag_join_condition = build_join_condition( + tag_2.id, + node_1.tag_2_id, + filter_column=tag_2.name, + filter_list=tag2 + ) + query = query.join( + tag_2, + and_( *tag_join_condition), + isouter=False + ) + else: + tag_join_condition = build_join_condition( + tag_2.id, + node_1.tag_2_id, + ) + query = query.join(tag_2, and_(*tag_join_condition), isouter=True) + if n_tags == 1: + query = query.filter(tag_2.id.is_(None)) + if n_tags == 2: + query = query.filter(tag_2.id.isnot(None)) + + if data_set or related or 'dataSet' in requested: + data_set_join_condition = build_join_condition( + data_set_1.id, node_1.dataset_id, data_set_1.name, data_set) + query = query.join(data_set_1, and_(*data_set_join_condition)) + + if related: + data_set_to_tag_1 = aliased(DatasetToTag, name='dtt') + related_tag_1 = aliased(Tag, name='rt') + + related_tag_sub_query = sess.query(related_tag_1.id).filter( + related_tag_1.name.in_(related)) + + data_set_tag_join_condition = build_join_condition( + data_set_to_tag_1.dataset_id, data_set_1.id, data_set_to_tag_1.tag_id, related_tag_sub_query) + query = query.join( + data_set_to_tag_1, and_(*data_set_tag_join_condition)) + + if feature or 'feature' in requested or feature_class: + is_outer = not bool(feature) + feature_join_condition = build_join_condition( + feature_1.id, node_1.node_feature_id, feature_1.name, feature) + query = query.join(feature_1, and_( + *feature_join_condition), isouter=is_outer) + + if feature_class: + query = query.filter(feature_1.feature_class.in_(feature_class)) + + if entrez or 'gene' in requested or gene_type: + is_outer = not bool(entrez) + gene_join_condition = build_join_condition( + gene_1.id, node_1.node_gene_id, gene_1.entrez_id, entrez) + query = query.join(gene_1, and_( + *gene_join_condition), isouter=is_outer) + + if gene_type: + gene_type_1 = aliased(GeneSet, name='gt') + gene_to_type_1 = aliased(GeneToGeneSet, name='ggt') + query = query.join(gene_to_type_1, and_( + gene_to_type_1.gene_id == gene_1.id, gene_to_type_1.gene_set_id.in_(sess.query(gene_type_1.id).filter(gene_type_1.name.in_(gene_type))))) + + return get_pagination_queries(query, paging, distinct, cursor_field=node_1.id) diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/paging_utils.py b/apps/iatlas/api/api/resolvers/resolver_helpers/paging_utils.py new file mode 100644 index 0000000000..7dcbe3a376 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/paging_utils.py @@ -0,0 +1,180 @@ +import base64 +import math +import uuid +from collections import deque +from api.database.database_helpers import temp_table, execute_sql + + +class Paging: + OFFSET = 'OFFSET' + CURSOR = 'CURSOR' + MAX_LIMIT = 100000 + ASC = 'ASC' + DESC = 'DESC' + DEFAULT = {'type': CURSOR, 'first': MAX_LIMIT} + + +paging_fields = {'type', 'page', 'pages', + 'total', 'first', 'last', 'before', 'after'} + + +def to_cursor_hash(val): + return str(base64.b64encode(str(val).encode("utf-8")), "utf-8") + + +def from_cursor_hash(encoded): + return str(base64.b64decode(str(encoded)), "utf-8") + + +def get_cursor(before, after): + if after != None: + return (from_cursor_hash(after), Paging.ASC) + if before != None: + return (from_cursor_hash(before), Paging.DESC) + return (None, Paging.ASC) + + +def parse_limit(n, max=Paging.MAX_LIMIT): + return min(max, int(n)) + + +def get_limit(first, last, limit, max=Paging.MAX_LIMIT): + if first and not math.isnan(first): + return (parse_limit(first, max), Paging.ASC) + if last and not math.isnan(last): + return (parse_limit(last, max), Paging.DESC) + if limit and not math.isnan(limit): + return (parse_limit(limit, max), Paging.ASC) + return (max, Paging.ASC) + + +def get_pagination_queries(query, paging, distinct, cursor_field=None): + count_query = query + if paging.get('type', Paging.CURSOR) == Paging.OFFSET or distinct == True: + if distinct == True: + return query.distinct(), count_query.distinct() + return query, count_query + # Handle cursor and sort order + cursor, sort_order = get_cursor(paging.get('before'), paging.get('after')) + order_by = cursor_field + if sort_order == Paging.ASC: + query = query.order_by(order_by) + else: + query = query.order_by(order_by.desc()) + if cursor: + if sort_order == Paging.ASC: + query = query.filter(cursor_field > cursor) + else: + query = query.filter(cursor_field < cursor) + # end handle cursor + return query, count_query + + +def create_temp_table(query, paging, distinct): + paging_type = paging.get('type', Paging.CURSOR) + page = None + before = None + after = None + first = paging.get('first') + last = paging.get('last') + limit = paging.get('limit') + limit, sort_order = get_limit(first, last, limit) + table_name = f'_temp_{uuid.uuid4()}'.replace('-', '') + if paging_type == Paging.OFFSET or distinct == True: + page = paging.get('page', 1) + # run the offset query + query = query.limit(limit) + query = query.offset((page - 1) * limit) + else: + # request 1 more than we need, so we can determine if additional pages are available. returns list. + # run the cursor query + # Store query results in temp table + query = query.limit(limit + 1) + conn = temp_table(table_name, query) + # items = query.all() # slower than querying the new temp table because we have to recreate filters and joins + # instead grab everything from the new temp table + item_query = f'SELECT * FROM {table_name}' + items = execute_sql(item_query, conn=conn) + return items, table_name, conn + + +def fetch_page(query, paging, distinct): + max = paging.get('max', Paging.MAX_LIMIT) + paging_type = paging.get('type', Paging.CURSOR) + page = paging.get('page', 1) + first = paging.get('first') + last = paging.get('last') + limit = paging.get('limit') + limit, order = get_limit(first, last, limit, max) + if paging_type == Paging.OFFSET or distinct == True: + if distinct: + query = query.distinct() + return query.paginate(page, limit).items + res = query.limit(limit + 1).all() + return res + + +def process_page(items, count_query, paging, distinct, response_builder, pagination_requested): + paging = paging if paging else {} + paging_type = paging.get('type', Paging.CURSOR) + page = None + max = paging.get('max', Paging.MAX_LIMIT) + first = paging.get('first') + last = paging.get('last') + limit = paging.get('limit') + limit, order = get_limit(first, last, limit, max) + pageInfo = { + 'type': paging_type, + 'page': page, + 'pages': None, + 'limit': limit, + 'returned': None, + 'total': None + } + if paging_type == Paging.OFFSET or distinct == True: + # if distinct is True, paging type must be OFFSET + pageInfo['type'] = Paging.OFFSET + pageInfo['page'] = paging.get('page', 1) + results = map(response_builder, items) if response_builder else items + else: + returned = len(items) + if order == Paging.ASC: + hasNextPage = items != None and returned == limit + 1 + pageInfo['hasNextPage'] = hasNextPage + pageInfo['hasPreviousPage'] = False + if hasNextPage: + items.pop(-1) # remove the extra last item + if order == Paging.DESC: + items.reverse() # We have to reverse the list to get previous pages in the expected order + pageInfo['hasNextPage'] = False + hasPreviousPage = items != None and returned == limit + 1 + pageInfo['hasPreviousPage'] = hasPreviousPage + if hasPreviousPage: + items.pop(0) # remove the extra first item + results = deque(map(response_builder, items) + if response_builder else items) + pageInfo['startCursor'] = to_cursor_hash( + results[0]['id']) if (len(results) > 0) else None + pageInfo['endCursor'] = to_cursor_hash( + results[-1]['id']) if (len(results) > 0) else None + if 'total' in pagination_requested or 'pages' in pagination_requested: + # TODO: Consider caching this value per query, and/or making count query in parallel + count = count_query.count() + pageInfo['total'] = count + pageInfo['pages'] = math.ceil(count / limit) + pageInfo['returned'] = len(items) + return { + 'items': results, + 'paging': pageInfo + } + + +def paginate(query, count_query, paging, distinct, response_builder, pagination_requested): + items = fetch_page(query, paging, distinct) + return process_page(items, count_query, paging, distinct, response_builder, pagination_requested) + + +def create_paging(paging=None, max_results=Paging.MAX_LIMIT): + paging = paging if paging else Paging.DEFAULT.copy() + paging['max'] = max_results + return(paging) diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/patient.py b/apps/iatlas/api/api/resolvers/resolver_helpers/patient.py new file mode 100644 index 0000000000..028e37c444 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/patient.py @@ -0,0 +1,215 @@ +from sqlalchemy import and_ +from sqlalchemy.orm import aliased +from itertools import groupby +from api import db +from api.db_models import Dataset, DatasetToSample, Patient, Sample, Slide +from .general_resolvers import build_join_condition, get_selected, get_value +from .paging_utils import get_pagination_queries + +simple_patient_request_fields = { + 'ageAtDiagnosis', + 'barcode', + 'ethnicity', + 'gender', + 'height', + 'race', + 'weight' +} + +patient_request_fields = simple_patient_request_fields.union( + {'samples', 'slides'}) + + +def build_patient_graphql_response(requested=dict(), slide_requested=dict(), sample=None, slide=None, prefix='patient_'): + from .slide import build_slide_graphql_response + + def f(patient): + if not patient: + return None + else: + patient_id = get_value(patient, prefix + 'id') + samples = get_samples(patient_id, requested, sample) + slides = get_slides(patient_id, requested, slide_requested, slide) + dict = { + 'id': patient_id, + 'ageAtDiagnosis': get_value(patient, prefix + 'age_at_diagnosis'), + 'barcode': get_value(patient, prefix + 'barcode'), + 'ethnicity': get_value(patient, prefix + 'ethnicity'), + 'gender': get_value(patient, prefix + 'gender'), + 'height': get_value(patient, prefix + 'height'), + 'race': get_value(patient, prefix + 'race'), + 'weight': get_value(patient, prefix + 'weight'), + 'samples': map(get_value, samples), + 'slides': map(build_slide_graphql_response(), slides) + } + return(dict) + return f + + +def build_patient_request( + requested, distinct=False, paging=None, max_age_at_diagnosis=None, min_age_at_diagnosis=None, barcode=None, data_set=None, ethnicity=None, gender=None, max_height=None, min_height=None, + race=None, max_weight=None, min_weight=None, sample=None, slide=None +): + """ + Builds a SQL query. + """ + sess = db.session + + patient_1 = aliased(Patient, name='p') + sample_1 = aliased(Sample, name='s') + slide_1 = aliased(Slide, name='sd') + + core_field_mapping = { + 'ageAtDiagnosis': patient_1.age_at_diagnosis.label('patient_age_at_diagnosis'), + 'barcode': patient_1.name.label('patient_barcode'), + 'ethnicity': patient_1.ethnicity.label('patient_ethnicity'), + 'gender': patient_1.gender.label('patient_gender'), + 'height': patient_1.height.label('patient_height'), + 'race': patient_1.race.label('patient_race'), + 'weight': patient_1.weight.label('patient_weight') + } + + core = get_selected(requested, core_field_mapping) + core.add(patient_1.id.label('patient_id')) + + query = sess.query(*core) + query = query.select_from(patient_1) + + if barcode: + query = query.filter(patient_1.name.in_(barcode)) + + if max_age_at_diagnosis: + query = query.filter(patient_1.age_at_diagnosis <= + max_age_at_diagnosis) + + if min_age_at_diagnosis: + query = query.filter(patient_1.age_at_diagnosis >= + min_age_at_diagnosis) + + if ethnicity: + query = query.filter(patient_1.ethnicity.in_(ethnicity)) + + if gender: + query = query.filter(patient_1.gender.in_(gender)) + + if max_height: + query = query.filter(patient_1.height <= max_height) + + if min_height: + query = query.filter(patient_1.height >= min_height) + + if race: + query = query.filter(patient_1.race.in_(race)) + + if max_weight: + query = query.filter(patient_1.weight <= max_weight) + + if min_weight: + query = query.filter(patient_1.weight >= min_weight) + + if sample or data_set: + data_set_1 = aliased(Dataset, name='d') + data_set_to_sample_1 = aliased(DatasetToSample, name='ds') + + is_outer = not bool(sample) + + sample_join_condition = build_join_condition( + patient_1.id, sample_1.patient_id, filter_column=sample_1.name, filter_list=sample) + query = query.join(sample_1, and_( + *sample_join_condition), isouter=is_outer) + + data_set_sub_query = sess.query(data_set_1.id).filter( + data_set_1.name.in_(data_set)) if data_set else None + + data_set_to_sample_join_condition = build_join_condition( + data_set_to_sample_1.sample_id, sample_1.id, data_set_to_sample_1.dataset_id, data_set_sub_query) + query = query.join(data_set_to_sample_1, and_( + *data_set_to_sample_join_condition)) + + if slide: + slide_join_condition = build_join_condition( + patient_1.id, slide_1.patient_id, filter_column=slide_1.name, filter_list=slide) + query = query.join(slide_1, and_( + *slide_join_condition), isouter=False) + + order = [] + append_to_order = order.append + if 'barcode' in requested: + append_to_order(patient_1.name) + if 'ageAtDiagnosis' in requested: + append_to_order(patient_1.age_at_diagnosis) + if 'gender' in requested: + append_to_order(patient_1.gender) + if 'race' in requested: + append_to_order(patient_1.race) + if 'ethnicity' in requested: + append_to_order(patient_1.ethnicity) + if 'weight' in requested: + append_to_order(patient_1.weight) + if 'height' in requested: + append_to_order(patient_1.height) + + query = query.order_by(*order) if order else query + + return get_pagination_queries(query, paging, distinct, cursor_field=patient_1.id) + + +def get_samples(id, requested, sample=None): + + if 'samples' in requested: + + sess = db.session + sample_1 = aliased(Sample, name='s') + core = {sample_1.name.label('name')} + query = sess.query(*core) + query = query.select_from(sample_1) + + query = query.filter(sample_1.patient_id == id) + + if sample: + query = query.filter(sample_1.name.in_(sample)) + + order = [] + append_to_order = order.append + if 'name' in requested: + append_to_order(sample_1.name) + + query = query.order_by(*order) if order else query + return query.all() + + return [] + + +def get_slides(id, requested, slide_requested, slide=None): + if 'slides' not in requested: + return [] + + else: + sess = db.session + slide_1 = aliased(Slide, name='sd') + + core_field_mapping = { + 'description': slide_1.description.label('slide_description'), + 'name': slide_1.name.label('slide_name') + } + + core = get_selected(slide_requested, core_field_mapping) + + query = sess.query(*core) + query = query.select_from(slide_1) + + query = query.filter(slide_1.patient_id == id) + + if slide: + query = query.filter(slide_1.name.in_(slide)) + + order = [] + append_to_order = order.append + if 'name' in slide_requested: + append_to_order(slide_1.name) + if 'description' in slide_requested: + append_to_order(slide_1.description) + + query = query.order_by(*order) if order else query + + return query.all() diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/publication.py b/apps/iatlas/api/api/resolvers/resolver_helpers/publication.py new file mode 100644 index 0000000000..7ad7371340 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/publication.py @@ -0,0 +1,21 @@ +from .general_resolvers import get_value + +simple_publication_request_fields = { + 'doId', 'firstAuthorLastName', 'journal', 'name', 'pubmedId', 'title', 'year'} + +publication_request_fields = simple_publication_request_fields.union({ + 'genes', 'geneTypes'}) + + +def build_publication_graphql_response(pub): + if not pub: + return None + return { + 'firstAuthorLastName': get_value(pub, 'first_author_last_name'), + 'doId': get_value(pub, 'do_id'), + 'journal': get_value(pub, 'journal'), + 'name': get_value(pub, 'publication_name') or get_value(pub), + 'pubmedId': get_value(pub, 'pubmed_id'), + 'title': get_value(pub, 'title'), + 'year': get_value(pub, 'year') + } diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/rare_variant_pathway_association.py b/apps/iatlas/api/api/resolvers/resolver_helpers/rare_variant_pathway_association.py new file mode 100644 index 0000000000..be0daefcba --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/rare_variant_pathway_association.py @@ -0,0 +1,131 @@ +from sqlalchemy import and_ +from sqlalchemy.orm import aliased +from api import db +from api.db_models import Dataset, RareVariantPathwayAssociation, Feature +from .general_resolvers import build_join_condition, get_selected, get_value +from .data_set import build_data_set_graphql_response +from .feature import build_feature_graphql_response +from .paging_utils import get_pagination_queries + +rare_variant_pathway_association_request_fields = { + 'id', + 'dataSet', + 'feature', + 'pathway', + 'pValue', + 'min', + 'max', + 'mean', + 'q1', + 'q2', + 'q3', + 'nTotal', + 'nMutants' +} + + +def build_rvpa_graphql_response(rare_variant_pathway_association): + return { + 'id': get_value(rare_variant_pathway_association, 'id'), + 'dataSet': build_data_set_graphql_response()(rare_variant_pathway_association), + 'feature': build_feature_graphql_response()(rare_variant_pathway_association), + 'pathway': get_value(rare_variant_pathway_association, 'pathway'), + 'pValue': get_value(rare_variant_pathway_association, 'p_value'), + 'min': get_value(rare_variant_pathway_association, 'min'), + 'max': get_value(rare_variant_pathway_association, 'max'), + 'mean': get_value(rare_variant_pathway_association, 'mean'), + 'q1': get_value(rare_variant_pathway_association, 'q1'), + 'q2': get_value(rare_variant_pathway_association, 'q2'), + 'q3': get_value(rare_variant_pathway_association, 'q3'), + 'nMutants': get_value(rare_variant_pathway_association, 'n_mutants'), + 'nTotal': get_value(rare_variant_pathway_association, 'n_total'), + + } + + +def build_rare_variant_pathway_association_request( + requested, data_set_requested, feature_requested, distinct=False, paging=None, data_set=None, feature=None, pathway=None, max_p_value=None, min_p_value=None): + """ + Builds a SQL request. + + All positional arguments are required. Positional arguments are: + 1st position - a set of the requested fields at the root of the graphql request + 2nd position - a set of the requested fields in the 'dataSet' node of the graphql request. If 'dataSet' is not requested, this will be an empty set. + 3rd position - a set of the requested fields in the 'feature' node of the graphql request. If 'feature' is not requested, this will be an empty set. + + All keyword arguments are optional. Keyword arguments are: + `distinct` - a boolean, indicates whether duplicate records should be filtered out + `paging` - a dict containing pagination metadata + `data_set` - a list of strings, data set names + `pathway` - a list of strings, pathway names + `feature` - a list of strings, feature names + `max_p_value` - a float, a maximum P value + `min_p_value` - a float, a minimum P value + """ + sess = db.session + + rare_variant_pathway_association_1 = aliased( + RareVariantPathwayAssociation, name='rvpa') + feature_1 = aliased(Feature, name='f') + data_set_1 = aliased(Dataset, name='ds') + + core_field_mapping = { + 'id': rare_variant_pathway_association_1.id.label('id'), + 'pathway': rare_variant_pathway_association_1.pathway.label('pathway'), + 'pValue': rare_variant_pathway_association_1.p_value.label('p_value'), + 'min': rare_variant_pathway_association_1.min.label('min'), + 'max': rare_variant_pathway_association_1.max.label('max'), + 'mean': rare_variant_pathway_association_1.mean.label('mean'), + 'q1': rare_variant_pathway_association_1.q1.label('q1'), + 'q2': rare_variant_pathway_association_1.q2.label('q2'), + 'q3': rare_variant_pathway_association_1.q3.label('q3'), + 'nTotal': rare_variant_pathway_association_1.n_total.label('n_total'), + 'nMutants': rare_variant_pathway_association_1.n_mutants.label('n_mutants')} + data_set_core_field_mapping = { + 'display': data_set_1.display.label('data_set_display'), + 'name': data_set_1.name.label('data_set_name'), + 'type': data_set_1.dataset_type.label('data_set_type') + } + feature_core_field_mapping = { + 'display': feature_1.display.label('feature_display'), + 'name': feature_1.name.label('feature_name'), + 'order': feature_1.order.label('feature_order'), + 'unit': feature_1.unit.label('feature_unit'), + 'germlineModule': feature_1.germline_module.label('feature_germline_module'), + 'germlineCategory': feature_1.germline_category.label('feature_germline_category') + } + + core = get_selected(requested, core_field_mapping) + core |= get_selected(data_set_requested, data_set_core_field_mapping) + core |= get_selected(feature_requested, feature_core_field_mapping) + + query = sess.query(*core) + query = query.select_from(rare_variant_pathway_association_1) + + if pathway: + query = query.filter( + rare_variant_pathway_association_1.pathway.in_(pathway)) + + if max_p_value or max_p_value == 0: + query = query.filter( + rare_variant_pathway_association_1.p_value <= max_p_value) + + if min_p_value or min_p_value == 0: + query = query.filter( + rare_variant_pathway_association_1.p_value >= min_p_value) + + if 'dataSet' in requested or data_set: + is_outer = not bool(data_set) + data_set_join_condition = build_join_condition( + data_set_1.id, rare_variant_pathway_association_1.dataset_id, filter_column=data_set_1.name, filter_list=data_set) + query = query.join(data_set_1, and_( + *data_set_join_condition), isouter=is_outer) + + if 'feature' in requested or feature: + is_outer = not bool(feature) + data_set_join_condition = build_join_condition( + feature_1.id, rare_variant_pathway_association_1.feature_id, filter_column=feature_1.name, filter_list=feature) + query = query.join(feature_1, and_( + *data_set_join_condition), isouter=is_outer) + + return get_pagination_queries(query, paging, distinct, cursor_field=rare_variant_pathway_association_1.id) diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/sample.py b/apps/iatlas/api/api/resolvers/resolver_helpers/sample.py new file mode 100644 index 0000000000..ae927e0c50 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/sample.py @@ -0,0 +1,208 @@ +from sqlalchemy import and_ +from sqlalchemy.orm import aliased +from api import db +from api.db_models import ( + Dataset, DatasetToSample, Feature, FeatureToSample, Patient, Sample +) +from .general_resolvers import build_join_condition, get_selected, get_value +from .paging_utils import get_pagination_queries + + +simple_sample_request_fields = {'name'} + +cohort_sample_request_fields = {'name', 'tag'} + +sample_request_fields = simple_sample_request_fields.union({'patient'}) + +feature_related_sample_request_fields = simple_sample_request_fields.union({ + 'value'}) + +gene_related_sample_request_fields = simple_sample_request_fields.union({ + 'rnaSeqExpr', 'nanostringExpr'}) + +mutation_related_sample_request_fields = sample_request_fields.union({ + 'status'}) + +sample_by_mutation_status_request_fields = {'status', 'samples'} + + +def build_sample_graphql_response(prefix='sample_'): + from .patient import build_patient_graphql_response + from .tag import build_tag_graphql_response, has_tag_fields + + def f(sample): + if not sample: + return None + else: + dict = { + 'id': get_value(sample, prefix + 'id'), + 'name': get_value(sample, prefix + 'name'), + 'status': get_value(sample, prefix + 'mutation_status'), + 'rnaSeqExpr': get_value(sample, prefix + 'gene_rna_seq_expr'), + 'nanostringExpr': get_value(sample, prefix + 'gene_nanostring_expr'), + 'value': get_value(sample, prefix + 'feature_value'), + 'patient': build_patient_graphql_response()(sample), + 'tag': build_tag_graphql_response()(sample) if has_tag_fields(sample) else None + } + return(dict) + return(f) + +def build_gene_expression_graphql_response(prefix='sample_'): + + def f(sample): + if not sample: + return None + else: + result_dict = { + 'id': get_value(sample, prefix + 'id'), + 'name': get_value(sample, prefix + 'name') + } + result_dict['rnaSeqExpr'] = get_value(sample, prefix + 'gene_rna_seq_expr') + result_dict['nanostringExpr'] = get_value(sample, prefix + 'gene_nanostring_expr') + return(result_dict) + return(f) + + +def build_sample_mutation_join_condition(sample_to_mutation_model, sample_model, mutation_status, mutation_id=None, status=None): + join_condition = build_join_condition(sample_to_mutation_model.sample_id, sample_model.id, + filter_column=sample_to_mutation_model.mutation_id, filter_list=mutation_id) + if mutation_status: + join_condition.append( + sample_to_mutation_model.status == mutation_status) + return join_condition + + +def build_sample_request( + requested, patient_requested, data_set=None, ethnicity=None, feature=None, feature_class=None, gender=None, max_age_at_diagnosis=None, max_height=None, max_weight=None, min_age_at_diagnosis=None, min_height=None, min_weight=None, patient=None, race=None, sample=None, distinct=False, paging=None): + ''' + Builds a SQL query. + + All positional arguments are required. Positional arguments are: + 1st position - a set of the requested fields at the root of the graphql request or in the 'samples' node if by mutation status or by tag. + 2nd position - a set of the requested fields in the 'patient' node of the graphql request. If 'patient' is not requested, this will be an empty set. + + All keyword arguments are optional. Keyword arguments are: + `data_set` - a list of strings, data set names + `ethnicity` - a list of strings, ethnicity enum + `feature` - a list of strings, feature names + `feature_class` - a list of strings, feature class names + `gender` - a list of strings, gender enum + `max_age_at_diagnosis` - an integer, a maximum age of a patient at the time of diagnosis + `max_height` - an integer, a maximum height of a patient + `max_weight` - an integer, a maximum weight of a patient + `min_age_at_diagnosis` - an integer, a minimum age of a patient at the time of diagnosis + `min_height` - an integer, a minimum height of a patient + `min_weight` - an integer, a minimum weight of a patient + `patient` - a list of strings, patient barcodes + `race` - a list of strings, race enum + `sample` - a list of strings, sample names + `distinct` - a boolean, indicates whether duplicate records should be filtered out + `paging` - a dict containing pagination metadata + ''' + sess = db.session + + has_patient_filters = bool( + patient or max_age_at_diagnosis or min_age_at_diagnosis or ethnicity or gender or max_height or min_height or race or max_weight or min_weight) + + data_set_to_sample_1 = aliased(DatasetToSample, name='ds') + patient_1 = aliased(Patient, name='p') + sample_1 = aliased(Sample, name='s') + + core_field_mapping = { + 'name': sample_1.name.label('sample_name') + } + patient_core_field_mapping = { + 'ageAtDiagnosis': patient_1.age_at_diagnosis.label('patient_age_at_diagnosis'), + 'barcode': patient_1.name.label('patient_barcode'), + 'ethnicity': patient_1.ethnicity.label('patient_ethnicity'), + 'gender': patient_1.gender.label('patient_gender'), + 'height': patient_1.height.label('patient_height'), + 'race': patient_1.race.label('patient_race'), + 'weight': patient_1.weight.label('patient_weight') + } + + core = get_selected(requested, core_field_mapping) + core.add(sample_1.id.label('sample_id')) + patient_core = get_selected(patient_requested, patient_core_field_mapping) + + query = sess.query(*[*core, *patient_core]) + query = query.select_from(sample_1) + + if sample: + query = query.filter(sample_1.name.in_(sample)) + + if has_patient_filters or 'patient' in requested: + is_outer = not has_patient_filters + + patient_join_condition = build_join_condition( + sample_1.patient_id, patient_1.id, patient_1.name, patient) + + if bool(max_age_at_diagnosis): + patient_join_condition.append( + patient_1.age_at_diagnosis <= max_age_at_diagnosis) + + if bool(min_age_at_diagnosis): + patient_join_condition.append( + patient_1.age_at_diagnosis >= min_age_at_diagnosis) + + if bool(ethnicity): + patient_join_condition.append(patient_1.ethnicity.in_(ethnicity)) + + if bool(gender): + patient_join_condition.append(patient_1.gender.in_(gender)) + + if bool(max_height): + patient_join_condition.append(patient_1.height <= max_height) + + if bool(min_height): + patient_join_condition.append(patient_1.height >= min_height) + + if bool(race): + patient_join_condition.append(patient_1.race.in_(race)) + + if bool(max_weight): + patient_join_condition.append(patient_1.weight <= max_weight) + + if bool(min_weight): + patient_join_condition.append(patient_1.weight >= min_weight) + + query = query.join(patient_1, and_( + *patient_join_condition), isouter=is_outer) + + if data_set: + data_set_1 = aliased(Dataset, name='d') + + data_set_sub_query = sess.query(data_set_1.id).filter( + data_set_1.name.in_(data_set)) if data_set else data_set + + data_set_to_sample_join_condition = build_join_condition( + data_set_to_sample_1.sample_id, sample_1.id, data_set_to_sample_1.dataset_id, data_set_sub_query) + query = query.join( + data_set_to_sample_1, and_(*data_set_to_sample_join_condition)) + + if feature or feature_class: + feature_1 = aliased(Feature, name='f') + feature_class_1 = aliased(FeatureClass, name='fc') + feature_to_sample_1 = aliased(FeatureToSample, name='fs') + + query = query.join(feature_to_sample_1, + feature_to_sample_1.sample_id == sample_1.id) + + feature_join_condition = build_join_condition( + feature_1.id, feature_to_sample_1.feature_id, feature_1.name, feature) + query = query.join(feature_1, and_(*feature_join_condition)) + + if feature_class: + feature_class_join_condition = build_join_condition( + feature_class_1.id, feature_1.class_id, feature_class_1.name, feature_class) + query = query.join( + feature_class_1, and_(*feature_class_join_condition)) + + order = [] + append_to_order = order.append + if 'name' in requested: + append_to_order(sample_1.name) + + query = query.order_by(*order) if order else query + + return get_pagination_queries(query, paging, distinct, cursor_field=sample_1.id) diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/slide.py b/apps/iatlas/api/api/resolvers/resolver_helpers/slide.py new file mode 100644 index 0000000000..3651fe46c1 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/slide.py @@ -0,0 +1,124 @@ +from sqlalchemy import and_ +from sqlalchemy.orm import aliased +from api import db +from api.db_models import Patient, Sample, Slide +from .general_resolvers import build_join_condition, get_selected, get_value +from .paging_utils import get_pagination_queries + + +simple_slide_request_fields = {'description', 'name'} + +slide_request_fields = simple_slide_request_fields.union({'patient'}) + + +def build_slide_graphql_response(prefix='slide_'): + from .patient import build_patient_graphql_response + + def f(slide): + if not slide: + return None + else: + dict = { + 'id': get_value(slide, prefix + 'id'), + 'description': get_value(slide, prefix + 'description'), + 'name': get_value(slide, prefix + 'name'), + 'patient': build_patient_graphql_response()(slide) + } + return(dict) + return(f) + + +def build_slide_request(requested, patient_requested, max_age_at_diagnosis=None, min_age_at_diagnosis=None, barcode=None, ethnicity=None, gender=None, max_height=None, min_height=None, + name=None, race=None, max_weight=None, min_weight=None, sample=None, distinct=False, paging=None): + """ + Builds a SQL query. + """ + sess = db.session + + has_patient_filters = bool( + barcode or max_age_at_diagnosis or min_age_at_diagnosis or ethnicity or gender or max_height or min_height or race or max_weight or min_weight) + + patient_1 = aliased(Patient, name='p') + sample_1 = aliased(Sample, name='s') + slide_1 = aliased(Slide, name='sd') + + core_field_mapping = { + 'description': slide_1.description.label('slide_description'), + 'name': slide_1.name.label('slide_name') + } + patient_core_field_mapping = { + 'ageAtDiagnosis': patient_1.age_at_diagnosis.label('patient_age_at_diagnosis'), + 'barcode': patient_1.name.label('patient_barcode'), + 'ethnicity': patient_1.ethnicity.label('patient_ethnicity'), + 'gender': patient_1.gender.label('patient_gender'), + 'height': patient_1.height.label('patient_height'), + 'race': patient_1.race.label('patient_race'), + 'weight': patient_1.weight.label('patient_weight') + } + + # Only select fields that were requested. + core = get_selected(requested, core_field_mapping) + core |= {slide_1.id.label('slide_id')} + + patient_core = get_selected(patient_requested, patient_core_field_mapping) + + query = sess.query(*[*core, *patient_core]) + query = query.select_from(slide_1) + + if name: + query = query.filter(slide_1.name.in_(name)) + + if has_patient_filters or 'patient' in requested: + is_outer = not has_patient_filters + + patient_join_condition = build_join_condition( + slide_1.patient_id, patient_1.id, patient_1.name, barcode) + + if bool(max_age_at_diagnosis): + patient_join_condition.append( + patient_1.age_at_diagnosis <= max_age_at_diagnosis) + + if bool(min_age_at_diagnosis): + patient_join_condition.append( + patient_1.age_at_diagnosis >= min_age_at_diagnosis) + + if bool(ethnicity): + patient_join_condition.append(patient_1.ethnicity.in_(ethnicity)) + + if bool(gender): + patient_join_condition.append(patient_1.gender.in_(gender)) + + if bool(max_height): + patient_join_condition.append(patient_1.height <= max_height) + + if bool(min_height): + patient_join_condition.append(patient_1.height >= min_height) + + if bool(race): + patient_join_condition.append(patient_1.race.in_(race)) + + if bool(max_weight): + patient_join_condition.append(patient_1.weight <= max_weight) + + if bool(min_weight): + patient_join_condition.append(patient_1.weight >= min_weight) + + query = query.join(patient_1, and_( + *patient_join_condition), isouter=is_outer) + + if sample: + sample_join_condition = build_join_condition( + slide_1.patient_id, sample_1.patient_id, filter_column=sample_1.name, filter_list=sample) + query = query.join(sample_1, and_( + *sample_join_condition), isouter=False) + + order = [] + append_to_order = order.append + if 'name' in requested: + append_to_order(slide_1.name) + if 'description' in requested: + append_to_order(slide_1.description) + + query = query.order_by(*order) if order else query + + return get_pagination_queries(query, paging, distinct, cursor_field=slide_1.id) diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/snp.py b/apps/iatlas/api/api/resolvers/resolver_helpers/snp.py new file mode 100644 index 0000000000..13ac121e59 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/snp.py @@ -0,0 +1,73 @@ +from sqlalchemy.orm import aliased +from api import db +from api.db_models import Snp +from .general_resolvers import get_selected, get_value +from .paging_utils import get_pagination_queries + +snp_request_fields = {'id', 'name', 'rsid', 'chr', 'bp'} + + +def build_snp_graphql_response(snp): + return { + 'id': get_value(snp, 'id'), + 'name': get_value(snp, 'snp_name') or get_value(snp), + 'rsid': get_value(snp, 'snp_rsid') or get_value(snp, 'rsid'), + 'chr': get_value(snp, 'snp_chr') or get_value(snp, 'chr'), + 'bp': get_value(snp, 'snp_bp') or get_value(snp, 'bp') + } + + +def build_snp_request( + requested, name=None, rsid=None, chr=None, max_bp=None, min_bp=None, distinct=False, paging=None): + """ + Builds a SQL request. + + All positional arguments are required. Positional arguments are: + 1st position - a set of the requested fields at the root of the graphql request + + All keyword arguments are optional. Keyword arguments are: + `name` - a list of strings + `rsid` - a list of strings + `chr` - a list of strings + `max_bp` - an integer + `min_bp` - an integer + `distinct` - a boolean, indicates whether duplicate records should be filtered out + `paging` - a dict containing pagination metadata + """ + sess = db.session + + snp_1 = aliased(Snp, name='snp') + + core_field_mapping = { + 'id': snp_1.id.label('id'), + 'name': snp_1.name.label('name'), + 'rsid': snp_1.rsid.label('rsid'), + 'chr': snp_1.chr.label('chr'), + 'bp': snp_1.bp.label('bp') + } + + core = get_selected(requested, core_field_mapping) + + if distinct == False: + # Add the id as a cursor if not selecting distinct + core |= {snp_1.id.label('id')} + + query = sess.query(*core) + query = query.select_from(snp_1) + + if name: + query = query.filter(snp_1.name.in_(name)) + + if rsid: + query = query.filter(snp_1.rsid.in_(rsid)) + + if chr: + query = query.filter(snp_1.chr.in_(chr)) + + if max_bp or max_bp == 0: + query = query.filter(snp_1.bp <= max_bp) + + if min_bp or min_bp == 0: + query = query.filter(snp_1.bp >= min_bp) + + return get_pagination_queries(query, paging, distinct, cursor_field=snp_1.id) diff --git a/apps/iatlas/api/api/resolvers/resolver_helpers/tag.py b/apps/iatlas/api/api/resolvers/resolver_helpers/tag.py new file mode 100644 index 0000000000..a8afeb8693 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/resolver_helpers/tag.py @@ -0,0 +1,309 @@ +from sqlalchemy import and_ +from sqlalchemy.orm import aliased +from api import db +from api.db_models import Dataset, DatasetToTag, Publication, Sample, SampleToTag, Tag, TagToPublication, TagToTag, Cohort, CohortToTag, CohortToSample +from .general_resolvers import build_join_condition, get_selected, get_value +from .paging_utils import get_pagination_queries + + +simple_tag_request_fields = { + 'characteristics', + 'color', + 'longDisplay', + 'name', + 'order', + 'shortDisplay', + 'tag', + 'type' +} + +tag_request_fields = simple_tag_request_fields.union({ + 'publications', + 'related', + 'sampleCount', + 'samples' +}) + + +def has_tag_fields(item, prefix='tag_'): + if not item: + return False + return(get_value(item, prefix + 'id') or get_value(item, prefix + 'name') or get_value( + item, prefix + 'characteristics') or get_value(item, prefix + 'short_display') or get_value(item, prefix + 'long_display') or get_value(item, prefix + 'type') or get_value(item, prefix + 'order')) + + +def build_tag_graphql_response(requested=[], sample_requested=[], publications_requested=[], related_requested=[], cohort=None, sample=None, prefix='tag_'): + from .publication import build_publication_graphql_response + from .sample import build_sample_graphql_response + + def f(tag): + if not tag: + return None + + tag_id = get_value(tag, prefix + 'id') + + sample_dict = get_samples( + tag_id=tag_id, requested=requested, sample_requested=sample_requested, cohort=cohort, sample=sample) + + publication_dict = get_publications( + tag_id=tag_id, requested=requested, publications_requested=publications_requested) + + related_dict = get_related( + tag_id=tag_id, requested=requested, related_requested=related_requested) + + result = { + 'id': tag_id, + 'name': get_value(tag, prefix + 'name') or get_value(tag, 'name'), + 'characteristics': get_value(tag, prefix + 'characteristics'), + 'color': get_value(tag, prefix + 'color'), + 'longDisplay': get_value(tag, prefix + 'long_display'), + 'shortDisplay': get_value(tag, prefix + 'short_display'), + 'type': get_value(tag, prefix + 'type'), + 'order': get_value(tag, prefix + 'order'), + 'sampleCount': len(sample_dict) if sample_dict and 'sampleCount' in requested else None, + 'publications': map(build_publication_graphql_response, publication_dict) if publication_dict else None, + 'related': map(build_tag_graphql_response(requested=related_requested), related_dict) if related_dict else None, + 'samples': map(build_sample_graphql_response(), sample_dict) if sample_dict and 'samples' in requested else None + } + return(result) + return(f) + + +def get_tag_column_labels(requested, tag, prefix='tag_', add_id=False): + mapping = { + 'characteristics': tag.description.label(prefix + 'characteristics'), + 'color': tag.color.label(prefix + 'color'), + 'longDisplay': tag.long_display.label(prefix + 'long_display'), + 'name': tag.name.label(prefix + 'name'), + 'order': tag.order.label(prefix + 'order'), + 'shortDisplay': tag.short_display.label(prefix + 'short_display'), + 'type': tag.tag_type.label(prefix + 'type'), + } + labels = get_selected(requested, mapping) + + if add_id: + labels |= {tag.id.label(prefix + 'id')} + + return(labels) + + +def build_tag_request(requested, distinct=False, paging=None, cohort=None, data_set=None, related=None, sample=None, tag=None, type=None): + + sess = db.session + + tag_1 = aliased(Tag, name='t') + sample_1 = aliased(Sample, name='s') + sample_to_tag_1 = aliased(SampleToTag, name='stt') + dataset_to_tag_1 = aliased(DatasetToTag, name='dtt') + dataset_1 = aliased(Dataset, name='d') + cohort_1 = aliased(Cohort, name='c') + cohort_to_tag_1 = aliased(CohortToTag, name='ctt') + tag_to_tag_1 = aliased(TagToTag, name='ttt') + + tag_core = get_tag_column_labels(requested, tag_1, add_id=True) + query = sess.query(*tag_core) + query = query.select_from(tag_1) + + if tag: + query = query.filter(tag_1.name.in_(tag)) + + if type: + query = query.filter(tag_1.tag_type.in_(type)) + + if data_set: + dataset_subquery = sess.query(dataset_to_tag_1.tag_id) + + dataset_join_condition = build_join_condition( + dataset_to_tag_1.dataset_id, dataset_1.id, filter_column=dataset_1.name, filter_list=data_set) + dataset_subquery = dataset_subquery.join(dataset_1, and_( + *dataset_join_condition), isouter=False) + + query = query.filter(tag_1.id.in_(dataset_subquery)) + + if cohort: + cohort_subquery = sess.query(cohort_to_tag_1.tag_id) + + cohort_join_condition = build_join_condition( + cohort_to_tag_1.cohort_id, cohort_1.id, filter_column=cohort_1.name, filter_list=cohort) + cohort_subquery = cohort_subquery.join(cohort_1, and_( + *cohort_join_condition), isouter=False) + + query = query.filter(tag_1.id.in_(cohort_subquery)) + + if related: + related_subquery = sess.query(tag_to_tag_1.tag_id) + + related_join_condition = build_join_condition( + tag_to_tag_1.related_tag_id, tag_1.id, filter_column=tag_1.name, filter_list=related) + related_subquery = related_subquery.join(tag_1, and_( + *related_join_condition), isouter=False) + + query = query.filter(tag_1.id.in_(related_subquery)) + + if sample: + sample_subquery = sess.query(sample_to_tag_1.tag_id) + + sample_join_condition = build_join_condition( + sample_to_tag_1.sample_id, sample_1.id, filter_column=sample_1.name, filter_list=sample) + sample_subquery = sample_subquery.join(sample_1, and_( + *sample_join_condition), isouter=False) + + query = query.filter(tag_1.id.in_(sample_subquery)) + + order = [] + append_to_order = order.append + if 'name' in requested: + append_to_order(tag_1.name) + if 'shortDisplay' in requested: + append_to_order(tag_1.short_display) + if 'longDisplay' in requested: + append_to_order(tag_1.long_display) + if 'color' in requested: + append_to_order(tag_1.color) + if 'characteristics' in requested: + append_to_order(tag_1.description) + + query = query.order_by(*order) if order else query + + return get_pagination_queries(query, paging, distinct, cursor_field=tag_1.id) + + +def get_publications(tag_id, requested, publications_requested): + if 'publications' in requested: + sess = db.session + + pub_1 = aliased(Publication, name='p') + tag_1 = aliased(Tag, name='t') + tag_to_pub_1 = aliased(TagToPublication, name='tp') + + core_field_mapping = { + 'doId': pub_1.do_id.label('do_id'), + 'firstAuthorLastName': pub_1.first_author_last_name.label('first_author_last_name'), + 'journal': pub_1.journal.label('journal'), + 'name': pub_1.title.label('name'), + 'pubmedId': pub_1.pubmed_id.label('pubmed_id'), + 'title': pub_1.title.label('title'), + 'year': pub_1.year.label('year') + } + + core = get_selected(publications_requested, core_field_mapping) + # Always select the publication id and the tag id. + core |= { + pub_1.id.label('id'), + tag_to_pub_1.tag_id.label('tag_id') + } + + pub_query = sess.query(*core) + pub_query = pub_query.select_from(pub_1) + + tag_sub_query = sess.query(tag_1.id).filter(tag_1.id.in_([tag_id])) + + tag_tag_join_condition = build_join_condition( + tag_to_pub_1.publication_id, pub_1.id, tag_to_pub_1.tag_id, tag_sub_query) + pub_query = pub_query.join( + tag_to_pub_1, and_(*tag_tag_join_condition)) + + order = [] + append_to_order = order.append + if 'name' in publications_requested: + append_to_order(pub_1.title) + if 'pubmedId' in publications_requested: + append_to_order(pub_1.pubmed_id) + if 'doId' in publications_requested: + append_to_order(pub_1.do_id) + if 'title' in publications_requested: + append_to_order(pub_1.title) + if 'firstAuthorLastName' in publications_requested: + append_to_order(pub_1.first_author_last_name) + if 'year' in publications_requested: + append_to_order(pub_1.year) + if 'journal' in publications_requested: + append_to_order(pub_1.journal) + pub_query = pub_query.order_by(*order) if order else pub_query + + return pub_query.distinct().all() + + return [] + + +def get_related(tag_id, requested, related_requested): + if 'related' in requested: + sess = db.session + + tag_1 = aliased(Tag, name='t') + tag_to_tag_1 = aliased(TagToTag, name='ttt') + related_tag_1 = aliased(Tag, name='rt') + + related_core_field_mapping = { + 'characteristics': related_tag_1.description.label('tag_characteristics'), + 'color': related_tag_1.color.label('tag_color'), + 'longDisplay': related_tag_1.long_display.label('tag_long_display'), + 'name': related_tag_1.name.label('tag_name'), + 'order': related_tag_1.order.label('tag_order'), + 'shortDisplay': related_tag_1.short_display.label('tag_short_display'), + 'type': related_tag_1.tag_type.label('tag_type'), + } + + related_core = get_selected( + related_requested, related_core_field_mapping) + + related_query = sess.query(*related_core) + related_query = related_query.select_from(related_tag_1) + + tag_sub_query = sess.query(tag_to_tag_1.related_tag_id) + + tag_tag_join_condition = build_join_condition( + tag_1.id, tag_to_tag_1.tag_id, tag_1.id, [tag_id]) + + tag_sub_query = tag_sub_query.join( + tag_1, and_(*tag_tag_join_condition)) + + related_query = related_query.filter( + related_tag_1.id.in_(tag_sub_query)) + + return related_query.distinct().all() + + return [] + + +def get_samples(tag_id, requested, sample_requested, cohort=None, sample=None): + if 'samples' in requested or 'sampleCount' in requested: + sess = db.session + + sample_1 = aliased(Sample, name='s') + sample_to_tag_1 = aliased(SampleToTag, name='stt') + cohort_1 = aliased(Cohort, name='c') + cohort_to_sample_1 = aliased(CohortToSample, name='cts') + + sample_core_field_mapping = { + 'name': sample_1.name.label('sample_name')} + + sample_core = get_selected(sample_requested, sample_core_field_mapping) + sample_core |= {sample_1.id.label('sample_id')} + + sample_query = sess.query(*sample_core) + sample_query = sample_query.select_from(sample_1) + + tag_subquery = sess.query( + sample_to_tag_1.sample_id).filter(sample_to_tag_1.tag_id.in_([tag_id])) + + sample_query = sample_query.filter( + sample_1.id.in_(tag_subquery)) + + if sample: + sample_query = sample_query.filter(sample_1.name.in_(sample)) + + if cohort: + cohort_subquery = sess.query(cohort_to_sample_1.sample_id) + + cohort_join_condition = build_join_condition( + cohort_to_sample_1.cohort_id, cohort_1.id, filter_column=cohort_1.name, filter_list=cohort) + cohort_subquery = cohort_subquery.join(cohort_1, and_( + *cohort_join_condition), isouter=False) + + sample_query = sample_query.filter( + sample_1.id.in_(cohort_subquery)) + + return sample_query.distinct().all() + + return [] diff --git a/apps/iatlas/api/api/resolvers/samples_resolver.py b/apps/iatlas/api/api/resolvers/samples_resolver.py new file mode 100644 index 0000000000..1b8f14bfc8 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/samples_resolver.py @@ -0,0 +1,21 @@ +from .resolver_helpers import get_requested, build_sample_graphql_response, build_sample_request, simple_patient_request_fields, sample_request_fields, get_selection_set +from .resolver_helpers.paging_utils import paginate, Paging, paging_fields + + +def resolve_samples(_obj, info, maxAgeAtDiagnosis=None, minAgeAtDiagnosis=None, ethnicity=None, gender=None, maxHeight=None, minHeight=None, name=None, patient=None, race=None, maxWeight=None, minWeight=None, paging=None, distinct=False): + + selection_set = get_selection_set(info=info, child_node='items') + + requested = get_requested( + selection_set=selection_set, requested_field_mapping=sample_request_fields) + + patient_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_patient_request_fields, child_node='patient') + + paging = paging if paging else Paging.DEFAULT + + query, count_query = build_sample_request(requested, patient_requested, max_age_at_diagnosis=maxAgeAtDiagnosis, min_age_at_diagnosis=minAgeAtDiagnosis, + ethnicity=ethnicity, gender=gender, max_height=maxHeight, min_height=minHeight, patient=patient, race=race, sample=name, max_weight=maxWeight, min_weight=minWeight, paging=paging, distinct=distinct) + + pagination_requested = get_requested(info, paging_fields, 'paging') + return paginate(query, count_query, paging, distinct, build_sample_graphql_response(), pagination_requested) diff --git a/apps/iatlas/api/api/resolvers/slide_resolver.py b/apps/iatlas/api/api/resolvers/slide_resolver.py new file mode 100644 index 0000000000..0d6af70d1b --- /dev/null +++ b/apps/iatlas/api/api/resolvers/slide_resolver.py @@ -0,0 +1,22 @@ +from .resolver_helpers import build_slide_graphql_response, get_requested, simple_patient_request_fields, slide_request_fields, get_selection_set, build_slide_request +from .resolver_helpers.paging_utils import paginate, Paging, paging_fields + + +def resolve_slides(_obj, info, maxAgeAtDiagnosis=None, minAgeAtDiagnosis=None, barcode=None, ethnicity=None, gender=None, maxHeight=None, minHeight=None, name=None, race=None, maxWeight=None, minWeight=None, sample=None, paging=None, distinct=False): + + selection_set = get_selection_set(info=info, child_node='items') + + requested = get_requested( + selection_set=selection_set, requested_field_mapping=slide_request_fields) + + patient_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_patient_request_fields, child_node='patient') + + paging = paging if paging else Paging.DEFAULT + + query, count_query = build_slide_request( + requested, patient_requested, max_age_at_diagnosis=maxAgeAtDiagnosis, min_age_at_diagnosis=minAgeAtDiagnosis, barcode=barcode, + ethnicity=ethnicity, gender=gender, max_height=maxHeight, min_height=minHeight, name=name, race=race, max_weight=maxWeight, min_weight=minWeight, sample=sample, paging=paging, distinct=distinct) + + pagination_requested = get_requested(info, paging_fields, 'paging') + return paginate(query, count_query, paging, distinct, build_slide_graphql_response(), pagination_requested) diff --git a/apps/iatlas/api/api/resolvers/snp_resolver.py b/apps/iatlas/api/api/resolvers/snp_resolver.py new file mode 100644 index 0000000000..dd53f4d90f --- /dev/null +++ b/apps/iatlas/api/api/resolvers/snp_resolver.py @@ -0,0 +1,16 @@ +from .resolver_helpers import ( + get_requested, snp_request_fields, build_snp_graphql_response, build_snp_request) + +from .resolver_helpers.paging_utils import paginate, Paging, paging_fields + + +def resolve_snps(_obj, info, name=None, rsid=None, chr=None, maxBP=None, minBP=None, paging=None, distinct=False): + requested = get_requested(info, snp_request_fields, "items") + + paging = paging if paging else Paging.DEFAULT + + query, count_query = build_snp_request( + requested, name=name, rsid=rsid, chr=chr, max_bp=maxBP, min_bp=minBP, paging=paging, distinct=distinct) + + pagination_requested = get_requested(info, paging_fields, 'paging') + return paginate(query, count_query, paging, distinct, build_snp_graphql_response, pagination_requested) diff --git a/apps/iatlas/api/api/resolvers/tags_resolver.py b/apps/iatlas/api/api/resolvers/tags_resolver.py new file mode 100644 index 0000000000..326207bcc9 --- /dev/null +++ b/apps/iatlas/api/api/resolvers/tags_resolver.py @@ -0,0 +1,39 @@ +from .resolver_helpers import build_tag_graphql_response, build_tag_request, get_requested, simple_sample_request_fields, simple_publication_request_fields, simple_tag_request_fields, tag_request_fields, get_selection_set +from .resolver_helpers.paging_utils import paginate, paging_fields, create_paging + + +def resolve_tags(_obj, info, distinct=False, paging=None, cohort=None, dataSet=None, related=None, sample=None, tag=None, type=None): + + selection_set = get_selection_set(info=info, child_node='items') + + requested = get_requested( + selection_set=selection_set, requested_field_mapping=tag_request_fields) + + sample_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_sample_request_fields, child_node='samples') + + publications_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_publication_request_fields, child_node='publications') + + related_requested = get_requested( + selection_set=selection_set, requested_field_mapping=simple_tag_request_fields, child_node='related') + + max_items = 10 if 'samples' in requested else 100_000 + + paging = create_paging(paging, max_items) + + query, count_query = build_tag_request( + requested, distinct=distinct, paging=paging, cohort=cohort, data_set=dataSet, related=related, sample=sample, tag=tag, type=type) + + pagination_requested = get_requested(info, paging_fields, 'paging') + + res = paginate( + query, + count_query, + paging, + distinct, + build_tag_graphql_response(requested, sample_requested, publications_requested, related_requested, cohort=cohort, sample=sample), + pagination_requested + ) + + return(res) diff --git a/apps/iatlas/api/api/resolvers/test_resolver.py b/apps/iatlas/api/api/resolvers/test_resolver.py new file mode 100644 index 0000000000..d0e4975e6f --- /dev/null +++ b/apps/iatlas/api/api/resolvers/test_resolver.py @@ -0,0 +1,21 @@ +def resolve_test(_obj, info): + request = info.context + headers = request.headers + content_length = headers.get('Content-Length') + content_type = headers.get('Content-Type') + host = headers.get('Host') + referer = headers.get('Referer') + user_agent = headers.get('User-Agent') + return { + 'items': { + 'contentType': content_type, + 'userAgent': user_agent, + 'headers': { + 'contentLength': content_length, + 'contentType': content_type, + 'host': host, + 'userAgent': user_agent + } + }, + 'page': 1 + } diff --git a/apps/iatlas/api/api/routes.py b/apps/iatlas/api/api/routes.py new file mode 100644 index 0000000000..7534ef2406 --- /dev/null +++ b/apps/iatlas/api/api/routes.py @@ -0,0 +1,48 @@ +from ariadne import graphql_sync +from ariadne.constants import PLAYGROUND_HTML +from ariadne.contrib.tracing.apollotracing import ApolloTracingExtensionSync +from flask import current_app, jsonify, request +import os +from .main import bp +from .schema import schema + + +@bp.route('/graphiql', methods=['GET']) +def graphql_playgroud(): + # On GET request serve GraphQL Playground + # You don't need to provide Playground if you don't want to + # but keep on mind this will not prohibit clients from + # exploring your API using desktop GraphQL Playground app. + return PLAYGROUND_HTML, 200 + + +@bp.route('/graphiql', methods=['POST']) +@bp.route('/api', methods=['POST']) +def graphql_server(): + # GraphQL queries are always sent as POST + data = request.get_json() + + # By default, no extensions. + # If the FLASK_ENV environment variable is set to something + # other than 'production', enable Apollo Tracing. + extensions = None + if ('FLASK_ENV' in os.environ and os.environ['FLASK_ENV'] != 'production'): + extensions = [ApolloTracingExtensionSync] + + # Note: Passing the request to the context is optional. + # In Flask, the current request is always accessible as flask.request + success, result = graphql_sync( + schema, + data, + context_value=request, + debug=current_app.debug, + extensions=extensions + ) + + status_code = 200 if success else 400 + return jsonify(result), status_code + + +@bp.route('/healthcheck') +def healthcheck(): + return 'Running', 200 diff --git a/apps/iatlas/api/api/schema/__init__.py b/apps/iatlas/api/api/schema/__init__.py new file mode 100644 index 0000000000..8bd4d41094 --- /dev/null +++ b/apps/iatlas/api/api/schema/__init__.py @@ -0,0 +1,284 @@ +from ariadne import load_schema_from_path, make_executable_schema, ObjectType, ScalarType +import os +from api.resolvers import ( + resolve_cohorts, + resolve_colocalizations, + resolve_copy_number_results, + resolve_data_sets, + resolve_driver_results, + resolve_edges, + resolve_features, + resolve_gene_types, + resolve_genes, + resolve_germline_gwas_results, + resolve_heritability_results, + resolve_mutations, + resolve_mutation_types, + resolve_neoantigens, + resolve_nodes, + resolve_rare_variant_pathway_associations, + resolve_patients, + resolve_samples, + resolve_slides, + resolve_snps, + resolve_tags, + resolve_test, + resolve_heritability_results +) + + +schema_dirname, _filename = os.path.split(os.path.abspath(__file__)) + +# Import GraphQl schemas/ +root_query = load_schema_from_path(schema_dirname + '/root.query.graphql') +paging_types = load_schema_from_path( + schema_dirname + '/paging.graphql') +cohort_query = load_schema_from_path( + schema_dirname + '/cohort.query.graphql') +colocalization_query = load_schema_from_path( + schema_dirname + '/colocalization.query.graphql') +copy_number_result_query = load_schema_from_path( + schema_dirname + '/copyNumberResult.query.graphql') +data_set_query = load_schema_from_path( + schema_dirname + '/dataset.query.graphql') +driver_result_query = load_schema_from_path( + schema_dirname + '/driverResult.query.graphql') +edge_query = load_schema_from_path( + schema_dirname + '/edge.query.graphql') +feature_query = load_schema_from_path( + schema_dirname + '/feature.query.graphql') +gene_query = load_schema_from_path(schema_dirname + '/gene.query.graphql') +gene_type_query = load_schema_from_path( + schema_dirname + '/geneType.query.graphql') +germline_gwas_result_query = load_schema_from_path( + schema_dirname + '/germlineGwasResult.query.graphql') +heritability_result_query = load_schema_from_path( + schema_dirname + '/heritabilityResult.query.graphql') +mutation_query = load_schema_from_path( + schema_dirname + '/mutation.query.graphql') +neoantigen_query = load_schema_from_path( + schema_dirname + '/neoantigen.query.graphql') +node_query = load_schema_from_path( + schema_dirname + '/node.query.graphql') +patient_query = load_schema_from_path( + schema_dirname + '/patient.query.graphql') +publication_query = load_schema_from_path( + schema_dirname + '/publication.query.graphql') +rare_variant_pathway_association_query = load_schema_from_path( + schema_dirname + '/rareVariantPathwayAssociation.query.graphql') +sample_query = load_schema_from_path(schema_dirname + '/sample.query.graphql') +slide_query = load_schema_from_path(schema_dirname + '/slide.query.graphql') +snp_query = load_schema_from_path(schema_dirname + '/snp.query.graphql') +tag_query = load_schema_from_path(schema_dirname + '/tag.query.graphql') + +type_defs = [ + root_query, + paging_types, + cohort_query, + colocalization_query, + copy_number_result_query, + data_set_query, + driver_result_query, + edge_query, + feature_query, + gene_query, + gene_type_query, + germline_gwas_result_query, + heritability_result_query, + neoantigen_query, + mutation_query, + node_query, + rare_variant_pathway_association_query, + patient_query, + publication_query, + sample_query, + slide_query, + snp_query, + tag_query +] + +# Initialize custom scalars. +direction_enum_scalar = ScalarType('DirectionEnum') + + +@direction_enum_scalar.serializer +def serialize_direction_enum(value): + return value if value == 'Amp' or value == 'Del' else None + + +ethnicity_enum_scalar = ScalarType('EthnicityEnum') + + +@ethnicity_enum_scalar.serializer +def serialize_ethnicity_enum(value): + return value if value == 'not hispanic or latino' or value == 'hispanic or latino' else None + + +gender_enum_scalar = ScalarType('GenderEnum') + + +@gender_enum_scalar.serializer +def serialize_gender_enum(value): + return value if value == 'female' or value == 'male' else None + + +race_enum_scalar = ScalarType('RaceEnum') + + +@race_enum_scalar.serializer +def serialize_race_enum(value): + race_set = { + 'american indian or alaska native', + 'native hawaiian or other pacific islander', + 'black or african american', + 'asian', + 'white' + } + return value if value in race_set else None + + +status_enum_scalar = ScalarType('StatusEnum') + + +@status_enum_scalar.serializer +def serialize_status_enum(value): + return value if value == 'Mut' or value == 'Wt' else None + + +qtl_type_enum = ScalarType('QTLTypeEnum') + + +@qtl_type_enum.serializer +def serialize_qtl_type_enum(value): + return value if value == 'sQTL' or value == 'eQTL' else None + + +ecaviar_pp_enum = ScalarType('ECaviarPPEnum') + + +@ecaviar_pp_enum.serializer +def serialize_ecaviar_pp_enum(value): + return value if value == 'C1' or value == 'C2' else None + + +coloc_plot_type_enum = ScalarType('ColocPlotTypeEnum') + + +@coloc_plot_type_enum.serializer +def serialize_coloc_plot_type_enum(value): + return value if value == '3 Level Plot' or value == 'Expanded Region' else None + + +# Initialize schema objects (general). +root = ObjectType('Query') +cohort = ObjectType('Cohort') +colocalization = ObjectType('Colocalization') +copy_number_result = ObjectType('CopyNumberResult') +data_set = ObjectType('DataSet') +driver_result = ObjectType('DriverResult') +edge_result = ObjectType('EdgeResult') +feature = ObjectType('Feature') +gene = ObjectType('Gene') +gene_type = ObjectType('GeneType') +germline_gwas_result_node = ObjectType('GermlineGwasResultNode') +germline_gwas_result = ObjectType('GermlineGwasResult') +heritability_result_node = ObjectType('HeritabilityResultNode') +heritability_result = ObjectType('HeritabilityResult') +mutation = ObjectType('Mutation') +mutation_type = ObjectType('MutationType') +neoantigen = ObjectType('Neoantigen') +node = ObjectType('Node') +node_result = ObjectType('NodeResult') +patient = ObjectType('Patient') +publication = ObjectType('Publication') +rare_variant_pathway_association = ObjectType( + 'RareVariantPathwayAssociationNode') +related_by_data_set = ObjectType('RelatedByDataSet') +sample = ObjectType('Sample') +sample_by_mutation_status = ObjectType('SampleByMutationStatus') +slide = ObjectType('Slide') +snp = ObjectType('Snp') +tag = ObjectType('Tag') + +# Initialize schema objects (simple). +simple_data_set = ObjectType('SimpleDataSet') +simple_feature = ObjectType('SimpleFeature') +simple_gene = ObjectType('SimpleGene') +simple_gene_type = ObjectType('SimpleGeneType') +simple_node = ObjectType('SimpleNode') +simple_publication = ObjectType('SimplePublication') +simple_tag = ObjectType('SimpleTag') + +''' +Associate resolvers with fields. +Fields should be names of objects in schema/root.query.graphql. +Values should be names of functions in resolvers +''' +root.set_field('cohorts', resolve_cohorts) +root.set_field('colocalizations', resolve_colocalizations) +root.set_field('copyNumberResults', resolve_copy_number_results) +root.set_field('dataSets', resolve_data_sets) +root.set_field('driverResults', resolve_driver_results) +root.set_field('edges', resolve_edges) +root.set_field('features', resolve_features) +root.set_field('geneTypes', resolve_gene_types) +root.set_field('genes', resolve_genes) +root.set_field('germlineGwasResults', resolve_germline_gwas_results) +root.set_field('heritabilityResults', resolve_heritability_results) +root.set_field('mutations', resolve_mutations) +root.set_field('mutationTypes', resolve_mutation_types) +root.set_field('neoantigens', resolve_neoantigens) +root.set_field('nodes', resolve_nodes) +root.set_field('patients', resolve_patients) +root.set_field('rareVariantPathwayAssociations', + resolve_rare_variant_pathway_associations) +root.set_field('samples', resolve_samples) +root.set_field('slides', resolve_slides) +root.set_field('snps', resolve_snps) +root.set_field('tags', resolve_tags) +root.set_field('test', resolve_test) + + +schema = make_executable_schema( + type_defs, + [ + root, + cohort, + colocalization, + copy_number_result, + data_set, + direction_enum_scalar, + driver_result, + edge_result, + ethnicity_enum_scalar, + feature, gender_enum_scalar, + gene, + gene_type, + germline_gwas_result, + germline_gwas_result_node, + heritability_result_node, + heritability_result, + mutation, + mutation_type, + neoantigen, + node, + node_result, + patient, + publication, + race_enum_scalar, + rare_variant_pathway_association, + related_by_data_set, + sample, + sample_by_mutation_status, + simple_data_set, + simple_feature, + simple_gene, + simple_gene_type, + simple_node, + simple_publication, + simple_tag, + slide, + snp, + tag + ] +) diff --git a/apps/iatlas/api/api/schema/cohort.query.graphql b/apps/iatlas/api/api/schema/cohort.query.graphql new file mode 100644 index 0000000000..3725c3719c --- /dev/null +++ b/apps/iatlas/api/api/schema/cohort.query.graphql @@ -0,0 +1,31 @@ + +""" +The "CohortNode" type +""" +type CohortNode implements BaseNode { + "A unique id for the data set generated by the database. Please note that this value may change as the database gets updated and should not be relied on." + id: ID + "The name of the cohort." + name: String! + "The data set associated with the cohort." + dataSet: SimpleDataSet! + "The tag associated with the cohort." + tag: SimpleTag + "The samples associated with the cohort." + samples: [CohortSample!]! + "The features associated with the cohort." + features: [SimpleFeature!]! + "The genes associated with the cohort." + genes: [SimpleGene!]! + "The mutations associated with the cohort." + mutations: [SimpleMutation!]! +} + +type Cohort implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned CohortNodes" + items: [CohortNode] +} diff --git a/apps/iatlas/api/api/schema/colocalization.query.graphql b/apps/iatlas/api/api/schema/colocalization.query.graphql new file mode 100644 index 0000000000..ed716b864b --- /dev/null +++ b/apps/iatlas/api/api/schema/colocalization.query.graphql @@ -0,0 +1,53 @@ +""" +'sQTL' or 'eQTL' +""" +scalar QTLTypeEnum + +""" +'C1' or 'C2' +""" +scalar ECaviarPPEnum + +""" +'3 Level Plot' or 'Expanded Region' +""" +scalar ColocPlotTypeEnum + +""" +The "ColocalizationNode" type +""" +type ColocalizationNode implements BaseNode { + "A unique id for the data set generated by the database. Please note that this value may change as the database gets updated and should not be relied on." + id: ID + "The QTL type of the colocalization to filter by. (either 'sQTL' or 'eQTL')." + qtlType: QTLTypeEnum! + "The eCaviar colocalization posterior probability locus model. (either 'C1' or 'C2')." + eCaviarPP: ECaviarPPEnum + "Type of colocalization plot. (either '3 Level Plot' or 'Expanded Region')." + plotType: ColocPlotTypeEnum + "Source tissue for QTL association, for gTEX colocalizations." + tissue: String + "For sQTLS, the location of the splice event." + spliceLoc: String + "A link to the associated plot." + plotLink: String + "The data set associated with the colocalization." + dataSet: SimpleDataSet! + "The data set whose genes were used to compute the colocalization." + colocDataSet: SimpleDataSet! + "The feature associated with the colocalization." + feature: SimpleFeature! + "The gene associated with the colocalization." + gene: SimpleGene! + "The snp associated with the colocalization." + snp: SnpNode! +} + +type Colocalization implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned ColocalizationNodes" + items: [ColocalizationNode] +} diff --git a/apps/iatlas/api/api/schema/copyNumberResult.query.graphql b/apps/iatlas/api/api/schema/copyNumberResult.query.graphql new file mode 100644 index 0000000000..9109d89f63 --- /dev/null +++ b/apps/iatlas/api/api/schema/copyNumberResult.query.graphql @@ -0,0 +1,41 @@ +""" +The "DirectionEnum" scalar will always be either `Amp`, `Del`. +""" +scalar DirectionEnum + +""" +The "CopyNumberResultNode" type +""" +type CopyNumberResultNode implements BaseNode { + "A unique id for the data set generated by the database. Please note that this value may change as the database gets updated and should not be relied on." + id: ID + "The direction of the copy number result (either 'Amp' or 'Del')." + direction: DirectionEnum! + "The mean normal value of the copy number result." + meanNormal: Float + "The mean CNV value of the copy number result." + meanCnv: Float + "The P value of the copy number result." + pValue: Float + "The log10 computed result of the P value." + log10PValue: Float + "The T Stat value of the copy number result." + tStat: Float + "The data set associated with the copy number result." + dataSet: SimpleDataSet! + "The feature associated with the copy number result." + feature: SimpleFeature! + "The gene associated with the copy number result." + gene: SimpleGene! + "The tag associated with the copy number result." + tag: SimpleTag! +} + +type CopyNumberResult implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned CopyNumberResultNodes" + items: [CopyNumberResultNode] +} diff --git a/apps/iatlas/api/api/schema/dataset.query.graphql b/apps/iatlas/api/api/schema/dataset.query.graphql new file mode 100644 index 0000000000..400087e92c --- /dev/null +++ b/apps/iatlas/api/api/schema/dataset.query.graphql @@ -0,0 +1,38 @@ +""" +The "Dataset" type +""" +type DataSetNode implements BaseNode { + "A unique id for the driver result generated by the database. Please note that this value may change as the database gets updated and should not be relied on." + id: ID! + "The 'name' of the data set." + name: String! + "A friendly name of the data set." + display: String + "A list of samples associated with the data set." + samples: [SimpleSample!] + "A list of samples associated with the data set." + tags: [SimpleTag!] + "The type of data set this is." + type: String +} + +type DataSet implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned DatasetNodes" + items: [DataSetNode] +} + +""" +The "SimpleDataSet" type is a version of a DataSet. Only basic attributes may be returned. +""" +type SimpleDataSet { + "The proper 'name' of the data set." + name: String! + "A friendly name of the data set." + display: String + "The type of data set this is." + type: String +} diff --git a/apps/iatlas/api/api/schema/driverResult.query.graphql b/apps/iatlas/api/api/schema/driverResult.query.graphql new file mode 100644 index 0000000000..09c75efa4b --- /dev/null +++ b/apps/iatlas/api/api/schema/driverResult.query.graphql @@ -0,0 +1,36 @@ +""" +The "DriverResultNode" type +""" +type DriverResultNode implements BaseNode { + "A unique id for the driver result generated by the database. Please note that this value may change as the database gets updated and should not be relied on." + id: ID + "The data set associated with the driver result." + dataSet: SimpleDataSet! + "The feature associated with the driver result." + feature: SimpleFeature! + "The mutation associated with the driver result." + mutation: SimpleMutation! + "A database generated id of the mutation related to the gene and mutation code associated with the driver result." + tag: SimpleTag! + "The P value of the driver result." + pValue: Float + "The fold change of the driver result." + foldChange: Float + "The log10 computed result of P value." + log10PValue: Float + "The log10 computed result of fold change." + log10FoldChange: Float + "The number or wild type genes." + numWildTypes: Int + "The number of mutant genes." + numMutants: Int +} + +type DriverResult implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned DriverResultNodes" + items: [DriverResultNode] +} diff --git a/apps/iatlas/api/api/schema/edge.query.graphql b/apps/iatlas/api/api/schema/edge.query.graphql new file mode 100644 index 0000000000..dc2fb8cfc7 --- /dev/null +++ b/apps/iatlas/api/api/schema/edge.query.graphql @@ -0,0 +1,31 @@ +""" +The "Edge" type +""" +type Edge implements BaseNode { + "A unique id for the edge generated by the database. Please note that this value may change as the database gets updated and should not be relied on." + id: ID + "The label of the specific edge." + label: String + "A unique name for the Edge, usually `__`." + name: String! + "The calculated value of the Edge." + score: Float + "The starting node in the Edge." + node1: SimpleNode! + "The ending node in the Edge." + node2: SimpleNode! +} + +""" +The "EdgeResult" type + +See `Edge` +""" +type EdgeResult implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned EdgeNodes" + items: [Edge] +} diff --git a/apps/iatlas/api/api/schema/feature.query.graphql b/apps/iatlas/api/api/schema/feature.query.graphql new file mode 100644 index 0000000000..ed9b71ce9f --- /dev/null +++ b/apps/iatlas/api/api/schema/feature.query.graphql @@ -0,0 +1,77 @@ +""" +The "Feature" type may return: + +See also "SimpleFeature". +""" +type FeatureNode implements BaseNode{ + "The 'id' of the feature. Please note that this `id` is generated by the database and may not be consistent in the long term." + id: ID! + "The feature class associated with this feature." + class: String + "A readable name for the feature." + display: String + "The method tag associated with this feature." + methodTag: String + "The feature's name (a unique string for this feature)." + name: String! + "A number representing the index order the feature would be displayed in." + order: Int + "Immune traits clustered based on their Pearson correlation coefficients, described by Sayaman et al, 2021." + germlineModule: String + "Immune traits clustered based on the approach used to derive them and the parameters they intend to measure , described by Sayaman et al, 2021." + germlineCategory: String + "A list of the names of the sample related to this feature that is associated with the value." + samples: [FeatureRelatedSample!]! + "The type of measurement of the value." + unit: String + "The maximum value of all relationships between a specific feature and the samples." + valueMax: Float + "The minimum value of all relationships between a specific feature and the samples." + valueMin: Float +} + +type Feature implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned FeatureNodes" + items: [FeatureNode] +} + +""" +The "SimpleFeature" is a version of a `Feature`. Only basic feature attributes may be returned. + +To get more properties of features, please see "Feature" +""" +type SimpleFeature { + "A readable name for the feature." + display: String + "The feature's name (a unique string for this feature)." + name: String! + "A number representing the index order the feature would be displayed in." + order: Int + "The type of measurement of the value." + unit: String + "Immune traits clustered based on their Pearson correlation coefficients, described by Sayaman et al, 2021." + germlineModule: String + "Immune traits clustered based on the approach used to derive them and the parameters they intend to measure , described by Sayaman et al, 2021." + germlineCategory: String +} + +""" +The "SimpleFeature2" is a version of a `Feature`. Used by featureValue. + +To get more properties of features, please see "Feature" +""" +type SimpleFeature2 { + "A readable name for the feature." + display: String + "The feature's name (a unique string for this feature)." + name: String! + "A number representing the index order the feature would be displayed in." + order: Int + "The feature class associated with this feature." + class: String +} + diff --git a/apps/iatlas/api/api/schema/gene.query.graphql b/apps/iatlas/api/api/schema/gene.query.graphql new file mode 100644 index 0000000000..78db0ff714 --- /dev/null +++ b/apps/iatlas/api/api/schema/gene.query.graphql @@ -0,0 +1,60 @@ +""" +The "Gene" type +""" +type GeneNode implements BaseNode{ + "The 'id' of the gene. Please note that this `id` is generated by the database and may not be consistent in the long term." + id: ID! + "The unique id of the gene to use on search engines." + entrez: Int! + "The HUGO Gene Nomenclature Committee." + hgnc: String! + "The 'description' of the gene." + description: String + "The 'friendlyName' of the gene." + friendlyName: String + "The IO Landscape Name of the gene." + ioLandscapeName: String + "The 'geneFamily' of the gene." + geneFamily: String + "The 'geneFunction' of the gene." + geneFunction: String + "A list of GeneTypes associated with the gene." + geneTypes: [SimpleGeneType!]! + "The 'immuneCheckpoint' of the gene." + immuneCheckpoint: String + "The 'pathway' of the gene." + pathway: String + "A list of Publications associated with the gene." + publications: [SimplePublication!]! + "A list of samples related to this gene that is associated with the value." + samples: [GeneRelatedSample!]! + "The 'superCategory' of the gene." + superCategory: String + "The 'therapyType' of the gene." + therapyType: String +} + +type Gene implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned GeneNodes" + items: [GeneNode] +} + +""" +The "SimpleGene" is a version of a gene. Only basic attributes may be returned. +""" +type SimpleGene { + "The unique id of the gene to use on search engines." + entrez: Int! + "The HUGO Gene Nomenclature Committee." + hgnc: String! + "The 'description' of the gene." + description: String + "The 'friendlyName' of the gene." + friendlyName: String + "The IO Landscape Name of the gene." + ioLandscapeName: String +} diff --git a/apps/iatlas/api/api/schema/geneType.query.graphql b/apps/iatlas/api/api/schema/geneType.query.graphql new file mode 100644 index 0000000000..f2a92bd313 --- /dev/null +++ b/apps/iatlas/api/api/schema/geneType.query.graphql @@ -0,0 +1,21 @@ +""" +The "GeneType" type +""" +type GeneType { + "A friendly name of the gene type." + display: String + "A list of genes related to the gene type" + genes: [SimpleGene!]! + "The proper name of the gene type." + name: String! +} + +""" +The "SimpleGeneType" is a version of a GeneType. Only basic attributes may be returned. +""" +type SimpleGeneType { + "The proper name of the gene type." + name: String! + "A friendly name of the gene type." + display: String +} diff --git a/apps/iatlas/api/api/schema/germlineGwasResult.query.graphql b/apps/iatlas/api/api/schema/germlineGwasResult.query.graphql new file mode 100644 index 0000000000..39d2c224ec --- /dev/null +++ b/apps/iatlas/api/api/schema/germlineGwasResult.query.graphql @@ -0,0 +1,26 @@ +""" +The "GermlineGwasResultNode" type +""" +type GermlineGwasResultNode implements BaseNode { + "A unique id for the germline gwas result generated by the database. Please note that this value may change as the database gets updated and should not be relied on." + id: ID + "The data set associated with the germline gwas result." + dataSet: SimpleDataSet! + "The feature associated with the germline gwas result." + feature: SimpleFeature! + "The snp associated with the germline gwas result." + snp: SnpNode! + "P value for gwas result." + pValue: Float + "MAF value for gwas result." + maf: Float +} + +type GermlineGwasResult implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned GermlineGwasResultNodes" + items: [GermlineGwasResultNode!] +} diff --git a/apps/iatlas/api/api/schema/heritabilityResult.query.graphql b/apps/iatlas/api/api/schema/heritabilityResult.query.graphql new file mode 100644 index 0000000000..88455d08f1 --- /dev/null +++ b/apps/iatlas/api/api/schema/heritabilityResult.query.graphql @@ -0,0 +1,30 @@ +""" +The "HeritabilityResultNode" type +""" +type HeritabilityResultNode implements BaseNode { + "A unique id for the heritability result generated by the database. Please note that this value may change as the database gets updated and should not be relied on." + id: ID + "The data set associated with the heritability result." + dataSet: SimpleDataSet! + "The feature associated with the heritability result." + feature: SimpleFeature! + "The Log-likelihood test (LRT) p-value of the heritability result." + pValue: Float + "Ancestry cluster, determined from ancestry analysis by Sayaman et al, 2021." + cluster: String + "Benjamini-Hochberg false discovery rate (FDR) adjustment for multiple testing calculated in R (p.adjust, method=BH)." + fdr: Float + "Ratio of genetic variance to phenotypic variance, estimate." + variance: Float + "Standard error." + se: Float +} + +type HeritabilityResult implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned HeritabilityResultNodes" + items: [HeritabilityResultNode] +} diff --git a/apps/iatlas/api/api/schema/mutation.query.graphql b/apps/iatlas/api/api/schema/mutation.query.graphql new file mode 100644 index 0000000000..079fe1b2b2 --- /dev/null +++ b/apps/iatlas/api/api/schema/mutation.query.graphql @@ -0,0 +1,57 @@ +""" +The "StatusEnum" scalar will always be either `Mut` or `Wt`. +""" +scalar StatusEnum + +""" +The "MutationNode" type +""" +type MutationNode implements BaseNode { + "The 'id' of the mutation. Please note that this `id` is generated by the database and may not be consistent in the long term." + id: ID! + "The name of the mutation" + name: String! + "The Gene related to the mutation." + gene: SimpleGene! + "The MutationCode related to that mutation." + mutationCode: String! + "The MutationType related to that mutation." + mutationType: MutationType + "A list of Samples related to that mutation" + samples: [MutationRelatedSample!] +} + +type Mutation implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned Nodes" + items: [MutationNode] +} + +""" +The "MutationType" type +""" +type MutationType { + "A friendly name of the mutation type" + display: String + "The proper 'name' of the mutation type" + name: String! +} + +""" +The "SimpleMutation" is a version of a gene. Only basic attributes may be returned. +""" +type SimpleMutation { + "The 'id' of the mutation. Please note that this `id` is generated by the database and may not be consistent in the long term." + id: ID! + "The name of the mutation" + name: String! + "The Gene related to the mutation." + gene: SimpleGene! + "The MutationCode related to that mutation." + mutationCode: String! + "The MutationType related to that mutation." + mutationType: MutationType +} diff --git a/apps/iatlas/api/api/schema/neoantigen.query.graphql b/apps/iatlas/api/api/schema/neoantigen.query.graphql new file mode 100644 index 0000000000..e1b899816d --- /dev/null +++ b/apps/iatlas/api/api/schema/neoantigen.query.graphql @@ -0,0 +1,30 @@ + +""" +The "NeoantigenNode" type +""" +type NeoantigenNode implements BaseNode { + "The 'id' of the neoantigen. Please note that this `id` is generated by the database and may not be consistent in the long term." + id: ID! + "The pmhc of the neoantigen" + pmhc: String! + "The frequency of the pmhc of the neoantigen" + freqPmhc: Int! + "The tpm of the neoantigen" + tpm: Float + "The Gene related to the neoantigen." + gene: SimpleGene + "The Patient related to the neoantigen." + patient: SimplePatient! +} + +""" +The "Neoantigen" type +""" +type Neoantigen implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned Nodes" + items: [NeoantigenNode] +} diff --git a/apps/iatlas/api/api/schema/node.query.graphql b/apps/iatlas/api/api/schema/node.query.graphql new file mode 100644 index 0000000000..e8e874d12b --- /dev/null +++ b/apps/iatlas/api/api/schema/node.query.graphql @@ -0,0 +1,60 @@ +""" +The "Node" type +""" +type Node implements BaseNode { + "A unique id for the node generated by the database. Please note that this value may change as the database gets updated and should not be relied on." + id: ID + "A network identifier." + label: String + "A unique name for the node." + name: String! + "The type of network associated with the node" + network: String! + "The calculated value of the node." + score: Float + "The initial x position of the node." + x: Float + "The initial y position of the node." + y: Float + "The data set related to the node." + dataSet: SimpleDataSet! + "The gene related to the node." + gene: SimpleGene + "The feature related to the node." + feature: SimpleFeature + "The tag associated with the node" + tag1: SimpleTag + "The secondary tag associated with the node if it is stratified" + tag2: SimpleTag +} + +type NodeResult implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned Nodes" + items: [Node] +} + +""" +The "SimpleNode" is a simple version of a Node; it has no related fields. + +See also `Node` +""" +type SimpleNode { + "A unique id for the node generated by the database. Please note that this value may change as the database gets updated and should not be relied on." + id: ID + "A network identifier." + label: String + "A unique name for the node." + name: String! + "The type of network associated with the node" + network: String! + "The calculated value of the node." + score: Float + "The initial x position of the node." + x: Float + "The initial y position of the node." + y: Float +} diff --git a/apps/iatlas/api/api/schema/paging.graphql b/apps/iatlas/api/api/schema/paging.graphql new file mode 100644 index 0000000000..1ad8a556f4 --- /dev/null +++ b/apps/iatlas/api/api/schema/paging.graphql @@ -0,0 +1,77 @@ +""" +The "BaseNode" interface defines an interface implemented by return types: +""" +interface BaseNode { + "A unique identifier for this record. Do not request 'id' if 'distinct' is 'true'" + id: ID +} + +""" +An object containing either cursor or offset pagination request values. +If PagingInput is omitted, paging will default to type "CURSOR", and "first" 100,000. +""" +input PagingInput { + "The type of pagination to perform. Must be either 'OFFSET' or 'CURSOR'." + type: PagingType + "When performing OFFSET paging, the page number requested." + page: Int + "When performing OFFSET paging, the number or records requested." + limit: Int + "When performing CURSOR paging: the CURSOR to be used in tandem with 'first'" + first: Int + "When performing CURSOR paging: the CURSOR to be used in tandem with 'last'" + last: Int + "When performing CURSOR paging, the number of records requested BEFORE the CURSOR." + before: String + "When performing CURSOR paging, the number of records requested AFTER the CURSOR." + after: String +} + +""" +An ENUM containing constants for the two types of pagination available. +'CURSOR' is default +""" +enum PagingType { + "'CURSOR': more performant pagination. Cannot seek to specific pages. May not be used in conjunction with 'distinct'" + CURSOR + "OFFSET: may be significantly less performant, especially when used in conjunction with 'distinct', but necessary when paged 'distinct' result sets are required." + OFFSET +} + +""" +Pagination metadata returned with each paginated request. +""" +type Paging { + "Must be set to 'OFFSET' or 'CURSOR'. When not passed, will default to 'CURSOR'. See (PagingType)." + type: PagingType + "The total number of pages available based on the requested limit/first/last value." + pages: Int + "The total number of records available matching the given request." + total: Int + "When performing OFFSET paging, the page number returned." + page: Int + "When performing OFFSET paging, the number of requested records per page." + limit: Int + "When performing CURSOR paging, a Boolean indicating the presence or absence of additional pages __after__ the __endCursor__." + hasNextPage: Boolean + "When performing CURSOR paging, a Boolean indicating the presence or absence of additional pages __before__ the __startCursor__." + hasPreviousPage: Boolean + "When performing CURSOR paging, the cursor of the first record returned." + startCursor: String + "When performing CURSOR paging, the cursor of the last record returned." + endCursor: String + "The number of items returned in this response. May be less than the number requested." + returned: Int +} + +""" +Provides a common interface for return data. +""" +interface BaseResult { + "Pagination metadata. (see Paging)" + paging: Paging + "A string error message returned by the API and may be displayed to the user by the client. This is separate from 'errors' which provides useful feedback during client development." + error: String + "A list of returned objects that implement the BaseNode interface." + items: [BaseNode] +} diff --git a/apps/iatlas/api/api/schema/patient.query.graphql b/apps/iatlas/api/api/schema/patient.query.graphql new file mode 100644 index 0000000000..d6aa548e52 --- /dev/null +++ b/apps/iatlas/api/api/schema/patient.query.graphql @@ -0,0 +1,69 @@ +""" +The "EthnicityEnum" scalar will always be either `not hispanic or latino` or `hispanic or latino`. +""" +scalar EthnicityEnum + +""" +The "GenderEnum" scalar will always be either `female` or `male`. +""" +scalar GenderEnum + +""" +The "RaceEnum" scalar will always be either `american indian or alaska native`, `black or african american`, `native hawaiian or other pacific islander`, `white`, or `asian`. +""" +scalar RaceEnum + +""" +The "Patient" type +""" +type PatientNode implements BaseNode { + "A unique id for the driver result generated by the database. Please note that this value may change as the database gets updated and should not be relied on." + id: ID! + "The age of the patient at the time of diagnosis." + ageAtDiagnosis: Int + "The barcode of the patient." + barcode: String! + "The ethnicity of the patient." + ethnicity: EthnicityEnum + "The gender of the patient." + gender: GenderEnum + "The height of the patient." + height: Float + "The race of the patient." + race: RaceEnum + "The weight of the patient." + weight: Float + "A list of samples associated with the patient." + samples: [String!]! + "A list of slides associated with the patient." + slides: [SimpleSlide!]! +} + +type Patient implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned PatientNode" + items: [PatientNode] +} + +""" +The "SimplePatient" type +""" +type SimplePatient { + "The age of the patient at the time of diagnosis." + ageAtDiagnosis: Int + "The barcode of the patient." + barcode: String! + "The ethnicity of the patient." + ethnicity: EthnicityEnum + "The gender of the patient." + gender: GenderEnum + "The height of the patient." + height: Float + "The race of the patient." + race: RaceEnum + "The weight of the patient." + weight: Float +} diff --git a/apps/iatlas/api/api/schema/publication.query.graphql b/apps/iatlas/api/api/schema/publication.query.graphql new file mode 100644 index 0000000000..149e3a47ab --- /dev/null +++ b/apps/iatlas/api/api/schema/publication.query.graphql @@ -0,0 +1,45 @@ +""" +The "Publication" type +""" +type Publication { + "The first last name of the publication's author" + firstAuthorLastName: String + "The id of the publication in the doi website. See: https://www.doi.org/" + doId: String + "A list of Genes related to the publication" + genes: [SimpleGene!] + "A list of GeneTypes related to the publication" + geneTypes: [SimpleGeneType!] + "The 'journal' of the publication." + journal: String + "The unique name of the publication. This is generated as a combination of the do id and the pubmed id." + name: String! + "The id of the publication in the pubmed website. See https://pubmed.ncbi.nlm.nih.gov/" + pubmedId: Int + "A list of Tags related to the publication" + tags: [SimpleTag!] + "The 'title' of the publication." + title: String + "The 'year' of the publication." + year: Int +} + +""" +The "SimplePublication" type is a version of a Publication. Only basic attributes may be returned. +""" +type SimplePublication { + "The first last name of the publication's author" + firstAuthorLastName: String + "The id of the publication in the doi website. See: https://www.doi.org/" + doId: String + "The 'journal' of the publication." + journal: String + "The unique name of the publication. This is generated as a combination of the do id and the pubmed id." + name: String! + "The id of the publication in the pubmed website. See https://pubmed.ncbi.nlm.nih.gov/" + pubmedId: Int + "The 'title' of the publication." + title: String + "The 'year' of the publication." + year: Int +} diff --git a/apps/iatlas/api/api/schema/rareVariantPathwayAssociation.query.graphql b/apps/iatlas/api/api/schema/rareVariantPathwayAssociation.query.graphql new file mode 100644 index 0000000000..b61ef4600d --- /dev/null +++ b/apps/iatlas/api/api/schema/rareVariantPathwayAssociation.query.graphql @@ -0,0 +1,40 @@ +""" +The "RareVariantPathwayAssociationNode" type +""" +type RareVariantPathwayAssociationNode implements BaseNode { + "A unique id for the rare variant pathway association generated by the database. Please note that this value may change as the database gets updated and should not be relied on." + id: ID + "The data set associated with the rare variant pathway association." + dataSet: SimpleDataSet! + "The feature associated with the rare variant pathway association." + feature: SimpleFeature! + "The biological or functional category." + pathway: String + "The P value of the rare variant pathway association." + pValue: Float + "Minimum value for the rare variant pathway association" + min: Float + "Maximum value for the rare variant pathway association" + max: Float + "Mean value for the rare variant pathway association" + mean: Float + "Quartile one value for the rare variant pathway association" + q1: Float + "Quartile two value for the rare variant pathway association" + q2: Float + "Quartile three value for the rare variant pathway association" + q3: Float + "Number of samples that were analyzed with a mutation in at least one gene in the pathway." + nMutants: Int + "Number of samples that were analyzed." + nTotal: Int +} + +type RareVariantPathwayAssociation implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned RareVariantPathwayAssociationNodes" + items: [RareVariantPathwayAssociationNode] +} diff --git a/apps/iatlas/api/api/schema/root.query.graphql b/apps/iatlas/api/api/schema/root.query.graphql new file mode 100644 index 0000000000..b951314c98 --- /dev/null +++ b/apps/iatlas/api/api/schema/root.query.graphql @@ -0,0 +1,575 @@ +type Query { + """ + The data structure containing Cohorts + + If no arguments are passed, this will return all cohorts + """ + cohorts( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "A unique id for the result generated by the database. PLEASE NOTE: this ID should not be relied on, it may change as the database changes." + id: ID + "A list of cohort names associated with the cohort to filter by." + cohort: [String!] + "A list of data set names associated with the cohort to filter by." + dataSet: [String!] + "A list of tag names associated with the cohort to filter by." + tag: [String!] + ): Cohort! + """ + The data structure containing Colocalizations. + + If no arguments are passed, this will return all colocalization. + """ + colocalizations( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "A unique id for the result generated by the database. PLEASE NOTE: this ID should not be relied on, it may change as the database changes." + id: ID + "A list of data set names associated with the colocalization to filter by." + dataSet: [String!] + "A list of data set names whose genes were used to compute the colocalization." + colocDataSet: [String!] + "A list of feature names associated with the colocalization to filter by." + feature: [String!] + "A list of gene entrez ids associated with the colocalization to filter by." + entrez: [Int!] + "A list of snp names associated with the colocalization to filter by." + snp: [String!] + "The QTL type of the colocalization to filter by. (either 'sQTL' or 'eQTL')." + qtlType: QTLTypeEnum + "The eCaviar colocalization posterior probability locus model. (either 'C1' or 'C2')." + eCaviarPP: ECaviarPPEnum + "Type of colocalization plot. (either '3 Level Plot' or 'Expanded Region')." + plotType: ColocPlotTypeEnum + ): Colocalization! + + """ + The data structure containing Copy Number Results. + + If no arguments are passed, this will return all copy number results. + """ + copyNumberResults( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "A unique id for the result generated by the database. PLEASE NOTE: this ID should not be relied on, it may change as the database changes." + id: ID + "A list of data set names associated with the copy number results to filter by." + dataSet: [String!] + "A list of 'related' tag names associated with the data set that is associated with the results to filter by." + related: [String!] + "A list of feature names associated with the copy number results to filter by." + feature: [String!] + "A list of gene entrez ids associated with the copy number results to filter by." + entrez: [Int!] + "A list of tag names associated with the copy number results to filter by." + tag: [String!] + "The direction of the copy number results to filter by. (either 'Amp' or 'Del')" + direction: DirectionEnum + "A minimum P value to filter the copy number results by." + minPValue: Float + "A maximum P value to filter the copy number results by." + maxPValue: Float + "A minimum log10 calculation of the P value to filter the copy number results by." + minLog10PValue: Float + "A maximum log10 calculation of the P value to filter the copy number results by." + maxLog10PValue: Float + "A minimum mean normal to filter the copy number results by." + minMeanNormal: Float + "A minimum mean CNV to filter the copy number results by." + minMeanCnv: Float + "A minimum T stat to filter the copy number results by." + minTStat: Float + ): CopyNumberResult! + + """ + The data structure containing Data Sets. + + If no arguments are passed, this will return all data sets. + """ + dataSets( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "A unique id for the result generated by the database. PLEASE NOTE: this ID should not be relied on, it may change as the database changes." + id: ID + "A list of data set names to look up." + dataSet: [String!] + "A list of sample names associated with the data sets to filter by." + sample: [String!] + "A list of data set types to filter by." + dataSetType: [String!] + ): DataSet! + + """ + The "driverResults" query + + If no arguments are passed, this will return all driver results. + """ + driverResults( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "A unique id for the result generated by the database. PLEASE NOTE: this ID should not be relied on, it may change as the database changes." + id: ID + "A list of data set names associated with the driver results to filter by" + dataSet: [String!] + "A list of 'related' tag names associated with the data set that is associated with the results to filter by." + entrez: [Int!] + "A list of feature names associated with the driver results to filter by." + feature: [String!] + "A list of mutation names associated with the driver results to filter by." + mutation: [String!] + "A list of mutation code names associated with the mutation of with the driver results to filter by." + mutationCode: [String!] + "A list of tag names associated with the driver results to filter by." + related: [String!] + "A list of gene entrez ids associated with the gene of the mutation of the driver results to filter by." + tag: [String!] + "A list of mutation code names associated with the mutation of with the driver results to filter by." + maxPValue: Float + "A minimum P value to filter the driver results by." + minPValue: Float + "A maximum log10 calculation of the P value to filter the driver results by." + maxLog10PValue: Float + "A minimum log10 calculation of the P value to filter the driver results by." + minLog10PValue: Float + "A minimum fold change to filter the driver results by." + minFoldChange: Float + "A minimum log10 calculation of the fold change to filter the driver results by." + minLog10FoldChange: Float + "A minimum of wild type genes to filter the driver results by." + minNumWildTypes: Int + "A minimum mutant genes to filter the driver results by." + minNumMutants: Int + ): DriverResult! + + """ + The "edges" query + + If no arguments are passed, this will return all edges (please note, there will be a LOT of results). + """ + edges( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "The maximum score value to return." + maxScore: Float + "The minimum score value to return." + minScore: Float + "A list of starting node names." + node1: [String!] + "A list of ending node names." + node2: [String!] + ): EdgeResult! + + """ + The "features" query + + If no arguments are passed, this will return all features. + + """ + features( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "A unique id for the gene generated by the database. PLEASE NOTE: this ID should not be relied on, it may change as the database changes." + id: ID + "A list of feature names associated with the features to filter by." + feature: [String!] + "A list of feature class names associated with the features to filter by." + featureClass: [String!] + "A list of sample names associated with the feature to filter by." + sample: [String!] + "A list of cohort names associated with the feature to filter by." + cohort: [String!] + "The maximum value (relationship between the feature and the sample) to filter by." + maxValue: Float + "The minimum value (relationship between the feature and the sample) to filter by." + minValue: Float + ): Feature! + + """ + The "genes" query + + If no arguments are passed, this will return all genes. + """ + genes( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "A unique id for the gene generated by the database. PLEASE NOTE: this ID should not be relied on, it may change as the database changes." + id: ID + "A list of data set names related to samples that are related to the genes to look up." + entrez: [Int!] + "A list of gene types related to the genes to look up." + geneType: [String!] + "A list of cohort names associated with the feature to filter by." + cohort: [String!] + "A list of tag names related to data sets to filter by." + sample: [String!] + "The maximum RNA Sequence Expression value related to the genes to look up." + maxRnaSeqExpr: Float + "The minimum RNA Sequence Expression value related to the genes to look up." + minRnaSeqExpr: Float + + ): Gene! + + """ + The "geneTypes" query + """ + geneTypes( + "A list of names of the gene types to look up." + name: [String!] + ): [GeneType!]! + + """ + The "germlineGwasResults" query + + If no arguments are passed, this will return all germline gwas results. + """ + germlineGwasResults( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "A list of data set names associated with the germline gwas results to filter by" + dataSet: [String!] + "A list of feature names associated with the germline gwas results to filter by." + feature: [String!] + "A list of snp names associated with the germline gwas results to filter by." + snp: [String!] + "A maximum P value to filter the germline gwas results by." + maxPValue: Float + "A minimum P value to filter the germline gwas results by." + minPValue: Float + ): GermlineGwasResult! + + + """ + The "heritabilityResults" query + + If no arguments are passed, this will return all heritability results. + """ + heritabilityResults( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "A list of data set names associated with the heritability results to filter by" + dataSet: [String!] + "A list of feature names associated with the heritability results to filter by." + feature: [String!] + "A maximum P value to filter the heritability results by." + maxPValue: Float + "A minimum P value to filter the heritability results by." + minPValue: Float + "Immune traits clustered based on their Pearson correlation coefficients, described by Sayaman et al, 2021." + cluster: [String!] + ): HeritabilityResult! + + """ + The "mutations" type + + If no arguments are passed, this will return all mutations. + """ + mutations( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "A unique id for the gene generated by the database. PLEASE NOTE: this ID should not be relied on, it may change as the database changes." + id: ID + "A list of names associated with the mutations to filter by." + mutation: [String!] + "A list of gene entrez ids associated with the mutations to filter by." + entrez: [Int!] + "A list of mutation code names associated with the mutations to filter by." + mutationCode: [String!] + "A list of mutation type names associated with the mutations to filter by." + mutationType: [String!] + "A list of cohort names associated with the mutations to filter by." + cohort: [String!] + "A list of sample names associated with the mutations to filter by." + sample: [String!] + "A list of statuses associated relationship between mutation and sample to filter by." + status: [StatusEnum!] + ): Mutation! + + """ + The "mutationTypes" query returns all mutation types. + """ + mutationTypes: [MutationType!]! + + """ + The "neoantigens" type + + If no arguments are passed, this will return all neoantigens. + """ + neoantigens( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "A unique id for the neoantigen. PLEASE NOTE: this ID should not be relied on, it may change as the database changes." + id: ID + "A list of phmhcs to filter by." + pmhc: [String!] + "A list of gene entrez ids associated with the neoantigens to filter by." + entrez: [Int!] + "A list of patient names associated with the neoantigens to filter by." + patient: [String!] + ): Neoantigen! + + """ + The "nodes" query + + If no arguments are passed, this will return all nodes (please note, there will be a LOT of results). + """ + nodes( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "A list of data set names associated with the nodes to filter by." + dataSet: [String!] + "A list of gene entrez ids associated with the nodes to filter by." + entrez: [Int!] + "A list of feature names associated with the nodes to filter by." + feature: [String!] + "A list of feature class names associated with features related to the nodes to filter by." + featureClass: [String!] + "A list of gene type names associated with genes related to the nodes to filter by." + geneType: [String!] + "The maximum score associated with the nodes to filter by." + maxScore: Float + "The minimum score associated with the nodes to filter by" + minScore: Float + "A list of tag names associated with the nodes that are also associated with the 'network' tag to filter by" + network: [String!] + "A list of tag names related to the data set associated with the nodes to filter by" + related: [String!] + "A list of tag names associated with the nodes to filter by" + tag1: [String!] + "A list of secondary tag names associated with the nodes to filter by" + tag2: [String!] + "The number of tags the node should have, either 1 or 2" + nTags: Int + ): NodeResult + + """ + The "patients" query + + If no arguments are passed, this will return all patients. + """ + patients( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "A unique id for the result generated by the database. PLEASE NOTE: this ID should not be relied on, it may change as the database changes." + id: ID + "A list of patient barcodes of the patients to look up." + barcode: [String!] + "A list of data set names associated with the samples related to the patient to filter by" + dataSet: [String!] + "A list of patient ethnicities to filter the patients by" + ethnicity: [EthnicityEnum!] + "A list of patient genders to filter the patients by" + gender: [GenderEnum!] + "A number representing the maximum age of the patient at the time of diagnosis to filter the patients by" + maxAgeAtDiagnosis: Int + "A number representing the maximum patient height to filter the patients by" + maxHeight: Float + "A number representing the maximum patient weight to filter the patients by" + maxWeight: Float + "A number representing the minimum age of the patient at the time of diagnosis to filter the patients by" + minAgeAtDiagnosis: Int + "A number representing the minimum patient height to filter the patients by" + minHeight: Float + "A number representing the minimum patient weight to filter the patients by" + minWeight: Float + "A list of patient races to filter the patients by" + race: [RaceEnum!] + "A list of sample names to filter the patients by" + sample: [String!] + "A list of slide names to filter the patients by" + slide: [String!] + ): Patient! + + """ + The "rareVariantPathwayAssociation" query + + If no arguments are passed, this will return all rare variant pathway associations. + """ + rareVariantPathwayAssociations( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "A unique id for the rare variant pathway association generated by the database. PLEASE NOTE: this ID should not be relied on, it may change as the database changes." + id: ID + "A list of data set names associated with the rare variant pathway associations to filter by" + dataSet: [String!] + "A list of feature names associated with the rare variant pathway associations to filter by." + feature: [String!] + "A list of pathways associated with the rare variant pathway associations to filter by." + pathway: [String!] + "A maximum P value to filter the rare variant pathway associations by." + maxPValue: Float + "A minimum P value to filter the rare variant pathway associations by." + minPValue: Float + ): RareVariantPathwayAssociation! + + """ + The "samples" query + + If no filters are passed, this will return all samples. + """ + samples( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "A unique id for the result generated by the database. PLEASE NOTE: this ID should not be relied on, it may change as the database changes." + id: ID + "A list of patient ethnicities to filter the samples by" + ethnicity: [EthnicityEnum!] + "A list of patient genders to filter the samples by" + gender: [GenderEnum!] + "A number representing the maximum age of the patient at the time of diagnosis to filter the samples by" + maxAgeAtDiagnosis: Int + "A number representing the maximum patient height to filter the samples by" + maxHeight: Float + "A number representing the maximum patient weight to filter the samples by" + maxWeight: Float + "A number representing the minimum age of the patient at the time of diagnosis to filter the samples by" + minAgeAtDiagnosis: Int + "A number representing the minimum patient height to filter the samples by" + minHeight: Float + "A number representing the minimum patient weight to filter the samples by" + minWeight: Float + "A list of sample names to filter the samples by" + name: [String!] + "A list of patient barcodes to filter the samples by" + patient: [String!] + "A list of patient races to filter the samples by" + race: [RaceEnum!] + ): Sample! + + + """ + The "slides" query + + If no arguments are passed, this will return all slides. + """ + slides( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "A unique id for the result generated by the database. PLEASE NOTE: this ID should not be relied on, it may change as the database changes." + id: ID + "A list of patient barcodes to filter the slides by." + barcode: [String!] + "A list of patient ethnicities to filter the slides by." + ethnicity: [EthnicityEnum!] + "A list of patient genders to filter the slides by." + gender: [GenderEnum!] + "A number representing the maximum age of the patient at the time of diagnosis to filter the slides by." + maxAgeAtDiagnosis: Int + "A number representing the maximum patient height to filter the slides by." + maxHeight: Float + "A number representing the maximum patient weight to filter the slides by." + maxWeight: Float + "A number representing the minimum age of the patient at the time of diagnosis to filter the slides by." + minAgeAtDiagnosis: Int + "A number representing the minimum patient height to filter the slides by." + minHeight: Float + "A number representing the minimum patient weight to filter the slides by." + minWeight: Float + "A list of slide names to look up." + name: [String!] + "A list of patient races to filter the slides by." + race: [RaceEnum!] + "A list of samples related to the slides to filter by." + sample: [String!] + ): Slide! + """ + The "snps" query + + If no arguments are passed, this will return all snps. + """ + snps( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "A list of snp names to filter by." + name: [String!] + "A list of snp rsids to filter by." + rsid: [String!] + "A list of chromosomes to filter by." + chr: [String!] + "A maximum basepair value to filter snps by." + maxBP: Int + "A minimum basepair value to filter snps by." + minBP: Int + ): Snp! + + """ + The "tags" query + """ + tags( + "An instance of PagingInput (see PagingInput)" + paging: PagingInput + "A boolean specifying whether or not duplicates should be filtered out. Default is false. Set to 'true' only when necessary, as it negatively impacts performance." + distinct: Boolean + "A list of data set names." + dataSet: [String!] + "A list of tag names related to the data set(s)." + related: [String!] + "A list of tag names." + tag: [String!] + "A list of sample names." + sample: [String!] + "A list of cohort names" + cohort: [String!] + "A list of tag types: 'group' or 'parent_group' or 'meta_group' or 'network'" + type: [TagTypeEnum!] + ): Tag! + + """ + A simple test query that is independent of the database. + """ + test: TestPage! +} + +type TestPage { + items: TestFields! + page: Int! +} + +type TestFields { + contentType: String! + userAgent: String! + headers: TestHeaders! +} + +type TestHeaders { + contentLength: Int! + contentType: String! + host: String! + userAgent: String! +} diff --git a/apps/iatlas/api/api/schema/sample.query.graphql b/apps/iatlas/api/api/schema/sample.query.graphql new file mode 100644 index 0000000000..8e8e6af21d --- /dev/null +++ b/apps/iatlas/api/api/schema/sample.query.graphql @@ -0,0 +1,90 @@ +""" +The "SampleNode" type +""" +type SampleNode implements BaseNode { + "A unique id for the driver result generated by the database. Please note that this value may change as the database gets updated and should not be relied on." + id: ID! + "The sample's name (often the 'sample' portion of a [TCGA barcode](https://docs.gdc.cancer.gov/Encyclopedia/pages/TCGA_Barcode/))." + name: String! + "A Patient structure related to the sample. There may only ever be one patient to a sample though a patient may be related to many samples." + patient: SimplePatient +} + +type Sample implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned SampleNodes" + items: [SampleNode] +} + +""" +The "GeneRelatedSample" type is a Sample that is specifically related to a Gene. +""" +type GeneRelatedSample { + "The sample's name (often the 'sample' portion of a [TCGA barcode](https://docs.gdc.cancer.gov/Encyclopedia/pages/TCGA_Barcode/))." + name: String! + "The the RNASeq expression value of the Sample related to the Gene." + rnaSeqExpr: Float + "The the nanostring expression value of the Sample related to the Gene." + nanostringExpr: Float +} + +""" +The "FeatureRelatedSample" type is a Sample that is specifically related to a Feature. + +See also `Feature` +""" +type FeatureRelatedSample { + "The sample's name (often the 'sample' portion of a [TCGA barcode](https://docs.gdc.cancer.gov/Encyclopedia/pages/TCGA_Barcode/))." + name: String! + "The calculated relational value or the Sample related to the Feature." + value: Float +} + +""" +The "MutationRelatedSample" type is a Sample that is specifically related to a Mutation. + +See also `Sample`, `Mutation`, `StatusEnum`, and `SimplePatient` +""" +type MutationRelatedSample { + "The sample's name (often the 'sample' portion of a [TCGA barcode](https://docs.gdc.cancer.gov/Encyclopedia/pages/TCGA_Barcode/))." + name: String! + "The Patient related to the Sample" + patient: SimplePatient + "The status of the Sample, either `Mut` or `Wt`, related to the Mutation." + status: StatusEnum! +} + +""" +The "SampleByMutationStatus" type +""" +type SampleByMutationStatus { + "The 'status' the Mutation, either `Mut` or `Wt`." + status: StatusEnum + "A list of Samples associated with the mutation status." + samples: [Sample!]! +} + +""" +The "SimpleSample" is a simple version of a Sample; it has no related fields. + +See also `Sample` +""" +type SimpleSample { + "The sample's name (often the 'sample' portion of a [TCGA barcode](https://docs.gdc.cancer.gov/Encyclopedia/pages/TCGA_Barcode/))." + name: String! +} + +""" +The "CohortSample" is a simple version of a Sample; it has no related fields. + +See also `Sample` +""" +type CohortSample { + "The sample's name (often the 'sample' portion of a [TCGA barcode](https://docs.gdc.cancer.gov/Encyclopedia/pages/TCGA_Barcode/))." + name: String! + "The Tag for the samples cohort." + tag: SimpleTag +} diff --git a/apps/iatlas/api/api/schema/slide.query.graphql b/apps/iatlas/api/api/schema/slide.query.graphql new file mode 100644 index 0000000000..1fb4ed683f --- /dev/null +++ b/apps/iatlas/api/api/schema/slide.query.graphql @@ -0,0 +1,32 @@ +""" +The "Slide" type +""" +type SlideNode implements BaseNode { + "A unique id for the slide by the database. Please note that this value may change as the database gets updated and should not be relied on." + id: ID! + "The name of the slide." + name: String! + "The description of the slide." + description: String + "The patient associated to the slide." + patient: SimplePatient +} + +type Slide implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned SlideNodes" + items: [SlideNode] +} + +""" +The "SimpleSlide" type +""" +type SimpleSlide { + "The name of the slide." + name: String! + "The description of the slide." + description: String +} diff --git a/apps/iatlas/api/api/schema/snp.query.graphql b/apps/iatlas/api/api/schema/snp.query.graphql new file mode 100644 index 0000000000..60d5ffe48b --- /dev/null +++ b/apps/iatlas/api/api/schema/snp.query.graphql @@ -0,0 +1,26 @@ +""" +The "SnpNode" type may return: + +""" +type SnpNode implements BaseNode { + "A unique id for the snp generated by the database. Please note that this value may change as the database gets updated and should not be relied on." + id: ID + "The snp's name (a unique string for this snp)." + name: String! + "The rsid of the snp." + rsid: String + "The chromosome of the snp." + chr: String + "The basepair location of the snp" + bp: Int +} + + +type Snp implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned Snps" + items: [SnpNode] +} \ No newline at end of file diff --git a/apps/iatlas/api/api/schema/tag.query.graphql b/apps/iatlas/api/api/schema/tag.query.graphql new file mode 100644 index 0000000000..386c300098 --- /dev/null +++ b/apps/iatlas/api/api/schema/tag.query.graphql @@ -0,0 +1,78 @@ +""" +'group' or 'parent_group' or 'meta_group' or 'network' +""" +scalar TagTypeEnum + + +""" +The "Tag" type + +See also `SimpleTag` +""" +type TagNode implements BaseNode { + "A unique id for the data set generated by the database. Please note that this value may change as the database gets updated and should not be relied on." + id: ID + "The 'characteristics' or description of the tag." + characteristics: String + "A color to represent the tag as a hex value." + color: String + "A long display name for the tag used in text descriptions." + longDisplay: String! + "The name of the tag." + name: String! + "A number representing the index order the tag would be displayed in." + order: Int + "A list of Publications related to the tag." + publications: [SimplePublication!] + "A tag related to the tag." + related: [SimpleTag!] + "The number of sample associated with the tag." + sampleCount: Int! + "A list of the samples associated with the tag." + samples: [SimpleSample!] + "A friendly name for the tag (used in plots)." + shortDisplay: String! + "The type of tag." + type: TagTypeEnum! +} + +type Tag implements BaseResult { + "A Paging object (see Paging)" + paging: Paging + "A string describing any error that may have occurred." + error: String + "A list of returned TagNodes" + items: [TagNode] +} + +""" +A "SimpleTag" type is a version of a `Tag`. Only basic tag attributes may be returned. +""" +type SimpleTag { + "The 'characteristics' or description of the tag." + characteristics: String + "A color to represent the tag as a hex value." + color: String + "A long display name for the tag used in text descriptions." + longDisplay: String + "The name of the tag." + name: String! + "A number representing the index order the tag would be displayed in." + order: Int + "A friendly name for the tag (used in plots)." + shortDisplay: String + "The type of tag." + type: TagTypeEnum! +} + +""" +The "RelatedByDataSet" type +""" +type RelatedByDataSet { + "The name of the DataSet" + dataSet: String! + "A friendly display name for the data set." + display: String + "A list of SimpleTags related to the DataSet" + related: [SimpleTag!]! +} diff --git a/apps/iatlas/api/api/telemetry/PROFILING.md b/apps/iatlas/api/api/telemetry/PROFILING.md new file mode 100644 index 0000000000..a2b7fc0c04 --- /dev/null +++ b/apps/iatlas/api/api/telemetry/PROFILING.md @@ -0,0 +1,14 @@ +# iAtlas API Profiling + +[BACK TO MAIN README](./../../README.md) + +## Deterministic Profiling + +Functions may be profiled using the `@profile(__name__)` decorator. Adding this decorator to a function will cause the app to write a profile for that function when it is called. The profile may be reviewed via [SnakeViz](https://jiffyclub.github.io/snakeviz/). Simply call: + +```sh +./view_profile.sh +``` + +from the root of the project. A list of profile options will be given. Once a profile is selected, the SnakeViz server will render the profile as a webpage. The webpage URL will be displayed in the console. Go to the page in your browser to view the profile. +By default, SnakeViz runs on port `8020`. To change this port, set the SNAKEVIZ_PORT variable in a `.env-dev` file in the root of the project (see the `.env-SAMPLE` for an example.) diff --git a/apps/iatlas/api/api/telemetry/__init__.py b/apps/iatlas/api/api/telemetry/__init__.py new file mode 100644 index 0000000000..c0c090a73e --- /dev/null +++ b/apps/iatlas/api/api/telemetry/__init__.py @@ -0,0 +1 @@ +from .profile import profile diff --git a/apps/iatlas/api/api/telemetry/profile.py b/apps/iatlas/api/api/telemetry/profile.py new file mode 100644 index 0000000000..de506130c2 --- /dev/null +++ b/apps/iatlas/api/api/telemetry/profile.py @@ -0,0 +1,96 @@ +import cProfile +import datetime +import os +import time +import logging + +from flask import current_app as app + +log = logging.getLogger('profiling') +log.setLevel(logging.DEBUG) + +# usage: @profile("profile_for_func1_001") + + +def profile(name): + def inner(func): + def wrapper(*args, **kwargs): + if not app.config['PROFILE']: + return func(*args, **kwargs) + prof = cProfile.Profile() + retval = prof.runcall(func, *args, **kwargs) + path = app.config['PROFILE_PATH'] + if not os.path.exists(path): + os.makedirs(path) + fname = func.__qualname__ + now = datetime.datetime.utcnow().timestamp() + prof.dump_stats(os.path.join( + path, '{}.{}-{}.profile'.format(name, fname, now))) + return retval + wrapper.__doc__ = func.__doc__ + wrapper.__name__ = func.__name__ + return wrapper + return inner + + +# Not currently compatible with Alpine +""" +import sys +from os.path import expanduser +from line_profiler import LineProfiler + + +def line_profile(f): + lp = LineProfiler() + lp_wrapper = lp(f) + + def wrapper(*args, **kwargs): + val = lp_wrapper(*args, **kwargs) + path = app.config['PROFILE_PATH'] + if not os.path.exists(path): + os.makedirs(path) + fname = '{}.txt'.format(f.__qualname__) + file = open(os.path.join(path, fname, ), 'w', encoding='utf-8') + lp.print_stats(stream=file, output_unit=1e-03) + lp.print_stats(stream=sys.stdout, output_unit=1e-03) + return val + return wrapper +""" + +from sqlalchemy import event +from sqlalchemy.engine import Engine + + +@event.listens_for(Engine, "before_cursor_execute") +def before_cursor_execute(conn, cursor, statement, + parameters, context, executemany): + conn.info.setdefault('query_start_time', []).append(time.time()) + log.debug("Start Query: %s", statement) + + +@event.listens_for(Engine, "after_cursor_execute") +def after_cursor_execute(conn, cursor, statement, + parameters, context, executemany): + total = time.time() - conn.info['query_start_time'].pop(-1) + log.debug("Query Complete!") + log.debug("Total Time: %f", total) + + +import cProfile +import io +import pstats +import contextlib + + +@contextlib.contextmanager +def profiled(): + pr = cProfile.Profile() + pr.enable() + yield + pr.disable() + s = io.StringIO() + ps = pstats.Stats(pr, stream=s).sort_stats('cumulative') + ps.print_stats() + # uncomment this to see who's calling what + # ps.print_callers() + print(s.getvalue()) diff --git a/apps/iatlas/api/docker-entrypoint.sh b/apps/iatlas/api/docker-entrypoint.sh new file mode 100644 index 0000000000..6f39dcde57 --- /dev/null +++ b/apps/iatlas/api/docker-entrypoint.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -e + +if [ "$1" = 'python' ]; then + cd ${APP_DIR} + exec gosu www-data "$@" +fi + +exec "$@" \ No newline at end of file diff --git a/apps/iatlas/api/iatlasapi.py b/apps/iatlas/api/iatlasapi.py new file mode 100644 index 0000000000..e52aa36612 --- /dev/null +++ b/apps/iatlas/api/iatlasapi.py @@ -0,0 +1,3 @@ +from api import create_app, db + +app = create_app() diff --git a/apps/iatlas/api/poetry.lock b/apps/iatlas/api/poetry.lock new file mode 100644 index 0000000000..87c6f94bf2 --- /dev/null +++ b/apps/iatlas/api/poetry.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +package = [] + +[metadata] +lock-version = "2.0" +python-versions = "3.10.13" +content-hash = "8a7d9427f7d52bcf786da6df65e140dbe9bd146232f01243a087bcc03b9a01dc" diff --git a/apps/iatlas/api/poetry.toml b/apps/iatlas/api/poetry.toml new file mode 100644 index 0000000000..62e2dff2a2 --- /dev/null +++ b/apps/iatlas/api/poetry.toml @@ -0,0 +1,3 @@ +[virtualenvs] +in-project = true +create = true diff --git a/apps/iatlas/api/prepare-python.sh b/apps/iatlas/api/prepare-python.sh new file mode 100755 index 0000000000..45505c4dca --- /dev/null +++ b/apps/iatlas/api/prepare-python.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +PYTHON_VERSION="3.10.13" + +pyenv install --skip-existing $PYTHON_VERSION + +# Initializing pyenv again solves an issue encountered by GitHub action where the version of Python +# installed above is not detected. +eval "$(pyenv init -)" + +pyenv local $PYTHON_VERSION +poetry env use $PYTHON_VERSION +poetry install +# poetry install --with prod,dev \ No newline at end of file diff --git a/apps/iatlas/api/project.json b/apps/iatlas/api/project.json new file mode 100644 index 0000000000..48b514f266 --- /dev/null +++ b/apps/iatlas/api/project.json @@ -0,0 +1,68 @@ +{ + "name": "iatlas-api", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/iatlas/api", + "projectType": "application", + "targets": { + "create-config": { + "executor": "nx:run-commands", + "options": { + "command": "cp -n .env.example .env", + "cwd": "{projectRoot}" + } + }, + "prepare": { + "executor": "nx:run-commands", + "options": { + "command": "./prepare-python.sh", + "cwd": "{projectRoot}" + } + }, + "serve": { + "executor": "nx:run-commands", + "options": { + "command": "docker compose up", + "cwd": "apps/iatlas/api" + }, + "dependsOn": [] + }, + "serve-detach": { + "executor": "nx:run-commands", + "options": { + "command": "docker/iatlas/serve-detach.sh {projectName}" + } + }, + "build-image": { + "executor": "@nx-tools/nx-container:build", + "options": { + "context": "apps/iatlas/api", + "metadata": { + "images": ["ghcr.io/sage-bionetworks/{projectName}"], + "tags": ["type=edge,branch=main", "type=raw,value=local", "type=sha"] + }, + "push": false + } + }, + "publish-image": { + "executor": "@nx-tools/nx-container:build", + "options": { + "context": "apps/iatlas/api", + "metadata": { + "images": ["ghcr.io/sage-bionetworks/{projectName}"], + "tags": ["type=edge,branch=main", "type=sha"] + }, + "push": true + }, + "dependsOn": ["build-image"] + }, + "scan-image": { + "executor": "nx:run-commands", + "options": { + "command": "trivy image ghcr.io/sage-bionetworks/{projectName}:local --quiet", + "color": true + } + } + }, + "tags": ["type:app", "scope:backend", "language:python"], + "implicitDependencies": [] +} diff --git a/apps/iatlas/api/pyproject.toml b/apps/iatlas/api/pyproject.toml new file mode 100644 index 0000000000..0d23fb8285 --- /dev/null +++ b/apps/iatlas/api/pyproject.toml @@ -0,0 +1,13 @@ +[tool.poetry] +name = "iatlas-api" +version = "0.1.0" +description = "" +authors = ["andrewelamb "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "3.10.13" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/apps/iatlas/api/requirements-dev.txt b/apps/iatlas/api/requirements-dev.txt new file mode 100644 index 0000000000..a5b9e2c10a --- /dev/null +++ b/apps/iatlas/api/requirements-dev.txt @@ -0,0 +1,8 @@ +autopep8==1.5.4 +pylint==2.6.0 +pylint-flask-sqlalchemy==0.2.0 +pytest==6.1.2 +pytest-cov==2.10.1 +pytest-flask-sqlalchemy==1.0.2 +pytest-xdist==2.1.0 +snakeviz==2.1.0 \ No newline at end of file diff --git a/apps/iatlas/api/requirements.txt b/apps/iatlas/api/requirements.txt new file mode 100644 index 0000000000..966bbab582 --- /dev/null +++ b/apps/iatlas/api/requirements.txt @@ -0,0 +1,13 @@ +ariadne==0.13.0 +click==7.1.2 +Flask==1.1.2 +Flask-SQLAlchemy==2.4.3 +graphql-core==3.1.0 +itsdangerous==1.1.0 +Jinja2==2.11.2 +MarkupSafe==1.1.1 +psycopg2-binary==2.8.5 +SQLAlchemy==1.3.17 +starlette==0.13.4 +typing-extensions==3.7.4.2 +Werkzeug==1.0.1 \ No newline at end of file