GithubHelp home page GithubHelp logo

python-injector / flask_injector Goto Github PK

View Code? Open in Web Editor NEW
270.0 5.0 42.0 200 KB

Adds Injector support to Flask.

License: BSD 3-Clause "New" or "Revised" License

Python 98.30% Makefile 1.70%

flask_injector's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

flask_injector's Issues

Use instance method for url rules

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.

Support flask >= 2.3

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!

program fall on FlaskInjector(app=app, injector=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?

Does not work if class based view is decorated.

    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']

Any examples for flask_restful Resources?

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

how request scope works with overrides? (it doesn't)

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!

flask-restful is a hard dependency

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'

Any examples for Flask-RestPlus Resource constructors ?

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.

Enhancement: Make underlying injector accessible from library

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.

flask_restplus and flask_injector do not work together :(

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: [])

What is the best solution using flask-injector for dependent extensions of each other

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))

Module as singleton?

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...

Error while using flask-injector decorator and flask-apispec doc in Flask together.

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

Class instance provider error

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
])

Provide init_app

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.

Strange exception when open swggaer doc -> TypeError('Injecting Any is not supported')

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.

Running In Serverless Lambda Using serverless-wsgi

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

Travis builds

Hey, can you enable Travis for this repository? I don't seem to be able to do that.

Error trying to use Werkzeug request object outside of request context

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:

  • Python: 3.8.1
  • Flask: 1.1.2
  • Injector: 0.18.3
  • FlaskInjector: 0.12.3
  • Werkzeug: 2.0.0rc1

Did anyone have the same issue?

Doing multiple requests on a flask test client on an injected route fails for all but the first request.

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.

Cannot inject into teardown_request handler while using request scope

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()

name 'Response' is not defined

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.

Proposition of fix

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

More information

the issue with get_type_hints and Forward Ref

Cannot inject dependency when using decorators with view functions.

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)

How to inject types which requires instantiation

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

Error with flask method view

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()

Memory leaks when exceptions thrown

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?

Support for flask rebar

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'

before_request

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

Rebind to a mock instance fail

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.

0.14.0 release breaks semantic versioning

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.

Better error handling.

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.

injector._BindingNotYetAvailable: name 'Response' is not defined

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

How to retrieve route instance of flask-restx

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?

Enhancement: Provide alias for `request` scope

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
.

handling requirements of the flask app itself (not a bug, looking for suggestions)

I'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).

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

Memory leak with eventlet enabled

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?

[Code review question] How to do teardown?

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()

KeyError: 'class_args' when testing with class-based views

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.

Attributes set into initial bind seems to disappear

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!

Type-hinted list arg causes "TypeError: Injecting partially applied functions is no longer supported."

How to reproduce:

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

Expected result:

No error, response {'list_arg': ['a', 'b']}

Actual result:

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.

Affected versions:

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

Notes:

  • If the type hint List[str] is removed from the argument list_arg, the injection works correctly

Error with error handler and typing

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.