FastAPI APIRouter prefix are showing twice. Why? - python

notecard_router = APIRouter(
prefix = "/notecard",
tags = ['notecard']
)
Hi, I am trying to include a router to my app, but for some reason, the prefix that I give in the API router comes twice in the API paths. I have no idea why this is happening.

This is happening because from what I'm able to tell, you're not structuring your endpoints the way you want them. Instead of creating the API router and adding your prefix to it in each endpoint,
from fastapi import APIRouter
app = APIRouter(tags=["notecard"], prefix="/notecard")
#app.get("/notecard/index")
async def root():
...
When you do something like this, your endpoint actually becomes /prefix/notecard/index, which is /notecard/notecard/index. It looks like that's what you're doing. Instead of that, you can do
from fastapi import APIRouter
app = APIRouter(tags=["notecard"], prefix="/notecard")
#app.get("/index")
async def root():
...
And FastAPI will handle the prefixing for you.
Also, relevant snippets of your code or anything that can be used to reproduce your issue would make answering easier :)

Related

How to get defined route paths in FastAPI?

I have my FastAPI app define in server.py
app = FastAPI(
debug=True, title="Microservice for APIs",
description="REST APIs",
version="0.0.1",
openapi_url="/v3/api-docs",
middleware=[
Middleware(AuthorizationMiddleware, authorizor=Auth())
])
In __init__.py, I have routes defined
from fastapi import APIRouter
api_router = APIRouter()
api_router.include_router(impl_controller.router, prefix="/impl/data",
tags=["APIs for Impl Management"])
In impl_controller.py, I have define routes like this
#router.get('{id}/get_all')
def hello_world():
return {"msg": "Hello World"}
#router.get('{id}/get_last')
def test():
return {"msg": "test"}
In the middleware, I'm trying to get request route and not the URL
def check_for_api_access(self, request: Request):
request_path = request.scope['path']
# route_path = request.scope['endpoint'] # This does not exists
url_list = [
{'path': route.path, 'name': route.name}
for route in request.app.routes
]
Result I'm expecting is: {id}/get_all for 1st request and {id}/get_last for 2nd request.
I'm able to get list of all paths in url_list but I want route path for the specific request
Tried solution provided here: https://github.com/tiangolo/fastapi/issues/486 that also not working for me
Try this:
def check_for_api_access(self, request: Request):
api_route = next(item for item in request.app.routes if isinstance(item, APIRoute) and item.dependant.cache_key[0] == request.scope['endpoint'])
print(api_route.path)
You may not be able to accomplish exactly what you need, though you can get very close with the standard framework (i.e. no fancy alterations of the framework).
In the middleware, you can access the request directly. This way, you'll be able to inspect the requested url, as documented in https://fastapi.tiangolo.com/advanced/using-request-directly/?h=request . The attributes that are accessible are described here https://www.starlette.io/requests/
N.B. Since you posted just snippets, it's very difficult to say where values/variables come from.
In your case, it's dead simple what is not working. If you looked at the urls I posted, the starlette docs, shows the attributes that you can access from a request. This contains exactly the attribute you are looking for.
Basically, change request_path = request.scope['path'] into request_path = request.url.path. If you have a prefix, then you'll also get that and that's why I said You may not be able to accomplish exactly what you need, though you can get very close with the standard framework (i.e. no fancy alterations of the framework). Still, if you know your prefix, you can remove it from the path (it's just a string).

Request context in FastAPI?

In Flask, the request is available to any function that's down the call-path, so it doesn't have to be passed explicitly.
Is there anything similar in FastAPI?
Basically, I want to allow, within the same app, a request to be "real" or "dummy", where the dummy will not actually carry out some actions, just emit them (yes, I know that checking it down the stack is not nice, but I don't have control over all the code).
let's say I have
#app.post("/someurl")
def my_response():
func1()
def func1():
func2()
def func2():
# access some part of the request
I.e. where I don't need to pass the request as a param all the way down to func2.
In Flask, I'd just access the request directly, but I don't know how to do it in FastAPI.
For example, in Flask I could do
def func2():
x = request.my_variable
# do somethinh with x
Request here is local to the specific URL call, so if there are two concurrent execution of the func2 (with whatever URL), they will get the correct request.
FastAPI uses Starlette for this.
(Code from docs)
from fastapi import FastAPI, Request
app = FastAPI()
#app.get("/items/{item_id}")
def read_root(item_id: str, request: Request):
client_host = request.client.host
return {"client_host": client_host, "item_id": item_id}
Reference:
Using Request Directly
Edit:
I do not think this is something FastAPI provides out of the box. Starlette also doesn't seem to, but there is this project which adds context middleware to Starlette which should be easy to integrate into a FastAPI app.
Starlette Context
I provided an answer that may be of help, here. It leverages ContextVars and Starlette middleware (used in FastAPI) to make request object information globally available. It doesn't make the entire request object globally available -- but if you have some specific data you need from the request object, this solution may help!

How to pytest a Flask Endpoint

I'm getting started with Flask and Pytest in order to implemente a rest service with unit test, but i'm having some troouble.
I'll like to make a simple test for my simple endpoint but i keep getting a Working outside of application context. error when running the test.
This is the end point:
from flask import jsonify, request, Blueprint
STATUS_API = Blueprint('status_api', __name__)
def get_blueprint():
"""Return the blueprint for the main app module"""
return STATUS_API
#STATUS_API.route('/status', methods=['GET'])
def get_status():
return jsonify({
'status' : 'alive'
})
And this is how I'm trying to test it (i know it should fail the test):
import pytest
from routes import status_api
def test_get_status():
assert status_api.get_status() == ''
I'm guessing I just cant try the method with out building the whole app. But if that's the case i dont really know how to aproach this problem
The Flask documentation on testing is pretty good.
Instead of importing the view functions, you should create a so called test client, e.g. as a pytest fixture.
For my last Flask app this looked like:
#pytest.fixture
def client():
app = create_app()
app.config['TESTING'] = True
with app.app_context():
with app.test_client() as client:
yield client
(create_app is my app factory)
Then you can easily create tests as follows:
def test_status(client):
rv = client.get('/stats')
assert ...
As mentioned at the beginning, the official documentation is really good.
Have you considered trying an API client/development tool? Insomnia and Postman are popular ones. Using one may be able to resolve this for you.

FastAPI - module 'app.routers.test' has no attribute 'routes'

I am trying to setup an app using FastAPI but keep getting this error which I can't make sense of. My main.py file is as follows:
from fastapi import FastAPI
from app.routers import test
app = FastAPI()
app.include_router(test, prefix="/api/v1/test")
And in my routers/test.py file I have:
from fastapi import APIRouter, File, UploadFile
import app.schemas.myschema as my_schema
router = APIRouter()
Response = my_schema.Response
#router.get("/", response_model=Response)
def process(file: UploadFile = File(...)):
# Do work
But I keep getting the following error:
File
"/Users/Desktop/test-service/venv/lib/python3.8/site-packages/fastapi/routing.py",
line 566, in include_router
for route in router.routes: AttributeError: module 'app.routers.test' has no attribute 'routes'
python-BaseException
I cant make sense of this as I can see something similar being done in the sample app here.
I think you want:
app.include_router(test.router, prefix="/api/v1/test")
rather than:
app.include_router(test, prefix="/api/v1/test")
No, you can not directly access it from the app, because when you add an instance of APIRouter with include_router, FastAPI adds every router to the app.routes.
for route in router.routes:
if isinstance(route, APIRoute):
self.add_api_route(
...
)
It does not add the route to the application instead it adds the routes, but since your router is an instance of APIRouter, you can reach the routes from that.
class APIRouter(routing.Router):
def __init__(
self,
routes: Optional[List[routing.BaseRoute]] = None,
...
)
The problem lies in your import statement, your import should be like
from parentfolder.file import attribute
Confused? no worries let me make it simple
Any variable you use to define and assign to APIRouter, becomes your attribute.
in your example in test.py, you defined routes as your attribute routes = APIRouter()
that means that if you want to use it in any other place, you need to do as below
from routers.test import routes
GoodLuck

Application context errors when locally testing a (Python) Google Cloud Function

I am trying to locally test a Python function that I hope to deploy as a Google Cloud Function. These functions seem to be essentially Flask based, and I have found that the best way to return JSON is to use Flask's jsonify function. This seems to work fine when deployed, but I want to set up some local unit tests, and here is where I got stuck. Simply adding the line to import jsonify, results in the following error:
RuntimeError: Working outside of application context.
There are several posts here on Stackoverflow that seem relevant to this issue, and yet Google Cloud Functions do not really follow the Flask pattern. There is no app context, as far as I can tell, and there are no decorators. All of the examples I've found have not been useful to this particular use case. Can anyone suggest a method for constructing a unit test that will respect the application context and still jibe with the GCF pattern here.
I have a unittest, which I can share, but you will see the same error when you run the following, with the method invocation inside of main.
import os
import json
from flask import jsonify
from unittest.mock import Mock
def dummy_request(request):
request_json = request.get_json()
if request_json and 'document' in request_json:
document = request_json['document']
else:
raise ValueError("JSON is invalid, or missing a 'docuemnt' property")
data = document
return jsonify(data)
if __name__ == '__main__':
data = {"document":"This is a test document"}
request = Mock(get_json=Mock(return_value=data), args=data)
result = dummy_request(request)
print(result)
You don't really need to test whether flask.jsonify works as expected, right? It's a third-party function.
What you're actually trying to test is that flask.jsonify was called with the right data, so instead you can just patch flask.jsonify, and make assertions on whether the mock was called:
import flask
from unittest.mock import Mock, patch
def dummy_request(request):
request_json = request.get_json()
if request_json and 'document' in request_json:
document = request_json['document']
else:
raise ValueError("JSON is invalid, or missing a 'docuemnt' property")
data = document
return flask.jsonify(data)
#patch('flask.jsonify')
def test(mock_jsonify):
data = {"document": "This is a test document"}
request = Mock(get_json=Mock(return_value=data), args=data)
dummy_request(request)
mock_jsonify.assert_called_once_with("This is a test document")
if __name__ == '__main__':
test()
I'd recommend you to take a look at Flask's documentation on how to test Flask apps, it's described pretty well how to setup a test and get an application context.
P.S. jsonify requires application context, but json.dumps is not. Maybe you can use the latter?
I came across the same issue. As you've said the flask testing doesn't seem to fit well with Cloud Functions and I was happy with how the code worked so didn't want to change that. Adding an application context in setUp() of testing then using it for the required calls worked for me. Something like this...
import unittest
import main
from flask import Flask
class TestSomething(unittest.TestCase):
def setUp(self):
self.app = Flask(__name__)
def test_something(self):
with self.app.app_context():
(body, code) = main.request_something()
self.assertEqual(200, code, "The request did not return a successful response")
if __name__ == '__main__':
unittest.main()

Categories

Resources