Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jinja/Flask get_flashed_messages fails #82

Open
NoRePercussions opened this issue Jul 12, 2023 · 3 comments
Open

Jinja/Flask get_flashed_messages fails #82

NoRePercussions opened this issue Jul 12, 2023 · 3 comments

Comments

@NoRePercussions
Copy link

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
@NoRePercussions
Copy link
Author

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.

@olehkodak
Copy link

olehkodak commented Aug 11, 2023

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.

@jstasiak
Copy link
Collaborator

jstasiak commented Sep 5, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants