fastapi-crudrouter icon indicating copy to clipboard operation
fastapi-crudrouter copied to clipboard

Pass custom tags values on the route-level for CRUDRouter routes

Open limsammy opened this issue 4 years ago • 0 comments
trafficstars

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:

image

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: image

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: image

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.

limsammy avatar Oct 19 '21 21:10 limsammy