Example #1
0
def register_jsonapi_exception_handlers(app: Starlette):
    """
    Registers exception handlers on a Starlette app,
    serializing uncaught Exception and HTTPException to a json:api compliant body.
    """
    async def _serialize_error(request: Request, exc: Exception) -> Response:
        return serialize_error(exc)

    app.add_exception_handler(Exception, _serialize_error)
    app.add_exception_handler(HTTPException, _serialize_error)
Example #2
0
    def build_starlette_app(self, config={}, **limiter_args):
        limiter_args.setdefault("key_func", get_remote_address)
        limiter = Limiter(**limiter_args)
        app = Starlette(debug=True)
        app.state.limiter = limiter
        app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
        app.add_middleware(SlowAPIMiddleware)

        mock_handler = mock.Mock()
        mock_handler.level = logging.INFO
        limiter.logger.addHandler(mock_handler)
        return app, limiter
                routes=routes,
                middleware=[Middleware(middleware_factory)],
                exception_handlers=exception_handlers,
            )
        else:
            app = Starlette(
                debug=debug, routes=routes, exception_handlers=exception_handlers
            )
            # in earlier versions of starlette, middleware is not a legal argument on the Starlette application class
            # In order to keep the counts the same, we add the middleware twice using the add_middleware interface
            app.add_middleware(middleware_factory)

        app.add_middleware(middleware_factory)
        app.middleware("http")(middleware_decorator)

    # Adding custom exception handlers
    app.add_exception_handler(HandledError, async_error_handler)

    # Add exception handler multiple times to verify the handler is not double wrapped
    app.add_exception_handler(NonAsyncHandledError, non_async_error_handler)
    app.add_exception_handler(NonAsyncHandledError, non_async_error_handler)

    if app_name == "non_async_error_handler_no_middleware":
        app.add_exception_handler(Exception, non_async_error_handler)
        app.add_exception_handler(404, missing_route_handler)
    elif app_name == "teapot_exception_handler_no_middleware":
        app.add_exception_handler(418, teapot_handler)

    # Assign to dict
    target_application[app_name] = AsgiTest(app)
        yield a
        a, b = b, a + b


fibo_row = list(fib(fibo_count))

fibo_path = '/' + '/'.join(map(str, fibo_row))
maze_link = '/maze'
treasure_link = f'{fibo_path}/treasure'
print(f'treasure path is: {treasure_link}')
# treasure_link = f'/treasure'

app = Starlette(debug=True)
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)


@app.route('/maze/hint')
async def hint(request):
    message = '\tHere is your hint:\n\n' \
              '\ta+b is to a as a is to b.\n\n' \
              '\tThe answer to all questions is, quote:\n' \
              '\t\t\"[...] the sort of number that you could ' \
              'without any fear introduce to your parents\"' \
              '\n'

    return PlainTextResponse(message)


@app.route('/maze')
Example #5
0
import uvicorn
from starlette.applications import Starlette

from app.errors import errors
from app.middlewares.database import DatabaseMiddleware
from config.application import settings
from db.application import database
from routes import applications

app = Starlette(debug=settings.DEBUG, routes=applications.routes)

app.add_middleware(DatabaseMiddleware)

app.add_exception_handler(404, errors.error_response)
app.add_exception_handler(500, errors.error_response)
Example #6
0
app.mount(path="/auth", app=starlette_auth.app, name="auth")
app.mount(path="/blog", app=blog_app, name="blog")

# static app
app.mount(
    path="/static",
    app=StaticFiles(directory="static", packages=["starlette_admin"]),
    name="static",
)

# middleware
app.add_middleware(AuthenticationMiddleware, backend=starlette_auth.ModelAuthBackend())
app.add_middleware(SessionMiddleware, secret_key=settings.SECRET_KEY)
app.add_middleware(CORSMiddleware, allow_origins=settings.ALLOWED_HOSTS)
app.add_middleware(starlette_core.middleware.DatabaseMiddleware)

# exception handlers
app.add_exception_handler(404, handlers.not_found)
app.add_exception_handler(500, handlers.server_error)

# sentry
if settings.SENTRY_DSN:
    try:
        from sentry_asgi import SentryMiddleware
        import sentry_sdk

        sentry_sdk.init(str(settings.SENTRY_DSN))
        app = SentryMiddleware(app)
    except ImportError:
        pass
