ebird-api-requests 4.2.0__tar.gz → 4.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ebird_api_requests-4.2.0/src/ebird_api_requests.egg-info → ebird_api_requests-4.3.0}/PKG-INFO +25 -1
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/README.md +24 -0
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/pyproject.toml +2 -2
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/src/ebird/api/requests/__init__.py +6 -1
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/src/ebird/api/requests/utils.py +123 -4
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0/src/ebird_api_requests.egg-info}/PKG-INFO +25 -1
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/LICENSE.txt +0 -0
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/setup.cfg +0 -0
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/setup.py +0 -0
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/src/ebird/api/requests/checklists.py +0 -0
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/src/ebird/api/requests/client.py +0 -0
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/src/ebird/api/requests/constants.py +0 -0
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/src/ebird/api/requests/hotspots.py +0 -0
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/src/ebird/api/requests/observations.py +0 -0
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/src/ebird/api/requests/regions.py +0 -0
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/src/ebird/api/requests/species.py +0 -0
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/src/ebird/api/requests/statistics.py +0 -0
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/src/ebird/api/requests/taxonomy.py +0 -0
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/src/ebird/api/requests/validation.py +0 -0
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/src/ebird_api_requests.egg-info/SOURCES.txt +0 -0
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/src/ebird_api_requests.egg-info/dependency_links.txt +0 -0
- {ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/src/ebird_api_requests.egg-info/top_level.txt +0 -0
{ebird_api_requests-4.2.0/src/ebird_api_requests.egg-info → ebird_api_requests-4.3.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ebird-api-requests
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3.0
|
|
4
4
|
Summary: A python library for accessing the eBird API, version 2.0
|
|
5
5
|
Author-email: Stuart MacKay <smackay@flagstonesoftware.com>
|
|
6
6
|
License: MIT License
|
|
@@ -346,6 +346,30 @@ client.get_observations('MX-OAX')
|
|
|
346
346
|
|
|
347
347
|
The client supports all the API functions.
|
|
348
348
|
|
|
349
|
+
### Rate Limiting
|
|
350
|
+
|
|
351
|
+
The eBird API limits how quickly requests can be made. If you exceed the
|
|
352
|
+
limit the API responds with HTTP 429 Too Many Requests. If you are making
|
|
353
|
+
lots of calls, for example downloading all the checklists for a region,
|
|
354
|
+
then set a minimum interval, in seconds, between requests:
|
|
355
|
+
|
|
356
|
+
```python
|
|
357
|
+
from ebird.api.requests import set_rate_limit
|
|
358
|
+
|
|
359
|
+
set_rate_limit(1.0)
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
The limit applies to all the requests made by the process. It is disabled
|
|
363
|
+
by default. If the API still responds with HTTP 429, the interval is doubled,
|
|
364
|
+
up to a maximum of 60 seconds, so the requests slow down to a rate the API
|
|
365
|
+
accepts. Use get_rate_limit() to see the current interval.
|
|
366
|
+
|
|
367
|
+
When an HTTP 429 response is returned, the request is retried after waiting
|
|
368
|
+
for the time given in the Retry-After header. If the request still fails
|
|
369
|
+
after all the retries are used, a RateLimitError is raised. It is a subclass
|
|
370
|
+
of HTTPError, and the retry_after attribute contains the number of seconds
|
|
371
|
+
the API asked you to wait, or None if no Retry-After header was returned.
|
|
372
|
+
|
|
349
373
|
## Formats
|
|
350
374
|
|
|
351
375
|
Most of the eBird API calls return JSON. Some of the calls such as getting
|
|
@@ -316,6 +316,30 @@ client.get_observations('MX-OAX')
|
|
|
316
316
|
|
|
317
317
|
The client supports all the API functions.
|
|
318
318
|
|
|
319
|
+
### Rate Limiting
|
|
320
|
+
|
|
321
|
+
The eBird API limits how quickly requests can be made. If you exceed the
|
|
322
|
+
limit the API responds with HTTP 429 Too Many Requests. If you are making
|
|
323
|
+
lots of calls, for example downloading all the checklists for a region,
|
|
324
|
+
then set a minimum interval, in seconds, between requests:
|
|
325
|
+
|
|
326
|
+
```python
|
|
327
|
+
from ebird.api.requests import set_rate_limit
|
|
328
|
+
|
|
329
|
+
set_rate_limit(1.0)
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
The limit applies to all the requests made by the process. It is disabled
|
|
333
|
+
by default. If the API still responds with HTTP 429, the interval is doubled,
|
|
334
|
+
up to a maximum of 60 seconds, so the requests slow down to a rate the API
|
|
335
|
+
accepts. Use get_rate_limit() to see the current interval.
|
|
336
|
+
|
|
337
|
+
When an HTTP 429 response is returned, the request is retried after waiting
|
|
338
|
+
for the time given in the Retry-After header. If the request still fails
|
|
339
|
+
after all the retries are used, a RateLimitError is raised. It is a subclass
|
|
340
|
+
of HTTPError, and the retry_after attribute contains the number of seconds
|
|
341
|
+
the API asked you to wait, or None if no Retry-After header was returned.
|
|
342
|
+
|
|
319
343
|
## Formats
|
|
320
344
|
|
|
321
345
|
Most of the eBird API calls return JSON. Some of the calls such as getting
|
|
@@ -29,7 +29,7 @@ license = {text = "MIT License"}
|
|
|
29
29
|
name = "ebird-api-requests"
|
|
30
30
|
readme = "README.md"
|
|
31
31
|
requires-python = ">= 3.10"
|
|
32
|
-
version = "4.
|
|
32
|
+
version = "4.3.0"
|
|
33
33
|
|
|
34
34
|
[project.urls]
|
|
35
35
|
Repository = "https://github.com/StuartMacKay/ebird-api-requests.git"
|
|
@@ -37,7 +37,7 @@ Issues = "https://github.com/StuartMacKay/ebird-api-requests/issues"
|
|
|
37
37
|
Changelog = "https://github.com/StuartMacKay/ebird-api-requests/blob/master/CHANGELOG.md"
|
|
38
38
|
|
|
39
39
|
[tool.bumpversion]
|
|
40
|
-
current_version = "4.
|
|
40
|
+
current_version = "4.3.0"
|
|
41
41
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
42
42
|
serialize = ["{major}.{minor}.{patch}"]
|
|
43
43
|
ignore_missing_version = false
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
"""A set of wrapper functions for accessing the eBird API."""
|
|
4
4
|
|
|
5
|
-
__version__ = "4.
|
|
5
|
+
__version__ = "4.3.0"
|
|
6
6
|
|
|
7
7
|
from ebird.api.requests.checklists import get_checklist, get_visits
|
|
8
8
|
from ebird.api.requests.client import Client
|
|
@@ -37,3 +37,8 @@ from ebird.api.requests.taxonomy import (
|
|
|
37
37
|
get_taxonomy_locales,
|
|
38
38
|
get_taxonomy_versions,
|
|
39
39
|
)
|
|
40
|
+
from ebird.api.requests.utils import (
|
|
41
|
+
RateLimitError,
|
|
42
|
+
get_rate_limit,
|
|
43
|
+
set_rate_limit,
|
|
44
|
+
)
|
|
@@ -1,13 +1,122 @@
|
|
|
1
1
|
"""Various functions used in the API."""
|
|
2
2
|
|
|
3
|
+
import datetime as dt
|
|
3
4
|
import json
|
|
5
|
+
import threading
|
|
4
6
|
import time
|
|
7
|
+
from email.utils import parsedate_to_datetime
|
|
5
8
|
from urllib.error import HTTPError
|
|
6
9
|
from urllib.parse import urlencode
|
|
7
10
|
from urllib.request import Request, urlopen
|
|
8
11
|
|
|
9
12
|
from ebird.api.requests import constants
|
|
10
13
|
|
|
14
|
+
# The maximum interval, in seconds, the rate limiter will slow down to
|
|
15
|
+
# after the eBird API responds with HTTP 429 Too Many Requests.
|
|
16
|
+
MAX_RATE_LIMIT_INTERVAL = 60.0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RateLimitError(HTTPError):
|
|
20
|
+
"""Raised when the eBird API still responds with HTTP 429 Too Many
|
|
21
|
+
Requests after all the retries have been used.
|
|
22
|
+
|
|
23
|
+
It is a subclass of HTTPError so existing error handling still works.
|
|
24
|
+
The retry_after attribute contains the number of seconds the API asked
|
|
25
|
+
the caller to wait, or None if no Retry-After header was returned.
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, exc, retry_after=None):
|
|
30
|
+
super().__init__(exc.url, exc.code, exc.msg, exc.headers, exc.fp)
|
|
31
|
+
self.retry_after = retry_after
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class RateLimiter:
|
|
35
|
+
"""Enforce a minimum interval between the start of successive requests.
|
|
36
|
+
|
|
37
|
+
An interval of zero (the default) disables the limiter.
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, interval=0.0):
|
|
42
|
+
self.interval = float(interval)
|
|
43
|
+
self.last_request = None
|
|
44
|
+
self.lock = threading.Lock()
|
|
45
|
+
|
|
46
|
+
def wait(self):
|
|
47
|
+
with self.lock:
|
|
48
|
+
if self.interval > 0 and self.last_request is not None:
|
|
49
|
+
delay = self.interval - (time.monotonic() - self.last_request)
|
|
50
|
+
if delay > 0:
|
|
51
|
+
time.sleep(delay)
|
|
52
|
+
self.last_request = time.monotonic()
|
|
53
|
+
|
|
54
|
+
def slow_down(self):
|
|
55
|
+
with self.lock:
|
|
56
|
+
if self.interval > 0:
|
|
57
|
+
self.interval = min(self.interval * 2, MAX_RATE_LIMIT_INTERVAL)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
_rate_limiter = RateLimiter()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def set_rate_limit(interval):
|
|
64
|
+
"""Set the minimum interval, in seconds, between requests to the eBird API.
|
|
65
|
+
|
|
66
|
+
The limit applies to all requests made by the process. Setting it to
|
|
67
|
+
zero, the default, disables rate limiting. If the API responds with
|
|
68
|
+
HTTP 429 Too Many Requests, the interval is doubled, up to a maximum
|
|
69
|
+
of MAX_RATE_LIMIT_INTERVAL, for the rest of the process, or until the
|
|
70
|
+
rate limit is set again.
|
|
71
|
+
|
|
72
|
+
:param interval: the number of seconds between requests.
|
|
73
|
+
:type interval: float
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
if interval < 0:
|
|
77
|
+
raise ValueError("The rate limit interval cannot be negative")
|
|
78
|
+
with _rate_limiter.lock:
|
|
79
|
+
_rate_limiter.interval = float(interval)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def get_rate_limit():
|
|
83
|
+
"""Get the current minimum interval, in seconds, between requests.
|
|
84
|
+
|
|
85
|
+
:return: the interval, which may have increased since it was set, if the
|
|
86
|
+
eBird API responded with HTTP 429 Too Many Requests.
|
|
87
|
+
:rtype: float
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
return _rate_limiter.interval
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_retry_after(exc):
|
|
94
|
+
"""Get the number of seconds to wait from a Retry-After header.
|
|
95
|
+
|
|
96
|
+
:param exc: the error raised for the HTTP 429 response.
|
|
97
|
+
:type exc: HTTPError
|
|
98
|
+
|
|
99
|
+
:return: the number of seconds to wait, or None if the header was not
|
|
100
|
+
present or could not be parsed.
|
|
101
|
+
:rtype: float | None
|
|
102
|
+
|
|
103
|
+
"""
|
|
104
|
+
value = exc.headers.get("Retry-After") if exc.headers else None
|
|
105
|
+
if not value:
|
|
106
|
+
return None
|
|
107
|
+
try:
|
|
108
|
+
return max(float(value), 0.0)
|
|
109
|
+
except ValueError:
|
|
110
|
+
pass
|
|
111
|
+
try:
|
|
112
|
+
when = parsedate_to_datetime(value)
|
|
113
|
+
except (TypeError, ValueError):
|
|
114
|
+
return None
|
|
115
|
+
if when.tzinfo is None:
|
|
116
|
+
when = when.replace(tzinfo=dt.timezone.utc)
|
|
117
|
+
return max((when - dt.datetime.now(dt.timezone.utc)).total_seconds(), 0.0)
|
|
118
|
+
|
|
119
|
+
|
|
11
120
|
_parameter_defaults = {
|
|
12
121
|
"back": constants.DEFAULT_BACK,
|
|
13
122
|
"cat": constants.DEFAULT_SPECIES_CATEGORY,
|
|
@@ -101,6 +210,9 @@ def get_response(url, params=None, headers=None, max_retries=3, backoff_factor=1
|
|
|
101
210
|
:raises URLError if there is an error with the connection to the
|
|
102
211
|
eBird site.
|
|
103
212
|
|
|
213
|
+
:raises RateLimitError if the eBird API still returns HTTP 429 Too Many
|
|
214
|
+
Requests after all the retries have been used.
|
|
215
|
+
|
|
104
216
|
:raises HTTPError if the eBird API returns an error.
|
|
105
217
|
|
|
106
218
|
"""
|
|
@@ -114,14 +226,21 @@ def get_response(url, params=None, headers=None, max_retries=3, backoff_factor=1
|
|
|
114
226
|
request.add_header(name, value)
|
|
115
227
|
|
|
116
228
|
for attempt in range(max_retries + 1):
|
|
229
|
+
_rate_limiter.wait()
|
|
117
230
|
try:
|
|
118
231
|
return urlopen(request).read()
|
|
119
232
|
except HTTPError as exc:
|
|
120
|
-
|
|
233
|
+
retry_after = None
|
|
234
|
+
if exc.code == 429:
|
|
235
|
+
retry_after = get_retry_after(exc)
|
|
236
|
+
_rate_limiter.slow_down()
|
|
237
|
+
if attempt == max_retries:
|
|
238
|
+
raise RateLimitError(exc, retry_after) from exc
|
|
239
|
+
elif attempt == max_retries:
|
|
121
240
|
raise
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
time.sleep(
|
|
241
|
+
if retry_after is None:
|
|
242
|
+
retry_after = backoff_factor * (2**attempt)
|
|
243
|
+
time.sleep(retry_after)
|
|
125
244
|
|
|
126
245
|
|
|
127
246
|
def get_json(content):
|
{ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0/src/ebird_api_requests.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ebird-api-requests
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3.0
|
|
4
4
|
Summary: A python library for accessing the eBird API, version 2.0
|
|
5
5
|
Author-email: Stuart MacKay <smackay@flagstonesoftware.com>
|
|
6
6
|
License: MIT License
|
|
@@ -346,6 +346,30 @@ client.get_observations('MX-OAX')
|
|
|
346
346
|
|
|
347
347
|
The client supports all the API functions.
|
|
348
348
|
|
|
349
|
+
### Rate Limiting
|
|
350
|
+
|
|
351
|
+
The eBird API limits how quickly requests can be made. If you exceed the
|
|
352
|
+
limit the API responds with HTTP 429 Too Many Requests. If you are making
|
|
353
|
+
lots of calls, for example downloading all the checklists for a region,
|
|
354
|
+
then set a minimum interval, in seconds, between requests:
|
|
355
|
+
|
|
356
|
+
```python
|
|
357
|
+
from ebird.api.requests import set_rate_limit
|
|
358
|
+
|
|
359
|
+
set_rate_limit(1.0)
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
The limit applies to all the requests made by the process. It is disabled
|
|
363
|
+
by default. If the API still responds with HTTP 429, the interval is doubled,
|
|
364
|
+
up to a maximum of 60 seconds, so the requests slow down to a rate the API
|
|
365
|
+
accepts. Use get_rate_limit() to see the current interval.
|
|
366
|
+
|
|
367
|
+
When an HTTP 429 response is returned, the request is retried after waiting
|
|
368
|
+
for the time given in the Retry-After header. If the request still fails
|
|
369
|
+
after all the retries are used, a RateLimitError is raised. It is a subclass
|
|
370
|
+
of HTTPError, and the retry_after attribute contains the number of seconds
|
|
371
|
+
the API asked you to wait, or None if no Retry-After header was returned.
|
|
372
|
+
|
|
349
373
|
## Formats
|
|
350
374
|
|
|
351
375
|
Most of the eBird API calls return JSON. Some of the calls such as getting
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/src/ebird/api/requests/observations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/src/ebird_api_requests.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{ebird_api_requests-4.2.0 → ebird_api_requests-4.3.0}/src/ebird_api_requests.egg-info/top_level.txt
RENAMED
|
File without changes
|