flake8-fastapi 0.7.0
flake8 plugin that checks FastAPI code against opiniated style rules 🤓
License: MIT
Requires Python: >=3.8,<4.0

<h1 align="center">
    <strong>flake8-fastapi</strong>
</h1>
<p align="center">
    <a href="https://github.com/Kludex/flake8-fastapi" target="_blank">
        <img src="https://img.shields.io/github/last-commit/Kludex/flake8-fastapi" alt="Latest Commit">
    </a>
        <img src="https://img.shields.io/github/workflow/status/Kludex/flake8-fastapi/Test">
        <img src="https://img.shields.io/codecov/c/github/Kludex/flake8-fastapi">
    <br />
    <a href="https://pypi.org/project/flake8-fastapi" target="_blank">
        <img src="https://img.shields.io/pypi/v/flake8-fastapi" alt="Package version">
    </a>
    <img src="https://img.shields.io/pypi/pyversions/flake8-fastapi">
    <img src="https://img.shields.io/github/license/Kludex/flake8-fastapi">
</p>

A [flake8](https://flake8.pycqa.org/en/latest/index.html) plugin that helps you avoid simple FastAPI mistakes.

## Installation

First, install the package:

``` bash
pip install flake8-fastapi
```

Then, check if the plugin is installed using `flake8`:

``` bash
$ flake8 --version
3.9.2 (flake8-fastapi: 0.2.0, mccabe: 0.6.1, pycodestyle: 2.7.0, pyflakes: 2.3.1) CPython 3.8.11 on Linux
```

## Rules

<!-- prettier-ignore-start -->
  - [CF001 - Route Decorator Error](#cf001---route-decorator-error)
  - [CF002 - Router Prefix Error](#cf002---router-prefix-error)
  - [CF008 - CORSMiddleware Order](#cf008---corsmiddleware-order)
  - [CF009 - Undocumented HTTPException](#cf009---undocumented-httpexception)
  - [CF011 - No Content Response](#cf011---no-content-response)
<!-- prettier-ignore-end -->

### CF001 - Route Decorator Error

Developers that were used to [flask](https://flask.palletsprojects.com/en/2.0.x/) can be persuaded or want to use the same pattern in FastAPI:

```python
from fastapi import FastAPI

app = FastAPI()


@app.route("/", methods=["GET"])
def home():
    return "Hello world!"
```

But on FastAPI, we have a simpler way to define this (and is the most known way to create endpoints):

```python
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def home():
    return "Hello world!"
```

### CF002 - Router Prefix Error

On old FastAPI versions, we were able to add a prefix only on the `include_router` method:

```python
from fastapi import APIRouter, FastAPI

router = APIRouter()


@router.get("/")
def home():
    ...


app = FastAPI()
app.include_router(router, prefix="/prefix")
```

Now, it's possible to add in the `Router` initialization:

```python
from fastapi import APIRouter, FastAPI

router = APIRouter(prefix="/prefix")


@router.get("/")
def home():
    ...


app = FastAPI()
app.include_router(router)
```


### CF008 - CORSMiddleware Order

There's a [tricky issue](https://github.com/tiangolo/fastapi/issues/1663) about [CORSMiddleware](https://www.starlette.io/middleware/#corsmiddleware) that people are usually unaware. Which is that this middleware should be the last one on the middleware stack. You can read more about it [here](https://github.com/tiangolo/fastapi/issues/1663).

Let's see an example of what doesn't work:

```python
from fastapi import FastAPI

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=['*'],
    allow_credentials=True,
    allow_methods=['*'],
    allow_headers=['*']
)
app.add_middleware(GZipMiddleware)
```

As you see, the last middleware added is not `CORSMiddleware`, so it will not work as expected. On the other hand, if you change the order, it will:

```python
from fastapi import FastAPI

app = FastAPI()

app.add_middleware(GZipMiddleware)
app.add_middleware(
    CORSMiddleware,
    allow_origins=['*'],
    allow_credentials=True,
    allow_methods=['*'],
    allow_headers=['*']
)
```

### CF009 - Undocumented HTTPException

Currently, there's no automatic solution to document the `HTTPException`s, besides the experimental package [`fastapi-responses`](https://github.com/Kludex/fastapi-responses).

For that reason, it's easy to forget the documentation, and have a lot of undocumented endpoints. Let's see an example:

```python
from fastapi import FastAPI, HTTPException

app = FastAPI()


@app.get("/")
def home():
    raise HTTPException(status_code=400, detail="Bad Request")
```

The above endpoint doesn't have a `responses` field, even if it's clear that the response will have a `400` status code.

### CF011 - No Content Response

Currently, if you try to send a response with no content (204), FastAPI will send a 204 status with a non-empty body.
It will send a body content-length being 4 bytes.

You can verify this statement running the following code:

```python
# main.py
from fastapi import FastAPI

app = FastAPI()


@app.get("/", status_code=204)
def home():
    ...
```

Now feel free to run with your favorite server implementation:

```bash
uvicorn main:app
```

Then use curl or any other tool to send a request:

```bash
$ curl localhost:8000
*   Trying 127.0.0.1:8000...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 204 No Content
< date: Sat, 24 Jul 2021 19:21:24 GMT
< server: uvicorn
< content-length: 4
< content-type: application/json
<
* Connection #0 to host localhost left intact
```

This goes against the [RFC](https://tools.ietf.org/html/rfc7231#section-6.3.5), which specifies that a 204 response should have no body.

## License

This project is licensed under the terms of the MIT license.