-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9af6cc5
commit 3c86a1e
Showing
3 changed files
with
155 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)), | ||
], | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |