FastAPI dynamic multiple path parameters - python

I would like to achieve the following :
My application contains some "sub-domains" which correspond to different parts of the app.
Each domain has its own entities
I would like to write a single controler like so :
#app.get("/{domain}/entity/{entity}/{id}")
async def read_users(domain: Domain, entity: Entity, id: Int):
pass
considering Entity would be an Enum that could change following the selected domain.
For instance, if the domain is "architecture", Entity could be defined like :
class Entity(str, Enum):
building = "building"
floor = "floor"
but if the selected domain is "vehicle", the matching Entity would be :
class Entity(str, Enum):
engine = "engine"
wheels = "wheels"
More generally, I guess what I'm looking for is a way to make a path parameter validation dependent on another path parameter.
This way :
GET /architecture/entity/floor/1 is valid since, floor is a valid entity for domain architecture
GET /vehicle/entity/wheels/5 is valid since, wheels is a valid entity for domain vehicle
GET /architecture/entity/engine/1 is invalid since, engine is not a valid entity for domain architecture
Is there any way to achieve this ?

You can use closures. The following code does not use Enums for brevity :
from fastapi import FastAPI
app = FastAPI()
domains = {"architecture": ["building","floor"], "vehicle": ["engine","wheels"]}
def set_route(domain,entity):
url = "/{}/entity/{}/{{id}}".format(domain,entity)
#app.get(url)
async def read_users(id: int):
return(f"Users of the {domain} {entity} #{id}")
for domain, entities in domains.items():
for entity in entities:
set_route(domain,entity)
And it yields the desired API schema :

I'm not sure if exactly what you want is possible, for sure you can write controller where you add pydantic validation, and handle exception if it throws validation error:
from pydantic import BaseModel, ValidationError
from enum import Enumfrom fastapi import FastAPI, Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from typing import Union, Literal
class ArchitectureEntity(BaseModel):
entity: Union[Literal['building'], Literal['floor']]
class VehicleEntity(BaseModel):
entity: Union[Literal['wheels'], Literal['engine']]
#app.exception_handler(ValidationError)
async def validation_exception_handler(request: Request, exc: ValidationError):
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content=jsonable_encoder({"detail": exc.errors(), "Error": "Entity not permitted"}),
)
#app.get("/{domain}/entity/{entity}/{id}")
async def read_users(domain: Domain, entity: Entity, id: int):
if domain == 'architecture':
entity = ArchitectureEntity(entity=entity)
elif domain == 'vehicle':
entity = VehicleEntity(entity=entity)
return {'architecture': entity}
However openapi docs will not show, that e.g architecture and engine are not allowed together.

Related

Python: FastAPI and Beanie - Scoped Object

In ASP.NET Core you have the concept of adding a service/object as "scoped" which means that it is the same object within a request. We used this concept e.g. to get the user out of the Bearer token header and provide the user data project-wide over a "CurrentUserService".
I'm searching for a way to do this using Pythons FastAPI and Beanie framework. Here is the use-case:
I want to use the event-based actions of beanie to automatically set the user data before a database update. Here is the current state of the code:
class DataEntity(Document):
name: str
last_modified_by: str
#before_event(Replace)
def set_version_data(self):
#here the current user should be set into last_modified_by
So my question is: How can I set the last_modified_by field on this event level?
I already tried to use the FastAPI dependencies, because they look like a scoped wrap-around I searched for:
async def get_current_user(x_user: str = Header(None)) -> str:
return x_user
class DataEntity(Document):
name: str
last_modified_by: str
#before_event(Replace)
def set_version_data(self, current_user: str = Depends(get_current_user)):
self.last_modified_by = current_user
But this is not handled right and I get the error "pydantic.error_wrappers.ValidationError: 1 validation error for DataEntity: last_modified_by - str type expected (type=type_error.str)".

Is it possible to get query params with Pydantic if param's name use special symbols?

