python-injector / flask_injector Goto Github PK
View Code? Open in Web Editor NEWAdds Injector support to Flask.
License: BSD 3-Clause "New" or "Revised" License
Adds Injector support to Flask.
License: BSD 3-Clause "New" or "Revised" License
Flask-Injector 0.14.0
Injector 0.20.1
Flask 2.1.3
Minimum reproducible example: https://gist.github.com/NoRePercussions/00a26a4d801a91b01a916f818dd27092
get_flashed_messages
from Flask gets a list of internal messagesget_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
All but the first call to the test client results in:
Traceback (most recent call last):
File "/home/nickh/env/local/lib/python2.7/site-packages/flask/app.py", line 1817, in wsgi_app
response = self.full_dispatch_request()
File "/home/nickh/env/local/lib/python2.7/site-packages/flask/app.py", line 1477, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/home/nickh/env/local/lib/python2.7/site-packages/flask/app.py", line 1381, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/home/nickh/env/local/lib/python2.7/site-packages/flask/app.py", line 1475, in full_dispatch_request
rv = self.dispatch_request()
File "/home/nickh/env/local/lib/python2.7/site-packages/flask/app.py", line 1461, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/home/nickh/env/local/lib/python2.7/site-packages/flask/views.py", line 83, in view
self = view.view_class(*class_args, **class_kwargs)
File "/home/nickh/env/local/lib/python2.7/site-packages/flask_injector.py", line 48, in cls
current_class, additional_kwargs=kwargs)
File "/home/nickh/env/local/lib/python2.7/site-packages/injector.py", line 692, in create_object
instance = cls.__new__(cls)
TypeError: function.__new__(X): X is not a type object (function)
This looks to be because the flask injector double-wraps the object being made.
@app.before_request
def get_token(a: MyClass):
# we can get a
@app.before_first_request
def get_token(a: MyClass):
# we cannot get a.
#error message: get_token missing 1 required positional argument
test_app.py
:
import connexion
from connexion.resolver import RestyResolver
from flask_injector import FlaskInjector
app = connexion.App(__name__)
app.add_api(
{
'swagger': '2.0',
'info': {
'version': '1.0.0',
'title': 'Test Service',
'license': {'name': 'MIT'}
},
'basePath': '/api',
'schemes': ['http'],
'consumes': ['application/json'],
'produces': ['application/json'],
'paths': {
'/test': {
'get': {
'operationId': 'test_api.test_func',
'parameters': [
{
'name': 'list_arg',
'in': 'query',
'required': False,
'type': 'array',
'items': {'type': 'string'},
'collectionFormat': 'multi'
},
],
'responses': {'200': {'description': 'Test endpoint'}},
}
},
},
},
resolver=RestyResolver('api'),
)
FlaskInjector(app=app.app, modules=[])
app.run(port=5000)
test_api.py
:
import typing as _t
import logging as _logging
def test_func(
list_arg: _t.List[str],
) -> dict:
_logging.info(f'{list_arg!r}')
return {'list_arg': list_arg}
Execute query GET http://test_app:5000/api/test?list_arg=a&list_arg=b
No error, response {'list_arg': ['a', 'b']}
Error occurs:
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/injector/__init__.py", line 440, in get_binding
return self._get_binding(key, only_this_binder=is_scope)
File "/usr/local/lib/python3.7/site-packages/injector/__init__.py", line 435, in _get_binding
raise KeyError
KeyError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
response = self.full_dispatch_request()
File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1951, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1820, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1949, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1935, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/usr/local/lib/python3.7/site-packages/flask_injector.py", line 76, in wrapper
return injector.call_with_injection(callable=fun, args=args, kwargs=kwargs)
File "/usr/local/lib/python3.7/site-packages/injector/__init__.py", line 778, in call_with_injection
owner_key=self_.__class__ if self_ is not None else callable.__module__,
File "/usr/local/lib/python3.7/site-packages/injector/__init__.py", line 57, in wrapper
return function(*args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/injector/__init__.py", line 819, in args_to_inject
instance = self.get(key.interface)
File "/usr/local/lib/python3.7/site-packages/injector/__init__.py", line 689, in get
binding, binder = self.binder.get_binding(None, key)
File "/usr/local/lib/python3.7/site-packages/injector/__init__.py", line 449, in get_binding
binding = self.create_binding(key.interface)
File "/usr/local/lib/python3.7/site-packages/injector/__init__.py", line 369, in create_binding
provider = self.provider_for(interface, to)
File "/usr/local/lib/python3.7/site-packages/injector/__init__.py", line 424, in provider_for
raise TypeError('Injecting partially applied functions is no longer supported.')
TypeError: Injecting partially applied functions is no longer supported.
pip freeze
:
backcall==0.1.0
certifi==2019.3.9
chardet==3.0.4
Click==7.0
clickclick==1.2.2
colorful==0.5.4
connexion==2.6.0
coverage==4.5.3
decorator==4.4.1
elasticsearch==6.3.1
elasticsearch-dsl==6.4.0
fastavro==0.21.19
Flask==1.1.1
Flask-Injector==0.11.0
idna==2.7
inflection==0.3.1
injector==0.16.0
ipython==7.12.0
ipython-genutils==0.2.0
itsdangerous==1.1.0
jedi==0.16.0
Jinja2==2.11.1
jsonschema==2.6.0
MarkupSafe==1.1.1
openapi-spec-validator==0.2.6
parso==0.6.1
pathlib==1.0.1
pexpect==4.8.0
pickleshare==0.7.5
pika==0.13.1
prettyprinter==0.18.0
prompt-toolkit==3.0.3
ptyprocess==0.6.0
Pygments==2.5.2
python-dateutil==2.8.1
PyYAML==5.1
redis==3.2.1
requests==2.20.1
six==1.12.0
traitlets==4.3.3
typing==3.6.6
urllib3==1.24.1
wcwidth==0.1.8
Werkzeug==0.15.1
List[str]
is removed from the argument list_arg
, the injection works correctlyI'm trying to refactor some existing flask apps, and have stumbled into this problem that I can't seem to figure out:
Given this app (relevant parts of the "main" flask app file):
configuration = ConfigGetter.get_config()
logger = log.setup_custom_logger(configuration.logging_level)
from awsinventory.api_inventory import ApiInventory
from awsinventory.healthz import HealthEndpoint
def configure_dependency_injection(binder):
binder.bind(Configuration, to=configuration, scope=singleton)
binder.bind(AwsInventory, scope=singleton)
if configuration.consul_enabled:
binder.bind(ConsulHelper, scope=singleton)
FlaskInjector(app=app, modules=[configure_dependency_injection])
We're using a configuration object with other classes can depend on, and it works great. However, I also need the same configuration object when instantiating the flask app itself (loglevel, flask host/port, etc are all in the configuration object).
In an ideal world I would have injected the configuration class into the flask app in the same way as for AwsInventory/ConsulHelper, but since the binder takes the flask app as a parameter, that's not possible - as far as I can see the flask app needs to be instantiated before the FlaskInjector can execute.
The result of these problems is that I'm unable to set up testing the way I want, because the flask app is still dependent on a "manually" instantiated configuration object, which means I can't just inject it during test as was my plan.
Again, this is not a bug and I totally understand it if you (maintainer) is unwilling to provide "support" like this (in that case I'll just close it).
I was trying to use a decorator in my view function but seems like Flask-Injector cannot inject dependencies when we use a decorator.
Here is the diff of the test case:
diff --git i/flask_injector_tests.py w/flask_injector_tests.py
index a038b38..8593841 100644
--- i/flask_injector_tests.py
+++ w/flask_injector_tests.py
@@ -28,7 +28,16 @@ def test_injections():
app = Flask(__name__)
+ # create a decorator
+ def appender(append = ' says'):
+ def decorator(func):
+ def wrapper(*args, **kwargs):
+ return func(*args, **kwargs) + append
+ return wrapper
+ return decorator
+
@app.route('/view1')
+ @appender()
def view1(content: str):
inc()
return render_template_string(content)
@@ -71,7 +80,7 @@ def test_injections():
with app.test_client() as c:
response = c.get('/view1')
- eq_(response.get_data(as_text=True), "something")
+ eq_(response.get_data(as_text=True), "something says")
with app.test_client() as c:
response = c.get('/view2')
Which then fails with the following error.
F............
======================================================================
FAIL: flask_injector_tests.test_injections
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/sherub/Documents/shuttl/experiments/flask_injector/venv/lib/python3.6/site-packages/nose/case.py", line 198, in runTest
self.test(*self.arg)
File "/Users/sherub/Documents/shuttl/experiments/flask_injector/flask_injector_tests.py", line 83, in test_injections
eq_(response.get_data(as_text=True), "something says")
AssertionError: '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n<title>500 Internal Server Error</title>\n<h1>Internal Server Error</h1>\n<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>\n' != 'something says'
-------------------- >> begin captured logging << --------------------
flask.app: ERROR: Exception on /view1 [GET]
Traceback (most recent call last):
File "/Users/sherub/Documents/shuttl/experiments/flask_injector/venv/lib/python3.6/site-packages/Flask-1.0.2-py3.6.egg/flask/app.py", line 2292, in wsgi_app
response = self.full_dispatch_request()
File "/Users/sherub/Documents/shuttl/experiments/flask_injector/venv/lib/python3.6/site-packages/Flask-1.0.2-py3.6.egg/flask/app.py", line 1815, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Users/sherub/Documents/shuttl/experiments/flask_injector/venv/lib/python3.6/site-packages/Flask-1.0.2-py3.6.egg/flask/app.py", line 1718, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/Users/sherub/Documents/shuttl/experiments/flask_injector/venv/lib/python3.6/site-packages/Flask-1.0.2-py3.6.egg/flask/_compat.py", line 35, in reraise
raise value
File "/Users/sherub/Documents/shuttl/experiments/flask_injector/venv/lib/python3.6/site-packages/Flask-1.0.2-py3.6.egg/flask/app.py", line 1813, in full_dispatch_request
rv = self.dispatch_request()
File "/Users/sherub/Documents/shuttl/experiments/flask_injector/venv/lib/python3.6/site-packages/Flask-1.0.2-py3.6.egg/flask/app.py", line 1799, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/Users/sherub/Documents/shuttl/experiments/flask_injector/flask_injector_tests.py", line 35, in wrapper
return func(*args, **kwargs) + append
TypeError: view1() missing 1 required positional argument: 'content'
--------------------- >> end captured logging << ---------------------
----------------------------------------------------------------------
Ran 13 tests in 0.541s
FAILED (failures=1)
Hi
If I Add FlaskDynaconf(app)
before FlaskInjector(app=app, modules=[init_inject])
, it'll result in TypeError: __name__ must be set to a string object
this is the dynaconf project: https://dynaconf.readthedocs.io/en/latest/guides/flask.html
Hi!
I seem to have a found a bug with the error handler and typing. Consider this code:
def init_error_handlers(app: Flask) -> None:
app.register_error_handler(SQLAlchemyError, handle_sqlalchemy_error)
def handle_sqlalchemy_error(error: SQLAlchemyError) -> Response:
logger.error("SQLAlchemy error: %s", error)
return _create_json_error_response(
errors.BadRequest.default_message, errors.BadRequest.http_status_code
)
If I remove the type on the error it works fine, but with the type:
injector.CallError: Call to handle_sqlalchemy_error(DataError('(psycopg2.DataError) invalid input syntax for type uuid: "61dc1bb7-ed81-448b-88ac-04996a0cff8"\nLINE 3: WHERE organisations.id = \'61dc1bb7-ed81-448b-88ac-04996a0cff...\n ^\n'), error=SQLAlchemyError()) failed: handle_sqlalchemy_error() got multiple values for argument 'error' (injection stack: [])
Full Call stack:
Traceback (most recent call last):
File "/Users/******/Core/venv/lib/python3.7/site-packages/flask/app.py", line 2292, in wsgi_app
response = self.full_dispatch_request()
File "/Users/******/venv/lib/python3.7/site-packages/flask/app.py", line 1815, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Users/******/venv/lib/python3.7/site-packages/flask_cors/extension.py", line 161, in wrapped_function
return cors_after_request(app.make_response(f(*args, **kwargs)))
File "/Users/******/venv/lib/python3.7/site-packages/flask/app.py", line 1719, in handle_user_exception
return handler(e)
File "/Users/******/venv/lib/python3.7/site-packages/flask_injector.py", line 76, in wrapper
return injector.call_with_injection(callable=fun, args=args, kwargs=kwargs)
File "/Users/******/venv/lib/python3.7/site-packages/injector/__init__.py", line 868, in call_with_injection
reraise(e, CallError(self_, callable, args, dependencies, e, self._stack))
File "/Users/******/venv/lib/python3.7/site-packages/injector/__init__.py", line 67, in reraise
raise exception.with_traceback(tb)
File "/Users/******/venv/lib/python3.7/site-packages/injector/__init__.py", line 866, in call_with_injection
**dependencies)
Hi :)
I've a project that uses both flask-injector, as well as flask-restx and I'm trying to figure out how to retrieve the actual controller instance I'm using on the app.
Considering this setup:
flask_injector = FlaskInjector(
app=app,
modules=[RequestedManufacturerNameModule],
)
If I try to run: flask_injector.injector.get(<MyControllerClass>)
it returns a valid instance of that class, but that is not the instance that is being used by flask-restx.
Any thoughts?
I get the following error when using FlaskInjector
:
File "/srv/app/src/app/public/web_app.py", line 15, in <module>
FlaskInjector(app=flask_app, injector=app.injector)
File "/usr/local/lib/python3.8/site-packages/Flask_Injector-0.12.3-py3.8.egg/flask_injector/__init__.py", line 327, in __init__
process_dict(container, injector)
File "/usr/local/lib/python3.8/site-packages/Flask_Injector-0.12.3-py3.8.egg/flask_injector/__init__.py", line 377, in process_dict
if isinstance(value, list):
File "/usr/local/lib/python3.8/site-packages/Werkzeug-2.0.0rc1-py3.8.egg/werkzeug/local.py", line 379, in __get__
obj = instance._get_current_object()
File "/usr/local/lib/python3.8/site-packages/Werkzeug-2.0.0rc1-py3.8.egg/werkzeug/local.py", line 499, in _get_current_object
return self.__local() # type: ignore
File "/usr/local/lib/python3.8/site-packages/Flask-1.1.2-py3.8.egg/flask/globals.py", line 38, in _lookup_req_object
raise RuntimeError(_request_ctx_err_msg)
RuntimeError: Working outside of request context.
It did not happened before and I have not changed versions of either Flask
, Injector
or FlaskInjector
in a long time, so I think it might be related to a new version of Flask dependency Werkzeug
.
Used versions:
Did anyone have the same issue?
Hi,
I have:
@request
class A:
and
class OverrideA(A):
at some point (during one request) I do:
override = OverrideA(some_data)
app.injector.binder.bind(A, override, scope=RequestScope)
I expect all other pieces of code to recieve my override instance during this request.
But A
is still recieved.
How can I get the desired behaviour (or where should I dig to fix it)?
Thanks!
Thanks for this project. I'm evaluating this as a way of managing dependencies in this large flask application I'm refactoring.
How would one manage cleanup of threadlocals, when you have to release things manually. In SQLAlchemy is this required, or anything that uses a threadpool.
I've made a working attempt (based off your example), but I'm not sure if it's the right direction.
class SQLAlchemyModule(Module):
def __init__(self, app):
self.app = app
def configure(self, binder):
db = self.configure_db()
binder.bind(Session, to=self.create_session, scope=request_scope)
def configure_db(self):
connection_string = (
'postgresql+psycopg2://{user}@{host}/{db}'
).format(
user=self.app.config['PGSQL_USER'],
host=self.app.config['PGSQL_HOST'],
db=self.app.config['PGSQL_DB'],
)
self.engine = create_engine(
connection_string,
convert_unicode=True,
)
self.Session = scoped_session(sessionmaker(bind=self.engine))
self.app.teardown_request(cleanup_session)
def create_session(self) -> Session:
return self.Session()
def destroy_session(self, *args):
self.Session.remove()
Is there anything I could do to make this class more idiomatic with Injector?
What I think would be really lovely, instead of binding to a function, you could bind to something with a yield statement.
So create_session
would become
@contextmanager
def create_session(self) -> Session:
try:
yield self.Session()
finally:
self.Session.close()
What is the best solution using flask-injector for dependent extensions of each other, for example flask-sqlalchemy and flask-security?
Ordinary simple code:
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.security import Security, SQLAlchemyUserDatastore
from models import User, Role
app = Flask("super_app")
db = SQLAlchemy(app)
security = Security(app=app, datastore=SQLAlchemyUserDatastore(db, User, Role))
While integrating Flask-Injector
I stumbled among a few cases where retrieving my injector-managed instances using dependency injection is not feasible. For example, I might have code like the following
@app.route("/foo")
@cost_center
def foo(db: sqlite3.Connection):
users = db.execute('SELECT * FROM users').all()
return render("foo.html")
where cost_center
is some function-based decorator that measures runtime stats about the respective route and uses a request
-scoped Stats-Collector to solve this task.
So far I think the best way to implement this is to give access to the underlying injector
used by Flask-Injector
itself, i.e. injector.get(StatsCollector, scope=request)
. Since this requires the developer to manage separate access to the injector, it would be nice if Flask-Injector
could support this use-case directly instead.
Given the current architecture, I am not sure whether there is a clean approach to this. At least I wanted to document this might be a valid use-case.
I tried to use your library in my project together with flask_restplus.
There is an issue with handling swagger console.
Given the simple code:
from flask import Flask
from flask_restplus import Api, Resource
from flask_injector import FlaskInjector
import injector
app = Flask(__name__)
api = Api(app)
class ToInject(object):
def __init__(self, name):
self.name=name
@api.route('/hello')
class Hello(Resource):
@injector.inject(name=ToInject('Tom'))
def __init__(self, name):
self.name = name
def get(self):
return 'Hello {}'.format(self.name);
FlaskInjector(app)
if __name__ == '__main__':
app.run(debug=True)
if u get http://localhost:5000/swagger.json you receive::
injector.CallError: Call to SwaggerView.init() failed: init() missing 1 required positional argument: 'api' (injection stack: [])
The request
scope name directly clashes with the global request
variable in Flask. Even when using the injector
-style approach and injecting flask.Request
into your handlers/services, the developer might still want to use a descriptive name like request
for it, raising an issue again.
Since renaming the scope would not be backwards-compatible, it would be nice if Flask-Injector
could at least provide a second alias like request_scoped
to avoid stuff like
from flask_injector import request as request_scoped
.
Hi, I'm using injector, flask and flask-restful classes.
When I bind my object obj
I set some models with meta syntax like:
setattr(obj, 'ModelName', MyModelClass)
# then
binder.bind(ObjClass, to=obj, scope=self.singleton)
If I check for this obj.ModelName
before requests, they are there and fine.
When I recover with the injector inside the Flask-Restful init function the object, the model is not there anymore.
Unluckily my case is more complicated than this, but I wanted to ask first if this is a possible normal behaviour and why. If not I may build a small app to replicate.
Thank you a lot for this cool project!
Apparently the version on http://pypi.python.org/pypi/Flask-Injector is more recent the the code that's in the repository (0.2.0 on PyPI vs 0.1.1 on Github)
Can you provide example of before_request? I have a fully working example with injection working on the routes but in the before request the injection is not using the singletons i have created in my AppModule configure function?
Thanks
Hi,
First, this issue assumes this repository's releases follow semantic versioning. If not, I ask to consider doing so.
Regarding 812168f, the change to require Flask 2.1.2 is a breaking change. Semantic versioning calls for a "major" version number increment when this occurs. This matters for automation pipelines, notably (given this is GitHub) Dependabot may attempt to update to this version when it should not. For a fix, I'm asking to "yank" version 0.14.0 and release a new major version instead.
I have upgraded my packages to:
flask-2.2.2
injector-0.20.1
Flask-injector-0.14.1
apispec-6.0.2
flask-apispec-0.11.4
I'm using Flask-apispec to auto-generate api documentation, and I'm using injector library. both independently works well in my code but when I use them together, somehow @Inject decorator doesn't work. and it throws the error missing positional argument.
I tried to debug the code, in stack trace, it completely skips the inject decorator.
Here is a sample code
import marshmallow as ma
from flask_apispec import doc, marshal_with, use_kwargs
from injector import inject
from flask_injector import FlaskInjector
from injector import inject, singleton
def configure(binder):
binder.bind(
str,
to="MyCat",
scope=singleton,
)
class Pet:
def __init__(self, name, type):
self.name = name
self.type = type
class PetSchema(ma.Schema):
name = ma.fields.Str()
type = ma.fields.Str()
###
import flask
import flask.views
from flask_apispec import FlaskApiSpec, MethodResource
app = flask.Flask(__name__)
docs = FlaskApiSpec(app)
@doc(
tags=['pets'],
params={'pet_id': {'description': 'the pet name'}},
)
class CatResource(MethodResource):
@inject
def __init__(self, pet_name: str):
self.pet_name = pet_name
super().__init__()
@marshal_with(PetSchema)
def get(self, pet_id):
return Pet(self.pet_name, 'cat')
@marshal_with(PetSchema)
def put(self, pet_id):
return Pet('calici', 'cat')
app.add_url_rule('/cat/<pet_id>', view_func=CatResource.as_view('CatResource'))
# registering view
docs.register(CatResource, endpoint='CatResource')
FlaskInjector(app=app, modules=[configure])
if __name__ == '__main__':
app.run(debug=True)
Everything works well if I comment out the "docs.register(CatResource, endpoint='CatResource')"'
Note: Everything was working fine before upgrading libraries to the latest
Hi again!
As a workaround for #27 I've decided to have some kind of factory:
@singleton
class AProvider(Module):
override = None
def set_override(self, override: A):
self.override = override
@provides
@request
def get(self) -> A:
if self.override is None:
return A()
else:
return_me = self.override
self.override = None
return return_me
The problem is - AProvider is not always a singleton. It is singleton for all manuall requests to injector, but it uses another copy of AProvider when calling provide
method. Thus this intent is now working.
Workaround for me is to use class.override instead on instance override. But I still expect singleton to be singleton...
If I have a request scoped variable, I cannot inject it into a teardown_request
handler. If I do, I get the following exception.
Error on request:
Traceback (most recent call last):
File "/home/nick/.local/share/virtualenvs/flask_injector_poc-sJYA9nHW/lib/python3.7/site-packages/werkzeug/local.py", line 72, in __getattr__
return self.__storage__[self.__ident_func__()][name]
KeyError: 139781434668800
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/nick/.local/share/virtualenvs/flask_injector_poc-sJYA9nHW/lib/python3.7/site-packages/werkzeug/serving.py", line 304, in run_wsgi
execute(self.server.app)
File "/home/nick/.local/share/virtualenvs/flask_injector_poc-sJYA9nHW/lib/python3.7/site-packages/werkzeug/serving.py", line 292, in execute
application_iter = app(environ, start_response)
File "/home/nick/.local/share/virtualenvs/flask_injector_poc-sJYA9nHW/lib/python3.7/site-packages/flask/app.py", line 2463, in __call__
return self.wsgi_app(environ, start_response)
File "/home/nick/.local/share/virtualenvs/flask_injector_poc-sJYA9nHW/lib/python3.7/site-packages/flask/app.py", line 2457, in wsgi_app
ctx.auto_pop(error)
File "/home/nick/.local/share/virtualenvs/flask_injector_poc-sJYA9nHW/lib/python3.7/site-packages/flask/ctx.py", line 452, in auto_pop
self.pop(exc)
File "/home/nick/.local/share/virtualenvs/flask_injector_poc-sJYA9nHW/lib/python3.7/site-packages/flask/ctx.py", line 415, in pop
self.app.do_teardown_request(exc)
File "/home/nick/.local/share/virtualenvs/flask_injector_poc-sJYA9nHW/lib/python3.7/site-packages/flask/app.py", line 2299, in do_teardown_request
func(exc)
File "/home/nick/.local/share/virtualenvs/flask_injector_poc-sJYA9nHW/lib/python3.7/site-packages/flask_injector.py", line 89, in wrapper
return injector.call_with_injection(callable=fun, args=args, kwargs=kwargs)
File "/home/nick/.local/share/virtualenvs/flask_injector_poc-sJYA9nHW/lib/python3.7/site-packages/injector/__init__.py", line 972, in call_with_injection
owner_key=self_.__class__ if self_ is not None else callable.__module__,
File "/home/nick/.local/share/virtualenvs/flask_injector_poc-sJYA9nHW/lib/python3.7/site-packages/injector/__init__.py", line 96, in wrapper
return function(*args, **kwargs)
File "/home/nick/.local/share/virtualenvs/flask_injector_poc-sJYA9nHW/lib/python3.7/site-packages/injector/__init__.py", line 1017, in args_to_inject
instance = self.get(interface) # type: Any
File "/home/nick/.local/share/virtualenvs/flask_injector_poc-sJYA9nHW/lib/python3.7/site-packages/injector/__init__.py", line 915, in get
result = scope_instance.get(interface, binding.provider).get(self)
File "/home/nick/.local/share/virtualenvs/flask_injector_poc-sJYA9nHW/lib/python3.7/site-packages/flask_injector.py", line 265, in get
return self._locals.scope[key]
File "/home/nick/.local/share/virtualenvs/flask_injector_poc-sJYA9nHW/lib/python3.7/site-packages/werkzeug/local.py", line 74, in __getattr__
raise AttributeError(name)
AttributeError: scope
After some digging, it seems that this is because Flask executes teardown handlers in the reverse order that they are inserted (see relevant source). Because flask_injector inserts the request scope clean up as a teardown handler, after all others are inserted, flask_injector's handler is run before mine.
I was able to hack around this by, after setting up my FlaskInjector
running app.teardown_request_funcs[None].reverse()
. This is not a great solution, IMO. I'm hesitant to submit a PR that just inserts this teardown handler as the first one, as the Flask docs don't seem to guarantee that the handlers are called in reverse order; it seems like an implementation detail.
What follows is an (admittedly silly) proof of concept that produces the above exception on request.
import datetime
import flask
import flask_injector
import injector
app = flask.Flask(__name__)
@app.route('/')
def index(time: datetime.datetime):
return f'The time is {time.isoformat()}'
@app.teardown_request
def teardown_handler(err: Exception, time: datetime.datetime):
print(f'Tearing down the request from {time.isoformat()}')
flask_injector.FlaskInjector(app=app, modules=[
lambda binder: binder.bind(datetime.datetime, to=datetime.datetime.now(), scope=flask_injector.request)
])
if __name__ == '__main__':
# HACK: works around the bug.
# app.teardown_request_funcs[None].reverse()
app.run()
Hi all,
Injector has deprecated support for injecting into Module.configure()
(python-injector/injector#31). This was the recommended pattern for working with Flask extensions (https://github.com/alecthomas/flask_injector#supporting-flask-extensions). Do you have any other recommendations for working with Flask extensions?
Thanks,
Gavin
When using flask's own MethodView with flask injector, the app fails with the error:
Traceback (most recent call last):
File "/Users/jacksontoomey1/.virtualenvs/rooster-d4LzYqiE/lib/python3.7/site-packages/flask_injector.py", line 101, in wrap_class_based_view
class_kwargs = fun_closure['class_kwargs']
KeyError: 'class_kwargs'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "example.py", line 28, in <module>
FlaskInjector(app=app, modules=[])
File "/Users/jacksontoomey1/.virtualenvs/rooster-d4LzYqiE/lib/python3.7/site-packages/flask_injector.py", line 317, in __init__
process_dict(container, injector)
File "/Users/jacksontoomey1/.virtualenvs/rooster-d4LzYqiE/lib/python3.7/site-packages/flask_injector.py", line 344, in process_dict
d[key] = wrap_fun(value, injector)
File "/Users/jacksontoomey1/.virtualenvs/rooster-d4LzYqiE/lib/python3.7/site-packages/flask_injector.py", line 81, in wrap_fun
return wrap_class_based_view(fun, injector)
File "/Users/jacksontoomey1/.virtualenvs/rooster-d4LzYqiE/lib/python3.7/site-packages/flask_injector.py", line 104, in wrap_class_based_view
flask_restful_api = fun_closure['self']
KeyError: 'self'
Here is an example that fails.
from functools import wraps
from flask import Flask
from flask.views import MethodView
from flask_injector import FlaskInjector
app = Flask(__name__)
def my_wrapper(f):
@wraps(f)
def _wrapper(*args, **kwargs):
return f(*args, **kwargs)
return _wrapper
class MyView(MethodView):
decorators = [my_wrapper]
def get(self):
return 'Hello'
app.add_url_rule('/', view_func=MyView.as_view('index'))
FlaskInjector(app=app, modules=[])
if __name__ == '__main__':
app.run()
Hi, I'm completely new to python world so excuse my lack of knowledge.
I read in the documentation that this also supports Flask-RestPlus Resource constructors, even though I could find neither here or on the internet any decent example that uses Flask-RestPlus with this library.
I tried my self and what I reached is that I need to consider the API resource like any other class and configure it with the binder, is this the correct way to do it?, because in the examples (while using with bare Flask) there was no need to configure the routes, as they are already a function, not a class, I don't know if this is the reason.
I was wondering if it would be complex to support flask-rebar (https://github.com/plangrid/flask-rebar). Basically:
Traceback (most recent call last):
File "/Users//****//Projects//****//Core/venv/lib/python3.7/site-packages/flask/app.py", line 1813, in full_dispatch_request
rv = self.dispatch_request()
File "/Users//****//Projects//****//Core/venv/lib/python3.7/site-packages/flask/app.py", line 1799, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/Users/****/Projects/****/Core/venv/lib/python3.7/site-packages/flask_rebar/rebar.py", line 127, in wrapped
rv = f(*args, **kwargs)
TypeError: auth_callback() missing 1 required positional argument: 'service'
As is custom with flask extensions, it would be nice to provide an init_app
function (basically you move the logic from the __init__
into this method.
Flask-restplus
has been forked and is now actively maintained at flask-restx. Any plans to add support? I could do a PR.
I'm experiencing memory leaks when using gunicorn eventlet workers.
Some digging shows me the leak comes from GreenThread objects not being released by RequestScope.
def reset(self):
self._local_manager.cleanup()
self._locals.scope = {}
Here, the self._local_manager.cleanup()
pops the reference to the GreenThread object but self._locals.scope = {}
adds the reference back to the __storage__
dictionary held by self._locals
, so GreenThread object is not dereferenced and causing the application memory usage increase.
The problem disappears when switching the order of the two lines. Any thought?
Injector supports implicit via annotations using use_annotations=True
flag (see https://github.com/alecthomas/injector/blob/master/injector_test_py3.py), would be nice to have this in flask_injector.
I have Flask app with flask-injector with creat function.
def create_app(testing: bool = False) -> Flask: app = Flask(__name__) config_app(app, testing) db.init_app(app) migrate.init_app(app, db) router.init_app(app) CORS(app) # Setup injector injector = Injector([AppModule(app.config)]) FlaskInjector(app=app, injector=injector) return app
when i start app (flask run) i get error:
` Traceback (most recent call last):
File "C:\Users\tmv28\AppData\Local\Programs\Python\Python39\lib\runpy.py", line 197, in _run_module_as_main
return _run_code(code, main_globals, None,
File "C:\Users\tmv28\AppData\Local\Programs\Python\Python39\lib\runpy.py", line 87, in run_code
exec(code, run_globals)
File "C:\Users\tmv28\Documents\Andrey\test\backend\venv\Scripts\flask.exe_main.py", line 7, in
File "c:\users\tmv28\documents\andrey\test\backend\venv\lib\site-packages\flask\cli.py", line 967, in main
cli.main(args=sys.argv[1:], prog_name="python -m flask" if as_module else None)
File "c:\users\tmv28\documents\andrey\test\backend\venv\lib\site-packages\flask\cli.py", line 586, in main
return super(FlaskGroup, self).main(*args, **kwargs)
File "c:\users\tmv28\documents\andrey\test\backend\venv\lib\site-packages\click\core.py", line 1059, in main
rv = self.invoke(ctx)
File "c:\users\tmv28\documents\andrey\test\backend\venv\lib\site-packages\click\core.py", line 1665, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "c:\users\tmv28\documents\andrey\test\backend\venv\lib\site-packages\click\core.py", line 1401, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "c:\users\tmv28\documents\andrey\test\backend\venv\lib\site-packages\click\core.py", line 767, in invoke
return __callback(*args, **kwargs)
File "c:\users\tmv28\documents\andrey\test\backend\venv\lib\site-packages\click\decorators.py", line 84, in new_func
return ctx.invoke(f, obj, *args, **kwargs)
File "c:\users\tmv28\documents\andrey\test\backend\venv\lib\site-packages\click\core.py", line 767, in invoke
return __callback(*args, **kwargs)
File "c:\users\tmv28\documents\andrey\test\backend\venv\lib\site-packages\flask\cli.py", line 848, in run_command
app = DispatchingApp(info.load_app, use_eager_loading=eager_loading)
File "c:\users\tmv28\documents\andrey\test\backend\venv\lib\site-packages\flask\cli.py", line 305, in init
self.load_unlocked()
File "c:\users\tmv28\documents\andrey\test\backend\venv\lib\site-packages\flask\cli.py", line 330, in load_unlocked
self.app = rv = self.loader()
File "c:\users\tmv28\documents\andrey\test\backend\venv\lib\site-packages\flask\cli.py", line 388, in load_app
app = locate_app(self, import_name, name)
File "c:\users\tmv28\documents\andrey\test\backend\venv\lib\site-packages\flask\cli.py", line 240, in locate_app
import(module_name)
File "C:\Users\tmv28\Documents\Andrey\test\backend\main\server.py", line 4, in
app = create_instance()
File "C:\Users\tmv28\Documents\Andrey\test\backend\main\apps\server_init.py", line 22, in create_instance
return create_app(kwargs.get('testing', False))
File "C:\Users\tmv28\Documents\Andrey\test\backend\main\apps\server\factory.py", line 41, in create_app
FlaskInjector(app=app, injector=injector)
File "c:\users\tmv28\documents\andrey\test\backend\venv\lib\site-packages\flask_injector_init.py", line 327, in init
process_dict(container, injector)
File "c:\users\tmv28\documents\andrey\test\backend\venv\lib\site-packages\flask_injector_init.py", line 379, in process_dict
elif hasattr(value, 'call'):
File "c:\users\tmv28\documents\andrey\test\backend\venv\lib\site-packages\werkzeug\local.py", line 422, in get
obj = instance._get_current_object()
File "c:\users\tmv28\documents\andrey\test\backend\venv\lib\site-packages\werkzeug\local.py", line 544, in _get_current_object
return self.__local() # type: ignore
File "c:\users\tmv28\documents\andrey\test\backend\venv\lib\site-packages\flask\globals.py", line 38, in _lookup_req_object
raise RuntimeError(_request_ctx_err_msg)
RuntimeError: Working outside of request context.
This typically means that you attempted to use functionality that needed
an active HTTP request. Consult the documentation on testing for
information about how to avoid this problem.
`
what did i wrong?
The following fails with: injector.UnknownProvider: couldn't determine provider for <class main.Foo at 0x10952aa10> to <main.Foo instance at 0x10955b320>
class Foo:
def init(self):
pass
class TestModule(Module):
def configure(self, binder):
binder.bind(Foo, to=Foo())
app = flask.Flask(name)
FlaskInjector(app=app, modules=[
TestModule
])
On Flask 2, I get this error every time I run a query. I have solved the issue only changing the code of Flask-Injector but I am not confident with the impact of the change I have done.
This error is present when you run the unit test suite.
The fix I propose broke the test test_forward_references_work
:(.
Version:
flask: 2.0.1
flask-injector: 0.13.0
flask-restx: 0.5.1
injector: 0.18.4
127.0.0.1 - - [09/Sep/2021 10:23:38] "GET /swaggerui/favicon-16x16.png HTTP/1.1" 500 -
Error on request:
Traceback (most recent call last):
File "/home/project/.venv/lib/python3.8/site-packages/injector/__init__.py", line 1176, in _infer_injected_bindings
bindings = get_type_hints(callable, include_extras=True)
File "/home/project/.venv/lib/python3.8/site-packages/typing_extensions.py", line 1924, in get_type_hints
hint = typing.get_type_hints(obj, globalns=globalns, localns=localns)
File "/usr/lib/python3.8/typing.py", line 1264, in get_type_hints
value = _eval_type(value, globalns, localns)
File "/usr/lib/python3.8/typing.py", line 270, in _eval_type
return t._evaluate(globalns, localns)
File "/usr/lib/python3.8/typing.py", line 518, in _evaluate
eval(self.__forward_code__, globalns, localns),
File "<string>", line 1, in <module>
import importlib.util
NameError: name 'Response' is not defined
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/project/.venv/lib/python3.8/site-packages/werkzeug/serving.py", line 319, in run_wsgi
execute(self.server.app)
File "/home/project/.venv/lib/python3.8/site-packages/werkzeug/serving.py", line 308, in execute
application_iter = app(environ, start_response)
File "/home/project/.venv/lib/python3.8/site-packages/flask/app.py", line 2088, in __call__
return self.wsgi_app(environ, start_response)
File "/home/project/.venv/lib/python3.8/site-packages/flask/app.py", line 2073, in wsgi_app
response = self.handle_exception(e)
File "/home/project/.venv/lib/python3.8/site-packages/flask_restx/api.py", line 672, in error_router
return original_handler(e)
File "/home/project/.venv/lib/python3.8/site-packages/flask/app.py", line 2070, in wsgi_app
response = self.full_dispatch_request()
File "/home/project/.venv/lib/python3.8/site-packages/flask/app.py", line 1515, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/home/project/.venv/lib/python3.8/site-packages/flask_restx/api.py", line 672, in error_router
return original_handler(e)
File "/home/project/.venv/lib/python3.8/site-packages/flask/app.py", line 1513, in full_dispatch_request
rv = self.dispatch_request()
File "/home/project/.venv/lib/python3.8/site-packages/flask/app.py", line 1499, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
File "/home/project/.venv/lib/python3.8/site-packages/flask_injector/__init__.py", line 89, in wrapper
return injector.call_with_injection(callable=fun, args=args, kwargs=kwargs)
File "/home/project/.venv/lib/python3.8/site-packages/injector/__init__.py", line 1010, in call_with_injection
bindings = get_bindings(callable)
File "/home/project/.venv/lib/python3.8/site-packages/injector/__init__.py", line 1163, in get_bindings
callable, _infer_injected_bindings(callable, only_explicit_bindings=look_for_explicit_bindings)
File "/home/project/.venv/lib/python3.8/site-packages/injector/__init__.py", line 1178, in _infer_injected_bindings
raise _BindingNotYetAvailable(e)
I track the root cause in wrap_fun
that is supposed to managed this sort of error. On Flask,
if hasattr(fun, '__call__') and not isinstance(fun, type):
try:
type_hints = get_type_hints(fun)
except (AttributeError, TypeError):
# Some callables aren't introspectable with get_type_hints,
# let's assume they don't have anything to inject. The exception
# types handled here are what I encountered so far.
# It used to be AttributeError, then https://github.com/python/typing/pull/314
# changed it to TypeError.
wrap_it = False
except NameError:
wrap_it = True
On Flask, the error is due to the usage of typehint with in flask/scaffold.py
if t.TYPE_CHECKING:
from .wrappers import Response
[...]
def send_static_file(self, filename: str) -> "Response":
The function get_type_hints raise a NameError
exception in this context.
I suppose the existance of NameError has a reason but moving it in Non wrapping condition solve the issue I have. I didn't find the reason of the existence of this condition.
if hasattr(fun, '__call__') and not isinstance(fun, type):
try:
type_hints = get_type_hints(fun)
except (AttributeError, TypeError, NameError):
# Some callables aren't introspectable with get_type_hints,
# let's assume they don't have anything to inject. The exception
# types handled here are what I encountered so far.
# It used to be AttributeError, then https://github.com/python/typing/pull/314
# changed it to TypeError.
wrap_it = False
the issue with get_type_hints and Forward Ref
Hi! I am facing very strange behaviour when trying to open swgger doc provided by flask smorest.
When trying to open swagger page (http://127.0.0.1:5001/application/swagger-ui) application falls down with error TypeError('Injecting Any is not supported').
I have removed all bindings to make example more understandable and clear, so sources:
from flask import Flask
from flask.views import MethodView
from flask_injector import FlaskInjector
from flask_smorest import Api, Blueprint
healthcheck_blueprint = Blueprint("healthcheck", __name__, description="App status checking")
def setup_app_injector(flask_app: Flask) -> None:
FlaskInjector(app=flask_app)
@healthcheck_blueprint.route("/healthcheck")
class HealthcheckView(MethodView):
@healthcheck_blueprint.response(status_code=200)
def get(self): # type: ignore
"""Simple healthcheck"""
return {"project": "test_injector"}
def create_flask_app() -> Flask:
flask_app = Flask(__name__)
class Config:
OPENAPI_VERSION = "3.0.2"
OPENAPI_URL_PREFIX = "/application"
OPENAPI_SWAGGER_UI_PATH = "/swagger-ui"
OPENAPI_SWAGGER_UI_VERSION = "3.24.2"
OPENAPI_SWAGGER_UI_URL = "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.24.2/"
OPENAPI_REDOC_PATH = "redoc"
flask_app.config["API_TITLE"] = "test_injector"
flask_app.config["API_VERSION"] = "v1"
flask_app.config.from_object(Config)
api = Api(app=flask_app)
api.register_blueprint(healthcheck_blueprint, url_prefix="/application")
setup_app_injector(flask_app=flask_app)
return flask_app
app = create_flask_app()
def main() -> None:
local_runtime_configuration: dict[str, int | bool] = {"port": 5001, "debug": True}
app.run(**local_runtime_configuration) # type: ignore[arg-type]
if __name__ == "__main__":
main()
My pyproject:
[tool.poetry]
name = "test-injector"
version = "0.1.0"
description = ""
authors = []
readme = "README.md"
packages = [{include = "test_injector"}]
[tool.poetry.dependencies]
python = "^3.11"
flask = "2.2.3"
flask-smorest = "0.40.0"
injector = "0.20.1"
flask-injector = "0.14.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Everything works fine if remove injector (comment line 40 setup_app_injector(flask_app=flask_app)
).
Full trace:
Traceback (most recent call last):
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/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/dreamer/Projects/Development/test_injector/venv/lib/python3.11/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/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/flask/app.py", line 2551, in __call__
return self.wsgi_app(environ, start_response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/flask/app.py", line 2531, in wsgi_app
response = self.handle_exception(e)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/flask/app.py", line 2528, in wsgi_app
response = self.full_dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/flask/app.py", line 1825, in full_dispatch_request
rv = self.handle_user_exception(e)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/flask/app.py", line 1823, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/flask/app.py", line 1799, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/flask_injector/__init__.py", line 45, in wrapper
return im(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/flask_smorest/spec/__init__.py", line 139, in _openapi_swagger_ui
return flask.render_template(
^^^^^^^^^^^^^^^^^^^^^^
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/flask/templating.py", line 147, in render_template
return _render(app, template, context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/flask/templating.py", line 130, in _render
rv = template.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/jinja2/environment.py", line 1301, in render
self.environment.handle_exception()
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/jinja2/environment.py", line 936, in handle_exception
raise rewrite_traceback_stack(source=source)
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/flask_smorest/spec/templates/swagger_ui.html", line 18, in top-level template code
url: "{{ url_for('api-docs.openapi_json') }}",
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/flask_injector/__init__.py", line 89, in wrapper
return injector.call_with_injection(callable=fun, args=args, kwargs=kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/injector/__init__.py", line 999, in call_with_injection
dependencies = self.args_to_inject(
^^^^^^^^^^^^^^^^^^^^
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/injector/__init__.py", line 91, in wrapper
return function(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/injector/__init__.py", line 1047, in args_to_inject
instance: Any = self.get(interface)
^^^^^^^^^^^^^^^^^^^
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/injector/__init__.py", line 932, in get
binding, binder = self.binder.get_binding(interface)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/injector/__init__.py", line 646, in get_binding
binding = self.create_binding(interface)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/injector/__init__.py", line 560, in create_binding
provider = self.provider_for(interface, to)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/dreamer/Projects/Development/test_injector/venv/lib/python3.11/site-packages/injector/__init__.py", line 571, in provider_for
raise TypeError('Injecting Any is not supported')
For someone who facing similiar problem -> Add flasgger to provide openapi documentation to your application.
closure_contents = (c.cell_contents for c in cast(Any, fun).__closure__)
fun_closure = dict(zip(fun.__code__.co_freevars, closure_contents))
try:
class_kwargs = fun_closure['class_kwargs']
except KeyError:
# Most likely flask_restful resource, we'll see in a second
flask_restful_api = fun_closure['self']
# flask_restful wraps ResourceClass.as_view() result in its own wrapper
# the as_view() result is available under 'resource' name in this closure
fun = fun_closure['resource']
fun_closure = {}
class_kwargs = {}
# if the lines above succeeded we're quite sure it's flask_restful resource
The argument fun
is the view function. If it's decorated, the value of fun.code.co_freevars will be scoped to the decorator, and it won't include fun_closure['self']
Flask has started removing deprecated features, making flask injector 0.14 incompatible with flask >=2.3
Flask changelog: https://github.com/pallets/flask/blob/main/CHANGES.rst
In particular
flask_injector/__init__.py:320: in __init__
process_list(app.before_first_request_funcs, injector)
AttributeError: 'ContextFlask' object has no attribute 'before_first_request_funcs'
Some features are also expected to be removed in 2.4
Would be awesome with an update to handle this!
Hey, can you enable Travis for this repository? I don't seem to be able to do that.
When running unittest with class-based views under Python 3.5 with flask_injector 0.6.2, I get the following error:
Error
Traceback (most recent call last):
File "/Users/shair/repos/flask-injection-bug/tests.py", line 14, in setUp
FlaskInjector(app=app, modules=[configure])
File "/Users/shair/.virtualenvs/flask-injection-bug/lib/python3.5/site-packages/flask_injector.py", line 221, in __init__
process_dict(container, injector)
File "/Users/shair/.virtualenvs/flask-injection-bug/lib/python3.5/site-packages/flask_injector.py", line 244, in process_dict
d[key] = wrap_fun(value, injector)
File "/Users/shair/.virtualenvs/flask-injection-bug/lib/python3.5/site-packages/flask_injector.py", line 63, in wrap_fun
class_args = fun_closure['class_args']
KeyError: 'class_args'
Here is code to reproduce the error:
app.py:
from flask import Flask
from flask.views import View
from flask_injector import FlaskInjector
from injector import inject
app = Flask(__name__)
class Message(object):
def __init__(self, message):
self.message = message
def __str__(self):
return self.message
class IndexView(View):
@inject(message=Message)
def __init__(self, message):
super(IndexView, self).__init__()
self.message = message
def dispatch_request(self):
return str(self.message)
class ConfigureBinder(object):
def __init__(self, message):
self.message = message
def configure_injector(self, binder):
binder.bind(Message, to=Message(self.message))
app.add_url_rule('/', view_func=IndexView.as_view('index'))
configure = ConfigureBinder('hello').configure_injector
FlaskInjector(app=app, modules=[configure])
if __name__ == '__main__':
app.run(debug=True)
tests.py:
from unittest import TestCase
from flask_injector import FlaskInjector
from app import app, ConfigureBinder
class TestApp(TestCase):
def setUp(self):
app.testing = True
configure = ConfigureBinder('testing').configure_injector
FlaskInjector(app=app, modules=[configure])
def test_message(self):
response = app.test_client().get('/')
self.assertIn('testing', str(response.data))
The problem seems to lie in injecting multiple times. Possibly related to #6?
Update: The offending line was indeed introduced in the fix for #6: 57fddc2#diff-76d5d260b4726f4ecf4f78cba40a6597R50
Update 2: Here is root offending line. The problem is that class_args is not passed into injector.create_object
, so we don't get a class_args
property from fun.__closure__
in wrap_class_based_view
.
I am trying to add instance methods to handle different endpoints. The instance of the class itself is created by injector and will add the routes to the flask app.
Because FlaskInjector needs to be run after adding all endpoints, I also bind the Flask app during the initialization step of the app.
from flask import Flask
from flask_injector import request, FlaskInjector
from injector import inject, Module, singleton, Injector
class HelloWorldServiceModule(Module):
def configure(self, binder):
binder.bind(HelloWorldService, scope=request)
class HelloWorldService:
def hello_world(self, str):
return "Hello " + str
class HelloWorldEndpointModule(Module):
def configure(self, binder):
binder.install(HelloWorldServiceModule)
binder.bind(HelloWorldEndpoint, scope=singleton)
class HelloWorldEndpoint:
@inject
def __init__(self, app: Flask):
self.app = app
self.app.add_url_rule('/hello/<name>', 'hello', self.say_hello)
def say_hello(self, name, hello_world_service: HelloWorldService):
return hello_world_service.hello_world(name)
def start_app(clazz, modules):
flask_app = Flask(__name__)
def configure_flask(binder):
binder.bind(Flask, to=flask_app, scope=singleton)
modules.insert(0, configure_flask)
injector = Injector(modules=modules, auto_bind=False)
injector.get(clazz)
FlaskInjector(app=flask_app, injector=injector)
flask_app.run('0.0.0.0')
if __name__ == "__main__":
start_app(HelloWorldEndpoint, [HelloWorldEndpointModule])
Running the app causes the following error:
Traceback (most recent call last):
File "/mnt/c/Users/Rene Hollander/repositories/rep0st/flask_injector_magic.py", line 47, in <module>
start_app(HelloWorldEndpoint, [HelloWorldEndpointModule])
File "/mnt/c/Users/Rene Hollander/repositories/rep0st/flask_injector_magic.py", line 41, in start_app
FlaskInjector(app=flask_app, injector=injector)
File "/mnt/c/Users/Rene Hollander/repositories/rep0st/venv/lib/python3.6/site-packages/flask_injector.py", line 309, in __init__
process_dict(container, injector)
File "/mnt/c/Users/Rene Hollander/repositories/rep0st/venv/lib/python3.6/site-packages/flask_injector.py", line 337, in process_dict
d[key] = wrap_fun(value, injector)
File "/mnt/c/Users/Rene Hollander/repositories/rep0st/venv/lib/python3.6/site-packages/flask_injector.py", line 65, in wrap_fun
return wrap_fun(inject(fun), injector)
File "/mnt/c/Users/Rene Hollander/repositories/rep0st/venv/lib/python3.6/site-packages/injector/__init__.py", line 1055, in inject
return method_wrapper(function, bindings)
File "/mnt/c/Users/Rene Hollander/repositories/rep0st/venv/lib/python3.6/site-packages/injector/__init__.py", line 1132, in method_wrapper
read_and_store_bindings(bindings)
File "/mnt/c/Users/Rene Hollander/repositories/rep0st/venv/lib/python3.6/site-packages/injector/__init__.py", line 1128, in read_and_store_bindings
f.__bindings__ = merged_bindings
AttributeError: 'method' object has no attribute '__bindings__'
I guess FlaskInjector just does not support instance methods at this point.
I will further investigate the issue tomorrow and maybe try to get a fix for it, hoping there is nothing obvious I am missing in regards to the issue.
Hi
I'm trying to (re)bind an abstract class (who inherits from abc.ABC) to a mock instance created with MagicMock with the aim of test a class who inherits from MethodView, I'm doing to rebind calling FlaskInjector into the setUp() method:
from abc import ABC, abstractmethod
from unittest import TestCase
from unittest.mock import MagicMock
from flask import Flask
from flask.json import jsonify
from flask.views import MethodView
from flask_injector import FlaskInjector
from injector import inject
class ServiceInterface(ABC):
@abstractmethod
def do_action(self) -> str:
pass
class Endpoints(MethodView):
@inject
def __init__(self, service: ServiceInterface):
self.service = service
def get(self):
return jsonify(self.service.do_action())
class Test(TestCase):
def conf(self, binder):
self.srv_mock = MagicMock(autospec=ServiceInterface)
binder.bind(ServiceInterface, self.srv_mock)
def setUp(self):
FlaskInjector(app=app, modules=[self.conf])
app.testing = True
self.client = app.test_client()
def test_mock(self):
self.srv_mock.do_action.return_value = 'test'
r = self.client.get('/endpoint/')
self.assertEqual(r.status_code, 200)
def configure_bindings(binder):
pass
app = Flask('app')
app.add_url_rule('/endpoint/', view_func=Endpoints.as_view('ep'), methods=['GET',])
FlaskInjector(app=app, modules=[configure_bindings])
And fail with an error:
[...]
File ".env/lib/python3.6/site-packages/injector.py", line 731, in create_object
instance = cls.__new__(cls)
injector.CallError: Call to ABCMeta.__new__() failed: Can't instantiate abstract class ServiceInterface with abstract methods do_action (injection stack: [<class 'flask-injector-mock.Endpoints'>])
I'm probably doing the rebind wrong.
I've injected some objects into a route that I found is commonly throwing exception. When this occurs, the app memory usage starts to climb every time another exception is thrown. I'm using the @injector
decorator on the function. I'm thinking that the objects are injected "per request" and are not being cleaned up. Does this seem possible or expected?
Somehow, injector fail to inject route param when flask application are run behind gunicorn
Hello,
based on code from this post: #40 injector throws the above error. My code is almost identical, the only difference is flask_restful instead of flask_restplus:
from flask import Flask
from flask_restful import Api, Resource
from flask_injector import FlaskInjector, inject, singleton
app = Flask(name)
app.secret_key = "123"
api = Api(app=app)
class MyConf():
def init(self, val: int):
self.val = val
class MyApi(Resource):
@Inject
def init(self, conf: MyConf, **kwargs): # <- here just added **kwargs to receice the extra passed api
parameter
self.val = conf.val
# Don't know if really needed
super().init(**kwargs)
def get(self, conf: MyConf):
return {'x': conf.val}, 200
api.add_resource(MyApi, '/myapi')
def configure(binder):
binder.bind(
MyConf,
to=MyConf(456),
scope=singleton
)
# No need to bind the resource itself
#binder.bind(
# MyApi,
# to=MyApi(myConf)
#)
FlaskInjector(app=app, modules=[configure])
app.run(port=555, debug=True)
Am I doing something wrong here or this is a bug?
My setup:
Flask==2.0.1
Flask-HTTPAuth==4.4.0
Flask-Injector==0.12.3
Flask-JWT==0.3.2
Flask-RESTful==0.3.9
typing-extensions==3.7.4.3
typing-inspect==0.6.0
injector==0.18.4
injector dropped use_annotation but flask injector still uses it.
flask injector automatically install injector 0.13.1 which causes errors
Hello,
Is there a way to use this package without app.run()
where serverless doesnt require running the app using the aforementioned command?
When using this package, for some reason I can't use "contructor injection", nor injector.get(INTERFACE)
but this works:
flask_injector = FlaskInjector(app, modules=modules)
# some code
# some other code
# also some other code
flask_injector.get(INTERFACE)
Thanks :D
I'm using flask-injector==0.10.1 alone with Flask-Restplus However Flask-Restful is a hard dependecy because otherwise the following error will be thrown:
api_next_1 | File "/usr/local/lib/python3.6/site-packages/flask_injector.py", line 65, in wrap_fun
api_next_1 | return wrap_class_based_view(fun, injector)
api_next_1 | File "/usr/local/lib/python3.6/site-packages/flask_injector.py", line 156, in wrap_class_based_view
api_next_1 | return wrap_flask_restful_resource(fun, flask_restful_api, injector)
api_next_1 | File "/usr/local/lib/python3.6/site-packages/flask_injector.py", line 171, in wrap_flask_restful_resource
api_next_1 | from flask_restful.utils import unpack
api_next_1 | ModuleNotFoundError: No module named 'flask_restful'
Hey!
Im trying to build an app using flask_restful library but I couldn't find an example of flask_injector
together with restful.
Is there any example out there? I tried to replicate the behavior used in flask_restplus
implementation but it is not working.
Thanks in advance
I'm getting aquainted with flask_injector, and find myself stuck with this problem:
Given a "Db" class which takes some instantiation code which can be quite slow (notice that this class gets injected a configuration object):
class DbContext:
@inject
def __init__(self, configuration: Configuration):
self.configuration = configuration
self.engine = patched_local_bloop_engine(
table_name_template=self.configuration.table_prefix,
endpoint=self.configuration.dynamodb_url,
)
self.engine.bind(User)
# This is just to generate some boilerplace data in dynamodb and to slow down instantiation a bit
record = User()
record.name = get_random_user_name()
self.engine.save(record)
my injector right now looks like this:
def configure(binder):
binder.bind(Configuration)
binder.bind(DbContext, scope=singleton)
and my flask resource (using flask-restful):
class ApiHome(Resource):
@inject
def __init__(self, configuration: Configuration, db_context: DbContext):
self.db_context = db_context
self.configuration = configuration
def get(self):
all_users = self.db_context.engine.scan(User).all()
all_users_ret = []
for usr in all_users:
all_users_ret.append(usr.to_json())
return {
"url": self.configuration.url,
"username": self.configuration.username,
"all_users": all_users_ret,
}
All of this works nicely, except for the fact that the DbContext's init isn't called until the first request requires it, leading the initial request to be quite slow. Since the DbContext requires a Configuration object injected, what would be the best way to ensure it's eagerly loaded?
Any pointers appreciated - full example code here:
https://gist.github.com/trondhindenes/3752f055c074cf4f5e2804194a62f4b6
When something isn't bound the error message doesn't help you find what it is. E.g., below.
TypeError: init() takes exactly 2 arguments (1 given)
It would be really useful if the KEY or type that is not bound is somewhere in the error. It would also be useful to have a mechanism to list all of the bindings.
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.