Unify exception handling across FastAPI and wsgi.

Also use JSON requests for roles tests instead of form-data - not sure how we're going to tackle that.
This commit is contained in:
John Chilton
2020-12-22 11:18:31 +01:00
committed by mvdbeek
parent 16951e4176
commit 29b11f66c8
4 changed files with 66 additions and 29 deletions
+18 -14
View File
@@ -299,17 +299,7 @@ def expose_api(func, to_json=True, user_required=True, user_or_session_required=
try:
rval = func(self, trans, *args, **kwargs)
except ValidationError as e:
invalid_found = False
missing_found = False
for error in e.errors():
if error["type"] == "value_error.missing":
missing_found = True
elif error["type"].startswith("type_error"):
invalid_found = True
if missing_found and not invalid_found:
raise RequestParameterMissingException(str(e))
else:
raise RequestParameterInvalidException(str(e))
raise validation_error_to_message_exception(e)
if to_json:
rval = format_return_as_json(rval, jsonp_callback, pretty=trans.debug)
return rval
@@ -353,7 +343,21 @@ def format_return_as_json(rval, jsonp_callback=None, pretty=False):
return json
def __api_error_message(trans, **kwds):
def validation_error_to_message_exception(e):
invalid_found = False
missing_found = False
for error in e.errors():
if error["type"] == "value_error.missing" or error["type"] == "type_error.none.not_allowed":
missing_found = True
elif error["type"].startswith("type_error"):
invalid_found = True
if missing_found and not invalid_found:
return RequestParameterMissingException(str(e), validation_errors=loads(e.json()))
else:
return RequestParameterInvalidException(str(e), validation_errors=loads(e.json()))
def api_error_message(trans, **kwds):
exception = kwds.get("exception", None)
if exception:
# If we are passed a MessageException use err_msg.
@@ -379,13 +383,13 @@ def __api_error_message(trans, **kwds):
# err_msg used a good number of places already. Might as well not change
# it?
error_response = dict(err_msg=err_msg, err_code=error_code, **extra_error_info)
if trans.debug: # TODO: Should admins get to see traceback as well?
if trans and trans.debug: # TODO: Should admins get to see traceback as well?
error_response["traceback"] = traceback_string
return error_response
def __api_error_response(trans, **kwds):
error_dict = __api_error_message(trans, **kwds)
error_dict = api_error_message(trans, **kwds)
exception = kwds.get("exception", None)
# If we are given an status code directly - use it - otherwise check
# the exception for a status_code attribute.
+3 -3
View File
@@ -63,18 +63,18 @@ def get_role_manager(app: UniverseApplication = Depends(get_app)) -> RoleManager
class FastAPIRoles:
role_manager: RoleManager = Depends(get_role_manager)
@router.get('/')
@router.get('/api/roles')
def index(self, trans: SessionRequestContext = Depends(get_trans)) -> RoleListModel:
roles = self.role_manager.list_displayable_roles(trans)
return RoleListModel(__root__=[role_to_model(trans, r) for r in roles])
@router.get('/{id}')
@router.get('/api/roles/{id}')
def show(self, id: EncodedDatabaseIdField, trans: SessionRequestContext = Depends(get_trans)) -> RoleModel:
role_id = trans.app.security.decode_id(id)
role = self.role_manager.get(trans, role_id)
return role_to_model(trans, role)
@router.put("/")
@router.post("/api/roles")
def create(self, trans: SessionRequestContext = Depends(get_trans), admin_user=Depends(get_admin_user), role_definition_model: RoleDefeinitionModel = Body(...)) -> RoleModel:
role = self.role_manager.create(trans, role_definition_model)
return role_to_model(trans, role)
+35 -3
View File
@@ -1,14 +1,46 @@
from fastapi import FastAPI
from fastapi import FastAPI, Request
from fastapi.middleware.wsgi import WSGIMiddleware
from fastapi.responses import JSONResponse
from pydantic.error_wrappers import ValidationError
from fastapi.exceptions import RequestValidationError
from starlette.middleware.base import RequestResponseEndpoint
from starlette.responses import Response
from galaxy.exceptions import MessageException
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 exception_middleware(request: Request, exc: MessageException) -> Response:
exc = validation_error_to_message_exception(exc)
error_dict = api_error_message(None, exception=exc)
return JSONResponse(
status_code=exc.status_code,
content=error_dict
)
@app.exception_handler(MessageException)
async def 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 initialize_fast_app(gx_app, app=None):
if app is None:
app = FastAPI()
add_exception_handler(app)
wsgi_handler = WSGIMiddleware(gx_app)
from galaxy.webapps.galaxy.api import jobs
from galaxy.webapps.galaxy.api import roles
app.include_router(jobs.router, prefix='/api/jobs')
app.include_router(roles.router, prefix='/api/roles')
app.include_router(jobs.router, prefix="/api/jobs")
app.include_router(roles.router)
app.mount('/', wsgi_handler)
return app
+10 -9
View File
@@ -65,9 +65,9 @@ class RolesApiTestCase(ApiTestCase):
description = "A test role."
payload = {
"name": name,
"user_ids": json.dumps([self.dataset_populator.user_id()]),
"user_ids": [self.dataset_populator.user_id()],
}
response = self._post("roles", payload, admin=True)
response = self._post("roles", payload, admin=True, json=True)
assert_status_code_is(response, 400)
assert_error_code_is(response, error_codes.USER_REQUEST_MISSING_PARAMETER)
assert "description" in response.json()["err_msg"]
@@ -76,9 +76,9 @@ class RolesApiTestCase(ApiTestCase):
payload = {
"name": None,
"description": description,
"user_ids": json.dumps([self.dataset_populator.user_id()]),
"user_ids": [self.dataset_populator.user_id()],
}
response = self._post("roles", payload, admin=True)
response = self._post("roles", payload, admin=True, json=True)
assert_status_code_is(response, 400)
assert_error_code_is(response, error_codes.USER_REQUEST_MISSING_PARAMETER)
assert "name" in response.json()["err_msg"]
@@ -87,12 +87,13 @@ class RolesApiTestCase(ApiTestCase):
payload = {
"name": ["a test", "name"],
"description": description,
"user_ids": json.dumps([self.dataset_populator.user_id()]),
"user_ids": [self.dataset_populator.user_id()],
}
response = self._post("roles", payload, admin=True)
response = self._post("roles", payload, admin=True, json=True)
assert_status_code_is(response, 400)
assert_error_code_is(response, error_codes.USER_REQUEST_INVALID_PARAMETER)
assert "name" in response.json()["err_msg"]
assert "validation_errors" in response.json()
def test_create_valid(self):
name = self.dataset_populator.get_random_name()
@@ -100,9 +101,9 @@ class RolesApiTestCase(ApiTestCase):
payload = {
"name": name,
"description": description,
"user_ids": json.dumps([self.dataset_populator.user_id()]),
"user_ids": [self.dataset_populator.user_id()],
}
response = self._post("roles", payload, admin=True)
response = self._post("roles", payload, admin=True, json=True)
assert_status_code_is(response, 200)
role = response.json()
RolesApiTestCase.check_role_dict(role)
@@ -133,7 +134,7 @@ class RolesApiTestCase(ApiTestCase):
assert_status_code_is(response, 400)
def test_create_only_admin(self):
response = self._post("roles")
response = self._post("roles", json=True)
assert_status_code_is(response, 403)
@staticmethod