Good day! Please tell me how you can solve the following problem in Python + FastAPI.
There is a test project:
app / main.py - main file
app / routes / users.py -set of api methods
app / repos / factory.py - repository factory
app / repos / user_repository.py - repositories
app / handlers / factory.py - handler factory
app / handlers / users.py - handlers
app / domain / user.py - data class
The main and routes structure is the same as in the example https://fastapi.tiangolo.com/tutorial/bigger-applications/
In the routes/users.py file:
from fastapi import APIRouter, Depends
from ..handlers import factory
router = APIRouter()
#router.get("/users/", tags=["users"])
def read_users(handler=Depends(factory.get_handler)):
return handler.get_all()
In the handlers/factory.py:
from fastapi import Depends
from .users import UserHandler1
def get_handler(handler=Depends(UserHandler1)):
return handler
In the handlers/users.py:
from fastapi import Depends
from ..repos import factory
class UserHandler1:
def __init__(self):
pass
def get_all(self, repo=Depends(factory.get_repo)):
return repo.get_all()
repos/factory.py:
from fastapi import Depends
from ..repos.user_repository import UserRepository
def get_repo(repo=Depends(UserRepository)):
return repo
repos/user_repository.py:
from ..domain.user import User
class UserRepository:
def __init__(self):
pass
def get_all(self):
return [User(1, 'A'), User(2, 'B'), User(3, 'C')]
domain/user.py:
class User:
id: int
name: str
def __init__(self, id, name):
self.id = id
self.name = name
Then I run hypercorn server: app.main:app --reload
Try call api method: http://127.0.0.1:8000/users/
And get the error AttributeError: 'Depends' object has no attribute 'get_all'
If you remove the handlers layer and do this, then everything will work.
routes/users.py:
from fastapi import APIRouter, Depends
from ..repos import factory
router = APIRouter()
#router.get("/users/", tags=["users"])
def read_users(repo=Depends(factory.get_repo)):
return repo.get_all()
It also works if you completely remove all Depends and create
UserRepository and UserHandler1 directly in factories.
Question 1: How do I use "Depends" in this case and why doesn't it work?
In general, the factory does not look like a good solution to this problem. I saw an example of DI implementation using multiple inheritance but as for me it is the same as factory method.
I also tried to use the Pinject library, but it requires the initial construction of a graph, which needs to be saved somewhere in order to access it in api handlers.
Question 2 (more important): How Dependency Injection can be applied in this case ?
The __call__ method must be implemented in the class.
As noted in the comments, a dependency can be anything that is a callable and thus a class as well. The only caveat in the latter case is that the class will only be initialized (i.e. only the __init__(...) function will be called).
So, in order to have a class as dependency, as in the example of https://fastapi.tiangolo.com/tutorial/dependencies/classes-as-dependencies/#shortcut you just need to call the target functions within the init and set the values as attributes of the class.
from ..domain.user import User
class UserRepository:
def __init__(self):
self.get_all()
def get_all(self):
self.users = [User(1, 'A'), User(2, 'B'), User(3, 'C')]
from fastapi import Depends
from ..repos.user_repository import UserRepository
def get_repo(repo=Depends(UserRepository)):
print(repo.users) # This will print the list of users
return repo
QUESTION 2
NB
This is a modelling question. Here I propose what I believe is
suitable from my point of view. It does not necessarily have to
be best or simplest approach.
Answering your second question, I would not advice for such complex dependencies. If the dependencies are at the router level, you can simply add them to the router, using the parameter depends=[...] and providing a list of dependency classes/functions.
Alternatively, you could declare all of the dependencies as function parameters of the endpoint, as you did for the factory. This method may lead to big chunks of code getting copied and pasted, so I advise for the above approach.
If you need to process the data parameters, then you add them to the request and access them from within the endpoint. See FastAPI get user ID from API key for a minimal example.
Related
I'm trying to add a custom user information retrieval from OAuth in superset, which is build on top of flask-appbuilder.
Official doc provides following information:
Decorate your method with the SecurityManager oauth_user_info_getter
decorator. Make your method accept the exact parameters as on this
example, and then return a dictionary with the retrieved user
information.
http://flask-appbuilder.readthedocs.io/en/latest/security.html#authentication-oauth
The example in the doc also does not help much, as decorator was put in the comments.
I am where to put custom decorator in Superset? I've put the custom decorator in superset_config.py but I didn't work for me.
The approach that I use boils down to the following:
# For superset version >= 0.25.0
from superset.security import SupersetSecurityManager
class CustomSecurityManager(SupersetSecurityManager):
def __init__(self, appbuilder):
super(CustomSecurityManager, self).__init__(appbuilder)
def whatever_you_want_to_override(self, ...):
# Your implementation here
CUSTOM_SECURITY_MANAGER = CustomSecurityManager
# For superset version < 0.25.0
from flask_appbuilder.security.sqla.manager import SecurityManager
class CustomSecurityManager(SecurityManager):
def __init__(self, appbuilder):
super(CustomSecurityManager, self).__init__(appbuilder)
def whatever_you_want_to_override(self, ...):
# Your implementation here
CUSTOM_SECURITY_MANAGER = CustomSecurityManager
i am in the current situation. I have a Service class, which uses a Logic component, which uses a Storage object.
So:
Service ---> Logic ---> Storage
The arrows represent a 'uses' relation.
Of course i am trying to use dependency injection, and i am using the injector package (https://github.com/alecthomas/injector).
The objective here is to have different versions of the Service instances, which will be using different Logic instances, that differ in the Storage instance used.
I think it's something that is presented here.
Now, the point is the following: i have not understood how to apply injection to several levels. Here's a little example:
from injector import inject, Injector, Module, Key
# My example
# Standard service using a ConsumerLogic which is requesting a Storage object
class Storage:
pass
class Storage2:
pass
class Logic:
def __init__(self, storage):
self.storage = storage
class Service:
def __init__(self, logic):
self.logic = logic
By using the BoundedKey class, i have worked something out:
from injector import inject, Injector, Module, Key
# My example
# Standard service using a ConsumerLogic which is requesting a Storage object
LogicKey = Key('Logic')
class Storage:
pass
class Storage2:
pass
class Logic:
#inject
def __init__(self, storage: Storage):
self.storage = storage
class Service:
#inject(logic=LogicKey)
def __init__(self, logic):
self.logic = logic
class Configuration(Module):
def configure(self, binder):
binder.bind(
LogicKey,
to=Logic(storage=Storage())
)
And to use the service instace i can do:
inj = Injector()
inj.get(Service)
obtaining that specific version of the service.
How can I make the get method to return me different version of the Service ? I have not been able to work out something with Module approach. But i must be missing something.
Thanks in advance for help.
I have a decorator that looks like so:
def validate_something(func):
def validate_s(request):
if request.property:
render_to_response('template.jinja', 'error'
return func(request)
return validate_something
I'm trying to test it like so. I load the local WSGI stack as an app.
from webtest import TestApp
def setUp(self):
self.app = TestApp(target_app())
self.config = testing.setUp(request=testing.DummyRequest)
def test_something(self):
def test_func(request):
return 1
request = testing.DummyRequest()
resp = validate_something(test_func(request))
result = resp(request)
The error I'm getting is (being generated at the innermost render_to_response):
ValueError: no such renderer factory .jinja
I understand that I need to mock render_to_response, but I'm at a bit of a loss as to how to exactly do that. If anyone has any suggestions, I would greatly appreciate it.
Mock library is awesome:
mock provides a core Mock class removing the need to create a host of
stubs throughout your test suite. After performing an action, you can
make assertions about which methods / attributes were used and
arguments they were called with. You can also specify return values
and set needed attributes in the normal way.
Additionally, mock provides a patch() decorator that handles patching
module and class level attributes within the scope of a test
Youc code would look like this:
def test_something(self):
test_func = Mock.MagicMock(return_value=1) # replaced stub function with a mock
request = testing.DummyRequest()
# patching a Pyramid method with a mock
with mock.patch('pyramid.renderers.render_to_response' as r2r:
resp = validate_something(test_func(request))
result = resp(request)
assert r2r.assert_called_with('template.jinja', 'error')
The following worked for me:
def setUp(self):
self.app = TestApp(target_app())
self.config = testing.setUp(request=testing.DummyRequest)
self.config.include('pyramid_jinja2')
By setting up the config to include jinja your tests can then find your template and the jinja environment. You may also need to provide a test version of the template in the same folder as your tests. If you get a message such as TemplateNotFound on running the tests then make sure a version of the template is located in the correct place.
I've got a Flask application which I'd like to run some unit tests on. To do so, I create a new Flask object, initialise blueprints, SQLA and a few other packages and execute the test case.
However, I've noticed that some endpoints on the test flask object are missing, which made me wonder about the general way of how initialisation is handled in flask.
Taking a minimal example, an endpoint would be created like so:
from flask import Flask
app = Flask(__name__)
#app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
If I was to create a new Flask object somewhere in my testcase's setUp method, it would most certainly not contain a route '/' as this route was created from another flask object (the one from the file above). So my question is: How should a test case be written and how is the initialisation meant to work in general? Somewhere I read that one should avoid initialisation at import (i.e. on a module level), but this seems to be impossible if annotations are used.
You don't create a new Flask object in your test cases. You import your existing app instead.
In many project setups you already added all your extensions to that app. In many of mine I have a factory method that'll take a configuration object and returns the fully initialized app object for me; I use this to create a base test case:
import unittest
import project
class Config(object):
DEBUG = False
TESTING = True
CACHE_NO_NULL_WARNING = True # silence Flask-Cache warning
SECRET_KEY = 'SECRET_KEY'
class ProjectCoreViewCase(unittest.TestCase):
"""Base test case for the Project core app"""
def setUp(self, **kwargs):
config = Config()
config.__dict__.update(kwargs)
app = project.create_app(config)
self.app = app.test_client()
and any tests can then use self.app as the test client in all tests.
This is a base test case, you'd inherit from it; the setUp() method allows for additional configuration to be set, by passing in keyword arguments to a super() call:
class ConcreteTestCase(ProjectCoreViewCase):
def setUp(self):
super(ConcreteTestCase, self).setUp(
SQLALCHEMY_DATABASE_URI='your_test_specific_connection_uri',
)
While trying the new CORS feature on flask-restful, I found out that the decorator can be only applied if the function returns a string.
For example, modifying the Quickstart example:
class HelloWorld(restful.Resource):
#cors.crossdomain(origin='*')
def get(self):
return {'hello': 'world'}
Throws:
TypeError: 'dict' object is not callable
Am I doing something wrong?
I recently came across this issue myself. #MartijnPieters is correct, decorators can't be called on single methods of the view.
I created an abstract base class that contained the decorator list. The class that consumes Resource (from flask-restful) also inherits the base class, which is the class actually applying the decorator list to the view.
class AbstractAPI():
decorators = [cors.crossdomain(origin='*')]
class HelloWorld(restful.Resource, AbstractAPI):
#content
nope.
just add the decorator list to the parameters after you create the Api instance
api = Api(app)
api.decorators=[cors.crossdomain(origin='*')]
The return value of the wrapped function is passed (as one argument) to flask.make_response(); anything that a normal Flask view can return is acceptable. The decorator is essentially the same as this Flask snippet.
Because the Flask-restful Resource is a subclass of flask.views.MethodView you should really not put decorators directly on the methods here. As documented in Decorating Views you should list view decorators in a special class attribute, decorators which is a list:
class HelloWorld(restful.Resource):
decorators = [cors.crossdomain(origin='*')]
def get(self):
return {'hello': 'world'}
and Flask will apply the view to the actual view method returned by HelloWorld.as_view(), which is what Flask actually calls when dispatching the route to the view.
Applying them directly to the methods will only server to confuse the restful.Resource dispatcher as it is expecting methods to return python datastructures suitable for encoding to JSON, which is not what cors.crossdomain() returns anyway.
I found that you can still use the decorator provided you return a string or JSON response (which is probably good practice for an API anyway). This is important if you want to do route-specific CORS headers, using the decorator makes life much easier. See this merged pull req for more information: https://github.com/flask-restful/flask-restful/pull/131
Here's an example:
from . import app
from flask_restful import reqparse, abort, Api, Resource
from flask.ext.cors import cross_origin
from datetime import datetime
from flask import jsonify
api = Api(app)
class DateTime(Resource):
#cross_origin(origins="http://localhost:63342*")
def get(self):
return jsonify({'DateTime': str(datetime.today())})
api_root = '/api/v1/'
api.add_resource(DateTime, api_root + 'DateTime')
If you're using flask-security, adding auth decorators had some weird behavior in my testing. I recommend assert current_user.is_authenticated instead. If you are allowing credentials, make sure to CSRF protect.