Example #7
0
class Constellation:
    """The class that represents the webserver.

    It runs multiple :class:`Star`, which represent the routes of the website.

    It also handles the :class:`Alchemy` connection, and Herald connections too."""
    def __init__(self,
                 alchemy_cfg: Dict[str, Any],
                 herald_cfg: Dict[str, Any],
                 packs_cfg: Dict[str, Any],
                 constellation_cfg: Dict[str, Any],
                 **_):
        if Starlette is None:
            raise ImportError("`constellation` extra is not installed")

        # Import packs
        pack_names = packs_cfg["active"]
        packs = {}
        for pack_name in pack_names:
            log.debug(f"Importing pack: {pack_name}")
            try:
                packs[pack_name] = importlib.import_module(pack_name)
            except ImportError as e:
                log.error(f"Error during the import of {pack_name}: {e}")
        log.info(f"Packs: {len(packs)} imported")

        self.alchemy = None
        """The :class:`~ra.Alchemy` of this Constellation."""

        # Alchemy
        if ra.Alchemy is None:
            log.info("Alchemy: not installed")
        elif not alchemy_cfg["enabled"]:
            log.info("Alchemy: disabled")
        else:
            # Find all tables
            tables = set()
            for pack in packs.values():
                try:
                    tables = tables.union(pack.available_tables)
                except AttributeError:
                    log.warning(f"Pack `{pack}` does not have the `available_tables` attribute.")
                    continue
            # Create the Alchemy
            self.alchemy = ra.Alchemy(alchemy_cfg["database_url"], tables)
            log.info(f"Alchemy: {self.alchemy}")

        # Herald
        self.herald: Optional[rh.Link] = None
        """The :class:`Link` object connecting the :class:`Constellation` to the rest of the herald network."""

        self.herald_task: Optional[aio.Task] = None
        """A reference to the :class:`aio.Task` that runs the :class:`rh.Link`."""

        self.Interface: Type[rc.CommandInterface] = self.interface_factory()
        """The :class:`~rc.CommandInterface` class of this :class:`Constellation`."""

        self.events: Dict[str, rc.Event] = {}
        """A dictionary containing all :class:`~rc.Event` that can be handled by this :class:`Constellation`."""

        self.starlette = Starlette(debug=__debug__)
        """The :class:`~starlette.Starlette` app."""

        # Register Events
        for pack_name in packs:
            pack = packs[pack_name]
            pack_cfg = packs_cfg.get(pack_name, {})
            try:
                events = pack.available_events
            except AttributeError:
                log.warning(f"Pack `{pack}` does not have the `available_events` attribute.")
            else:
                self.register_events(events, pack_cfg)
        log.info(f"Events: {len(self.events)} events")

        if rh.Link is None:
            log.info("Herald: not installed")
        elif not herald_cfg["enabled"]:
            log.info("Herald: disabled")
        else:
            self.init_herald(herald_cfg)
            log.info(f"Herald: enabled")

        # Register PageStars and ExceptionStars
        for pack_name in packs:
            pack = packs[pack_name]
            pack_cfg = packs_cfg.get(pack_name, {})
            try:
                page_stars = pack.available_page_stars
            except AttributeError:
                log.warning(f"Pack `{pack}` does not have the `available_page_stars` attribute.")
            else:
                self.register_page_stars(page_stars, pack_cfg)
            try:
                exc_stars = pack.available_exception_stars
            except AttributeError:
                log.warning(f"Pack `{pack}` does not have the `available_exception_stars` attribute.")
            else:
                self.register_exc_stars(exc_stars, pack_cfg)
        log.info(f"PageStars: {len(self.starlette.routes)} stars")
        log.info(f"ExceptionStars: {len(self.starlette.exception_handlers)} stars")

        self.running: bool = False
        """Is the :class:`Constellation` server currently running?"""

        self.address: str = constellation_cfg["address"]
        """The address that the :class:`Constellation` will bind to when run."""

        self.port: int = constellation_cfg["port"]
        """The port on which the :class:`Constellation` will listen for connection on."""

    # TODO: is this a good idea?
    def interface_factory(self) -> Type[rc.CommandInterface]:
        """Create the :class:`rc.CommandInterface` class for the :class:`Constellation`."""

        # noinspection PyMethodParameters
        class GenericInterface(rc.CommandInterface):
            alchemy: ra.Alchemy = self.alchemy
            constellation = self

            async def call_herald_event(ci, destination: str, event_name: str, **kwargs) -> Dict:
                """Send a :class:`rh.Request` to a specific destination, and wait for a
                :class:`rh.Response`."""
                if self.herald is None:
                    raise rc.UnsupportedError("`royalherald` is not enabled on this Constellation.")
                request: rh.Request = rh.Request(handler=event_name, data=kwargs)
                response: rh.Response = await self.herald.request(destination=destination, request=request)
                if isinstance(response, rh.ResponseFailure):
                    if response.name == "no_event":
                        raise rc.CommandError(f"There is no event named {event_name} in {destination}.")
                    elif response.name == "exception_in_event":
                        # TODO: pretty sure there's a better way to do this
                        if response.extra_info["type"] == "CommandError":
                            raise rc.CommandError(response.extra_info["message"])
                        elif response.extra_info["type"] == "UserError":
                            raise rc.UserError(response.extra_info["message"])
                        elif response.extra_info["type"] == "InvalidInputError":
                            raise rc.InvalidInputError(response.extra_info["message"])
                        elif response.extra_info["type"] == "UnsupportedError":
                            raise rc.UnsupportedError(response.extra_info["message"])
                        elif response.extra_info["type"] == "ConfigurationError":
                            raise rc.ConfigurationError(response.extra_info["message"])
                        elif response.extra_info["type"] == "ExternalError":
                            raise rc.ExternalError(response.extra_info["message"])
                        else:
                            raise TypeError(f"Herald action call returned invalid error:\n"
                                            f"[p]{response}[/p]")
                elif isinstance(response, rh.ResponseSuccess):
                    return response.data
                else:
                    raise TypeError(f"Other Herald Link returned unknown response:\n"
                                    f"[p]{response}[/p]")

        return GenericInterface

    def init_herald(self, herald_cfg: Dict[str, Any]):
        """Create a :class:`rh.Link`."""
        herald_cfg["name"] = "constellation"
        self.herald: rh.Link = rh.Link(rh.Config.from_config(**herald_cfg), self.network_handler)

    async def network_handler(self, message: Union[rh.Request, rh.Broadcast]) -> rh.Response:
        try:
            event: rc.Event = self.events[message.handler]
        except KeyError:
            log.warning(f"No event for '{message.handler}'")
            return rh.ResponseFailure("no_event", f"This serf does not have any event for {message.handler}.")
        log.debug(f"Event called: {event.name}")
        if isinstance(message, rh.Request):
            try:
                response_data = await event.run(**message.data)
                return rh.ResponseSuccess(data=response_data)
            except Exception as e:
                ru.sentry_exc(e)
                return rh.ResponseFailure("exception_in_event",
                                          f"An exception was raised in the event for '{message.handler}'.",
                                          extra_info={
                                              "type": e.__class__.__qualname__,
                                              "message": str(e)
                                          })
        elif isinstance(message, rh.Broadcast):
            await event.run(**message.data)

    def register_events(self, events: List[Type[rc.Event]], pack_cfg: Dict[str, Any]):
        for SelectedEvent in events:
            # Create a new interface
            interface = self.Interface(config=pack_cfg)
            # Initialize the event
            try:
                event = SelectedEvent(interface)
            except Exception as e:
                log.error(f"Skipping: "
                          f"{SelectedEvent.__qualname__} - {e.__class__.__qualname__} in the initialization.")
                ru.sentry_exc(e)
                continue
            # Register the event
            if SelectedEvent.name in self.events:
                log.warning(f"Overriding (already defined): {SelectedEvent.__qualname__} -> {SelectedEvent.name}")
            else:
                log.debug(f"Registering: {SelectedEvent.__qualname__} -> {SelectedEvent.name}")
            self.events[SelectedEvent.name] = event

    def register_page_stars(self, page_stars: List[Type[PageStar]], pack_cfg: Dict[str, Any]):
        for SelectedPageStar in page_stars:
            log.debug(f"Registering: {SelectedPageStar.path} -> {SelectedPageStar.__qualname__}")
            try:
                page_star_instance = SelectedPageStar(constellation=self, config=pack_cfg)
            except Exception as e:
                log.error(f"Skipping: "
                          f"{SelectedPageStar.__qualname__} - {e.__class__.__qualname__} in the initialization.")
                ru.sentry_exc(e)
                continue
            self.starlette.add_route(page_star_instance.path, page_star_instance.page, page_star_instance.methods)

    def register_exc_stars(self, exc_stars: List[Type[ExceptionStar]], pack_cfg: Dict[str, Any]):
        for SelectedPageStar in exc_stars:
            log.debug(f"Registering: {SelectedPageStar.error} -> {SelectedPageStar.__qualname__}")
            try:
                page_star_instance = SelectedPageStar(constellation=self, config=pack_cfg)
            except Exception as e:
                log.error(f"Skipping: "
                          f"{SelectedPageStar.__qualname__} - {e.__class__.__qualname__} in the initialization.")
                ru.sentry_exc(e)
                continue
            self.starlette.add_exception_handler(page_star_instance.error, page_star_instance.page)

    def run_blocking(self):
        log.info(f"Running Constellation on https://{self.address}:{self.port}/...")
        loop: aio.AbstractEventLoop = aio.get_event_loop()
        self.running = True
        # TODO: figure out how to run the correct event loop
        # loop.create_task(self.herald.run())
        try:
            uvicorn.run(self.starlette, host=self.address, port=self.port, log_config=UVICORN_LOGGING_CONFIG)
        finally:
            self.running = False

    @classmethod
    def run_process(cls,
                    alchemy_cfg: Dict[str, Any],
                    herald_cfg: Dict[str, Any],
                    sentry_cfg: Dict[str, Any],
                    packs_cfg: Dict[str, Any],
                    constellation_cfg: Dict[str, Any],
                    logging_cfg: Dict[str, Any]):
        """Blockingly create and run the Constellation.

        This should be used as the target of a :class:`multiprocessing.Process`."""
        ru.init_logging(logging_cfg)

        if sentry_cfg is None or not sentry_cfg["enabled"]:
            log.info("Sentry: disabled")
        else:
            try:
                ru.init_sentry(sentry_cfg)
            except ImportError:
                log.info("Sentry: not installed")

        constellation = cls(alchemy_cfg=alchemy_cfg,
                            herald_cfg=herald_cfg,
                            packs_cfg=packs_cfg,
                            constellation_cfg=constellation_cfg)

        # Run the server
        constellation.run_blocking()

    def __repr__(self):
        return f"<{self.__class__.__qualname__}: {'running' if self.running else 'inactive'}>"
