Skip to content

Commit

Permalink
add ratelimiter model
Browse files Browse the repository at this point in the history
  • Loading branch information
danangmassandy committed Oct 30, 2024
1 parent 9af6cc5 commit 3c86a1e
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 0 deletions.
26 changes: 26 additions & 0 deletions django_project/gap_api/migrations/0004_apiratelimiter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Django 4.2.7 on 2024-10-30 11:51

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('gap_api', '0003_location_location_user_locationname'),
]

operations = [
migrations.CreateModel(
name='APIRateLimiter',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('minute_limit', models.IntegerField()),
('hour_limit', models.IntegerField()),
('day_limit', models.IntegerField()),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
),
]
1 change: 1 addition & 0 deletions django_project/gap_api/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from gap_api.models.api_request_log import * # noqa
from gap_api.models.api_config import * # noqa
from gap_api.models.location import * # noqa
from gap_api.models.rate_limiter import * # noqa
128 changes: 128 additions & 0 deletions django_project/gap_api/models/rate_limiter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# coding=utf-8
"""
Tomorrow Now GAP API.
.. note:: Models for Rate Limiter
"""

from django.db import models
from django.conf import settings
from django.core.cache import cache
from django.db.models.signals import post_save
from django.dispatch import receiver


class APIRateLimiter(models.Model):
"""Models that stores GAP API rate limiter."""

GLOBAL_CACHE_KEY = 'gap-api-ratelimit-global'

user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True
)
minute_limit = models.IntegerField()
hour_limit = models.IntegerField()
day_limit = models.IntegerField()

@property
def config_name(self):
"""Return config name."""
if self.user:
return self.user.username
return 'global'

@property
def cache_key(self):
"""Return cache key for this config."""
if self.user:
return f'gap-api-ratelimit-{self.user.id}'
return APIRateLimiter.GLOBAL_CACHE_KEY

@property
def cache_value(self):
"""Get dict cache value."""
return {
'minute': self.minute_limit,
'hour': self.hour_limit,
'day': self.day_limit
}

def set_cache(self):
"""Set rate limit to cache."""
cache.set(
self.cache_key,
f'{self.minute_limit}:{self.hour_limit}:{self.day_limit}',
timeout=None
)

def clear_cache(self):
"""Clear cache for this config."""
cache.delete(self.cache_key)

@staticmethod
def parse_cache_value(cache_str: str):
"""Parse cache value."""
values = cache_str.split(':')
return {
'minute': values[0],
'hour': values[1],
'day': values[2]
}

@staticmethod
def get_global_config():
"""Get global config cache."""
config_cache = cache.get(APIRateLimiter.GLOBAL_CACHE_KEY, None)
if config_cache:
return APIRateLimiter.parse_cache_value(config_cache)

limit = APIRateLimiter.objects.filter(
user=None
).first()
if limit:
# set to cache
limit.set_cache()
return limit.cache_value

return None

@staticmethod
def get_config(user):
"""Return config for given user."""
cache_key = f'gap-api-ratelimit-{user.id}'
config_cache = cache.get(cache_key, None)

if config_cache == 'global':
# use global config
pass
elif config_cache is None:
# find from table
limit = APIRateLimiter.objects.filter(
user=user
).first()

if limit:
# set to cache if found
limit.set_cache()
return limit.cache_value
else:
# set to use global config
cache.set(cache_key, 'global')
else:
# parse config for the user
return APIRateLimiter.parse_cache_value(config_cache)

return APIRateLimiter.get_global_config()


@receiver(post_save, sender=APIRateLimiter)
def ratelimiter_post_create(
sender, instance: APIRateLimiter, created, *args, **kwargs):
"""Clear cache after saving the object."""
if created:
return

cache.delete(instance.cache_key)

0 comments on commit 3c86a1e

Please sign in to comment.