# rrl - Redis Rate Limiting

Very simple reusable redis-backed rate limiting code.  Designed for OpenStates.org.

![Test](https://github.com/jamesturk/rrl/workflows/Test/badge.svg)

## Configuration

`rrl`, as the name implies, requires a [Redis](https://redis.io) server.

`rrl` is entirely configured by environment variables:

**RRL_REDIS_HOST** - hostname of Redis instance (default: *localhost*)
**RRL_REDIS_PORT** - port of Redis instance (default: *6379*)
**RRL_REDIS_DB** - database ID to use for RRL (default: *0*)

## Usage

Usage can be throttled on a per-minute, per-hour, and/or per-day basis.

`rrl` has the concept of a `Tier` which associates a name with a set of limitations, for instance:

```
# create two tiers
bronze = Tier(name="bronze", per_minute=1, per_hour=0, per_day=500)
silver = Tier(name="silver", per_minute=5, per_hour=0, per_day=4000)
```

These tiers do not use the per_hour feature, but will limit users to 1 or 5 requests per minute respectively.  There's also a daily limit at 500 or 4000 requests per day.

Then you'll need an instance of `rrl.RateLimiter`, which will be instantiated with these tiers:

```
limiter = RateLimiter(tiers=[bronze, silver])
```

Then to apply limiting, you'll call the `check_limit` function, which takes three parameters:

* `key` - A unique-per user key, often the user's API key or username. (Note: `rrl` does not know if a key is valid or not, that validation should be in your application and usually occur before the call to `check_limit`.)
* `tier_name` - The name of one of the tiers as specified when instantiating the `RateLimiter` class.  (Note: `rrl` does not have a concept of which users are in which tier, that logic should be handled by your key validation code.)

Example call:

```
limiter.check_limit(key="1234", tier_name="bronze")
```

This call will return without any error if the call is deemed allowed.

If any of the rate limits are exceeded it will instead raise a `RateLimitExceeded` exception describing which limit was exceeded.  
If multiple limits were exceeded it will return the shortest limit violated.

## Advanced Usage

### Obtaining Usage Information

Your `RateLimiter` instance also has a method named `get_usage_since`, which takes four parameters:

* `key` - Which key you're requesting usage information for.
* `start` - Date that you'd like usage since, as a `datetime.date` object.
* `end` - Optional end date if you'd only like usage within a certain window, otherwise the current day is used.

This will return a list of `DailyUsage` dataclasses with the following attributes:

* `date` - `datetime.date`
* `calls` - Number of calls made on that date.

This method can be useful for showing users an overview of their data.

### Advanced Configuration

When instantiating a `RateLimiter` there are several keyword-only parameters you may set:

**prefix**

Passing a prefix like: 
```
limiter = RateLimiter(tiers, prefix="v1")
```
will scope all calls to limiter to a given prefix, this can be useful if you want multiple limiters but want to ensure that they do not interfere with one another.

**use_redis_time**

`True` by default, but if you set to `False` the application's system time will be used instead. 

The tradeoff here is one fewer call to Redis per call to `check_limit`, but if your machines experience any clock drift unexpected results may occur.

**track_daily_usage**

`True` by default, but if set to `False`, `rrl` will not store the necessary information to make `get_usage_since` work.  This results in a slight overhead reduction, but the usage information will not be stored in Redis and will be impossible to retrieve.