Example #8
0
File: app.py Project: ambrozic/dms
import logging

from starlette.applications import Starlette
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.middleware.sessions import SessionMiddleware

from dms import db, logger, middlewares, router, settings, views

app = Starlette(debug=settings.DEBUG, routes=router.routes)
app.add_middleware(
    AuthenticationMiddleware, backend=middlewares.AdminAuthenticationBackend()
)
app.add_middleware(SessionMiddleware, secret_key=settings.SECRET_KEY)
app.add_exception_handler(404, views.NotFound)
app.add_exception_handler(500, views.ServerError)


@app.on_event("startup")
async def startup() -> None:
    logger.setup()
    logging.info("dms: startup")
    await db.database.connect()


@app.on_event("shutdown")
async def shutdown() -> None:
    logging.info("dms: shutdown")
    await db.database.disconnect()
Example #9
0
def add_exception_handlers(app: Starlette) -> None:
    app.add_exception_handler(HTTPException, http_exception_handler)
    app.add_exception_handler(AuthException, auth_exception_handler)
    app.add_exception_handler(ValidationError, validation_error_handler)
Example #10
0
    websocket_url = 'wss://www.seismicportal.eu/standing_order/websocket'
    async with websockets.connect(websocket_url) as ws:
        while True:
            msg = await ws.recv()
            print(f'received msg: {msg}')


