Skip to content

Commit

Permalink
Merge pull request #62 from bento-platform/features/configure-response
Browse files Browse the repository at this point in the history
Features/configure response
  • Loading branch information
gsfk authored Oct 31, 2023
2 parents d14dfe1 + 8a24dba commit 743af83
Show file tree
Hide file tree
Showing 14 changed files with 309 additions and 215 deletions.
3 changes: 2 additions & 1 deletion bento_beacon/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from .authz.middleware import authz_middleware
from .config_files.config import Config
from .utils.beacon_response import beacon_error_response
from .utils.beacon_request import save_request_data, validate_request
from .utils.beacon_request import save_request_data, validate_request, verify_permissions
from .utils.beacon_response import init_response_data
from .utils.katsu_utils import katsu_censorship_settings

Expand Down Expand Up @@ -99,6 +99,7 @@
@app.before_request
def before_request():
validate_request()
verify_permissions()
save_request_data()
init_response_data()

Expand Down
4 changes: 2 additions & 2 deletions bento_beacon/authz/middleware.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from flask import request
from bento_lib.auth.middleware.flask import FlaskAuthMiddleware
from ..config_files.config import Config
from ..utils.beacon_response import build_response_meta
from ..utils.beacon_response import middleware_meta_callback

__all__ = [
"authz_middleware",
Expand All @@ -19,7 +19,7 @@
authz_middleware = FlaskAuthMiddleware(
Config.AUTHZ_URL,
enabled=Config.AUTHZ_ENABLED,
beacon_meta_callback=build_response_meta,
beacon_meta_callback=middleware_meta_callback,
debug_mode=Config.BENTO_DEBUG
)

Expand Down
40 changes: 35 additions & 5 deletions bento_beacon/config_files/config.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import json
import os

GA4GH_BEACON_REPO_URL = "https://raw.githubusercontent.com/ga4gh-beacon/beacon-v2"


class Config:
BEACON_SPEC_VERSION = "v2.0.0"

# version of this implementation
BENTO_BEACON_VERSION = os.environ.get("BENTO_BEACON_VERSION")

# max granularity for unauthorized users
# default when no requested granularity, as well as max granularity for anonymous users
DEFAULT_GRANULARITY = {
"individuals": "count",
"variants": "count",
Expand Down Expand Up @@ -42,7 +44,7 @@ class Config:
"defaultSchema": {
"id": "ga4gh-beacon-biosample-v2.0.0",
"name": "Default schema for biosamples",
"referenceToSchemaDefinition": "https://github.com/ga4gh-beacon/beacon-v2/blob/main/models/json/beacon-v2-default-model/biosamples/defaultSchema.json",
"referenceToSchemaDefinition": f"{GA4GH_BEACON_REPO_URL}/main/models/json/beacon-v2-default-model/biosamples/defaultSchema.json",
"schemaVersion": "v2.0.0"
},
"partOfSpecification": "Beacon v2.0.0"
Expand All @@ -54,7 +56,7 @@ class Config:
"defaultSchema": {
"id": "ga4gh-beacon-cohort-v2.0.0",
"name": "Default schema for cohorts",
"referenceToSchemaDefinition": "https://raw.githubusercontent.com/ga4gh-beacon/beacon-v2/main/models/json/beacon-v2-default-model/cohorts/defaultSchema.json",
"referenceToSchemaDefinition": f"{GA4GH_BEACON_REPO_URL}/main/models/json/beacon-v2-default-model/cohorts/defaultSchema.json",
"schemaVersion": "v2.0.0"
},
"partOfSpecification": "Beacon v2.0.0"
Expand All @@ -67,7 +69,7 @@ class Config:
"defaultSchema": {
"id": "ga4gh-beacon-dataset-v2.0.0",
"name": "Default schema for datasets",
"referenceToSchemaDefinition": "https://raw.githubusercontent.com/ga4gh-beacon/beacon-v2/main/models/json/beacon-v2-default-model/datasets/defaultSchema.json",
"referenceToSchemaDefinition": f"{GA4GH_BEACON_REPO_URL}/main/models/json/beacon-v2-default-model/datasets/defaultSchema.json",
"schemaVersion": "v2.0.0"
},
"partOfSpecification": "Beacon v2.0.0"
Expand All @@ -92,13 +94,41 @@ class Config:
"defaultSchema": {
"id": "ga4gh-beacon-variant-v2.0.0",
"name": "Default schema for a genomic variation",
"referenceToSchemaDefinition": "https://raw.githubusercontent.com/ga4gh-beacon/beacon-v2/main/models/json/beacon-v2-default-model/genomicVariations/defaultSchema.json",
"referenceToSchemaDefinition": f"{GA4GH_BEACON_REPO_URL}/main/models/json/beacon-v2-default-model/genomicVariations/defaultSchema.json",
"schemaVersion": "v2.0.0"
},
"partOfSpecification": "Beacon v2.0.0"

}
}