I'm handling this request in my code (Python3.9, FastAPI, Pydantic):
https://myapi.com/api?params[A]=1&params[B]=2
I tried to make following model:
BaseModel for handling special get request
(for fastapi.Query and pydantic.Field is same)
I also set up aliases for it, but in swagger docs I see next field:
Snap of the swagger docs
There are fields that are specified as extra_data
So, if I specify query params in parameters of my endpoint like this:
#app.get('/')
def my_handler(a: str = Query(None, alias="params[A]")):
return None
Everything works fine. How can I fix it? I want to initialize my pydantic.BaseModel with speacial aliases using this way and avoid usage of query-params in
class MyModel(BaseModel):
a = Field(alias="params[A]")
b = Field(alias="params[B]")
def my_handler(model: MyModel = Depends()):
return model.dict()

How to generate response decriptions in FastAPI

I want to generate a description of all available responses (along with code 200 example), which are represented in the code, like here.
from typing import Any
import uvicorn
from fastapi import FastAPI, HTTPException
router = FastAPI()
from pydantic import BaseModel
class FileItemBase(BaseModel):
current_project: str = "Test project"
class FileItemInDBBase(FileItemBase):
id: int
folder_path: str
class Config:
orm_mode = True
class FileResponse(FileItemInDBBase):
pass
#router.get("/", response_model=FileResponse)
def example_code() -> Any:
"""
# beautiful description
to demonstrate functionality
"""
demo=True
if demo:
raise HTTPException(418, "That is a teapot.")
if __name__ =="__main__":
uvicorn.run(router)
What I got with this is such a description.
When I try this out - I got an error response (as expected).
What I want - is the description of an error included in the example responses, like here. A Frontend-developer can look at this description and process such cases in the right way without testing the API.
I know how it can be made within OpenAPI specs.
Is there a way to generate this description with FastAPI?
You can add a responses parameter to your path operation.
Then you can pass your model there. It will create a schema for that model.
class FileItemBase(BaseModel):
current_project: str = "Test project"
#app.get("/", response_model=FileItemBase, responses={418: {"model": FileItemBase}})
def example_code():
"""
# beautiful description
to demonstrate functionality
"""
demo = True
if demo:
raise HTTPException(418, "That is a teapot.")

How to get user auth info in FastAPI, when using `app.add_route()` for GraphQL?

I'm using FastAPI and now want to add GraphQL using graphene.
I'm using:
fastapi-user
starlette.graphql.GraphQLApp
Here's the routing for GraphQL and how I used fastapi-user package.
import fastapi_users
from starlette.graphql import GraphQLApp
from mypackage.schema import my_schema
...
app.include_router(fastapi_users.router, prefix="/users", tags=["users"])
app.include_router(google_oauth_router, prefix="/google-oauth", tags=["users"])
app.add_route("/", GraphQLApp(schema=my_schema))
...
In the schema, I need to get user information and control the role based auth.
How to, or how can I use get_current_active_user() method with GraphQL schema?
I'm assuming that my_schema inherits graphene.ObjectType and that the first argument is info as shown in the example at:
https://fastapi.tiangolo.com/advanced/graphql/
from graphene import ObjectType, String
class Query(ObjectType):
hello = String(name=String(default_value="stranger"))
def resolve_hello(self, info, name):
return "Hello " + name
info contains the session object with your user information.
info.context['request'].session['user']

Equivalent of #Named API parameter in Python

Is there any way to used named method parameter in Python - corresponding to this Java example:
#ApiMethod(
name = "foos.remove",
path = "foos/{id}",
httpMethod = HttpMethod.DELETE,
)
public void removeFoo(#Named("id") String id) {
}
In my Python version, if I set the #endpoints.method path to foos/{id} the URL gets matched correctly, but how do I access the parameter?
There is not strict equivalent, but if {id} is in your path, then there must be a field called id in the protorpc message class you use for the request class in the method.
For example:
from google.appengine.ext import endpoints
from protorpc import messages
from protorpc import remote
class MyMessageClass(messages.Message):
id = messages.StringField(1) # Or any other field type
#endpoints.api(...)
class MyApi(remote.Service):
#endpoints.method(MyMessageClass, SomeResponseClass,
..., path='foos/{id}')
def my_method(self, request):
...

Categories

Resources