alex-oleshkevich / starception Goto Github PK
View Code? Open in Web Editor NEWBeautiful exception page for Starlette apps.
License: MIT License
Beautiful exception page for Starlette apps.
License: MIT License
Any plans to include werkzeug-like python interpreter to starception? We use docker and fastapi in our work environment. There fore we cannot use vscode or pycharm debugger. It would be great if we can drop into Python intertpreter via browser if an exception occurs.
when using starsessions
, excellent middleware btw :), there is a bug when trying to render the template, here is the trace
backend-1 | ERROR: Exception in ASGI application
backend-1 | Traceback (most recent call last):
backend-1 | File "/opt/venv/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 401, in run_asgi
backend-1 | result = await app(self.scope, self.receive, self.send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
backend-1 | return await self.app(scope, receive, send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/uvicorn/middleware/message_logger.py", line 86, in __call__
backend-1 | raise exc from None
backend-1 | File "/opt/venv/lib/python3.10/site-packages/uvicorn/middleware/message_logger.py", line 82, in __call__
backend-1 | await self.app(scope, inner_receive, inner_send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/fastapi/applications.py", line 269, in __call__
backend-1 | await super().__call__(scope, receive, send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starlette/applications.py", line 124, in __call__
backend-1 | await self.middleware_stack(scope, receive, send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 184, in __call__
backend-1 | raise exc
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 162, in __call__
backend-1 | await self.app(scope, receive, _send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starsessions/middleware.py", line 86, in __call__
backend-1 | await self.app(scope, receive, send_wrapper)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starception/middleware.py", line 44, in __call__
backend-1 | response = self.debug_response(request, exc)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starception/middleware.py", line 57, in debug_response
backend-1 | return exception_handler(request, exc)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starception/exception_handler.py", line 111, in exception_handler
backend-1 | content = generate_html(request, exc)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starception/exception_handler.py", line 126, in generate_html
backend-1 | return template.render(
backend-1 | File "/opt/venv/lib/python3.10/site-packages/jinja2/environment.py", line 1301, in render
backend-1 | self.environment.handle_exception()
backend-1 | File "/opt/venv/lib/python3.10/site-packages/jinja2/environment.py", line 936, in handle_exception
backend-1 | raise rewrite_traceback_stack(source=source)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starception/templates/index.html", line 41, in top-level template code
backend-1 | {{ lib.details_row('Session', session) }}
backend-1 | File "/opt/venv/lib/python3.10/site-packages/jinja2/runtime.py", line 777, in _invoke
backend-1 | rv = self._func(*arguments)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starception/templates/lib.html", line 4, in template
backend-1 | {% for label, value in items|items %}
backend-1 | File "/opt/venv/lib/python3.10/site-packages/jinja2/filters.py", line 246, in do_items
backend-1 | raise TypeError("Can only get item pairs from a mapping.")
backend-1 | TypeError: Can only get item pairs from a mapping.
the fix is simple and could happen here using request.session.data
instead of request.session
starception/starception/exception_handler.py
Line 157 in c8030d9
when an object has superior or inferior signs in its __repr__
the details_row
macro if I get it right displays it "weirdly" :
an example, I have request.app.state._state["arqredis"]
whose repr is ArqRedis<ConnectionPool<Connection<host=abdul_redis,port=6379,db=0>>>
and in the template it looks like the image below
minmal reproducible example below
import json
import typing
import uvicorn
from asyncpg.pgproto.pgproto import UUID
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.requests import Request
from starlette.routing import Route
from starception import StarceptionMiddleware
class WithHintError(Exception):
solution = (
'The connection to the database cannot be established. '
'Either the database server is down or connection credentials are invalid.'
)
def index_view(request: Request) -> typing.NoReturn:
uuid_dict = {"id": UUID('a4ccb480-e020-4f90-8951-ff859f731b40') }
json.dumps(uuid_dict)
def hint_view(request: Request) -> typing.NoReturn:
raise WithHintError('Oops, something really went wrong...')
app = Starlette(
debug=True,
routes=[Route('/', index_view), Route('/hint', hint_view)],
middleware=[Middleware(StarceptionMiddleware)],
)
if __name__ == '__main__':
uvicorn.run("starlette_app:app")
Hi there,
This was flagged on florimondmanca/awesome-asgi#84 as a tool providing "amazing output in the developement phase". It surely looks so!
awesome-asgi
is focused on tools and libraries that are as close to the ASGI spec as possible, i.e. "framework agnostic".
StarceptionMiddleware
is already a pure ASGI middleware so it's already fairly agnostic.
From a quick analysis, it seems the only bit preventing this middleware from being usable with any ASGI framework is this line:
https://github.com/alex-oleshkevich/starception/blob/master/starception/middleware.py#L42
This would notably work with Starlette, which sets scope["app"]
and exposes a .debug
attribute. But this is not guaranteed to work for other frameworks if they don't comply with this interface (which will almost certainly be the case).
Maybe this middleware could keep the "automatic debug detection" functionality for Starlette-based framework, and rely on a debug=...
parameter for other cases? Something like this...
class StarceptionMiddleware:
def __init__(self, app, *, debug=None):
self.app = app
self._debug = debug
async def __call__(self, scope, receive, send):
...
try:
debug = request.app.debug
except AttributeError:
if self._debug is None:
raise RuntimeError(
"Could not infer whether the app is running in debug mode, as "
"'request.app.debug' is unavailable. "
"Hint: you can pass a debug flag explicitly using "
"'StarceptionMiddleware(app, debug=...)'."
)
debug = self._debug
Then, I believe the Integration with other frameworks documentation could hint at using StarceptionMiddleware(app, debug=...)
instead of hinting users at using the exception_handler
.
I believe that would work better because a) most (all?) of the ASGI frameworks have some middleware mounting API, but b) most frameworks that have an error handler API won't accept a Starlette Request
instance.
Then I think the library would be fully framework-agnostic! On top of allowing support for the widest userbase possible, in my experience this also reduces the maintenance burden. Starlette can be hidden as an implementation detail, and not exposed in the library public API at all.
What do you think? I'll be happy to provide a PR.
I was confused why my code snippets did not have syntax highlighting but screenshots featured it.
It took me a while to understand that pygments
is not listed in the package dependency list and I have to install it manually.
what do you think of having url / uri keywords added to masked_secrets as they often contains passwords for redis, postgresql etc or maybe have an option to not display some added keywords
greetings, wanted to try this with an asyncpg error I was debugging, the below raise an exception and I have the classical Starlette template outputing fine,
backend-1 | ERROR: Exception in ASGI application
backend-1 | Traceback (most recent call last):
backend-1 | File "/opt/venv/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 401, in run_asgi
backend-1 | result = await app(self.scope, self.receive, self.send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
backend-1 | return await self.app(scope, receive, send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/uvicorn/middleware/message_logger.py", line 86, in __call__
backend-1 | raise exc from None
backend-1 | File "/opt/venv/lib/python3.10/site-packages/uvicorn/middleware/message_logger.py", line 82, in __call__
backend-1 | await self.app(scope, inner_receive, inner_send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/fastapi/applications.py", line 269, in __call__
backend-1 | await super().__call__(scope, receive, send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starlette/applications.py", line 124, in __call__
backend-1 | await self.middleware_stack(scope, receive, send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 184, in __call__
backend-1 | raise exc
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 162, in __call__
backend-1 | await self.app(scope, receive, _send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starsessions/middleware.py", line 86, in __call__
backend-1 | await self.app(scope, receive, send_wrapper)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starlette/exceptions.py", line 93, in __call__
backend-1 | raise exc
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starlette/exceptions.py", line 82, in __call__
backend-1 | await self.app(scope, receive, sender)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__
backend-1 | raise e
backend-1 | File "/opt/venv/lib/python3.10/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
backend-1 | await self.app(scope, receive, send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starlette/routing.py", line 670, in __call__
backend-1 | await route.handle(scope, receive, send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starlette/routing.py", line 266, in handle
backend-1 | await self.app(scope, receive, send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starlette/routing.py", line 65, in app
backend-1 | response = await func(request)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/fastapi/routing.py", line 227, in app
backend-1 | raw_response = await run_endpoint_function(
backend-1 | File "/opt/venv/lib/python3.10/site-packages/fastapi/routing.py", line 160, in run_endpoint_function
backend-1 | return await dependant.call(**values)
backend-1 | File "/opt/project/./abdul/routers/cart_router.py", line 110, in cart_get
backend-1 | products, total_usd_decimal, total_xmr_decimal, order_now = await calculate_prices(
backend-1 | File "/opt/project/./abdul/routers/cart_router.py", line 54, in calculate_prices
backend-1 | xmr_price = await conn.fetchval_b(query_xmr_price, order_now=order_now)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/buildpg/asyncpg.py", line 69, in fetchval_b
backend-1 | return await self.fetchval(query, *args, timeout=_timeout, column=_column)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/asyncpg/connection.py", line 645, in fetchval
backend-1 | data = await self._execute(query, args, 1, timeout)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/asyncpg/connection.py", line 1659, in _execute
backend-1 | result, _ = await self.__execute(
backend-1 | File "/opt/venv/lib/python3.10/site-packages/asyncpg/connection.py", line 1684, in __execute
backend-1 | return await self._do_execute(
backend-1 | File "/opt/venv/lib/python3.10/site-packages/asyncpg/connection.py", line 1711, in _do_execute
backend-1 | stmt = await self._get_statement(
backend-1 | File "/opt/venv/lib/python3.10/site-packages/asyncpg/connection.py", line 398, in _get_statement
backend-1 | statement = await self._protocol.prepare(
backend-1 | File "asyncpg/protocol/protocol.pyx", line 168, in prepare
backend-1 | asyncpg.exceptions.UndefinedFunctionError: operator does not exist: timestamp without time zone > interval
backend-1 | HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
but as soon as I add the middleware with app.add_middleware(StarceptionMiddleware, debug=True)
I get no template and a Internal Server Error 500 page, and this error, not sure the issue is here or in Starlette itself though
backend-1 | ERROR: Exception in ASGI application
backend-1 | Traceback (most recent call last):
backend-1 | File "/opt/venv/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 401, in run_asgi
backend-1 | result = await app(self.scope, self.receive, self.send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
backend-1 | return await self.app(scope, receive, send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/uvicorn/middleware/message_logger.py", line 86, in __call__
backend-1 | raise exc from None
backend-1 | File "/opt/venv/lib/python3.10/site-packages/uvicorn/middleware/message_logger.py", line 82, in __call__
backend-1 | await self.app(scope, inner_receive, inner_send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/fastapi/applications.py", line 269, in __call__
backend-1 | await super().__call__(scope, receive, send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starlette/applications.py", line 124, in __call__
backend-1 | await self.middleware_stack(scope, receive, send)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 167, in __call__
backend-1 | response = self.debug_response(request, exc)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 248, in debug_response
backend-1 | content = self.generate_html(exc)
backend-1 | File "/opt/venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 220, in generate_html
backend-1 | traceback_obj = traceback.TracebackException.from_exception(
backend-1 | File "/usr/local/lib/python3.10/traceback.py", line 572, in from_exception
backend-1 | return cls(type(exc), exc, exc.__traceback__, *args, **kwargs)
backend-1 | File "/usr/local/lib/python3.10/traceback.py", line 502, in __init__
backend-1 | self.stack = StackSummary.extract(
backend-1 | File "/usr/local/lib/python3.10/traceback.py", line 376, in extract
backend-1 | result.append(FrameSummary(
backend-1 | File "/usr/local/lib/python3.10/traceback.py", line 276, in __init__
backend-1 | self.locals = {k: repr(v) for k, v in locals.items()} if locals else None
backend-1 | File "/usr/local/lib/python3.10/traceback.py", line 276, in <dictcomp>
backend-1 | self.locals = {k: repr(v) for k, v in locals.items()} if locals else None
backend-1 | File "/opt/venv/lib/python3.10/site-packages/jinja2/runtime.py", line 581, in __repr__
backend-1 | return f"<{type(self).__name__} {self.index}/{self.length}>"
backend-1 | File "/opt/venv/lib/python3.10/site-packages/jinja2/runtime.py", line 440, in length
backend-1 | iterable = list(self._iterator)
backend-1 | AttributeError: 'LoopContext' object has no attribute '_iterator'
backend-1 | INFO: 172.20.0.1:60112 - "GET /cart/ HTTP/1.1" 500
What if we do syntax highlighting in the browser? Starception already uses js to switch between frames so moving syntax highlighting to the client side is something that can be considered.
I am evaluating Highlight.js for this. A custom build can be created that contains just two themes and highlighters for selected languages (python, js, html, (s)css) and then integrated into Starception.
This would allow us to drop pygments
dependency and use prefers-color-scheme
media feature to automatically switch between light and dark themes. Lack of auto switching depending on day/night is the most irritating thing for me right now.
What are your thoughts @alex-oleshkevich ? Would you be willing to accept a PR with such change?
When encode/starlette#1803 gets merged, then we have to promote exception_handler
usage instead of middleware.
Take 1 month and if no issues found release 1.0.
I think this should work, it's easily reproducible using url_for it seems
I have an endpoint that renders a template
If I make a mistake in the template on the line below (it should be order.conversations_uuid
with an a
and not converstions_uuid
), then starception template is not correctly displayed (see screen)
<a href="{{ url_for("conversation_get", uuid=order.converstions_uuid ) }}">
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.