INFO_ENDPOINTS_SCHEMAS = {
"/": {
"entityType": "info",
"schema": f"{GA4GH_BEACON_REPO_URL}/main/framework/json/responses/beaconInfoResponse.json"
},
"/configuration": {
"entityType": "configuration",
"schema": f"{GA4GH_BEACON_REPO_URL}/main/framework/json/responses/beaconConfigurationResponse.json"
},
"/entry_types": {
"entityType": "entryType",
"schema": f"{GA4GH_BEACON_REPO_URL}/main/framework/json/responses/beaconEntryTypesResponse.json"
},
"/filtering_terms": {
"entityType": "filteringTerm",
"schema": f"{GA4GH_BEACON_REPO_URL}/main/framework/json/responses/beaconFilteringTermsResponse.json"
},
"/info": {
"entityType": "info",
"schema": f"{GA4GH_BEACON_REPO_URL}/main/framework/json/responses/beaconInfoResponse.json"
},
"/map": {
"entityType": "map",
"schema": f"{GA4GH_BEACON_REPO_URL}/main/framework/json/responses/beaconMapResponse.json"
},
"/overview": {}
}
# -------------------
# katsu

Expand Down
3 changes: 3 additions & 0 deletions bento_beacon/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
GRANULARITY_BOOLEAN = "boolean"
GRANULARITY_COUNT = "count"
GRANULARITY_RECORD = "record"
34 changes: 3 additions & 31 deletions bento_beacon/endpoints/biosamples.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,17 @@
from json import JSONDecodeError
import requests
from flask import Blueprint, jsonify, current_app
from ..utils.exceptions import APIException, NotImplemented
from ..utils.beacon_response import beacon_response, katsu_not_found
from ..utils.beacon_mappings import katsu_biosample_to_beacon_biosample
from flask import Blueprint
from ..utils.exceptions import NotImplemented

biosamples = Blueprint("biosamples", __name__)

biosamples = Blueprint("biosamples", __name__)

# TODO: pass beacon filtering terms as katsu queries

@biosamples.route("/biosamples", methods=['GET', 'POST'])
def get_biosamples(): # TODO: authz
# katsu_response = query_katsu(
# current_app.config["KATSU_BIOSAMPLES_ENDPOINT"])

# current_app.logger.info(katsu_response)
# if katsu_not_found(katsu_response):
# current_app.logger.info("katsu not found")
# return beacon_response([])

# # expect response with properties "count", "next", "previous", "results"
# results = katsu_response.get("results")
# mapped_results = list(map(katsu_biosample_to_beacon_biosample, results))
# return beacon_response(mapped_results)
raise NotImplemented()


@biosamples.route("/biosamples/<id>", methods=['GET', 'POST'])
def get_katsu_biosamples_by_id(id): # TODO: authz
# katsu_response = query_katsu(
# current_app.config["KATSU_BIOSAMPLES_ENDPOINT"], id)

# current_app.logger.info(katsu_response)

# if katsu_not_found(katsu_response):
# current_app.logger.info("katsu returned 'not found'")
# return beacon_response([])

