Metadata-Version: 2.4
Name: ratemesh
Version: 0.1.2
Summary: A Python middleware toolkit for rate limiting, caching, and related request controls.
Requires-Python: >=3.13
Description-Content-Type: text/markdown
Requires-Dist: redis>=5.0
Requires-Dist: starlette>=0.27
Provides-Extra: test
Requires-Dist: fakeredis>=2.0; extra == "test"
Requires-Dist: pytest>=8.0; extra == "test"
Requires-Dist: pytest-asyncio>=0.24; extra == "test"

# RateMesh

A Python middleware toolkit built around reusable request controls. Rate
limiting is the first capability, with room for caching and other middleware
features as the library grows.

## Current Architecture

```text
ratemesh/
├── ratemesh/                # Installable Python package
│   ├── core/                # Rate-limiter interface and algorithms
│   ├── strategies/          # Request-to-rate-limit-key translation
│   ├── middleware/          # Starlette request interception
│   ├── client/              # Async Redis client wrappers
│   ├── lua_scripts/         # Atomic Redis Lua scripts
│   └── utils/               # Environment configuration
└── tests/                   # Pytest coverage for core algorithms
```

The request path is:

```text
Request
  -> RateLimiterMiddleware
  -> route-specific or default configuration
  -> BaseStrategy.build_key(request)
  -> BaseRateLimiter.is_allowed(key)
  -> application response or HTTP 429
```

The middleware uses route-specific configuration when the request path is
present in `route_config`; otherwise it uses `default_config`. A strategy
creates the key used by the limiter. The included strategies create keys from
the client IP or an authenticated token.

## Implemented Components

### Rate-limit algorithms

All algorithms implement `BaseRateLimiter`:

- `FixedWindow`: allows a fixed number of requests per time window.
- `SlidingWindow`: keeps request timestamps and removes entries outside the
  active window.
- `LeakyBucket`: drains the current bucket level at a configured rate.
- `TokenBucket`: refills available tokens at a configured rate.

The current implementations keep their state in Redis. Each request executes
one atomic Lua script using a key-specific Redis record, so multiple workers
and application instances share the same limit. Limiter keys use the request
identity as a Redis hash tag, for example `ratelimit:{user:42}`, which keeps
related operations for one identity on the same Redis Cluster slot.

### Key strategies

`IpBasedStrategy` accepts a callable that extracts an IP address from a
Starlette request. `AuthStrategy` accepts one callable that retrieves a token
and another that decodes it to a user identifier. Both prefix generated keys
with `limiter:`.

### Middleware

`RateLimiterMiddleware` is built on Starlette's `BaseHTTPMiddleware`. It calls
the configured limiter before the application handler. Requests that exceed the
limit receive `429 Too Many Requests`; allowed requests continue to the
application.

The middleware accepts a limiter, so the Redis backend is selected when the
limiter is constructed. Both standalone Redis and Redis Cluster clients expose
the same async client shape:

```python
from ratemesh.client import RedisClient, RedisClusterClient
from ratemesh.core.fixed_window import FixedWindow

# Standalone Redis:
redis_client = RedisClient()

# Or Redis Cluster:
# from redis.asyncio.cluster import ClusterNode
# redis_client = RedisClusterClient([
#     ClusterNode("127.0.0.1", 7000),
#     ClusterNode("127.0.0.1", 7001),
# ])

limiter = FixedWindow(redis_client.redis, capacity=100, window_size=60)
```

Redis Cluster does not use the standalone Redis `REDIS_DB` setting. Configure
cluster-specific options, such as startup nodes or address remapping, through
the `RedisClusterClient` constructor.

## Setup

The project targets Python 3.13 and uses Pipenv:

```bash
pipenv install --dev
```

The Redis client reads these environment variables when imported:

```bash
export REDIS_HOST=localhost
export REDIS_PORT=6379
export REDIS_DB=0
# Optional:
export REDIS_PASSWORD=your-password
```

The algorithms require Redis and use the async `redis-py` client. Tests use
`fakeredis` with Lua support so they can execute the scripts without a local
Redis server.

## Usage

The core algorithms can be used directly:

```python
from ratemesh.client import RedisClient
from ratemesh.core.token_bucket import TokenBucket

redis_client = RedisClient()
limiter = TokenBucket(
  redis_client.redis,
  capacity=10,
  refill_rate=2,
)

if await limiter.is_allowed("user:42"):
    # Process the request.
    pass
else:
    # Return HTTP 429 from the surrounding application.
    pass
```

Middleware configuration uses a limiter and a strategy per route or as a
default:

```python
from ratemesh.middleware.rate_limiter_middleware import RateLimiterMiddleware
from ratemesh.core.fixed_window import FixedWindow
from ratemesh.strategies.ip_strategy import IpBasedStrategy

limiter = FixedWindow(capacity=100, window_size=60)
strategy = IpBasedStrategy(lambda request: request.client.host)

default_config = {"limiter": limiter, "strategy": strategy}
route_config = {}
```

Register the middleware with a Starlette-compatible application:

```python
from starlette.applications import Starlette

app = Starlette()
app.add_middleware(
  RateLimiterMiddleware,
  default_config=default_config,
  route_config=route_config,
)
```

To use Redis Cluster, replace `RedisClient()` with
`RedisClusterClient([...])` when constructing `limiter`; the middleware
configuration remains unchanged.

## Testing

Run the current deterministic algorithm tests with:

```bash
pipenv run pytest -q
```

The tests use reusable `fakeredis` fixtures with Lua support, so the Redis
scripts are exercised without requiring a local Redis server.

## Roadmap

- Add a dedicated backend protocol for alternate storage implementations.
- Add integration tests against a real Redis service.
- Add middleware integration tests and application examples.
- Add concurrency and multi-process tests.
- Add benchmarks and local Docker Compose infrastructure.
