Skip to content
Advertisement

Set an optional endpoint parameter dynamically

What is the best way to set an optional endpoint parameter dynamically?

Naively, I have tried with activated_on: Optional[date] = Depends(date.today) thinking FastAPI would call the callable, but it doesn’t work.

@router.get("/dummy/path")
async def ep_name(
    *,
    db: Session = Depends(deps.get_db),
    activated_on: Optional[date] = None,
    current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
    if activated_on is None:  # the lines I would like to remove
        activated_on = date.today()  # and have it set by FastAPI instead

Advertisement

Answer

TL;DR

Change the callable of your Depends to a helper function, and inject the optional value into the helper function instead of into the route function. This does the same thing as your code, except now you can reuse it across as many routes as you’d like.

Why it didn’t work the other way

Unfortunately activated_on: Optional[date] = Depends(date.today) won’t work for two reasons.

Firstly, if you’re only wanting to call the dependable if activated_on is not passed as a querystring, that won’t work. Depends will mean that the argument is populated with the response of the callable, it will ignore the fact that your arg name activated_on happens to be the name of a querystring passed in. Basically it will call the callable (date.today) everytime.

Secondly, in this particular case, actually it won’t call date.today because when you declare a dependency the DI framework tries to inspect the function signature (via inspect). With this particular callable date.today it can’t (I’m assuing this is from datetime btw). There are a few reasons why this might be the case and tbh I didn’t quite get to the bottom of why that is the case for today, but I think it’s calling a function outside python and so it can’t inspect it to determine the function’s signature. I may be wrong about the reason here but either way you can’t use that function as a dependency.

The solution

You could use a small helper function (dependency) _activated_on as your dependable:

from typing import Optional
from datetime import date
from fastapi import FastAPI, Depends, Query

async def _activated_on(q: Optional[date] = Query(None, alias="activated_on")):
    return q if q is not None else date.today()


@app.get("/dummy/path")
async def ep_name(activated_on: date = Depends(_activated_on)):
    return f"activated_on: {activated_on}"

Your route will then handle the following cases:

Case 1: user passes a valid date

E.g. https://example.com/dummy/path?activated_on=2021-12-25

When you call your route, if you pass in a querystring “activated_on” then this is passed into the _activated_on function (after being parsed to a date by pydantic) and then used to inject into the activated_on arg of your route. You could just make the helper function:

async def _activated_on(activated_on: Optional[date] = None):
    return activated_on if activated_on is not None else date.today()

but then you have the same variable name as your route which can get a bit confusing. Both versions do the same thing though so do whichever feels least confusing.

Case 2: user passes nothing

E.g. https://example.com/dummy/path

q will be None and so the the helper function will call date.today() and return that value which will be injected into the activated_on arg of your route.

Case 3: user passes invalid date

E.g. https://example.com/dummy/path?activated_on=2021-12-32

Since pydantic is parsing and validating the querystring (if it exists), if you provide a string that cannot be parsed to a date the helper function will never be called and you’ll get a helpful error response. If you instead want to use today’s date if activated_on is not provided or if it is provided but is invalid, you could change the type hint in the helper function from date to str then do the conversion yourself with a fallback to today if it fails.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement