Skip to content

FAST003 fix introduces syntax errors #19831

@dscorbett

Description

@dscorbett

Summary

The fix for fast-api-unused-path-parameter (FAST003) can introduce a syntax error.

When the function’s first parameter uses *, FAST003 doesn’t add a comma before it. Example:

$ cat >fast003_1.py <<'# EOF'
from fastapi import FastAPI
app = FastAPI()
@app.get("/things/{thing_id}")
async def read_thing(*query: str): ...
# EOF

$ ruff --isolated check fast003_1.py --select FAST003 --unsafe-fixes --diff 2>&1 | grep error:
error: Fix introduced a syntax error. Reverting all changes.

Ditto for **. Example:

$ cat >fast003_2.py <<'# EOF'
from fastapi import FastAPI
app = FastAPI()
@app.get("/things/{thing_id}")
async def read_thing(**query: str): ...
# EOF

$ ruff --isolated check fast003_2.py --select FAST003 --unsafe-fixes --diff 2>&1 | grep error:
error: Fix introduced a syntax error. Reverting all changes.

When the parameter list ends with /, FAST003 puts the new parameter before it, introducing a syntax error. Example:

$ cat >fast003_3.py <<'# EOF'
from fastapi import FastAPI
app = FastAPI()
@app.get("/queries/{thing_id}")
async def read_query(query: str, /): ...
# EOF

$ ruff --isolated check fast003_3.py --select FAST003 --unsafe-fixes --diff 2>&1 | grep error:
error: Fix introduced a syntax error. Reverting all changes.

The fix introduces a syntax error when the path parameter name is __debug__. It detects the error but modifies the file anyway. The fix should be suppressed in this case, as it already is for path parameters that are Python keywords. Example:

$ cat >fast003_4.py <<'# EOF'
from fastapi import FastAPI
app = FastAPI()
@app.get("/queries/{__debug__}")
async def read_query(query: str): ...
# EOF

$ ruff --isolated check fast003_4.py --select FAST003 --unsafe-fixes --fix --output-format concise -q
fast003_4.py:4:34: invalid-syntax: cannot assign to `__debug__`

$ cat fast003_4.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/queries/{__debug__}")
async def read_query(query: str, __debug__): ...

$ python fast003_4.py 2>&1 | tail -n 1
SyntaxError: cannot assign to __debug__

The fix fails to converge when the path parameter is equivalent under NFKC to a function parameter but not equal to it. FastAPI only supports ASCII in path parameter names anyway, so the fix should ignore non-ASCII path parameters. Example:

$ cat >fast003_5.py <<'# EOF'
from fastapi import FastAPI
app = FastAPI()
@app.get("/queries/{𝑞𝑢𝑒𝑟𝑦}")
async def read_query(query: str): ...
# EOF

$ ruff --isolated check fast003_5.py --select FAST003 --unsafe-fixes --diff

error: Failed to converge after 100 iterations.

This indicates a bug in Ruff. If you could open an issue at:

    https://github.com/astral-sh/ruff/issues/new?title=%5BInfinite%20loop%5D

...quoting the contents of `fast003_5.py`, the rule codes FAST003, along with the `pyproject.toml` settings and executed command, we'd be very appreciative!

--- fast003_5.py
+++ fast003_5.py
@@ -1,4 +1,4 @@
 from fastapi import FastAPI
 app = FastAPI()
 @app.get("/queries/{𝑞𝑢𝑒𝑟𝑦}")
-async def read_query(query: str): ...
+async def read_query(query: str, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦, 𝑞𝑢𝑒𝑟𝑦): ...

Would fix 100 errors.

The fix can add a parameter without a default after a positional-only parameter with a default, which is a syntax error. Example:

$ cat >fast003_6.py <<'# EOF'
from fastapi import FastAPI
app = FastAPI()
@app.get("/things/{thing_id}")
async def read_thing(query: str = "", /,): ...
# EOF

$ ruff --isolated check fast003_6.py --select FAST003 --unsafe-fixes --diff 2>&1 | grep error:
error: Fix introduced a syntax error. Reverting all changes.

The fix can create a positional-only parameter. That isn’t a syntax error but it doesn’t fix the problem, so it is not a useful change. As the FAST003 documentation says, “If a path parameter is declared in the route path, but as a positional-only argument in the function signature, it will also not be accessible in the function body, as FastAPI will not inject the parameter.” Example:

$ cat >fast003_7.py <<'# EOF'
from fastapi import FastAPI
app = FastAPI()
@app.get("/things/{thing_id}")
async def read_thing(query: str = "", /, x=None): ...
# EOF

$ ruff --isolated check fast003_7.py --select FAST003 --unsafe-fixes --fix --output-format concise -q
fast003_7.py:3:19: FAST003 Parameter `thing_id` appears in route path, but only as a positional-only argument in `read_thing` signature

$ cat fast003_7.py 
from fastapi import FastAPI
app = FastAPI()
@app.get("/things/{thing_id}")
async def read_thing(thing_id, query: str = "", /, x=None): ...

Version

ruff 0.12.8 (f51a228 2025-08-07)

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingfixesRelated to suggested fixes for violations

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions