Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MNT] Switched to lifespan events #287

Closed
wants to merge 8 commits into from
108 changes: 52 additions & 56 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import os
import warnings
from contextlib import asynccontextmanager
from pathlib import Path
from tempfile import TemporaryDirectory

Expand All @@ -14,8 +15,58 @@
from .api import utility as util
from .api.routers import attributes, query


@asynccontextmanager
async def lifespan(app: FastAPI):
"""
Load and set up resources before the application starts receiving requests.
Clean up resources after the application finishes handling requests.
"""
# Check if username and password environment variables are set
if (
# TODO: Check if this error is still raised when variables are empty strings
os.environ.get(util.GRAPH_USERNAME.name) is None
or os.environ.get(util.GRAPH_PASSWORD.name) is None
):
raise RuntimeError(
f"The application was launched but could not find the {util.GRAPH_USERNAME.name} and / or {util.GRAPH_PASSWORD.name} environment variables."
)

# Raises warning if allowed origins environment variable has not been set or is an empty string.
if os.environ.get(util.ALLOWED_ORIGINS.name, "") == "":
warnings.warn(
f"The API was launched without providing any values for the {util.ALLOWED_ORIGINS.name} environment variable. "
"This means that the API will only be accessible from the same origin it is hosted from: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy. "
f"If you want to access the API from tools hosted at other origins such as the Neurobagel query tool, explicitly set the value of {util.ALLOWED_ORIGINS.name} to the origin(s) of these tools (e.g. http://localhost:3000). "
"Multiple allowed origins should be separated with spaces in a single string enclosed in quotes. "
)

# We use Starlette's ability (FastAPI is Starlette underneath) to store arbitrary state on the app instance (https://www.starlette.io/applications/#storing-state-on-the-app-instance)
# to store a temporary directory object and its corresponding path. These data are local to the instance and will be recreated on every app launch (i.e. not persisted).
app.state.vocab_dir = TemporaryDirectory()
app.state.vocab_dir_path = Path(app.state.vocab_dir.name)

# TODO: Maybe store these paths in one dictionary on the app instance instead of separate variables?
app.state.cogatlas_term_lookup_path = (
app.state.vocab_dir_path / "cogatlas_task_term_labels.json"
)
app.state.snomed_term_lookup_path = (
app.state.vocab_dir_path / "snomedct_disorder_term_labels.json"
)

util.fetch_and_save_cogatlas(app.state.cogatlas_term_lookup_path)
util.create_snomed_term_lookup(app.state.snomed_term_lookup_path)

yield
# Clean up the temporary directory created on startup
app.state.vocab_dir.cleanup()


app = FastAPI(
default_response_class=ORJSONResponse, docs_url=None, redoc_url=None
default_response_class=ORJSONResponse,
docs_url=None,
redoc_url=None,
lifespan=lifespan,
)
favicon_url = "https://raw.githubusercontent.com/neurobagel/documentation/main/docs/imgs/logo/neurobagel_favicon.png"

Expand Down Expand Up @@ -75,61 +126,6 @@ def overridden_redoc():
)


@app.on_event("startup")
async def auth_check():
"""Checks whether username and password environment variables are set."""
if (
# TODO: Check if this error is still raised when variables are empty strings
os.environ.get(util.GRAPH_USERNAME.name) is None
or os.environ.get(util.GRAPH_PASSWORD.name) is None
):
raise RuntimeError(
f"The application was launched but could not find the {util.GRAPH_USERNAME.name} and / or {util.GRAPH_PASSWORD.name} environment variables."
)


@app.on_event("startup")
async def allowed_origins_check():
"""Raises warning if allowed origins environment variable has not been set or is an empty string."""
if os.environ.get(util.ALLOWED_ORIGINS.name, "") == "":
warnings.warn(
f"The API was launched without providing any values for the {util.ALLOWED_ORIGINS.name} environment variable. "
"This means that the API will only be accessible from the same origin it is hosted from: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy. "
f"If you want to access the API from tools hosted at other origins such as the Neurobagel query tool, explicitly set the value of {util.ALLOWED_ORIGINS.name} to the origin(s) of these tools (e.g. http://localhost:3000). "
"Multiple allowed origins should be separated with spaces in a single string enclosed in quotes. "
)


@app.on_event("startup")
async def fetch_vocabularies_to_temp_dir():
"""
Create and store on the app instance a temporary directory for vocabulary term lookup JSON files
(each of which contain key-value pairings of IDs to human-readable names of terms),
and then fetch vocabularies using their respective native APIs and save them to the temporary directory for reuse.
"""
# We use Starlette's ability (FastAPI is Starlette underneath) to store arbitrary state on the app instance (https://www.starlette.io/applications/#storing-state-on-the-app-instance)
# to store a temporary directory object and its corresponding path. These data are local to the instance and will be recreated on every app launch (i.e. not persisted).
app.state.vocab_dir = TemporaryDirectory()
app.state.vocab_dir_path = Path(app.state.vocab_dir.name)

# TODO: Maybe store these paths in one dictionary on the app instance instead of separate variables?
app.state.cogatlas_term_lookup_path = (
app.state.vocab_dir_path / "cogatlas_task_term_labels.json"
)
app.state.snomed_term_lookup_path = (
app.state.vocab_dir_path / "snomedct_disorder_term_labels.json"
)

util.fetch_and_save_cogatlas(app.state.cogatlas_term_lookup_path)
util.create_snomed_term_lookup(app.state.snomed_term_lookup_path)


@app.on_event("shutdown")
async def cleanup_temp_vocab_dir():
"""Clean up the temporary directory created on startup."""
app.state.vocab_dir.cleanup()


app.include_router(query.router)
app.include_router(attributes.router)

Expand Down