ebird-api 4.0.0__tar.gz → 4.1.1__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-4.0.0 → ebird_api-4.1.1}/CHANGELOG.md +8 -1
- {ebird_api-4.0.0/src/ebird_api.egg-info → ebird_api-4.1.1}/PKG-INFO +1 -1
- {ebird_api-4.0.0 → ebird_api-4.1.1}/pyproject.toml +2 -2
- {ebird_api-4.0.0 → ebird_api-4.1.1}/src/ebird/api/requests/__init__.py +1 -1
- {ebird_api-4.0.0 → ebird_api-4.1.1}/src/ebird/api/requests/hotspots.py +9 -4
- {ebird_api-4.0.0 → ebird_api-4.1.1}/src/ebird/api/requests/utils.py +36 -6
- {ebird_api-4.0.0 → ebird_api-4.1.1/src/ebird_api.egg-info}/PKG-INFO +1 -1
- {ebird_api-4.0.0 → ebird_api-4.1.1}/LICENSE.txt +0 -0
- {ebird_api-4.0.0 → ebird_api-4.1.1}/README.md +0 -0
- {ebird_api-4.0.0 → ebird_api-4.1.1}/setup.cfg +0 -0
- {ebird_api-4.0.0 → ebird_api-4.1.1}/setup.py +0 -0
- {ebird_api-4.0.0 → ebird_api-4.1.1}/src/ebird/api/requests/checklists.py +0 -0
- {ebird_api-4.0.0 → ebird_api-4.1.1}/src/ebird/api/requests/client.py +0 -0
- {ebird_api-4.0.0 → ebird_api-4.1.1}/src/ebird/api/requests/constants.py +0 -0
- {ebird_api-4.0.0 → ebird_api-4.1.1}/src/ebird/api/requests/observations.py +0 -0
- {ebird_api-4.0.0 → ebird_api-4.1.1}/src/ebird/api/requests/regions.py +0 -0
- {ebird_api-4.0.0 → ebird_api-4.1.1}/src/ebird/api/requests/species.py +0 -0
- {ebird_api-4.0.0 → ebird_api-4.1.1}/src/ebird/api/requests/statistics.py +0 -0
- {ebird_api-4.0.0 → ebird_api-4.1.1}/src/ebird/api/requests/taxonomy.py +0 -0
- {ebird_api-4.0.0 → ebird_api-4.1.1}/src/ebird/api/requests/validation.py +0 -0
- {ebird_api-4.0.0 → ebird_api-4.1.1}/src/ebird_api.egg-info/SOURCES.txt +0 -0
- {ebird_api-4.0.0 → ebird_api-4.1.1}/src/ebird_api.egg-info/dependency_links.txt +0 -0
- {ebird_api-4.0.0 → ebird_api-4.1.1}/src/ebird_api.egg-info/top_level.txt +0 -0
|
@@ -6,7 +6,14 @@ the git log for details.
|
|
|
6
6
|
|
|
7
7
|
The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
8
8
|
This project adheres to [PEP440](https://www.python.org/dev/peps/pep-0440/)
|
|
9
|
-
and by implication,
|
|
9
|
+
and by implication,
|
|
10
|
+
|
|
11
|
+
## [4.1.1] - 2026-10-10
|
|
12
|
+
- Changed get_location() to handle an API change where an empty response
|
|
13
|
+
is now returned for private locations instead of HHTP 410 Gone.
|
|
14
|
+
|
|
15
|
+
## [4.1.0] - 2026-06-14
|
|
16
|
+
- Retry requests, with exponentinal back-off, if an error occurs.
|
|
10
17
|
|
|
11
18
|
## [4.0.0] - 2025-07-09
|
|
12
19
|
- Moved the package to ebird.api.requests to make space for other projects.
|
|
@@ -29,7 +29,7 @@ license = {text = "MIT License"}
|
|
|
29
29
|
name = "ebird-api"
|
|
30
30
|
readme = "README.md"
|
|
31
31
|
requires-python = ">= 3.10"
|
|
32
|
-
version = "4.
|
|
32
|
+
version = "4.1.1"
|
|
33
33
|
|
|
34
34
|
[project.urls]
|
|
35
35
|
Repository = "https://github.com/ProjectBabbler/ebird-api.git"
|
|
@@ -37,7 +37,7 @@ Issues = "https://github.com/ProjectBabbler/ebird-api/issues"
|
|
|
37
37
|
Changelog = "https://github.com/ProjectBabbler/ebird-api/blob/master/CHANGELOG.md"
|
|
38
38
|
|
|
39
39
|
[tool.bumpversion]
|
|
40
|
-
current_version = "4.
|
|
40
|
+
current_version = "4.1.1"
|
|
41
41
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
42
42
|
serialize = ["{major}.{minor}.{patch}"]
|
|
43
43
|
ignore_missing_version = false
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Functions for fetching information about hotspots."""
|
|
2
2
|
|
|
3
|
+
from json import JSONDecodeError
|
|
3
4
|
from urllib.error import HTTPError
|
|
4
5
|
|
|
5
6
|
from ebird.api.requests.utils import call
|
|
@@ -124,8 +125,8 @@ def get_hotspot(token, loc_id):
|
|
|
124
125
|
"""Get the geographical details of a hotspot.
|
|
125
126
|
|
|
126
127
|
The call only work for hotspots. If you use the identifier for a private
|
|
127
|
-
location the eBird API will return
|
|
128
|
-
raised.
|
|
128
|
+
location the eBird API will return an empty response (as of Oct 2026) and
|
|
129
|
+
a JSONDecodeError will be raised.
|
|
129
130
|
|
|
130
131
|
This maps to the end point in the eBird API 2.0,
|
|
131
132
|
https://documenter.getpostman.com/view/664302/S1ENwy59?version=latest#e25218db-566b-4d8b-81ca-e79a8f68c599
|
|
@@ -143,6 +144,8 @@ def get_hotspot(token, loc_id):
|
|
|
143
144
|
|
|
144
145
|
:raises HTTPError if the eBird API returns an error.
|
|
145
146
|
|
|
147
|
+
:raises JSONDecodeError if the JSON data returned by the API is invalid.
|
|
148
|
+
|
|
146
149
|
"""
|
|
147
150
|
url = HOTSPOT_INFO_URL % clean_location(loc_id)
|
|
148
151
|
|
|
@@ -185,11 +188,13 @@ def get_location(token, loc_id):
|
|
|
185
188
|
|
|
186
189
|
# get_region() does not return "isHotspot" in the results, so we
|
|
187
190
|
# try get_hotspot() first and if the call fails with HTTP 410 Gone,
|
|
188
|
-
#
|
|
189
|
-
# massage the result.
|
|
191
|
+
# or as of Oct 2026, HTTP 200 and an empty response, indicating
|
|
192
|
+
# it's a private location, we call get_region and massage the result.
|
|
190
193
|
|
|
191
194
|
try:
|
|
192
195
|
return get_hotspot(token, loc_id)
|
|
196
|
+
except JSONDecodeError:
|
|
197
|
+
pass
|
|
193
198
|
except HTTPError as err:
|
|
194
199
|
if err.code != 410:
|
|
195
200
|
raise
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""Various functions used in the API."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import time
|
|
5
|
+
from urllib.error import HTTPError
|
|
4
6
|
from urllib.parse import urlencode
|
|
5
7
|
from urllib.request import Request, urlopen
|
|
6
8
|
|
|
@@ -74,7 +76,7 @@ def filter_parameters(params):
|
|
|
74
76
|
return filtered
|
|
75
77
|
|
|
76
78
|
|
|
77
|
-
def get_response(url, params=None, headers=None):
|
|
79
|
+
def get_response(url, params=None, headers=None, max_retries=3, backoff_factor=1.0):
|
|
78
80
|
"""Get the content from the eBird API.
|
|
79
81
|
|
|
80
82
|
:param url: the URL for the API call.
|
|
@@ -84,7 +86,14 @@ def get_response(url, params=None, headers=None):
|
|
|
84
86
|
:type params: dict
|
|
85
87
|
|
|
86
88
|
:param headers: the headers to add to the request.
|
|
87
|
-
:type
|
|
89
|
+
:type headers: dict
|
|
90
|
+
|
|
91
|
+
:param max_retries: number of times to retry after a 429 response.
|
|
92
|
+
:type max_retries: int
|
|
93
|
+
|
|
94
|
+
:param backoff_factor: seconds to wait before the first retry; doubles
|
|
95
|
+
on each subsequent attempt.
|
|
96
|
+
:type backoff_factor: float
|
|
88
97
|
|
|
89
98
|
:return: the content returned by the API
|
|
90
99
|
:rtype: str
|
|
@@ -104,7 +113,15 @@ def get_response(url, params=None, headers=None):
|
|
|
104
113
|
for name, value in headers.items():
|
|
105
114
|
request.add_header(name, value)
|
|
106
115
|
|
|
107
|
-
|
|
116
|
+
for attempt in range(max_retries + 1):
|
|
117
|
+
try:
|
|
118
|
+
return urlopen(request).read()
|
|
119
|
+
except HTTPError as exc:
|
|
120
|
+
if attempt == max_retries:
|
|
121
|
+
raise
|
|
122
|
+
retry_after = exc.headers.get("Retry-After") if exc.code == 429 else None
|
|
123
|
+
delay = float(retry_after) if retry_after else backoff_factor * (2**attempt)
|
|
124
|
+
time.sleep(delay)
|
|
108
125
|
|
|
109
126
|
|
|
110
127
|
def get_json(content):
|
|
@@ -138,7 +155,7 @@ def save_json(filename, data, indent=None):
|
|
|
138
155
|
json.dump(data, outfile, indent=indent)
|
|
139
156
|
|
|
140
157
|
|
|
141
|
-
def call(url, params, headers):
|
|
158
|
+
def call(url, params, headers, max_retries=3, backoff_factor=1.0):
|
|
142
159
|
"""Call the eBird API.
|
|
143
160
|
|
|
144
161
|
:param url: the URL for the API call.
|
|
@@ -148,7 +165,14 @@ def call(url, params, headers):
|
|
|
148
165
|
:type params: dict
|
|
149
166
|
|
|
150
167
|
:param headers: the headers to add to the request.
|
|
151
|
-
:type
|
|
168
|
+
:type headers: dict
|
|
169
|
+
|
|
170
|
+
:param max_retries: number of times to retry after a 429 response.
|
|
171
|
+
:type max_retries: int
|
|
172
|
+
|
|
173
|
+
:param backoff_factor: seconds to wait before the first retry; doubles
|
|
174
|
+
on each subsequent attempt.
|
|
175
|
+
:type backoff_factor: float
|
|
152
176
|
|
|
153
177
|
:return: the content returned by the API
|
|
154
178
|
:rtype: dict | list
|
|
@@ -158,7 +182,13 @@ def call(url, params, headers):
|
|
|
158
182
|
|
|
159
183
|
:raises HTTPError if the eBird API returns an error.
|
|
160
184
|
|
|
185
|
+
:raises JSONDecodeError if the JSON data returned by the API is invalid.
|
|
186
|
+
|
|
161
187
|
"""
|
|
162
188
|
filtered = filter_parameters(params)
|
|
163
189
|
mapped = map_parameters(filtered)
|
|
164
|
-
return get_json(
|
|
190
|
+
return get_json(
|
|
191
|
+
get_response(
|
|
192
|
+
url, mapped, headers, max_retries=max_retries, backoff_factor=backoff_factor
|
|
193
|
+
)
|
|
194
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|