Flash messages support
Do we need some sort of flash messages support? Or may be this feature is out of scope of aiohttp_session?
Most frameworks have flash messages: pyramid: http://docs.pylonsproject.org/projects/pyramid/en/1.0-branch/narr/sessions.html#flash-messages flask: http://flask.pocoo.org/docs/0.10/patterns/flashing/
Particularly pyramid has it integrated to session object.
I think it will look better as a separate package because it brings some dependencies like aiohttp_jinja2 for context_processor implementation.
Do not follow, it is couple of methods:
flash() - add message to the queue, where queue is located in session object
pop_flash() - pop messages from the queue
Do not see why it should depend on aiohttp_jinja2
Because flash messages doesn't make much sense without templates. And I think there is not so much code to split it into parts. Exactly it took 22 loc in my last implementation: https://gist.github.com/imbolc/ca43545ca31329b4f9e2
Also, this approach has benefits of brevity:
flash(request, ('error', 'Foo error'))
instead of
session = await get_session(request)
session.flash(request, ('error', 'Foo error'))
@imbolc request is not necessary in your last example. session is coupled to request itself.
Anyway, let's postpone the issue.
For now I have a feeling that flash messages are part of UI library (while they are built on top of user session, sure).
Another interesting question is API for CSRF checks BTW.
I also do not understand how flash depends on template engine. As @asvetlov mentioned, flash is necessary to implement CSRF check.
I created the simple session flash library based on @imbolc's gist:
https://github.com/IlyaSemenov/aiohttp_session_flash
It has a few enhancements over the original gist:
- session is not re-saved on each HTTP request unless flash messages were pushed or popped
- it doesn't lose flash messages followed by
raise web.HTTPFound(...) - it has tests and pypi package
I did not know about the flash example and I implemented my own thing. I didn't use middleware though. I did this:
class Session:
def __init__(self, session):
self.session = session
async def get(self, getter):
return self.session[getter] if getter in self.session else None
async def set(self, setter, content):
self.session[setter] = content
Is the middleware just so that we can access via request? Would be great to have the flash example commented.
Here is my super flash function: (I used with jinja2)
flash.py
from functools import partial
from aiohttp_session import Session
def message(session: Session, message: str, level: str = "info") -> None:
session.setdefault("flash", []).append((message, level))
debug = partial(message, level="debug")
info = partial(message, level="info")
success = partial(message, level="success")
warning = partial(message, level="warning")
error = partial(message, level="danger")
app.py
import flash
from aiohttp import web
from aiohttp_jinja2 import setup as set_jinja
from aiohttp_session import get_session, setup as set_session
from jinja2 import FileSystemLoader
@template('index.html')
async def index(rq: web.Request):
session = await get_session(rq)
flash.error(session, 'Hello world!')
...
async def jinja_ctx_processor(rq: web.Request):
session = await get_session(rq)
return {"get_flashed_messages": lambda: session.pop("flash", [])}
app = web.Application()
app.add_routes([web.get('/', index)])
set_session(app, EncryptedCookieStorage('secret_key', cookie_name='session_app'))
set_jinja(app, loader=FileSystemLoader('templates_path'), context_processors=[jinja_ctx_processor])
web.run_app(app)
index.html
<html>
{% for message, level in get_flashed_messages() %}
<div class="alert alert-{{ level }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
</html>