Skip to content

Commit

Permalink
misc. scoping fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
gsfk committed Dec 11, 2024
1 parent 42cabb8 commit a6648c5
Show file tree
Hide file tree
Showing 7 changed files with 43 additions and 40 deletions.
16 changes: 9 additions & 7 deletions bento_beacon/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from flask import Flask, current_app, request
from urllib.parse import urlunsplit
from .endpoints.info import info
from .endpoints.info_permissions_required import info_permissions_required
from .endpoints.individuals import individuals
from .endpoints.variants import variants
from .endpoints.biosamples import biosamples
Expand Down Expand Up @@ -40,8 +41,8 @@

# blueprints
# always load info endpoints, load everything else based on config

app.register_blueprint(info)
app.register_blueprint(info_permissions_required)

endpoint_blueprints = {
"biosamples": biosamples,
Expand All @@ -66,12 +67,13 @@

@app.before_request
async def before_request():
if request.blueprint != "info":
validate_request()
await verify_permissions()
await save_request_data()
await set_censorship()
init_response_data()
if request.blueprint == "info":
return
validate_request()
await verify_permissions()
await save_request_data()
await set_censorship()
init_response_data()


@app.errorhandler(Exception)
Expand Down
2 changes: 1 addition & 1 deletion bento_beacon/config_files/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ class Config:
KATSU_BASE_URL = os.environ.get("KATSU_BASE_URL")
KATSU_BIOSAMPLES_ENDPOINT = "/api/biosamples"
KATSU_INDIVIDUALS_ENDPOINT = "/api/individuals"
KATSU_BATCH_INDIVIDUALS_ENDPOINT = "/api/batch/individuals"
KATSU_PROJECTS_ENDPOINT = "/api/projects"
KATSU_DATASETS_ENDPOINT = "/api/datasets"
KATSU_SEARCH_ENDPOINT = "/private/search"
KATSU_RESOURCES_ENDPOINT = "/api/resources"
Expand Down
7 changes: 2 additions & 5 deletions bento_beacon/endpoints/info.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
from flask import Blueprint, current_app
from ..authz.middleware import authz_middleware
from ..utils.beacon_response import beacon_info_response, add_info_to_response, summary_stats
from ..utils.beacon_response import beacon_info_response
from ..utils.katsu_utils import (
get_filtering_terms,
katsu_total_individuals_count,
katsu_get,
katsu_datasets,
)
from ..utils.gohan_utils import gohan_counts_for_overview
from ..utils.scope import scoped_route_decorator_for_blueprint


Expand Down Expand Up @@ -42,7 +39,7 @@ async def service_info(project_id=None):
async def beacon_info(project_id=None):
"""
service info in beacon format
dataset info is scoped, rest of response unscoped
description field is scoped, rest of response unscoped
"""
return beacon_info_response(await beacon_format_service_details(project_id))

Expand Down
18 changes: 7 additions & 11 deletions bento_beacon/endpoints/info_permissions_required.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from flask import Blueprint, current_app
from ..authz.middleware import authz_middleware
from .info import beacon_format_service_details
from .info import beacon_format_service_details
from ..utils.beacon_response import beacon_info_response, add_info_to_response, summary_stats
from ..utils.katsu_utils import (
get_filtering_terms,
Expand All @@ -15,29 +15,26 @@


@route_with_optional_project_id("/info")
@authz_middleware.deco_public_endpoint
async def beacon_info_with_overview(project_id=None):
"""
as above but with beacon overview details
unscoped, overview details currently node-level only
returns same beacon-format service info served from root endpoint, but with an overview added
overview is unscoped, no overview info is returned for scoped requests
description field is scoped (this is pulled from datasets data)
"""

service_info = await beacon_format_service_details(project_id)
return beacon_info_response({**service_info, "overview": await overview()})


@route_with_optional_project_id("/overview")
@authz_middleware.deco_public_endpoint
async def beacon_overview(project_id=None):
"""
Custom endpoint not in beacon spec
currently node-level overview only
currently node-level overview only
"""
service_info = await beacon_format_service_details(project_id)
return beacon_info_response({**service_info, "overview": await overview(project_id)})


# xxxxxxxxxxxxxxxx move this to its own blueprint, since this now looks at permissions xxxxxxxxxxxxxxxxx
# could be scoped by dataset only by adding query params for dataset (endpoint is GET only)
# this endpoint normally doesn't take queries at all, the only params it recognizes are for pagination
# could also annotating filtering terms with a dataset id in cases where it's limited to a particular dataset
Expand All @@ -54,7 +51,7 @@ async def filtering_terms(project_id=None):


# -------------------------------------------------------
#
#
# -------------------------------------------------------


Expand All @@ -72,11 +69,10 @@ async def overview(project_id=None, dataset_id=None):
else:
variants = {}

# can be removed once bento_public is updated (it reads from counts.variants for assemblyIDs)
# can be removed once bento_public and beacon network nodes are updated (it reads from counts.variants for assemblyIDs)
legacy_format_overview = {"counts": {"individuals": await katsu_total_individuals_count(), "variants": variants}}

katsu_stats = await summary_stats(None)
beacon_overview = {"variants": variants, **katsu_stats}

return {**legacy_format_overview, **beacon_overview}

26 changes: 17 additions & 9 deletions bento_beacon/utils/beacon_response.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from flask import current_app, g, request
from flask import current_app, g, request, url_for
from .katsu_utils import search_summary_statistics, overview_statistics
from .censorship import (
get_censorship_threshold,
Expand Down Expand Up @@ -28,23 +28,30 @@ def add_message(message_obj):

def add_no_results_censorship_message_to_response():
add_info_to_response(MESSAGE_FOR_CENSORED_QUERY_WITH_NO_RESULTS)
add_info_to_response(f"censorship threshold: {current_app.config['COUNT_THRESHOLD']}")
add_info_to_response(f"censorship threshold: {g.count_threshold}")


async def add_stats_to_response(ids):
stats = await summary_stats(ids)
if stats:
g.response_info["bento"] = await summary_stats(ids)


async def add_overview_stats_to_response():
await add_stats_to_response(None)


async def summary_stats(ids):
if ids is not None and len(ids) <= (await get_censorship_threshold()):
return
return None

if ids is None:
stats = await overview_statistics()
else:
stats = await search_summary_statistics(ids)
packaged_stats = await package_biosample_and_experiment_stats(stats)
g.response_info["bento"] = packaged_stats

return packaged_stats

async def add_overview_stats_to_response():
await add_stats_to_response(None)


async def package_biosample_and_experiment_stats(stats):
Expand Down Expand Up @@ -137,7 +144,7 @@ async def build_query_response(ids=None, numTotalResults=None, full_record_handl
if full_record_handler is None:
# user asked for full response where it doesn't exist yet, e.g. in variants
raise InvalidQuery("record response not available for this entry type")
result_sets, numTotalResults = full_record_handler(ids)
result_sets, numTotalResults = await full_record_handler(ids)
return beacon_result_set_response(result_sets, numTotalResults)


Expand Down Expand Up @@ -260,7 +267,8 @@ def response_info():


def info_endpoint_schema():
return [current_app.config["INFO_ENDPOINTS_SCHEMAS"][request.path]]
path_without_optional_project_id = url_for(request.endpoint)
return [current_app.config["INFO_ENDPOINTS_SCHEMAS"][path_without_optional_project_id]]


def schemas_this_query():
Expand Down
2 changes: 1 addition & 1 deletion bento_beacon/utils/censorship.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ async def reject_if_too_many_filters() -> None:
if g.permission_query_data:
return
max_filters = await get_max_filters()
request_filters = g.get("request_data", {}).get("filters")
request_filters = g.get("request_data", {}).get("filters", [])
if len(request_filters) > max_filters:
raise InvalidQuery(f"too many filters in request, maximum of {max_filters} permitted")

Expand Down
12 changes: 6 additions & 6 deletions bento_beacon/utils/scope.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
def scoped_route_decorator_generator(blueprint):
def scoped_route_decorator_for_blueprint(blueprint):
"""
# Generates a decorator equivalent to two flask "@route" decorators, one with a project_id prefix, and one without
Generates a decorator equivalent to two flask "@route" decorators, one with a project_id prefix, and one without.
# input: a flask blueprint (all beacon routes belong to a particular blueprint)
input: a flask blueprint (all beacon routes belong to a particular blueprint)
# output: a decorator that's equivalent to invoking these two flask decorators together:
# @my_blueprint.route("my_route", **options)
# @my_blueprint.route("/<project_id>/my_route", **options)
output: a decorator that's equivalent to invoking these two flask decorators together:
@my_blueprint.route("my_route", **options)
@my_blueprint.route("/<project_id>/my_route", **options)
"""

def scoped_route(rule, **options):
Expand Down

0 comments on commit a6648c5

Please sign in to comment.