In symplified case we've got a projects and files. Files belong to projects. Every of them has their own router to perfom CRUD actions via API.
So, in code it should look like this:
from fastapi import FastAPI, APIRouter
app = FastAPI()
projects_router = APIRouter()
files_router = APIRouter()
app.include_router(projects_router, prefix="/projects")
projects_router.include_router(files_router, prefix="/{project_id}/files")
#files_router.get("/")
def list_files(project_id: int):
# Some code, that list all project's files by project_id
But the list_files function can't get project_id. How can it?
You need to add project_id as a path parameter. By default it's a query parameter.
Three dots ... in Path parameter make it required:
from fastapi import Path
#files_router.get("/")
def list_files(project_id: int = Path(..., gt=0)):
# Some code, that list all project's files by project_id
Related
Suppose I have something like this in app/models.py:
from flask import current_app as app
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.sql import func
db = SQLAlchemy()
class LoginLink(db.Model):
...
expiration_date = db.Column(
db.DateTime(timezone=True), nullable=False,
server_default=func.now() + str(app.config["LOGIN_LINK_EXP_TIME"]) # Error here!!!
)
And this in app/__init__.py:
from flask import Flask
from config import CONFIG_OBJECT
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(CONFIG_OBJECT[config_name])
from app.models import db
db.init_app(app)
db.create_all(app=app)
return app
Finally, this is my config.py:
from datetime import timedelta
CONFIG_OBJECT = {
"dev": "config.DevConfig",
"prod": "config.ProdConfig"
}
class Config:
...
class DevConfig(Config):
LOGIN_LINK_EXP_TIME = timedelta(seconds=30)
class ProdConfig(Config):
LOGIN_LINK_EXP_TIME = timedelta(minutes=30)
I tried to use app.app_context() everywhere (believe me) and I'm still getting this error:
RuntimeError: Working outside of application context.
I'm just trying to do the following: in a development environment I want the login links to expire in 30 seconds (for testing and demonstration purposes), but login links will last 30 minutes in a production environment.
How to accomplish this using different config environments?
Note: this is intended to be a generic question.
I think I had a misconception about application factories. We have the following from the Flask documentation:
The downside is that you cannot use the application object in the blueprints at import time. You can however use it from within a request.
Additionally:
It’s preferable to create your extensions and app factories so that the extension object does not initially get bound to the application.
What is in bold is what I was doing wrong: using the application object outside a request and bounding the extension to the application.
Therefore, I only see two solutions:
Use the app.config object (or dict) only within requests (the best IMHO).
Don't include configs that require to be used outside of requests in the app.config object (although this may complicate testing a bit).
In a Python Django project, I have a module with a class (let's say SomeDataStore) that abstracts file storage behaviour and requires a config setting with the correct path (different for development, prod, ...)
Currently, I've done it like this:
# settings.py
RELEVANT_PATH = os.environ.get("DJANGO_RELEVANT_PATH", "/some/default")
...
# datastore.py
from django.conf import settings
class SomeDataStore:
def list_files(self):
p = Path(settings.RELEVANT_PATH)
files = p.glob("*.csv")
return files
...
# views.py
from datastatus import SomeDataStore
def show_files(request):
files = SomeDataStore().get_files()
...
Goal: I'd like to decouple the datastore.py module completely from django, so that it can easily be used standalone. That would mean to get rid of the django.conf.settings usage in datastore.py
One obvious way to do it would be to provide the path as an init parameter to the DataStore class.
# datastore.py
class SomeDataStore:
def __init__(self, path) -> None:
self.path=path
...
However, I use this DataStore in many Django views, and that would require me to always specify the path when instantiating the class, e.g. like so
# views.py
from datastatus import SomeDataStore
def show_files(request):
files = SomeDataStore(settings.RELEVANT_PATH).get_files()
...
def show_something_else(request):
somethings = SomeDataStore(settings.RELEVANT_PATH).get_something_else()
...
I'd like to avoid the need in each instantiation to always specify the config setting for the path.
Question: Is there some clean way, pattern or approach to deal with this in Django? Or am I overlooking something obvious here?
Something I thought of is instantiating the DataStore in settings.py, but creating objects in settings.py seems bloating it. Or isn't it?
You could have a my_settings.py holding the PATH:
# my_settings.py
import os
RELEVANT_PATH = os.environ.get("whatever", "/some/default")
and use like
# datastore.py
from my_settings import RELEVANT_PATH
class SomeDataStore:
def list_files(self):
p = Path(RELEVANT_PATH)
files = p.glob("*.csv")
return files
...
In case you need this path whithin Django elsewhere, you could have this path as part of settings.py as well
# settings.py
from my_settings import RELEVANT_PATH as my_relevant_path
RELEVANT_PATH = my_relevant_path
# usage in other django app files
from django.conf import settings
# use settings.RELEVANT_PATH as usual
This would provide for some decoupling and you can change the path at a single place my_settings.py and import the path outside django as well as use it inside django with the usual django.conf.settings.xx syntax.
I'm using FastAPI to create backend for my project. I have a method that allows to upload a file. I implemented it as follows:
from fastapi import APIRouter, UploadFile, File
from app.models.schemas.files import FileInResponse
router = APIRouter()
#router.post("", name="files:create-file", response_model=FileInResponse)
async def create(file: UploadFile = File(...)) -> FileInResponse:
pass
As you can see, I use a dedicated pydantic model for a method result—FileInResponse:
from pathlib import Path
from pydantic import BaseModel
class FileInResponse(BaseModel):
path: Path
And I follow this naming pattern for models (naming models as <Entity>InCreate, <Entity>InResponse, and so on) throughout the API. However, I couldn't create a pydantic model with a field of the type File, so I had to declare it directly in the route definition (i.e. without a model containing it). As a result, I have this long auto generated name Body_files_create_file_api_files_post in the OpenAPI docs:
Is there a way to change the schema name?
If you take a look at response model you see this:
It receives the same type you would declare for a Pydantic model atribute, so it can be a Pydantic model, but it can also be, e.g. a list of Pydantic models, like List[Item].
FastAPI will use this response_model to:
Convert the output data to its type declaration.
Validate the data.
Add a JSON Schema for the response, in the OpenAPI path operation.
Will be used by the automatic documentation systems.
That mean that response model is use to return json not a file like you want to do.
I couldn't create a pydantic model with a field of the type File
This is totaly normal
so I had to declare it directly in the route definition (i.e. without a model containing it)
And that's normal too, remember that File is not a python type nor a pydantic specific type so it can't be part of a model inheritating from pydantic BaseModel.
To do a file response you can follow the official doc:
from fastapi import FastAPI
from fastapi.responses import FileResponse
some_file_path = "large-video-file.mp4"
app = FastAPI()
#app.get("/")
async def main():
return FileResponse(some_file_path)
if you want to return a file like object (i.e. file from an instance of UploadFile) follow this part of the doc:
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
some_file_path = "large-video-file.mp4"
app = FastAPI()
#app.get("/")
def main():
def iterfile():
with open(some_file_path, mode="rb") as file_like:
yield from file_like
return StreamingResponse(iterfile(), media_type="video/mp4")
I have a configuration like this in the routes/__init__.py
## api/routes/__init__.py
router = APIRouter()
router.include_router(models_router, prefix="/models", tags=["models"])
...
And here is the main.py that includes them.
## main.py
from api.routes import router as api_router
def get_app():
app = FastAPI()
app.include_router(api_router, prefix = "/api")
...
app = get_app()
Now inside the models router I have two more nested routes like this:
## api/routes/models.py
router.include_router(
fields_router,
prefix="/{model_id}/fields",
tags=["fields"],
dependencies=[Depends(pre_model_validation)]
)
router.include_router(
model_data_router,
prefix="/{model_id}/data",
tags=["model_data"],
dependencies=[Depends(pre_model_validation)]
)
While this works, when I open the localhost and use the generated SwaggerUI docs, it shows something like this
:
The nested endpoints are also appearing from inside the /models API as well as from their separate /fields and /model_data APIs. How do I isolate the nested routes in a way that they appear as separate API in swagger docs but stay defined inside the /models API?
If I understand correctly you want all endpoints to be under the root path /api/models/ but want the Swagger docs to just show them only once, under the respective 'fields' or 'model_data' tags, while keeping the following under the 'models' tag:
Get Models For Site
Create Models
Update Content Type
If the above is correct, instead of nesting the imports, you might want to split them using the same root path as needed, as follows:
# api/routes/__init__.py
router = APIRouter()
router.include_router(
models_router,
prefix="/models",
tags=["models"]
)
router.include_router(
fields_router,
prefix="/models/{model_id}/fields",
tags=["fields"]
)
router.include_router(
models_router,
prefix="/models/{model_id}/data",
tags=["model_data"]
)
Some workaround can be implemented having the following structure. For demonstration purposes everything is put together:
fields_router = APIRouter()
...
model_data_router = APIRouter()
...
models_router = APIRouter()
...
aggregated_models_router = APIRouter()
aggregated_models_router.include_router(
fields_router,
prefix="/{model_id}/fields",
tags=["fields"],
dependencies=[Depends(pre_model_validation)]
)
aggregated_models_router.include_router(
model_data_router,
prefix="/{model_id}/data",
tags=["model_data"],
dependencies=[Depends(pre_model_validation)]
)
aggregated_models_router.include_router(
models_router,
prefix="",
tags=["models"]
)
...
router = APIRouter()
router.include_router(aggregated_models_router, prefix="/models")
...
Without the tags argument in the main router you'll get only fields, model_data and models sections without any duplicates
I am working with Flask-Resplus API
I want to create multiple endpoints into a single namespace. That is simple, but if I want to split the code to add endpoints into multiple files, getting issues there.
Following is my app file:
from flask_restplus import Namespace, Api
from flask import Blueprint
from test_controller1 import test_ns
blueprint = Blueprint('api', __name__)
api = Api(blueprint,
title='Test API',
version='1.0',
description='Test API',
)
api.add_namespace(test_ns, path='/test')
test_controller1.py
#test_ns.route("/test1")
class Test(Resource):
def put(self):
pass
test_controller2.py
from test_controller1 import test_ns
#test_ns.route("/test2")
class Test(Resource):
def get(self):
pass
If I import test_ns from test_controller_1, only test1 endpoint will be added in the namespace.
How can I add both the endpoints(available in different files) in the same namespace?
This can be done by defining Namespace(with the same name) across the classes.
test_controller1.py
test_ns1 = Namespace("test", "Namespace for test")
test_controller2.py
test_ns2 = Namespace("test", "Namespace for test")
Add both the namespaces to the blueprint.