uriyyo / fastapi-pagination

FastAPI pagination 📖
https://uriyyo-fastapi-pagination.netlify.app/
MIT License
1.16k stars 133 forks source link

Using Python reserved keyword for parameter alias causes error on start up #1103

Closed cwtfp closed 3 months ago

cwtfp commented 6 months ago

I have requirements to implement pagination with parameters size and from. I am forming my custom parameter class like:

class FromSizeParams(BaseModel, AbstractParams):
    size: int = Query(50, ge=1, le=100)
    # use from_ since from is reserved, set alias to "from"
    from_: int = Query(0, ge=0, alias="from")

    def to_raw_params(self) -> RawParams:
        return RawParams(limit=self.size, offset=self.from_)

Pydantic does allow using a reserved keywords as an alias. However, add_pagination(app) fails on start up.

Traceback (most recent call last):
  File "/usr/local/lib/python3.10/multiprocessing/process.py", line 314, in _bootstrap
    self.run()
  File "/usr/local/lib/python3.10/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "/root/.cache/pypoetry/virtualenvs/api-project-8bpWA_BU-py3.10/lib/python3.10/site-packages/uvicorn/_subprocess.py", line 76, in subprocess_started
    target(sockets=sockets)
  File "/root/.cache/pypoetry/virtualenvs/api-project-8bpWA_BU-py3.10/lib/python3.10/site-packages/uvicorn/server.py", line 60, in run
    return asyncio.run(self.serve(sockets=sockets))
  File "/usr/local/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "uvloop/loop.pyx", line 1517, in uvloop.loop.Loop.run_until_complete
  File "/root/.cache/pypoetry/virtualenvs/api-project-8bpWA_BU-py3.10/lib/python3.10/site-packages/uvicorn/server.py", line 67, in serve
    config.load()
  File "/root/.cache/pypoetry/virtualenvs/api-project-8bpWA_BU-py3.10/lib/python3.10/site-packages/uvicorn/config.py", line 479, in load
    self.loaded_app = import_from_string(self.app)
  File "/root/.cache/pypoetry/virtualenvs/api-project-8bpWA_BU-py3.10/lib/python3.10/site-packages/uvicorn/importer.py", line 21, in import_from_string
    module = importlib.import_module(module_str)
  File "/usr/local/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/opt/fp/api-project/./api_project/main.py", line 86, in <module>
    add_pagination(app)
  File "/root/.cache/pypoetry/virtualenvs/api-project-8bpWA_BU-py3.10/lib/python3.10/site-packages/fastapi_pagination/api.py", line 373, in add_pagination
    _add_pagination(parent)
  File "/root/.cache/pypoetry/virtualenvs/api-project-8bpWA_BU-py3.10/lib/python3.10/site-packages/fastapi_pagination/api.py", line 369, in _add_pagination
    _update_route(route)
  File "/root/.cache/pypoetry/virtualenvs/api-project-8bpWA_BU-py3.10/lib/python3.10/site-packages/fastapi_pagination/api.py", line 348, in _update_route
    dep = Depends(pagination_ctx(cls, __page_ctx_dep__=True))
  File "/root/.cache/pypoetry/virtualenvs/api-project-8bpWA_BU-py3.10/lib/python3.10/site-packages/fastapi_pagination/api.py", line 311, in pagination_ctx
    params_dep: Any = _create_params_dependency(params) if params is not None else _noop_dep
  File "/root/.cache/pypoetry/virtualenvs/api-project-8bpWA_BU-py3.10/lib/python3.10/site-packages/fastapi_pagination/api.py", line 289, in _create_params_dependency
    sign_params[name] = sign_params[name].replace(default=field)
KeyError: 'from'

It appears the signature of FromSizeParams still has from_ instead of the alias, causing the key error. (*, size: typing.Annotated[int, Ge(ge=1), Le(le=100)] = 50, from_: typing.Annotated[int, Ge(ge=0)] = 0) -> None Aliases of words that are not reserved work fine.

Keywords other than from like: import and def also fail.

Is there a good fix or work around to use a reserved python keyword as an alias?

uriyyo commented 6 months ago

@cwtfp Looks like it's fastapi-pagination bug, I will take a look

uriyyo commented 6 months ago

Hi @cwtfp,

Looks like it's not fastapi-pagination issue, also I have found workaround. You just need to replace alias with validation_alias:

class FromSizeParams(BaseModel, AbstractParams):
    size: int = Query(50, ge=1, le=100)
    from_: int = Query(0, ge=0, validation_alias="from")

    def to_raw_params(self) -> RawParams:
        return RawParams(limit=self.size, offset=self.from_)
cwtfp commented 5 months ago

Apologies for the delay in response.

I did try that as a work around before posting the issue. However, it didn't pick up the from query parameter, when making the request.

# picks up size but not from
curl localhost:8000/api/v1/things?size=1&from=1
# does still work
curl localhost:8000/api/v1/things?size=1&from_=1

I did, however, find a work around that worked. Only drawback is that from_ remains visible in the OpenAPI spec.

class FromSizeParams(BaseModel, AbstractParams):
    size: int = Query(50, ge=1, le=100)
    # labeled as depcreated and unsupported to discourage use
    from_: int | None = Query(
        None, ge=0, deprecated=True, description="Unsupported. Use `from` instead"
    )

    def to_raw_params(self) -> RawParams:
        return RawParams(limit=self.size, offset=self.from_)

async def set_pagination_params(
    from_workaround: Annotated[int, Query(ge=0, alias="from")] = 0,
    params: FromSizeParams = Depends(),
) -> FromSizeParams:
    params.from_ = from_workaround
    return params

class FromSizePage(AbstractPage[T], Generic[T]):
    # page stuff
    __params_type__ = FromSizeParams

@router.get("/things", response_model=FromSizePage[Thing])
def get_queries(
    params=Depends(set_pagination_params),
):
    return paginate(..., params)

Also needed some extra custom code in FromSizePage to generate links with this as well.

uriyyo commented 5 months ago

Hi @cwtfp

New version 0.12.22 has been released, this issue should be fixed.

uriyyo commented 3 months ago

Hi @cwtfp,

I'm closing this issue, please reopen it in case issue still exists on your side.