1
1
from json .decoder import JSONDecodeError
2
2
import string
3
3
import urllib
4
+ import time
4
5
5
6
from cached_property import cached_property
6
7
import inflection
7
8
import requests
8
-
9
+ from requests_ratelimiter import LimiterSession
9
10
10
11
API_PATH = 'api/docs.json/'
11
12
@@ -19,12 +20,13 @@ def __init__(
19
20
api_path = API_PATH
20
21
):
21
22
self .api_base_url = base_url
22
- self .token_id = None
23
- self .token_secret = None
23
+ self .token_id = token_id
24
+ self .token_secret = token_secret
24
25
self ._api_path = api_path
26
+ self .rate_limit = 180 # Bookstack default
25
27
26
28
self .available_api_methods = set ()
27
- self ._session = BaseURLSession (self .api_base_url )
29
+ self ._session = BaseURLSession (self .api_base_url , per_minute = self . rate_limit )
28
30
self ._session .auth = Auth (token_id , token_secret )
29
31
30
32
def generate_api_methods (self ):
@@ -41,7 +43,16 @@ def generate_api_methods(self):
41
43
self .available_api_methods .add (method_name )
42
44
43
45
def _get_api (self ):
44
- return self ._session .request ('GET' , self ._api_path ).json ()
46
+ r = self ._session .request ('GET' , self ._api_path )
47
+
48
+ rate_limit = int (r .headers .get ('X-RateLimit-Limit' , self .rate_limit ))
49
+ if rate_limit != self .rate_limit :
50
+ # Create a new rate limited session with the correct rate limit
51
+ self ._session = BaseURLSession (self .api_base_url , per_minute = self .rate_limit )
52
+ self ._session .auth = Auth (self .token_id , self .token_secret )
53
+ self .rate_limit = rate_limit
54
+
55
+ return r .json ()
45
56
46
57
def _create_api_method (self , method_info ):
47
58
def request_method (* args , ** kwargs ):
@@ -85,15 +96,22 @@ def _get_response_content(response):
85
96
return content
86
97
87
98
88
- class BaseURLSession (requests . Session ):
99
+ class BaseURLSession (LimiterSession ):
89
100
def __init__ (self , base_url , * args , ** kwargs ):
90
101
super ().__init__ (* args , ** kwargs )
91
102
self .base_url = base_url
92
103
93
104
def request (self , method , url_path , * args , ** kwargs ):
94
105
url = urllib .parse .urljoin (self .base_url , url_path )
95
-
96
- return super ().request (method , url , * args , ** kwargs )
106
+ response = super ().request (method , url , * args , ** kwargs )
107
+
108
+ # Handle 'Too Many Attempts' response
109
+ if response .status_code == 429 :
110
+ retry_after = int (response .headers .get ('Retry-After' , 60 ))
111
+ time .sleep (retry_after )
112
+ response = super ().request (method , url , * args , ** kwargs )
113
+
114
+ return response
97
115
98
116
99
117
class Auth (requests .auth .AuthBase ):
0 commit comments