async def health(request):
    return JSONResponse({'status': 'ok'})


file_path = Path(__file__).resolve()
html_dir = file_path.parents[0].joinpath('html')
log.info(f'html_dir: {html_dir}')
routes = [
    Route('/', index),
    Route('/health', health),
    Route('/quakes', quakes),
    Mount('/html', StaticFiles(directory=html_dir))
]
middlewares = [
    #Middleware(HTTPSRedirectMiddleware),
    Middleware(CORSMiddleware, allow_origins=['*'])
]

app = Starlette(debug=False, routes=routes, middleware=middlewares)

#app.add_middleware(HTTPSRedirectMiddleware)
#app.add_middleware(CORSMiddleware, allow_origins=['*'])
app.add_exception_handler(WebargsHTTPException, validation_exception)
Example #11
0
NECRO_BASE = os.getenv('NECRO_BASE')
if not NECRO_BASE:
    log.warning('NECRO_BASE env var is not defined. Elasticsearch queries to '
                'necropolis will not work!')


def http_exception_handler(request, exc):
    # We assume that an HTTPException, which has been raised by our code,
    # has already been logged.
    return JSONResponse(exc.detail, status_code=exc.status_code)


def validation_exception_handler(request, exc):
    return JSONResponse(exc.messages, status_code=400)


def misc_exception_handler(request, exc):
    log.exception(exc)
    return JSONResponse('Unexpected error', status_code=500)


app = Starlette(debug=False)
app.mount('', Router(routes.routes))
app.add_exception_handler(HTTPException, http_exception_handler)
app.add_exception_handler(ValidationError, validation_exception_handler)
app.add_exception_handler(Exception, misc_exception_handler)
app.add_middleware(CORSMiddleware,
                   allow_origins=['*'],
                   allow_methods=['GET', 'POST'])