I,m getting this error when I try to run my FastApi api.
app = cls(app=app, **options)
TypeError: 'module' object is not callable
I'm trying to add a middleware on other folder separeted from main.py and don't know why isn't working. Otherwise when I add the middleware code into main.py works without problems. Here is my code, thank you for your help and excuse my english.
main.py
from fastapi import FastAPI
from fastapi import Depends, FastAPI, HTTPException
from fastapi import Request
from routers import rutas
from utils import CheckApiKey
from utils.CheckApiKey import check_api_key
app = FastAPI()
app.add_middleware(CheckApiKey, dispatch=check_api_key) <--- Here calling middleware
app.include_router(rutas.router)
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True)
Middleware
from fastapi import Request
async def check_api_key(request: Request, call_next):
print("ok")
response = await call_next(request)
return response
I found the solution...I have to create a class that inherite from starlette basehttpmiddleare, like this.
from starlette.middleware.base import BaseHTTPMiddleware
class CheckApiKey(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
print("ok")
response = await call_next(request)
return response
The CheckApiKey seems like a python module in your case and check_api_key is the middleware function.
The issue was, the add_middleware() method expects the first argument as a callable function or callable class. But in your case, you were given a module.
So,
Change your statement as,
app.add_middleware(check_api_key)
Related
I need to create a session for authentication in the session_set endpoint. However, for some reason, the session is still being created in the session_info endpoint. How to make a session created only in session_set? Otherwise, I have a new session in the response with each request.
Here is my code:
import uvicorn
from fastapi import FastAPI, Request
from starlette.middleware.sessions import SessionMiddleware
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="some-random-string", max_age=None)
#app.get("/a")
async def session_set(request: Request):
request.session["my_var"] = "1234"
return 'ok'
#app.get("/b")
async def session_info(request: Request):
my_var = request.session.get("my_var", None)
return my_var
if __name__ == '__main__':
uvicorn.run('http-session:app', port=5000, reload=True)
You could use a Middleware to override the session value in the Response cookies (check the documentation in Starlette as well) every time a new request arrives; hence, the session will remain the same.
Note: Remember to declare your custom middleware, after adding the SessionMiddleware to the app instance, as the order that endpoints/sub-applications are defined in your application matters, as described in this answer (see the relevant FastAPI documentation as well).
Working Example:
from fastapi import FastAPI, Request
from starlette.middleware.sessions import SessionMiddleware
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="some-random-string")
#app.middleware("http")
async def some_middleware(request: Request, call_next):
response = await call_next(request)
session = request.cookies.get('session')
if session:
response.set_cookie(key='session', value=request.cookies.get('session'), httponly=True)
return response
#app.get("/a")
def func_a(request: Request):
request.session["my_var"] = "1234"
print(request.cookies.get('session'))
return 'OK'
#app.get("/b")
def func_b(request: Request):
my_var = request.session.get("my_var", None)
print(request.cookies.get('session'))
return my_var
I am trying to include a router in the main FastAPI router:
from fastapi import FastAPI
from test.main.app.google_calendar_wrapper import app as calendar_manager_router
app = FastAPI()
#
app.include_router(calendar_manager_router, prefix="/calendar_manager", tags=["calendar_manager"])
#app.get("/")
def root():
return {"message": "Home Page"}
However, when running
uvicorn test.main.app.webhook.router:app --port 8050 --reload
I get an error:
AttributeError: 'FastAPI' object has no attribute 'default_response_class'
My file structure is:
test
| main
| app
| google_calendar_wrapper
| endpoints.py
| __init__.py
| webhooks
| router.py
So far I have tried:
Not including the router, in this case the application starts normally
google_calendar_wrapper with and without __init__.py. If with an __init__.py, I tried exporting the google_calendar_wrapper and it still raises the same error
Both routers work independently of each other but nothing has helped so far and I have not found any solutions.
Here is the calendar_manager_router definition:
from fastapi import FastAPI
app = FastAPI()
#app.get("/")
def root():
return {"message": "Hello World"}
#app.get("/health")
def health():
"""Api health endpoint."""
return {"Api is up and running"}
FastAPI's include_router accepts an APIRouter, but the object you imported in the main file, calendar_manager_router, is another FastAPI object. In your google_calendar_wrapper, you should be defining an APIRouter and that's what you import and include in your main app.
In google_calendar_wrapper, change it to:
from fastapi import APIRouter
router = APIRouter() # <---------
#router.get("/")
def root():
return {"message": "Hello World"}
#router.get("/health")
def health():
"""Api health endpoint."""
return {"Api is up and running"}
Notice the change to use APIRouter.
Then in your main app:
from test.main.app.google_calendar_wrapper import router as calendar_manager_router
...
app = FastAPI()
app.include_router(
calendar_manager_router,
prefix="/calendar_manager",
tags=["calendar_manager"]
)
See the FastAPI tutorials on Bigger Applications - Multiple Files:
You want to have the path operations related to your users separated from the rest of the code, to keep it organized.
But it's still part of the same FastAPI application/web API (it's part of the same "Python Package").
You can create the path operations for that module using APIRouter.
...
You can think of APIRouter as a "mini FastAPI" class.
I have read FastAPI's documentation about middlewares (specifically, the middleware tutorial, the CORS middleware section and the advanced middleware guide), but couldn't find a concrete example of how to write a middleware class which you can add using the add_middleware function (in contrast to a basic middleware function added using a decorator) there nor on this site.
The reason I prefer to use add_middleware over the app based decorator, is that I want to write a middleware in a shared library that will be used by several different projects, and therefore I can't tie it to a specific FastAPI instance.
So my question is: how do you do it?
As FastAPI is actually Starlette underneath, you could use BaseHTTPMiddleware that allows you to implement a middleware class (you may want to have a look at this post as well). Below are given two variants of the same approach on how to do that, where the add_middleware() function is used to add the middleware class. Please note that is currently not possible to use BackgroundTasks (if that's a requirement for your task) with BaseHTTPMiddleware—check #1438 and #1640 for more details. Alternatives can be found in this answer and this answer.
Option 1
middleware.py
from fastapi import Request
class MyMiddleware:
def __init__(
self,
some_attribute: str,
):
self.some_attribute = some_attribute
async def __call__(self, request: Request, call_next):
# do something with the request object
content_type = request.headers.get('Content-Type')
print(content_type)
# process the request and get the response
response = await call_next(request)
return response
app.py
from fastapi import FastAPI
from middleware import MyMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
app = FastAPI()
my_middleware = MyMiddleware(some_attribute="some_attribute_here_if_needed")
app.add_middleware(BaseHTTPMiddleware, dispatch=my_middleware)
Option 2
middleware.py
from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware
class MyMiddleware(BaseHTTPMiddleware):
def __init__(
self,
app,
some_attribute: str,
):
super().__init__(app)
self.some_attribute = some_attribute
async def dispatch(self, request: Request, call_next):
# do something with the request object, for example
content_type = request.headers.get('Content-Type')
print(content_type)
# process the request and get the response
response = await call_next(request)
return response
app.py
from fastapi import FastAPI
from middleware import MyMiddleware
app = FastAPI()
app.add_middleware(MyMiddleware, some_attribute="some_attribute_here_if_needed")
A potential workaround for the BaseHTTPMiddleware bug raised by #Error - Syntactical Remorse, which seems to work for me at least, is to use partial and use a functional approach to your middleware definition:
middleware.py
from typing import Any, Callable, Coroutine
from fastapi import Response
async def my_middleware(request: Request, call_next: Callable, some_attribute: Any) -> Response:
request.state.attr = some_attribute # Do what you need with your attribute
return await call_next(request)
app.py
from functools import partial
from fastapi import FastAPI
from middleware import my_middleware
app = FastAPI()
my_custom_middleware: partial[Coroutine[Any, Any, Any]] = partial(my_middleware, some_attribute="my-app")
app.middleware("http")(my_custom_middlware)
I am running unittests in a Flask app and I keep getting 404 when views.py file is not imported even though it is not used. I have such tests.py package:
import unittest
from presence_analyzer import main, utils
from presence_analyzer import views
class PresenceAnalyzerViewsTestCase(unittest.TestCase):
def setUp(self):
self.client = main.app.test_client()
def test_mainpage(self):
resp = self.client.get('/')
self.assertEqual(resp.status_code, 302)
When I delete views import the described problem occurs. Views are organized in a similar way to this:
from presence_analyzer.main import app
#app.route('/')
def mainpage():
return redirect('/static/presence_weekday.html')
And the main.py file:
import os.path
from flask import Flask
app = Flask(__name__) # pylint: disable=invalid-name
app.config.update(
DEBUG=True,
)
I guess it's something similar to what happened in this case, so I'm trying to change the application so that I don't have to make this dumb imports while testing. I've been trying to make use of the answer from above, but still can't make it work and these docs don't seem helpful. What am I doing wrong? main.py:
from flask.blueprints import Blueprint
PROJECT_NAME = 'presence_analyzer'
blue_print = Blueprint(PROJECT_NAME, __name__)
def create_app():
app_to_create = Flask(__name__) # pylint: disable=invalid-name
app_to_create.register_blueprint(blue_print)
return app_to_create
app = create_app()
views.py:
from presence_analyzer.main import app, blue_print
#blue_print.route('/')
def mainpage():
return redirect('/static/presence_weekday.html')
And tests.py has remained unchanged.
You must import views, or the route will not be registered. No, you are not executing the views directly, but importing executes code all module-level code. Executing code calls route. route registers the view function. You cannot get around needing to import a module in order to use the module.
In my pyramid application I am trying to implement authorization by decorating the view function.
When I use the config.scan() function none of the views are added, however if I explicitly add them using config.add_view() everything works fine.
I have two file one which defines all the view functions (views.py)
from pyramid.view import view_config
from pyramid.response import Response
from functools import wraps
def authorized(func): #decorator difnition
#wraps(func)
def new_func(request):
if(request.cookies.get('user')): # authorization
return func(request)
else:
return Response('not authirised')
return new_func
#view_config(route_name='hello') # view function being decorated
#authorized
def privileged_action(request):
return Response('Hello %(name)s!' % request.matchdict)
And another file to create the server (serve.py) which imports views.py
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from views import privileged_action
if __name__ == '__main__':
config = Configurator()
config.add_route('hello', '/hello/{name}')
# config.add_view(privileged_action, route_name='hello') # This works
config.scan() # This doesn't work
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 8080, app)
server.serve_forever()
This gives 404 not found error if I access using 'http://localhost:8080/hello/a'
Why does this not work?
Is there any way to make this work?
Your code with the decorators looks fine.
The documentation for Configurator.scan() states for its first argument:
The package argument should be a Python package or module object (or a dotted Python name which refers to such a package or module). If package is None, the package of the caller is used.
So make sure you are doingconfig.scan(views), to get your web app dynamically adding your views.