Skip to content

Commit a5840fe

Browse files
Pyrate rate limiting (#190)
Match Comicvine's documented ratelimiting, to do this I switched from `ratelimit` to `pyrate-limiter`. Closes #178
1 parent 8a3c42b commit a5840fe

File tree

2 files changed

+29
-7
lines changed

2 files changed

+29
-7
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ classifiers = [
4444
dependencies = [
4545
"eval-type-backport >= 0.2.0; python_version < '3.10'",
4646
"pydantic >= 2.11.0",
47-
"ratelimit >= 2.2.1",
47+
"pyrate-limiter >= 3.7.1",
4848
"requests >= 2.32.3"
4949
]
5050
description = "A Python wrapper for the Comicvine API."

simyan/comicvine.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010
import platform
1111
import re
1212
from enum import Enum
13-
from typing import Any, Optional, TypeVar, Union
14-
from urllib.parse import urlencode
13+
from typing import Any, ClassVar, Final, Optional, TypeVar, Union
14+
from urllib.parse import urlencode, urlparse
1515

1616
from pydantic import TypeAdapter, ValidationError
17-
from ratelimit import limits, sleep_and_retry
17+
from pyrate_limiter import Duration, Limiter, Rate, SQLiteBucket
1818
from requests import get
1919
from requests.exceptions import (
2020
ConnectionError, # noqa: A004
@@ -39,10 +39,25 @@
3939
from simyan.schemas.volume import BasicVolume, Volume
4040
from simyan.sqlite_cache import SQLiteCache
4141

42-
MINUTE = 60
42+
# Constants
43+
SECOND_RATE: Final[int] = 1
44+
HOUR_RATE: Final[int] = 200
4345
T = TypeVar("T")
4446

4547

48+
def rate_mapping(*args: Any, **kwargs: Any) -> tuple[str, int]:
49+
if kwargs and "url" in kwargs:
50+
url = kwargs["url"]
51+
else:
52+
return "comicvine", 1
53+
parts = urlparse(url).path.strip("/").split("/")
54+
if not parts or len(parts) < 2:
55+
return "comicvine", 1
56+
if len(parts) == 3:
57+
return f"get_{parts[1]}", 1
58+
return parts[1], 1
59+
60+
4661
class ComicvineResource(Enum):
4762
"""Enum class for Comicvine Resources."""
4863

@@ -98,6 +113,14 @@ class Comicvine:
98113

99114
API_URL = "https://comicvine.gamespot.com/api"
100115

116+
_second_rate = Rate(SECOND_RATE, Duration.SECOND)
117+
_hour_rate = Rate(HOUR_RATE, Duration.HOUR)
118+
_rates: ClassVar[list[Rate]] = [_second_rate, _hour_rate]
119+
_bucket = SQLiteBucket.init_from_file(_rates) # Save between sessions
120+
# Can a `BucketFullException` be raised when used as a decorator?
121+
_limiter = Limiter(_bucket, raise_when_fail=False, max_delay=Duration.DAY)
122+
decorator = _limiter.as_decorator()
123+
101124
def __init__(self, api_key: str, timeout: int = 30, cache: Optional[SQLiteCache] = None):
102125
self.headers = {
103126
"Accept": "application/json",
@@ -107,8 +130,7 @@ def __init__(self, api_key: str, timeout: int = 30, cache: Optional[SQLiteCache]
107130
self.timeout = timeout
108131
self.cache = cache
109132

110-
@sleep_and_retry
111-
@limits(calls=20, period=MINUTE)
133+
@decorator(rate_mapping)
112134
def _perform_get_request(
113135
self, url: str, params: Optional[dict[str, str]] = None
114136
) -> dict[str, Any]:

0 commit comments

Comments
 (0)