# mapped_repsonse = katsu_biosample_to_beacon_biosample(katsu_response)
# return beacon_response([mapped_repsonse])
raise NotImplemented()


Expand Down
30 changes: 12 additions & 18 deletions bento_beacon/endpoints/cohorts.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,28 @@
from flask import Blueprint, current_app
from ..authz.middleware import authz_middleware
from ..utils.exceptions import NotImplemented
from ..utils.beacon_response import beacon_response
from ..utils.beacon_response import beacon_collections_response

cohorts = Blueprint("cohorts", __name__)


@cohorts.route("/cohorts", methods=['GET', 'POST'])
@authz_middleware.deco_public_endpoint
def get_cohorts():
granularity = current_app.config["DEFAULT_GRANULARITY"]["cohorts"]

cohort = current_app.config["BEACON_COHORT"]
results = {"collections": cohort}
return beacon_response(results, collection_response=True)
cohorts = current_app.config["BEACON_COHORT"]
return beacon_collections_response({"collections": cohorts})


@cohorts.route("/cohorts/<id>", methods=['GET', 'POST'])
@authz_middleware.deco_public_endpoint
def get_cohort_by_id(id):
cohort = current_app.config["BEACON_COHORT"]
response = cohort if id == cohort.get("id") else {}
return beacon_response(response, collection_response=True)


@cohorts.route("/cohorts/<id>/individuals", methods=['GET', 'POST'])
def individuals_by_cohort(id): # TODO: authz
raise NotImplemented()
cohorts = current_app.config["BEACON_COHORT"]
cohort_this_id = next((c for c in cohorts if c.get("id") == id), [])
return beacon_collections_response({"collections": cohort_this_id})


@cohorts.route("/cohorts/<id>/filtering_terms", methods=['GET', 'POST'])
def filtering_terms_by_cohort(id): # TODO: authz
raise NotImplemented()
# -------------------------------------------------------
# endpoints in beacon model not yet implemented:
#
# /cohorts/<id>/individuals
# /cohorts/<id>/filtering_terms
# -------------------------------------------------------
48 changes: 16 additions & 32 deletions bento_beacon/endpoints/datasets.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from flask import Blueprint, current_app
from flask import Blueprint
from ..authz.middleware import authz_middleware
from ..utils.exceptions import NotImplemented
from ..utils.beacon_response import beacon_response
from ..utils.beacon_response import beacon_collections_response
from ..utils.katsu_utils import katsu_datasets
from ..utils.beacon_mappings import katsu_to_beacon_dataset_mapping

Expand All @@ -11,39 +10,24 @@
@datasets.route("/datasets", methods=['GET', 'POST'])
@authz_middleware.deco_public_endpoint # TODO: authz - more flexibility in what is visible (?)
def get_datasets():
granularity = current_app.config["DEFAULT_GRANULARITY"]["datasets"]

k_datasets = katsu_datasets()
datasets_beacon_format = list(
map(katsu_to_beacon_dataset_mapping, k_datasets))
results = {"collections": datasets_beacon_format}
return beacon_response(results, collection_response=True)
datasets_beacon_format = list(map(katsu_to_beacon_dataset_mapping, k_datasets))
return beacon_collections_response({"collections": datasets_beacon_format})


@datasets.route("/datasets/<id>", methods=['GET', 'POST'])
@authz_middleware.deco_public_endpoint # TODO: authz - more flexibility in what is visible (?)
def get_datasets_by_id(id):
k_dataset = katsu_datasets(id)
dataset_beacon_format = katsu_to_beacon_dataset_mapping(
k_dataset) if k_dataset else {}
return beacon_response(dataset_beacon_format, collection_response=True)


@datasets.route("/datasets/<id>/g_variants", methods=['GET', 'POST'])
def variants_by_dataset(id): # TODO: authz
raise NotImplemented()


@datasets.route("/datasets/<id>/biosamples", methods=['GET', 'POST'])
def biosamples_by_dataset(id): # TODO: authz
raise NotImplemented()


