flask_injector
flask_injector copied to clipboard
Jinja/Flask `get_flashed_messages` fails
Flask-Injector 0.14.0 Injector 0.20.1 Flask 2.1.3
Minimum reproducible example: https://gist.github.com/NoRePercussions/00a26a4d801a91b01a916f818dd27092
Summary
- Flask-Injector adds injection capabilities to Jinja templates
-
get_flashed_messages
from Flask gets a list of internal messages - While calling
get_flashed_messages
, F-I attempts to inject:
> Providing {'with_categories': <class 'bool'>, 'category_filter': typing.Iterable[str]} for <function get_flashed_messages at 0x103838670>
>> Injector.get(<class 'bool'>, scope=<class 'injector.NoScope'>) using <injector.ClassProvider object at 0x107b89b20>
>> Creating <class 'bool'> object with {}
>> Providing {} for <slot wrapper '__init__' of 'object' objects>
>> -> False
[2023-07-12 17:35:53,377] ERROR in app: Exception on / [GET]
Traceback (most recent call last):
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/injector/__init__.py", line 637, in get_binding
return self._get_binding(interface, only_this_binder=is_scope or is_assisted_builder)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/injector/__init__.py", line 631, in _get_binding
raise KeyError
KeyError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/flask/app.py", line 2073, in wsgi_app
response = self.full_dispatch_request()
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/flask/app.py", line 1519, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/flask/app.py", line 1517, in full_dispatch_request
rv = self.dispatch_request()
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/flask/app.py", line 1503, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/app.py", line 8, in hello_world
return render_template("helloworld.html")
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/flask/templating.py", line 154, in render_template
return _render(
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/flask/templating.py", line 128, in _render
rv = template.render(context)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/jinja2/environment.py", line 1301, in render
self.environment.handle_exception()
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/jinja2/environment.py", line 936, in handle_exception
raise rewrite_traceback_stack(source=source)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/templates/helloworld.html", line 1, in top-level template code
<!DOCTYPE html>
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/flask_injector/__init__.py", line 89, in wrapper
return injector.call_with_injection(callable=fun, args=args, kwargs=kwargs)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/injector/__init__.py", line 999, in call_with_injection
dependencies = self.args_to_inject(
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/injector/__init__.py", line 91, in wrapper
return function(*args, **kwargs)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/injector/__init__.py", line 1047, in args_to_inject
instance: Any = self.get(interface)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/injector/__init__.py", line 932, in get
binding, binder = self.binder.get_binding(interface)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/injector/__init__.py", line 646, in get_binding
binding = self.create_binding(interface)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/injector/__init__.py", line 560, in create_binding
provider = self.provider_for(interface, to)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/injector/__init__.py", line 622, in provider_for
raise UnknownProvider('couldn\'t determine provider for %r to %r' % (interface, to))
injector.UnknownProvider: couldn't determine provider for typing.Iterable[str] to None
Other concerns
- Jinja's error trace appears to not identify the right error location?
- F-I's injection appears to be blindly applied to all methods I call in Jinja, regardless of whether I want injection on them
If I disable auto_bind
:
[2023-07-12 17:54:14,660] ERROR in app: Exception on / [GET]
Traceback (most recent call last):
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/injector/__init__.py", line 1047, in args_to_inject
instance: Any = self.get(interface)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/injector/__init__.py", line 932, in get
binding, binder = self.binder.get_binding(interface)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/injector/__init__.py", line 650, in get_binding
raise UnsatisfiedRequirement(None, interface)
injector.UnsatisfiedRequirement: unsatisfied requirement on bool
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/flask/app.py", line 2073, in wsgi_app
response = self.full_dispatch_request()
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/flask/app.py", line 1519, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/flask/app.py", line 1517, in full_dispatch_request
rv = self.dispatch_request()
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/flask/app.py", line 1503, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/app.py", line 9, in hello_world
return render_template("helloworld.html")
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/flask/templating.py", line 154, in render_template
return _render(
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/flask/templating.py", line 128, in _render
rv = template.render(context)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/jinja2/environment.py", line 1301, in render
self.environment.handle_exception()
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/jinja2/environment.py", line 936, in handle_exception
raise rewrite_traceback_stack(source=source)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/templates/helloworld.html", line 1, in top-level template code
<!DOCTYPE html>
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/flask_injector/__init__.py", line 89, in wrapper
return injector.call_with_injection(callable=fun, args=args, kwargs=kwargs)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/injector/__init__.py", line 999, in call_with_injection
dependencies = self.args_to_inject(
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/injector/__init__.py", line 91, in wrapper
return function(*args, **kwargs)
File "/Users/-/Projects/PycharmProjects/FlaskInjectorError/venv/lib/python3.9/site-packages/injector/__init__.py", line 1051, in args_to_inject
raise e
injector.UnsatisfiedRequirement: flask.helpers has an unsatisfied requirement on bool
However, the app runs fine if Flask-Injector is not installed.
So far I haven't found any other methods in my actual web app with this issue.
It's because: Flask-Injector lets you inject dependencies into Jinja environment globals functions (And I would even say it obliges them to be introduced into the Standard Context.)
and,
The following global variables are available within Jinja2 templates by default in the Standard Context (by the way, the "url_for()" is there, too)
So,
# Somewhere in imports
....
from injector import Module, provider
from typing import Iterable
....
# flask.get_flashed_messages(with_categories=False, category_filter=())
# Parameters:
# with_categories ([bool])– set to True to also receive categories.
# category_filter (Iterable][str]) – filter of categories to limit return values. Only categories in the list will be returned.
class GetFlashModule(Module):
def configure(self, binder):
binder.bind(bool, to=False) # provide with_categories
@provider
def category_filter(self) -> Iterable[str]:
return ()
FlaskInjector(app=app, modules=[GetFlashModule])
...
Or, the best solution, in my opinion:
# Somewhere in imports
...
from flask.helpers import get_flashed_messages
.
.
.
FlaskInjector(app=app, modules=[])
# after init the FlaskInjector
app.jinja_env.globals.update({'url_for': app.url_for, 'get_flashed_messages': get_flashed_messages})
# or, use 'context_processor'
# @app.context_processor
# def noninject_ctx_processor():
# return dict(url_for=app.url_for, get_flashed_messages=get_flashed_messages)
...
I can't imagine a single use case where dependencies must be injected into the Standard template context. It's annoying and painful.
I think the conclusion here would be the "inject in many places even if they don't declare they need injection" aspect of Flask-Injector's behavior is problematic.
It'd be a breaking change but I'm open to changing that.