fastapi-crudrouter
fastapi-crudrouter copied to clipboard
Pass custom tags values on the route-level for CRUDRouter routes
Hello, First off, absolutely love this library. Fantastic work @awtkns.
I was wondering if there is any sort of known solution for implementing tags on a per-route basis?
I am currently using @kael-shipman's awesome Swagger-UI plugin, Hierarchical Tags. This allows my to Swagger docs to look like this, with specific routes grouped and nested:

Essentially, with this plugin I can establish parent and child "nodes". Here is how I achieve the above screenshot with a vanilla FastAPI router:
# Create list of dict where the first dict is the parent node, and the following dicts are children-nodes
tags_metadata = [
{"name": "Trusted", "description": "API endpoints for Trusted resources."},
{
"name": "Trusted|Groups",
"description": "Query operations for Trusted Group resources. List (with offset and limit params supported) or get Groups by ID.",
},
]
# Create the router
router = APIRouter(
prefix="/trusted",
responses={404: {"description": "Not found"}},
)
# Create _list_ route for Group resource, assign this route as a child node to Trusted > Groups > List
@router.get(
"/groups/",
dependencies=[Depends(JWTBearer())],
response_model=List[TrustedGroupSchemaWithUnits],
tags=["Trusted|Groups|List"], # This tags=[] assignment here, how can I do that with CRUDRouter?
)
async def list_groups(
db: Session = Depends(get_trusted_db),
skip: int = 0,
limit: int = 25,
):
groups = db.query(TrustedGroup).offset(skip).limit(limit).all()
return groups
# Create _get_ route for Group resource, assign this route as a child node to Trusted > Groups > List
@router.get(
"/group/{id}",
dependencies=[Depends(JWTBearer())],
response_model=TrustedGroupSchemaWithUnits,
tags=["Trusted|Groups|Get"], # Assigned hierarchically as Trusted > Groups > Get
)
async def get_group(id: int, db: Session = Depends(get_trusted_db)):
group = db.query(TrustedGroup).get(id)
return group
# .... omitting rest of routes for simplicity's sake, but hierarchical tag setup matches the above
# Instantiate the FastAPI app instance with my "parent" tags defined above
app = FastAPI(
title="POC for awesome FastAPI-CRUDRouter library,
description="This API is really cool, but is not real",
version="1.3.3.7",
openapi_tags=tags_metadata # assign the parent/sub-parent tags defined on line 1 to openapi_tags
)
# Include router
app.include_router(router)
This is a small snippet that should demonstrate the basics of how the hierarchical tags plugin works (eg. matches tag pattern and uses | delimiter to denote sub-nodes).
So, in summary, I need to be able to override the tags param for each of the routes CRUDRouter generates. I am a bit unclear from the documentation on how to go about doing this. From the Overriding Routes documentation, I have attempted to override my CRUDRouter routes to include a custom tags param but I seem to lose the rest of the functionality CRUDRouter offers.
For example, here is what a route looks like without overriding:
Code:
router = SQLAlchemyCRUDRouter(
schema=NonS1EventSchema,
create_schema=NonS1EventSchemaCreate,
db_model=NonS1Event,
db=get_event_db,
dependencies=[Depends(JWTBearer())],
prefix="non-s1",
tags=["Event|Non S1 Events"],
)
Screenshot:

And then when I attempt to override a function, and pass only the tags param (I do not want to override any of the other pieces CRUDRouter generates), I run into difficulties:
Code:
router = SQLAlchemyCRUDRouter(
schema=NonS1EventSchema,
create_schema=NonS1EventSchemaCreate,
db_model=NonS1Event,
db=get_event_db,
dependencies=[Depends(JWTBearer())],
prefix="non-s1",
tags=["Event|Non S1 Events"],
)
@router.get("", tags=["Event|Non S1 Events|List"])
def list_s1_events():
pass
Screenshot:

The issues here:
- The route is duplicated in Swagger (perhaps it is still applying the default tags so it is listed twice?)
- I lose the parameters section
- I lose the Example Value in the response section
- I lose the Validation Error in the response section
- And I may also lose actual query functionality, but I have not tested this yet.
Is this because my override function body only contains pass? My presumption was that this approach would behave similar to overriding a class and calling super(), still inheriting all the parameters passed to the SQLAlchemyCRUDRouter() (eg. schema, create_schema, db_model, etc.) and generate the rest of the path/query parameters CRUDRouter generates.
Is there a way I can achieve this functionality (passing custom tags on the router function level) without modifying the source? Perhaps by passing the tags as a kwarg somewhere, or overriding "correctly"-- upon reflection the pass makes sense in why it is not "inheriting" so to speak. Am I able to inherit from a function?
Or should I fork and submit a PR?
Another idea I have is implementing a custom SQLAlchemyCRUDRouter class....
Again, thanks so much for this amazing project.