Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[fastapi] Implement FAST001 (fastapi-redundant-response-model) and FAST002 (fastapi-non-annotated-dependency) #11579

Merged

Conversation

TomerBin
Copy link
Contributor

Summary

Implements ruff specific role for fastapi routes, and its autofix.

Test Plan

cargo test / cargo insta review

Copy link
Contributor

github-actions bot commented May 28, 2024

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+3 -0 violations, +0 -0 fixes in 1 projects; 49 projects unchanged)

lnbits/lnbits (+3 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --output-format concise --preview

+ lnbits/core/views/api.py:66:37: RUF102 FastAPI route with redundant response_model argument
+ lnbits/core/views/node_api.py:79:34: RUF102 FastAPI route with redundant response_model argument
+ lnbits/core/views/wallet_api.py:72:25: RUF102 FastAPI route with redundant response_model argument

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
RUF102 3 3 0 0 0

Copy link

codspeed-hq bot commented May 28, 2024

CodSpeed Performance Report

Merging #11579 will not alter performance

Comparing TomerBin:fastapi-redundant-response-model (dd50d3b) with TomerBin:fastapi-redundant-response-model (13df570)

Summary

✅ 33 untouched benchmarks

@charliermarsh
Copy link
Member

Thank you for this! Two reactions, which I'd in-turn like your reaction on:

  1. I think we'll need to find someone who feels comfortable reviewing these rules for correctness and quality (e.g., is this a recommended pattern?). I'm not familiar enough with FastAPI to be a good judge here. Perhaps I can ask Sebastian what he thinks of it.
  2. Ideally we'd implement multiple FastAPI rules before committing to supporting a one-off rule. This helps prevent Ruff from feeling like an unwieldy collection of one-off rules.

@TomerBin
Copy link
Contributor Author

@charliermarsh Thanks for your comments.

  1. It would be great if Sebastian could confirm that rule. Could you please ask for his opinion?
  2. Another rule I'm considering is to use the new Annotated style for dependencies declaration. Other rules I have in mind require complicated type inference and cross-file analysis, so they are probably out of scope for this PR. Do you think multiple rules are mandatory for merging this PR?

@TomerBin
Copy link
Contributor Author

TomerBin commented Jul 6, 2024

@charliermarsh Any chance you could provide feedback on my previous message when you have a moment?

@tiangolo
Copy link

Thanks @TomerBin for emailing me! This looks great.

And recommending Annotated would be awesome as well.

Additionally, if there was a way to auto-fix code that uses the old style to now use Annotated in Ruff, that would be a dream come true. It applies to dependencies or to Security as well (another type of dependency.

An old dependency and Security param would look like:

@app.get("/items/")
def get_items(
    current_user: User = Depends(get_current_user),
    some_security_param: str = Security(get_oauth2_user),
):
    # do stuff
    pass

That would be ideally transformed into:

@app.get("/items/")
def get_items(
    current_user: Annotated[User, Depends(get_current_user)],
    some_security_param: Annotated[str, Security(get_oauth2_user)],
):
    # do stuff
    pass

Additionally, the same transformation would apply to other types of parameters, like Query, Path, Body, Cookie.

The only caveat is that if one of those has a default argument (or the first argument), as in Query(default="foo") then that would become the new default value. For example:

@app.post("/stuff/")
def do_stuff(
    some_query_param: str | None = Query(default=None),
    some_path_param: str = Path(),
    some_body_param: str = Body("foo"),
    some_cookie_param: str = Cookie(),
    some_header_param: int = Header(default=5),
    some_file_param: UploadFile = File(),
    some_form_param: str = Form(),
):
    # do stuff
    pass

would ideally be transformed into:

@app.post("/stuff/")
def do_stuff(
    some_query_param: Annotated[str | None, Query()] = None,
    some_path_param: Annotated[str, Path()],
    some_body_param: Annotated[str, Body()] = "foo",
    some_cookie_param: Annotated[str, Cookie()],
    some_header_param: Annotated[int, Header()] = 5,
    some_file_param: Annotated[UploadFile, File()],
    some_form_param: Annotated[str, Form()],
):
    # do stuff
    pass

Note: please keep pinging me on email if I miss a GitHub notification around this, I miss most GitHub notifications as I still have around 10k unread. 😅

@charliermarsh
Copy link
Member

Thank you @TomerBin and @tiangolo 🙏

@TomerBin TomerBin force-pushed the fastapi-redundant-response-model branch from b6da4d7 to c19547f Compare July 18, 2024 17:18
@charliermarsh charliermarsh changed the title [ruff] Implement RUF102 (fastapi-redundant-response-model) [fastapi] Implement FAST001 (fastapi-redundant-response-model) and FAST002 (fastapi-non-annotated-dependency) Jul 21, 2024
Copy link
Member

@charliermarsh charliermarsh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I decided to move these to a dedicated FastAPI category. There's precedence for it with NumPy (NPY), and I think I'd prefer to keep the Ruff rules framework-agnostic.

@charliermarsh charliermarsh added rule Implementing or modifying a lint rule preview Related to preview mode features labels Jul 21, 2024
@charliermarsh charliermarsh enabled auto-merge (squash) July 21, 2024 18:27
@charliermarsh charliermarsh merged commit 0532436 into astral-sh:main Jul 21, 2024
17 checks passed
@marcuslimdw
Copy link

marcuslimdw commented Jul 30, 2024

@TomerBin, thanks for your work on this!

Is this rule intended to apply only to route functions? I ask because at my company, we have a pretty comprehensive set of free functions that are used as dependency providers, for example:

controller.py:

@app.post("/")
def route(dependency: Annotated[str, Depends(providers.base_provider)]) -> None:
    ...

providers.py:

def base_provider(sub_provider: Annotated[str, Depends(sub_provder)]) -> str:
    ...

def sub_provider() -> str:
    ...

Currently, this rule only detects and fixes the route function, and not the provider function in providers.py (which would be pretty nice). As far as I can tell, this wouldn't be susceptible to false positives, since FastAPI dependencies are marked with a FastAPI-specific class.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
preview Related to preview mode features rule Implementing or modifying a lint rule
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants