From 45de3f337dcda49394cff800ff4a0064376432fd Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Mon, 19 Apr 2021 17:15:14 +0200 Subject: [PATCH 1/5] Add minimal fast_app for the ToolShed --- lib/tool_shed/webapp/fast_app.py | 9 +++++++++ lib/tool_shed/webapp/fast_factory.py | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 lib/tool_shed/webapp/fast_app.py create mode 100644 lib/tool_shed/webapp/fast_factory.py diff --git a/lib/tool_shed/webapp/fast_app.py b/lib/tool_shed/webapp/fast_app.py new file mode 100644 index 00000000000..04137b0a326 --- /dev/null +++ b/lib/tool_shed/webapp/fast_app.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI +from fastapi.middleware.wsgi import WSGIMiddleware + + +def initialize_fast_app(gx_webapp): + app = FastAPI() + wsgi_handler = WSGIMiddleware(gx_webapp) + app.mount('/', wsgi_handler) + return app diff --git a/lib/tool_shed/webapp/fast_factory.py b/lib/tool_shed/webapp/fast_factory.py new file mode 100644 index 00000000000..c1ca38b42eb --- /dev/null +++ b/lib/tool_shed/webapp/fast_factory.py @@ -0,0 +1,27 @@ +import os + +from galaxy.util.properties import find_config_file +from galaxy.web_stack import get_app_kwds +from tool_shed.webapp.buildapp import app_factory +from .fast_app import initialize_fast_app + +APP_NAME = "tool_shed" + + +def factory(): + kwds = get_app_kwds(APP_NAME, APP_NAME) + config_file = kwds.get("config_file") + if not config_file and os.environ.get('TOOL_SHED_CONFIG_FILE'): + config_file = os.path.abspath(os.environ["TOOL_SHED_CONFIG_FILE"]) + else: + config_file = find_config_file([APP_NAME]) + + config_section = APP_NAME + + if 'config_file' not in kwds: + kwds['config_file'] = config_file + if 'config_section' not in kwds: + kwds['config_section'] = config_section + global_conf = {} + gx_webapp = app_factory(global_conf=global_conf, load_app_kwds=kwds, wsgi_preflight=False) + return initialize_fast_app(gx_webapp) From 6ffa07d1bbc4db2df8bd3235afe9c230b701415f Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Mon, 19 Apr 2021 18:20:42 +0200 Subject: [PATCH 2/5] Add minimal fast_app for the reports app --- lib/galaxy/webapps/reports/app.py | 10 +++++++- lib/galaxy/webapps/reports/fast_app.py | 9 ++++++++ lib/galaxy/webapps/reports/fast_factory.py | 27 ++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 lib/galaxy/webapps/reports/fast_app.py create mode 100644 lib/galaxy/webapps/reports/fast_factory.py diff --git a/lib/galaxy/webapps/reports/app.py b/lib/galaxy/webapps/reports/app.py index 59c7425d1a4..71fcfcbbf88 100644 --- a/lib/galaxy/webapps/reports/app.py +++ b/lib/galaxy/webapps/reports/app.py @@ -4,17 +4,21 @@ import time import galaxy.model from galaxy.config import configure_logging +from galaxy.model.base import SharedModelMapping from galaxy.security import idencoding +from galaxy.structured_app import BasicApp from galaxy.web_stack import application_stack_instance from . import config log = logging.getLogger(__name__) -class UniverseApplication: +class UniverseApplication(BasicApp): """Encapsulates the state of a Universe application""" def __init__(self, **kwargs): + super().__init__() + self[BasicApp] = self log.debug("python path is: %s", ", ".join(sys.path)) self.name = "reports" # Read config file and check for errors @@ -38,6 +42,10 @@ class UniverseApplication: self.targets_mysql = 'mysql' in self.config.database_connection # Security helper self.security = idencoding.IdEncodingHelper(id_secret=self.config.id_secret) + + self._register_singleton(idencoding.IdEncodingHelper, self.security) + self._register_singleton(SharedModelMapping, self.model) + # used for cachebusting -- refactor this into a *SINGLE* UniverseApplication base. self.server_starttime = int(time.time()) diff --git a/lib/galaxy/webapps/reports/fast_app.py b/lib/galaxy/webapps/reports/fast_app.py new file mode 100644 index 00000000000..04137b0a326 --- /dev/null +++ b/lib/galaxy/webapps/reports/fast_app.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI +from fastapi.middleware.wsgi import WSGIMiddleware + + +def initialize_fast_app(gx_webapp): + app = FastAPI() + wsgi_handler = WSGIMiddleware(gx_webapp) + app.mount('/', wsgi_handler) + return app diff --git a/lib/galaxy/webapps/reports/fast_factory.py b/lib/galaxy/webapps/reports/fast_factory.py new file mode 100644 index 00000000000..639a4140439 --- /dev/null +++ b/lib/galaxy/webapps/reports/fast_factory.py @@ -0,0 +1,27 @@ +import os + +from galaxy.util.properties import find_config_file +from galaxy.web_stack import get_app_kwds +from galaxy.webapps.reports.buildapp import app_factory +from .fast_app import initialize_fast_app + +APP_NAME = "reports" + + +def factory(): + kwds = get_app_kwds(APP_NAME, APP_NAME) + config_file = kwds.get("config_file") + if not config_file and os.environ.get('GALAXY_REPORTS_CONFIG'): + config_file = os.path.abspath(os.environ["GALAXY_REPORTS_CONFIG"]) + else: + config_file = find_config_file([APP_NAME]) + + config_section = APP_NAME + + if 'config_file' not in kwds: + kwds['config_file'] = config_file + if 'config_section' not in kwds: + kwds['config_section'] = config_section + global_conf = {} + gx_webapp = app_factory(global_conf=global_conf, load_app_kwds=kwds, wsgi_preflight=False) + return initialize_fast_app(gx_webapp) From 4d6f9358f7c16d4e2ff0ccb13f3cc26a735b8914 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Tue, 20 Apr 2021 11:33:22 +0200 Subject: [PATCH 3/5] Add basic FastAPI metatada to entry points --- lib/galaxy/webapps/reports/fast_app.py | 9 ++++++++- lib/tool_shed/webapp/fast_app.py | 8 +++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/webapps/reports/fast_app.py b/lib/galaxy/webapps/reports/fast_app.py index 04137b0a326..6ac6624a389 100644 --- a/lib/galaxy/webapps/reports/fast_app.py +++ b/lib/galaxy/webapps/reports/fast_app.py @@ -3,7 +3,14 @@ from fastapi.middleware.wsgi import WSGIMiddleware def initialize_fast_app(gx_webapp): - app = FastAPI() + app = FastAPI( + title="Galaxy Reports API", + description=( + "This API will give you insights into the Galaxy instance's usage and load. " + "It aims to provide data about users, jobs, workflows, disk space, and much more." + ), + docs_url="/api/docs", + ) wsgi_handler = WSGIMiddleware(gx_webapp) app.mount('/', wsgi_handler) return app diff --git a/lib/tool_shed/webapp/fast_app.py b/lib/tool_shed/webapp/fast_app.py index 04137b0a326..a52863417a1 100644 --- a/lib/tool_shed/webapp/fast_app.py +++ b/lib/tool_shed/webapp/fast_app.py @@ -3,7 +3,13 @@ from fastapi.middleware.wsgi import WSGIMiddleware def initialize_fast_app(gx_webapp): - app = FastAPI() + app = FastAPI( + title="Galaxy Tool Shed API", + description=( + "This API allows you to manage the Tool Shed repositories." + ), + docs_url="/api/docs", + ) wsgi_handler = WSGIMiddleware(gx_webapp) app.mount('/', wsgi_handler) return app From 2dd7d1dc57365328f9fcaf2b58ca44c6896c2c6e Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Tue, 20 Apr 2021 17:11:11 +0200 Subject: [PATCH 4/5] Unify fast_factory config resolution --- lib/galaxy/main_config.py | 85 +++++++++++++++++++++- lib/galaxy/webapps/galaxy/fast_factory.py | 42 +++-------- lib/galaxy/webapps/reports/fast_factory.py | 67 ++++++++++++----- lib/tool_shed/webapp/fast_factory.py | 68 ++++++++++++----- 4 files changed, 188 insertions(+), 74 deletions(-) diff --git a/lib/galaxy/main_config.py b/lib/galaxy/main_config.py index c1f981ab148..6c433cfe460 100644 --- a/lib/galaxy/main_config.py +++ b/lib/galaxy/main_config.py @@ -3,12 +3,24 @@ This is for use by web framework code and scripts (e.g. scripts/galaxy_main.py). """ import os +from typing import ( + List, + NamedTuple, + Optional, +) + +from galaxy.util.properties import find_config_file +from galaxy.web_stack import get_app_kwds + -DEFAULT_INIS = ["config/galaxy.yml", "config/galaxy.ini", "universe_wsgi.ini", "config/galaxy.yml.sample"] DEFAULT_INI_APP = "main" DEFAULT_CONFIG_SECTION = "galaxy" +def default_relative_config_paths_for(app_name: str) -> List[str]: + return [f"config/{app_name}.yml", f"config/{app_name}.ini", "universe_wsgi.ini", f"config/{app_name}.yml.sample"] + + def absolute_config_path(path, galaxy_root): if path and not os.path.isabs(path): path = os.path.join(galaxy_root, path) @@ -19,18 +31,83 @@ def config_is_ini(config_file): return config_file and (config_file.endswith('.ini') or config_file.endswith('.ini.sample')) -def find_config(supplied_config, galaxy_root): +def find_config(supplied_config, galaxy_root, app_name="galaxy"): if supplied_config: return supplied_config if galaxy_root is None: - return os.path.abspath('galaxy.yml') + return os.path.abspath(f'{app_name}.yml') # If not explicitly supplied an config, check galaxy.ini and then # just resort to sample if that has not been configured. - for guess in DEFAULT_INIS: + guess = None + for guess in default_relative_config_paths_for(app_name): config_path = os.path.join(galaxy_root, guess) if os.path.exists(config_path): return config_path return guess + + +class WebappSetupProps(NamedTuple): + """Basic properties to provide information about the App and the environment variables + used to resolve the App configuration.""" + app_name: str + default_section_name: str + env_config_file: str + env_config_section: Optional[str] = None + check_galaxy_root: bool = False + + +class WebappConfig(NamedTuple): + """The resolved configuration values for a Webapp.""" + global_conf: dict + load_app_kwds: dict + wsgi_preflight: bool = False + + +class WebappConfigResolver: + + def __init__(self, props: WebappSetupProps) -> None: + self.props = props + self.app_kwds = get_app_kwds(props.default_section_name, props.app_name) + self.config_file = self._resolve_config_file_path() + self.is_ini_file = config_is_ini(self.config_file) + self.config_section = self._resolve_section_name() + self._update_kwds() + + def resolve_config(self) -> WebappConfig: + global_conf = {} + if self.is_ini_file: + global_conf["__file__"] = self.config_file + + return WebappConfig(global_conf=global_conf, load_app_kwds=self.app_kwds) + + def _resolve_config_file_path(self) -> str: + config_file = self.app_kwds.get("config_file") + if not config_file and os.environ.get(self.props.env_config_file): + config_file = os.path.abspath(os.environ[self.props.env_config_file]) + elif self.props.check_galaxy_root: + galaxy_root = self.app_kwds.get("galaxy_root") or os.environ.get("GALAXY_ROOT_DIR") + config_file = find_config(config_file, galaxy_root, app_name=self.props.app_name) + config_file = absolute_config_path(config_file, galaxy_root=galaxy_root) + else: + config_file = find_config_file([self.props.app_name]) + + if not config_file or not os.path.exists(config_file): + raise FileNotFoundError(f"Can not find a configuration file for {self.props.app_name}") + return config_file + + def _resolve_section_name(self) -> str: + config_section = self.props.default_section_name + if self.props.env_config_section and self.props.env_config_section in os.environ: + config_section = os.environ[self.props.env_config_section] + elif self.is_ini_file: + config_section = f"app:{DEFAULT_INI_APP}" + return config_section + + def _update_kwds(self) -> None: + if "config_file" not in self.app_kwds: + self.app_kwds["config_file"] = self.config_file + if "config_section" not in self.app_kwds: + self.app_kwds["config_section"] = self.config_section diff --git a/lib/galaxy/webapps/galaxy/fast_factory.py b/lib/galaxy/webapps/galaxy/fast_factory.py index 0e1c95526eb..cf386d985d4 100644 --- a/lib/galaxy/webapps/galaxy/fast_factory.py +++ b/lib/galaxy/webapps/galaxy/fast_factory.py @@ -40,43 +40,25 @@ with the following command-line. gunicorn 'galaxy.webapps.galaxy.fast_factory:factory()' --env GALAXY_CONFIG_FILE=config/galaxy.ini --pythonpath lib -w 4 -k uvicorn.workers.UvicornWorker """ -import os from galaxy.main_config import ( - absolute_config_path, - config_is_ini, DEFAULT_CONFIG_SECTION, - DEFAULT_INI_APP, - find_config, + WebappConfigResolver, + WebappSetupProps, ) -from galaxy.web_stack import get_app_kwds from galaxy.webapps.galaxy.buildapp import app_pair from .fast_app import initialize_fast_app def factory(): - kwds = get_app_kwds("galaxy", "galaxy") - config_file = kwds.get("config_file") - if not config_file and os.environ.get('GALAXY_CONFIG_FILE'): - config_file = os.path.abspath(os.environ["GALAXY_CONFIG_FILE"]) - else: - galaxy_root = kwds.get("galaxy_root") or os.environ.get("GALAXY_ROOT_DIR") - config_file = find_config(config_file, galaxy_root) - config_file = absolute_config_path(config_file, galaxy_root=galaxy_root) - - if "GALAXY_CONFIG_SECTION" in os.environ: - config_section = os.environ["GALAXY_CONFIG_SECTION"] - elif config_is_ini(config_file): - config_section = "app:%s" % DEFAULT_INI_APP - else: - config_section = DEFAULT_CONFIG_SECTION - - if 'config_file' not in kwds: - kwds['config_file'] = config_file - if 'config_section' not in kwds: - kwds['config_section'] = config_section - global_conf = {} - if config_is_ini(config_file): - global_conf["__file__"] = config_file - gx_webapp, gx_app = app_pair(global_conf=global_conf, load_app_kwds=kwds, wsgi_preflight=False) + props = WebappSetupProps( + app_name='galaxy', + default_section_name=DEFAULT_CONFIG_SECTION, + env_config_file='GALAXY_CONFIG_FILE', + env_config_section='GALAXY_CONFIG_SECTION', + check_galaxy_root=True + ) + config_provider = WebappConfigResolver(props) + config = config_provider.resolve_config() + gx_webapp, gx_app = app_pair(global_conf=config.global_conf, load_app_kwds=config.load_app_kwds, wsgi_preflight=config.wsgi_preflight) return initialize_fast_app(gx_webapp, gx_app) diff --git a/lib/galaxy/webapps/reports/fast_factory.py b/lib/galaxy/webapps/reports/fast_factory.py index 639a4140439..9158be2604e 100644 --- a/lib/galaxy/webapps/reports/fast_factory.py +++ b/lib/galaxy/webapps/reports/fast_factory.py @@ -1,27 +1,54 @@ -import os +"""Module containing factory class for building uvicorn app for the Galaxy Tool Shed. -from galaxy.util.properties import find_config_file -from galaxy.web_stack import get_app_kwds +Information on uvicorn, its various settings, and how to invoke it can +be found at https://www.uvicorn.org/. + +The Galaxy Tool Shed can be launched with uvicorn using the following invocation: + +:: + + uvicorn --app-dir lib --factory galaxy.webapps.reports.fast_factory:factory + +Use the environment variable ``GALAXY_REPORTS_CONFIG`` to specify a Galaxy Reports +configuration file. + +:: + + GALAXY_REPORTS_CONFIG=config/reports.yml uvicorn --app-dir lib --factory galaxy.webapps.reports.fast_factory:factory + +.. note:: + + Information on additional ways to configure uvicorn can be found at + https://www.uvicorn.org/. + + +`Gunicorn `__ is a server with +more complex management options. + +This factory function can be executed as a uvicorn worker managed with gunicorn +with the following command-line. + +:: + + gunicorn 'galaxy.webapps.reports.fast_factory:factory()' --env GALAXY_REPORTS_CONFIG=config/reports.yml --pythonpath lib -w 4 -k uvicorn.workers.UvicornWorker + +""" + +from galaxy.main_config import ( + WebappConfigResolver, + WebappSetupProps +) from galaxy.webapps.reports.buildapp import app_factory from .fast_app import initialize_fast_app -APP_NAME = "reports" - def factory(): - kwds = get_app_kwds(APP_NAME, APP_NAME) - config_file = kwds.get("config_file") - if not config_file and os.environ.get('GALAXY_REPORTS_CONFIG'): - config_file = os.path.abspath(os.environ["GALAXY_REPORTS_CONFIG"]) - else: - config_file = find_config_file([APP_NAME]) - - config_section = APP_NAME - - if 'config_file' not in kwds: - kwds['config_file'] = config_file - if 'config_section' not in kwds: - kwds['config_section'] = config_section - global_conf = {} - gx_webapp = app_factory(global_conf=global_conf, load_app_kwds=kwds, wsgi_preflight=False) + props = WebappSetupProps( + app_name='reports', + default_section_name='reports', + env_config_file='GALAXY_REPORTS_CONFIG', + ) + config_provider = WebappConfigResolver(props) + config = config_provider.resolve_config() + gx_webapp = app_factory(global_conf=config.global_conf, load_app_kwds=config.load_app_kwds, wsgi_preflight=config.wsgi_preflight) return initialize_fast_app(gx_webapp) diff --git a/lib/tool_shed/webapp/fast_factory.py b/lib/tool_shed/webapp/fast_factory.py index c1ca38b42eb..e8cf8480f90 100644 --- a/lib/tool_shed/webapp/fast_factory.py +++ b/lib/tool_shed/webapp/fast_factory.py @@ -1,27 +1,55 @@ -import os +"""Module containing factory class for building uvicorn app for the Galaxy Tool Shed. -from galaxy.util.properties import find_config_file -from galaxy.web_stack import get_app_kwds +Information on uvicorn, its various settings, and how to invoke it can +be found at https://www.uvicorn.org/. + +The Galaxy Tool Shed can be launched with uvicorn using the following invocation: + +:: + + uvicorn --app-dir lib --factory tool_shed.webapp.fast_factory:factory + +Use the environment variable ``TOOL_SHED_CONFIG_FILE`` to specify a Tool Shed +configuration file. + +:: + + TOOL_SHED_CONFIG_FILE=config/tool_shed.yml uvicorn --app-dir lib --factory tool_shed.webapp.fast_factory:factory + +.. note:: + + Information on additional ways to configure uvicorn can be found at + https://www.uvicorn.org/. + + +`Gunicorn `__ is a server with +more complex management options. + +This factory function can be executed as a uvicorn worker managed with gunicorn +with the following command-line. + +:: + + gunicorn 'tool_shed.webapp.fast_factory:factory()' --env TOOL_SHED_CONFIG_FILE=config/tool_shed.yml --pythonpath lib -w 4 -k uvicorn.workers.UvicornWorker + +""" + +from galaxy.main_config import ( + WebappConfigResolver, + WebappSetupProps +) from tool_shed.webapp.buildapp import app_factory +from tool_shed.webapp.config import TOOLSHED_APP_NAME from .fast_app import initialize_fast_app -APP_NAME = "tool_shed" - def factory(): - kwds = get_app_kwds(APP_NAME, APP_NAME) - config_file = kwds.get("config_file") - if not config_file and os.environ.get('TOOL_SHED_CONFIG_FILE'): - config_file = os.path.abspath(os.environ["TOOL_SHED_CONFIG_FILE"]) - else: - config_file = find_config_file([APP_NAME]) - - config_section = APP_NAME - - if 'config_file' not in kwds: - kwds['config_file'] = config_file - if 'config_section' not in kwds: - kwds['config_section'] = config_section - global_conf = {} - gx_webapp = app_factory(global_conf=global_conf, load_app_kwds=kwds, wsgi_preflight=False) + props = WebappSetupProps( + app_name=TOOLSHED_APP_NAME, + default_section_name=TOOLSHED_APP_NAME, + env_config_file='TOOL_SHED_CONFIG_FILE', + ) + config_provider = WebappConfigResolver(props) + config = config_provider.resolve_config() + gx_webapp = app_factory(global_conf=config.global_conf, load_app_kwds=config.load_app_kwds, wsgi_preflight=config.wsgi_preflight) return initialize_fast_app(gx_webapp) From db464b1c93f8fa7b73884236927dab29871f7981 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Tue, 20 Apr 2021 18:03:44 +0200 Subject: [PATCH 5/5] Move common FastAPI configuration code to webapps/base/api.py --- lib/galaxy/webapps/base/api.py | 49 +++++++++++++++++++++ lib/galaxy/webapps/galaxy/fast_app.py | 51 ++++------------------ lib/galaxy/webapps/reports/api/__init__.py | 0 lib/galaxy/webapps/reports/fast_app.py | 9 ++++ lib/tool_shed/webapp/fast_app.py | 9 ++++ 5 files changed, 75 insertions(+), 43 deletions(-) create mode 100644 lib/galaxy/webapps/base/api.py create mode 100644 lib/galaxy/webapps/reports/api/__init__.py diff --git a/lib/galaxy/webapps/base/api.py b/lib/galaxy/webapps/base/api.py new file mode 100644 index 00000000000..06d9fb98bd8 --- /dev/null +++ b/lib/galaxy/webapps/base/api.py @@ -0,0 +1,49 @@ +from fastapi import FastAPI, Request +from fastapi.exceptions import RequestValidationError +from fastapi.responses import JSONResponse +from starlette.responses import Response +try: + from starlette_context.middleware import RawContextMiddleware + from starlette_context.plugins import RequestIdPlugin +except ImportError: + pass + +from galaxy.exceptions import MessageException +from galaxy.web.framework.base import walk_controller_modules +from galaxy.web.framework.decorators import ( + api_error_message, + validation_error_to_message_exception +) + + +def add_exception_handler( + app: FastAPI +) -> None: + + @app.exception_handler(RequestValidationError) + async def validate_exception_middleware(request: Request, exc: RequestValidationError) -> Response: + exc = validation_error_to_message_exception(exc) + error_dict = api_error_message(None, exception=exc) + return JSONResponse( + status_code=400, + content=error_dict + ) + + @app.exception_handler(MessageException) + async def message_exception_middleware(request: Request, exc: MessageException) -> Response: + error_dict = api_error_message(None, exception=exc) + return JSONResponse( + status_code=exc.status_code, + content=error_dict + ) + + +def add_request_id_middleware(app: FastAPI): + app.add_middleware(RawContextMiddleware, plugins=(RequestIdPlugin(force_new_uuid=True),)) + + +def include_all_package_routers(app: FastAPI, package_name: str): + for _, module in walk_controller_modules(package_name): + router = getattr(module, "router", None) + if router: + app.include_router(router) diff --git a/lib/galaxy/webapps/galaxy/fast_app.py b/lib/galaxy/webapps/galaxy/fast_app.py index 954d9c45b1c..9e9a9983dba 100644 --- a/lib/galaxy/webapps/galaxy/fast_app.py +++ b/lib/galaxy/webapps/galaxy/fast_app.py @@ -1,20 +1,12 @@ from fastapi import FastAPI, Request -from fastapi.exceptions import RequestValidationError from fastapi.middleware.wsgi import WSGIMiddleware -from fastapi.responses import JSONResponse from starlette.middleware.cors import CORSMiddleware from starlette.responses import Response -try: - from starlette_context.middleware import RawContextMiddleware - from starlette_context.plugins import RequestIdPlugin -except ImportError: - pass -from galaxy.exceptions import MessageException -from galaxy.web.framework.base import walk_controller_modules -from galaxy.web.framework.decorators import ( - api_error_message, - validation_error_to_message_exception +from galaxy.webapps.base.api import ( + add_exception_handler, + add_request_id_middleware, + include_all_package_routers, ) from galaxy.webapps.base.webapp import config_allows_origin @@ -66,28 +58,6 @@ class GalaxyCORSMiddleware(CORSMiddleware): return config_allows_origin(origin, self.config) -def add_exception_handler( - app: FastAPI -) -> None: - - @app.exception_handler(RequestValidationError) - async def validate_exception_middleware(request: Request, exc: RequestValidationError) -> Response: - exc = validation_error_to_message_exception(exc) - error_dict = api_error_message(None, exception=exc) - return JSONResponse( - status_code=400, - content=error_dict - ) - - @app.exception_handler(MessageException) - async def message_exception_middleware(request: Request, exc: MessageException) -> Response: - error_dict = api_error_message(None, exception=exc) - return JSONResponse( - status_code=exc.status_code, - content=error_dict - ) - - def add_galaxy_middleware(app: FastAPI, gx_app): x_frame_options = getattr(gx_app.config, 'x_frame_options', None) if x_frame_options: @@ -117,21 +87,16 @@ def add_galaxy_middleware(app: FastAPI, gx_app): return response -def add_request_id_middleware(app: FastAPI): - app.add_middleware(RawContextMiddleware, plugins=(RequestIdPlugin(force_new_uuid=True),)) - - def initialize_fast_app(gx_webapp, gx_app): app = FastAPI( - openapi_tags=api_tags_metadata + title="Galaxy API", + docs_url="/api/docs", + openapi_tags=api_tags_metadata, ) add_exception_handler(app) add_galaxy_middleware(app, gx_app) add_request_id_middleware(app) + include_all_package_routers(app, 'galaxy.webapps.galaxy.api') wsgi_handler = WSGIMiddleware(gx_webapp) - for _, module in walk_controller_modules('galaxy.webapps.galaxy.api'): - router = getattr(module, "router", None) - if router: - app.include_router(router) app.mount('/', wsgi_handler) return app diff --git a/lib/galaxy/webapps/reports/api/__init__.py b/lib/galaxy/webapps/reports/api/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/galaxy/webapps/reports/fast_app.py b/lib/galaxy/webapps/reports/fast_app.py index 6ac6624a389..f5affed37a9 100644 --- a/lib/galaxy/webapps/reports/fast_app.py +++ b/lib/galaxy/webapps/reports/fast_app.py @@ -1,6 +1,12 @@ from fastapi import FastAPI from fastapi.middleware.wsgi import WSGIMiddleware +from galaxy.webapps.base.api import ( + add_exception_handler, + add_request_id_middleware, + include_all_package_routers, +) + def initialize_fast_app(gx_webapp): app = FastAPI( @@ -11,6 +17,9 @@ def initialize_fast_app(gx_webapp): ), docs_url="/api/docs", ) + add_exception_handler(app) + add_request_id_middleware(app) + include_all_package_routers(app, 'galaxy.webapps.reports.api') wsgi_handler = WSGIMiddleware(gx_webapp) app.mount('/', wsgi_handler) return app diff --git a/lib/tool_shed/webapp/fast_app.py b/lib/tool_shed/webapp/fast_app.py index a52863417a1..2d0c70d4d97 100644 --- a/lib/tool_shed/webapp/fast_app.py +++ b/lib/tool_shed/webapp/fast_app.py @@ -1,6 +1,12 @@ from fastapi import FastAPI from fastapi.middleware.wsgi import WSGIMiddleware +from galaxy.webapps.base.api import ( + add_exception_handler, + add_request_id_middleware, + include_all_package_routers, +) + def initialize_fast_app(gx_webapp): app = FastAPI( @@ -10,6 +16,9 @@ def initialize_fast_app(gx_webapp): ), docs_url="/api/docs", ) + add_exception_handler(app) + add_request_id_middleware(app) + include_all_package_routers(app, 'tool_shed.webapp.api') wsgi_handler = WSGIMiddleware(gx_webapp) app.mount('/', wsgi_handler) return app