1010import platform
1111import re
1212from 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
1616from pydantic import TypeAdapter , ValidationError
17- from ratelimit import limits , sleep_and_retry
17+ from pyrate_limiter import Duration , Limiter , Rate , SQLiteBucket
1818from requests import get
1919from requests .exceptions import (
2020 ConnectionError , # noqa: A004
3939from simyan .schemas .volume import BasicVolume , Volume
4040from simyan .sqlite_cache import SQLiteCache
4141
42- MINUTE = 60
42+ # Constants
43+ SECOND_RATE : Final [int ] = 1
44+ HOUR_RATE : Final [int ] = 200
4345T = 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+
4661class 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