@datasets.route("/datasets/<id>/individuals", methods=['GET', 'POST'])
def individuals_by_dataset(id): # TODO: authz
raise NotImplemented()


@datasets.route("/datasets/<id>/filtering_terms", methods=['GET', 'POST'])
def filtering_terms_by_dataset(id): # TODO: authz
raise NotImplemented()
dataset_beacon_format = katsu_to_beacon_dataset_mapping(k_dataset) if k_dataset else []
return beacon_collections_response({"collections": dataset_beacon_format})


# -------------------------------------------------------
# endpoints in beacon model not yet implemented:
#
# /datasets/<id>/g_variants
# /datasets/<id>/biosamples
# /datasets/<id>/individuals
# /datasets/<id>/filtering_terms
# ---------------------------------------------------
29 changes: 15 additions & 14 deletions bento_beacon/endpoints/individuals.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
from flask import Blueprint
from functools import reduce
from ..authz.middleware import authz_middleware, PERMISSION_DOWNLOAD_DATA, PERMISSION_QUERY_DATA, check_permission
from ..authz.middleware import (
authz_middleware,
PERMISSION_DOWNLOAD_DATA,
PERMISSION_QUERY_DATA,
check_permission
)
from ..utils.beacon_request import (
query_parameters_from_request,
summary_stats_requested,
)
from ..utils.beacon_response import (
beacon_response,
add_info_to_response,
add_stats_to_response,
add_overview_stats_to_response,
zero_count_response,
beacon_full_response
build_query_response,
beacon_result_set_response
)
from ..utils.katsu_utils import (
katsu_filters_and_sample_ids_query,
Expand All @@ -27,9 +32,6 @@

@individuals.route("/individuals", methods=['GET', 'POST'])
def get_individuals():

full_response = check_permission(PERMISSION_QUERY_DATA)

variants_query, phenopacket_filters, experiment_filters, config_filters = query_parameters_from_request()

no_query = not (variants_query or phenopacket_filters or experiment_filters or config_filters)
Expand All @@ -43,7 +45,7 @@ def get_individuals():
total_count = katsu_total_individuals_count()
if summary_stats_requested():
add_overview_stats_to_response()
return beacon_response({"count": total_count})
return build_query_response(numTotalResults=total_count)

# ----------------------------------------------------------
# collect biosample ids from variant and experiment search
Expand Down Expand Up @@ -82,14 +84,11 @@ def get_individuals():
if summary_stats_requested():
add_stats_to_response(individual_ids)

if full_response:
return individuals_full_response(individual_ids)

return beacon_response({"count": len(individual_ids), "results": individual_ids})
return build_query_response(ids=individual_ids, full_record_handler=individuals_full_results)


# TODO: pagination (ideally after katsu search gets paginated)
def individuals_full_response(ids):
def individuals_full_results(ids):

# temp
# if len(ids) > 100:
Expand All @@ -100,10 +99,12 @@ def individuals_full_response(ids):
phenopackets_by_result_set = phenopackets_for_ids(ids).get("results", {})
result_ids = list(phenopackets_by_result_set.keys())
result_sets = {}
numTotalResults = 0

for r_id in result_ids:
results_this_id = phenopackets_by_result_set.get(r_id, {}).get("matches", [])
results_count = len(results_this_id)
numTotalResults += results_count
result = {
"id": r_id,
"setType": "individual",
Expand All @@ -116,14 +117,14 @@ def individuals_full_response(ids):
result["resultsHandovers"] = handover_this_id
result_sets[r_id] = result

return beacon_full_response(result_sets)
return result_sets, numTotalResults


@individuals.route("/individuals/<id>", methods=['GET', 'POST'])
@authz_middleware.deco_require_permissions_on_resource({PERMISSION_QUERY_DATA})
def individual_by_id(id):
# forbidden / unauthorized if no permissions
return individuals_full_response([id])
return beacon_result_set_response([id], 1)


# -------------------------------------------------------
Expand Down
Loading

0 comments on commit 743af83

Please sign in to comment.