Skip to content

Infer whether a parameter should be optional or not #450

@jonathanunderwood

Description

@jonathanunderwood

Description
Consider the following code:

from fastapi import FastAPI,  APIRouter


app = FastAPI()  # pylint: disable=invalid-name


user_router = APIRouter()  # pylint: disable=invalid-name
item_router = APIRouter()  # pylint: disable=invalid-name


@user_router.get("/")
def get_users():
    return [{"user_id": "u1"}, {"user_id": "u2"}]


@user_router.get("/{user_id}")
def get_user(user_id):
    return {"user_id": user_id}


@item_router.get("/")
def get_items(user_id):
    return [{"item_id": "i1"}, {"item_id": "i2"}]


@item_router.get("/{item_id}")
def get_item(item_id, user_id):
    return {"item": item_id}


app.include_router(user_router, prefix="/users")
app.include_router(item_router, prefix="/items")

app.include_router(item_router, prefix="/users/{user_id}/items")

This creates the following routes:

  • /users
  • /users/{user_id}
  • /items
    • required query parameters: user_id
  • /items/{item_id}
    • required query parameters: user_id
  • /users/{user_id}/items
  • /users/{user_id}/items/{itemid}

whereas what I'd like is:

  • /users
  • /users/{user_id}
  • /items
    • optional query parameters: user_id
  • /items/{item_id}
    • optional query parameters: user_id
  • /users/{user_id}/items
  • /users/{user_id}/items/{itemid}

The obvious thing to do is this:

@item_router.get("/")
def get_items(user_id: str = None):
    return [{"item_id": "i1"}, {"item_id": "i2"}]

@item_router.get("/{item_id}")
def get_item(item_id, user_id: str = None):
    return {"item": item_id}

But of course, that will break the route /users/{user_id}/items/{itemid}:

Traceback (most recent call last):
  File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/process.py", line 297, in _bootstrap
    self.run()
  File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/process.py", line 99, in run
    self._target(*self._args, **self._kwargs)
  File "/Users/PLL740/.virtualenvs/hatch-api/lib/python3.7/site-packages/uvicorn/supervisors/statreload.py", line 28, in handle_fds
    target(**kwargs)
  File "/Users/PLL740/.virtualenvs/hatch-api/lib/python3.7/site-packages/uvicorn/main.py", line 307, in run
    loop.run_until_complete(self.serve(sockets=sockets))
  File "uvloop/loop.pyx", line 1451, in uvloop.loop.Loop.run_until_complete
  File "/Users/PLL740/.virtualenvs/hatch-api/lib/python3.7/site-packages/uvicorn/main.py", line 314, in serve
    config.load()
  File "/Users/PLL740/.virtualenvs/hatch-api/lib/python3.7/site-packages/uvicorn/config.py", line 186, in load
    self.loaded_app = import_from_string(self.app)
  File "/Users/PLL740/.virtualenvs/hatch-api/lib/python3.7/site-packages/uvicorn/importer.py", line 20, in import_from_string
    module = importlib.import_module(module_str)
  File "/Users/PLL740/.virtualenvs/hatch-api/lib/python3.7/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 728, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "./hatch_api/test.py", line 34, in <module>
    app.include_router(item_router, prefix="/users/{user_id}/items")
  File "/Users/PLL740/.virtualenvs/hatch-api/lib/python3.7/site-packages/fastapi/applications.py", line 248, in include_router
    responses=responses or {},
  File "/Users/PLL740/.virtualenvs/hatch-api/lib/python3.7/site-packages/fastapi/routing.py", line 477, in include_router
    name=route.name,
  File "/Users/PLL740/.virtualenvs/hatch-api/lib/python3.7/site-packages/fastapi/routing.py", line 370, in add_api_route
    dependency_overrides_provider=self.dependency_overrides_provider,
  File "/Users/PLL740/.virtualenvs/hatch-api/lib/python3.7/site-packages/fastapi/routing.py", line 288, in __init__
    self.dependant = get_dependant(path=self.path_format, call=self.endpoint)
  File "/Users/PLL740/.virtualenvs/hatch-api/lib/python3.7/site-packages/fastapi/dependencies/utils.py", line 188, in get_dependant
    ), "Path params must have no defaults or use Path(...)"
AssertionError: Path params must have no defaults or use Path(...)
INFO: Stopping reloader process [37825]

Describe the solution you'd like

It would be nice if the semantics of route functions like

@router.restverb(...)
def router_fn(param: type = None)

could be enhanced such that the default value is disregarded if the param appears in the fully resolved path.

In other words, the semantics of (param: type = None) would become "this parameter should be optional anywhere it's not a path variable".

Describe alternatives you've considered
The current solution would be to just define the different functions by hand.

Additional context
None

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions