Metadata-Version: 2.3
Name: fapi-limiter
Version: 0.2.0
Summary: Flexible rate limiting for FastAPI using pyrate-limiter
Author: Joshue Abance
Author-email: Joshue Abance <iamcoderx@gmail.com>
License: MIT License
         
         Copyright (c) 2026 Joshue Abance
         
         Permission is hereby granted, free of charge, to any person obtaining a copy
         of this software and associated documentation files (the "Software"), to deal
         in the Software without restriction, including without limitation the rights
         to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         copies of the Software, and to permit persons to whom the Software is
         furnished to do so, subject to the following conditions:
         
         The above copyright notice and this permission notice shall be included in all
         copies or substantial portions of the Software.
         
         THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         SOFTWARE.
Requires-Dist: fastapi>=0.135.3
Requires-Dist: pyrate-limiter>=4.1.0
Requires-Python: >=3.13
Project-URL: Homepage, https://codeberg.org/tbdsux/fapi-limiter
Project-URL: Repository, https://codeberg.org/tbdsux/fapi-limiter.git
Project-URL: Issues, https://codeberg.org/tbdsux/fapi-limiter/issues
Description-Content-Type: text/markdown

# fapi-limiter

[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

Flexible rate limiting for [FastAPI](https://fastapi.tiangolo.com/) powered by [pyrate-limiter](https://github.com/vutran1710/PyrateLimiter).

> This project is being developed with AI assistance (GitHub Copilot), but all code, design decisions, and implementation details are reviewed and validated by the author.

## Installation

```bash
# pip
pip install fapi-limiter

# uv
uv add fapi-limiter
```

## Usage

### Middleware (app-wide)

Apply rate limiting to every request in the application.

```python
from fastapi import FastAPI
from pyrate_limiter import Duration, Limiter, Rate

from fapi_limiter import DynamicBucketFactory, RateLimiterMiddleware, setup_inmemory_bucket

app = FastAPI()

limiter = Limiter(DynamicBucketFactory(setup_inmemory_bucket([Rate(10, Duration.MINUTE)])))
app.add_middleware(RateLimiterMiddleware, limiter=limiter)
```

### Dependency (per-route)

Apply rate limiting to specific routes using FastAPI's dependency injection.

```python
from fastapi import Depends, FastAPI
from pyrate_limiter import Duration, Limiter, Rate

from fapi_limiter import DynamicBucketFactory, RateLimiter, setup_inmemory_bucket

app = FastAPI()

limiter = Limiter(DynamicBucketFactory(setup_inmemory_bucket([Rate(10, Duration.MINUTE)])))


@app.get("/limited", dependencies=[Depends(RateLimiter(limiter))])
def limited():
    return {"ok": True}
```

### SQLite backend (persistent, multi-process)

```python
from fastapi import FastAPI
from pyrate_limiter import Duration, Limiter, Rate

from fapi_limiter import DynamicBucketFactory, RateLimiterMiddleware, setup_sqlite_bucket

app = FastAPI()

limiter = Limiter(
    DynamicBucketFactory(
        setup_sqlite_bucket(
            [Rate(10, Duration.MINUTE)],
            db_path="rate_limiter.db",
            use_file_lock=True,  # required when sharing across processes
        )
    )
)
app.add_middleware(RateLimiterMiddleware, limiter=limiter)
```

### Redis backend

```python
from fastapi import FastAPI
from pyrate_limiter import Duration, Limiter, Rate
from redis import Redis

from fapi_limiter import DynamicBucketFactory, RateLimiterMiddleware, setup_redis_bucket

app = FastAPI()

limiter = Limiter(
    DynamicBucketFactory(
        setup_redis_bucket([Rate(10, Duration.MINUTE)], redis=Redis(host="localhost"))
    )
)
app.add_middleware(RateLimiterMiddleware, limiter=limiter)
```

## Response Headers

On every request, the following headers are set:

| Header                  | Description                                |
| ----------------------- | ------------------------------------------ |
| `X-RateLimit-Limit`     | Maximum requests allowed in the window     |
| `X-RateLimit-Remaining` | Requests remaining in the current window   |
| `X-RateLimit-Reset`     | Unix timestamp (ms) when the window resets |

When the limit is exceeded, `429 Too Many Requests` is returned and two additional headers are set:

| Header        | Description                                          |
| ------------- | ---------------------------------------------------- |
| `Retry-After` | Unix timestamp (ms) after which the client may retry |

## Configuration

Both `RateLimiterMiddleware` and `RateLimiter` accept the same options:

| Parameter    | Type                       | Default                  | Description                                      |
| ------------ | -------------------------- | ------------------------ | ------------------------------------------------ |
| `limiter`    | `Limiter`                  | required                 | A pyrate-limiter `Limiter` instance              |
| `callback`   | `CallbackFunction`         | `default_callback`       | Called when the limit is exceeded                |
| `identifier` | `Callable[[Request], str]` | `get_default_identifier` | Extracts a key from the request (e.g. IP + path) |
| `blocking`   | `bool`                     | `False`                  | Whether to block and wait instead of rejecting   |

`RateLimiterMiddleware` also accepts:

| Parameter | Type                   | Default | Description                                              |
| --------- | ---------------------- | ------- | -------------------------------------------------------- |
| `skip`    | `SkipFunction \| None` | `None`  | If it returns `True`, the request bypasses rate limiting |

## Custom Callback

The callback is invoked when a request is rate-limited. It receives the `Request` and a `headers` dict, and should raise an `HTTPException`:

```python
from fastapi import HTTPException, Request
from fapi_limiter import RateLimiterMiddleware

def my_callback(request: Request, headers=None):
    raise HTTPException(status_code=429, detail="Slow down!", headers=headers)

app.add_middleware(RateLimiterMiddleware, limiter=limiter, callback=my_callback)
```

## Custom Identifier

By default the identifier is `{request_method}:{client_ip}:{path}`. You can override it to key by user, API token, etc.:

```python
def by_user(request: Request) -> str:
    return request.headers.get("X-User-Id", "anonymous") + ":" + request.url.path

app.add_middleware(RateLimiterMiddleware, limiter=limiter, identifier=by_user)
```

## Skip Function

Use `skip` on `RateLimiterMiddleware` to bypass rate limiting for certain requests (e.g. health checks):

```python
def skip_health(request: Request) -> bool:
    return request.url.path == "/health"

app.add_middleware(RateLimiterMiddleware, limiter=limiter, skip=skip_health)
```

## Credits

- [fastapi-limiter](https://github.com/long2ice/fastapi-limiter) by [long2ice](https://github.com/long2ice)
- [requests-limiter](https://github.com/JWCook/requests-ratelimiter) by [JWCook](https://github.com/JWCook)

## License

[MIT](LICENSE)
