Skip to content

Commit

Permalink
Merge pull request #42 from cuenca-mx/knox
Browse files Browse the repository at this point in the history
error handlers + `FastAgaveError`
  • Loading branch information
felipao-mx authored Oct 6, 2021
2 parents 36caa5b + 8a83b4d commit 650abf3
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 17 deletions.
7 changes: 5 additions & 2 deletions examples/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@

from mongoengine import connect
from fastapi import FastAPI
from starlette.middleware import Middleware

from fast_agave.middlewares import FastAgaveErrorHandler
from .resources import app as resources
from .middlewares import AuthedMiddleware


connect(host='mongomock://localhost:27017/db')
app = FastAPI(middleware=[Middleware(AuthedMiddleware)])
app = FastAPI(title='example')
app.include_router(resources)

app.add_middleware(AuthedMiddleware)
app.add_middleware(FastAgaveErrorHandler)


@app.get('/')
async def iam_healty() -> Dict:
Expand Down
4 changes: 3 additions & 1 deletion examples/middlewares/authed.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from starlette.middleware.base import RequestResponseEndpoint
from starlette.middleware.base import (
RequestResponseEndpoint,
)
from starlette.responses import Response
from starlette_context.middleware import ContextMiddleware
from starlette_context import _request_scope_context_storage
Expand Down
23 changes: 22 additions & 1 deletion examples/resources/base.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
from typing import Dict
from typing import Dict, NoReturn

from cuenca_validations.errors import WrongCredsError

from fast_agave.blueprints import RestApiBlueprint
from fast_agave.exc import UnauthorizedError

app = RestApiBlueprint()


@app.get('/healthy_auth')
def health_auth_check() -> Dict:
return dict(greeting="I'm authenticated and healthy !!!")


@app.get('/raise_cuenca_errors')
def raise_cuenca_errors() -> NoReturn:
raise WrongCredsError('you are not lucky enough!')


@app.get('/raise_fast_agave_errors')
def raise_fast_agave_errors() -> NoReturn:
raise UnauthorizedError('nice try!')


@app.get('/you_shall_not_pass')
def you_shall_not_pass() -> None:
# Este endpoint nunca será ejecutado
# La prueba de este endpoint hace un mock a nivel middleware
# para responder con un `UnauthorizedError`
...
31 changes: 19 additions & 12 deletions fast_agave/exc.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
from dataclasses import dataclass

from fastapi import HTTPException

@dataclass
class FastAgaveError(Exception):
error: str
status_code: int


@dataclass
class Error(HTTPException):
def __init__(self, detail=''):
super().__init__(status_code=self.status_code, detail=detail)
class BadRequestError(FastAgaveError):
status_code: int = 400


class BadRequestError(Error):
status_code = 400
@dataclass
class UnauthorizedError(FastAgaveError):
status_code: int = 401


class UnauthorizedError(Error):
status_code = 401
@dataclass
class ForbiddenError(FastAgaveError):
status_code: int = 403


class ForbiddenError(Error):
status_code = 403
@dataclass
class NotFoundError(FastAgaveError):
status_code: int = 404


class NotFoundError(Error):
status_code = 404
@dataclass
class FastAgaveViewError(FastAgaveError):
status_code: int = 500
3 changes: 3 additions & 0 deletions fast_agave/middlewares/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .error_handlers import FastAgaveErrorHandler

__all__ = ['FastAgaveErrorHandler']
29 changes: 29 additions & 0 deletions fast_agave/middlewares/error_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from cuenca_validations.errors import CuencaError
from fastapi import Request, Response
from fastapi.responses import JSONResponse
from starlette.middleware.base import (
BaseHTTPMiddleware,
RequestResponseEndpoint,
)

from fast_agave.exc import FastAgaveError


class FastAgaveErrorHandler(BaseHTTPMiddleware):
async def dispatch(
self, request: Request, call_next: RequestResponseEndpoint
) -> Response:
try:
return await call_next(request)
except CuencaError as exc:
return JSONResponse(
status_code=exc.status_code,
content=dict(
code=exc.code,
error=str(exc),
),
)
except FastAgaveError as exc:
return JSONResponse(
status_code=exc.status_code, content=dict(error=exc.error)
)
Empty file removed fast_agave/models/__init__.py
Empty file.
2 changes: 1 addition & 1 deletion fast_agave/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.0.3'
__version__ = '0.1.0'
31 changes: 31 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,38 @@
from unittest.mock import AsyncMock

from _pytest.monkeypatch import MonkeyPatch
from fastapi.testclient import TestClient

from examples.middlewares.authed import AuthedMiddleware
from fast_agave.exc import UnauthorizedError


def test_iam_healthy(client: TestClient) -> None:
resp = client.get('/')
assert resp.status_code == 200
assert resp.json() == dict(greeting="I'm healthy!!!")


def test_cuenca_error_handler(client: TestClient) -> None:
resp = client.get('/raise_cuenca_errors')
assert resp.status_code == 401
assert resp.json() == dict(error='you are not lucky enough!', code=101)


def test_fast_agave_error_handler(client: TestClient) -> None:
resp = client.get('/raise_fast_agave_errors')
assert resp.status_code == 401
assert resp.json() == dict(error='nice try!')


def test_fast_agave_error_handler_from_middleware(
client: TestClient, monkeypatch: MonkeyPatch
) -> None:
monkeypatch.setattr(
AuthedMiddleware,
'authorize',
AsyncMock(side_effect=UnauthorizedError('come back to the shadows!')),
)
resp = client.get('/you_shall_not_pass')
assert resp.status_code == 401
assert resp.json() == dict(error='come back to the shadows!')

0 comments on commit 650abf3

Please sign in to comment.