ebird-api 4.1.0__tar.gz → 4.2.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-4.1.0 → ebird_api-4.2.0}/CHANGELOG.md +11 -18
- {ebird_api-4.1.0/src/ebird_api.egg-info → ebird_api-4.2.0}/PKG-INFO +1 -1
- {ebird_api-4.1.0 → ebird_api-4.2.0}/pyproject.toml +2 -2
- {ebird_api-4.1.0 → ebird_api-4.2.0}/src/ebird/api/requests/__init__.py +1 -1
- {ebird_api-4.1.0 → ebird_api-4.2.0}/src/ebird/api/requests/client.py +5 -1
- {ebird_api-4.1.0 → ebird_api-4.2.0}/src/ebird/api/requests/constants.py +3 -0
- {ebird_api-4.1.0 → ebird_api-4.2.0}/src/ebird/api/requests/hotspots.py +9 -4
- {ebird_api-4.1.0 → ebird_api-4.2.0}/src/ebird/api/requests/observations.py +6 -1
- {ebird_api-4.1.0 → ebird_api-4.2.0}/src/ebird/api/requests/statistics.py +2 -2
- {ebird_api-4.1.0 → ebird_api-4.2.0}/src/ebird/api/requests/utils.py +4 -2
- {ebird_api-4.1.0 → ebird_api-4.2.0}/src/ebird/api/requests/validation.py +27 -5
- {ebird_api-4.1.0 → ebird_api-4.2.0/src/ebird_api.egg-info}/PKG-INFO +1 -1
- {ebird_api-4.1.0 → ebird_api-4.2.0}/LICENSE.txt +0 -0
- {ebird_api-4.1.0 → ebird_api-4.2.0}/README.md +0 -0
- {ebird_api-4.1.0 → ebird_api-4.2.0}/setup.cfg +0 -0
- {ebird_api-4.1.0 → ebird_api-4.2.0}/setup.py +0 -0
- {ebird_api-4.1.0 → ebird_api-4.2.0}/src/ebird/api/requests/checklists.py +0 -0
- {ebird_api-4.1.0 → ebird_api-4.2.0}/src/ebird/api/requests/regions.py +0 -0
- {ebird_api-4.1.0 → ebird_api-4.2.0}/src/ebird/api/requests/species.py +0 -0
- {ebird_api-4.1.0 → ebird_api-4.2.0}/src/ebird/api/requests/taxonomy.py +0 -0
- {ebird_api-4.1.0 → ebird_api-4.2.0}/src/ebird_api.egg-info/SOURCES.txt +0 -0
- {ebird_api-4.1.0 → ebird_api-4.2.0}/src/ebird_api.egg-info/dependency_links.txt +0 -0
- {ebird_api-4.1.0 → ebird_api-4.2.0}/src/ebird_api.egg-info/top_level.txt +0 -0
|
@@ -8,6 +8,17 @@ 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
9
|
and by implication, [Semantic Versioning](http://semver.org/).
|
|
10
10
|
|
|
11
|
+
## [4.2.0] - 2026-10-12
|
|
12
|
+
- Changed get_historic_observations() so observations can be returned by
|
|
13
|
+
date created or date last modified.
|
|
14
|
+
- Renamed clean_rank() to clean_observer_rank(). clean_rank() is deprecated
|
|
15
|
+
and will be removed in 5.0.0.
|
|
16
|
+
- Allow single-character subnational1 codes, e.g. IE-L.
|
|
17
|
+
|
|
18
|
+
## [4.1.1] - 2026-10-10
|
|
19
|
+
- Changed get_location() to handle an API change where an empty response
|
|
20
|
+
is now returned for private locations instead of HHTP 410 Gone.
|
|
21
|
+
|
|
11
22
|
## [4.1.0] - 2026-06-14
|
|
12
23
|
- Retry requests, with exponentinal back-off, if an error occurs.
|
|
13
24
|
|
|
@@ -146,21 +157,3 @@ avoid any issues with representation.
|
|
|
146
157
|
## [0.1.0] - 2017-02-13
|
|
147
158
|
### Added
|
|
148
159
|
- Core functions for accessing end-points for fetching observations.
|
|
149
|
-
|
|
150
|
-
[Unreleased]: https://github.com/ProjectBabbler/ebird-api/compare/3.0.6...HEAD
|
|
151
|
-
[3.0.6]: https://github.com/ProjectBabbler/ebird-api/compare/3.0.5...3.0.6
|
|
152
|
-
[3.0.5]: https://github.com/ProjectBabbler/ebird-api/compare/3.0.5...3.0.5
|
|
153
|
-
[3.0.5]: https://github.com/ProjectBabbler/ebird-api/compare/3.0.4...3.0.5
|
|
154
|
-
[3.0.4]: https://github.com/ProjectBabbler/ebird-api/compare/3.0.3...3.0.4
|
|
155
|
-
[3.0.3]: https://github.com/ProjectBabbler/ebird-api/compare/3.0.3...3.0.3
|
|
156
|
-
[3.0.3]: https://github.com/ProjectBabbler/ebird-api/compare/3.0.2...3.0.3
|
|
157
|
-
[3.0.2]: https://github.com/ProjectBabbler/ebird-api/compare/3.0.1...3.0.2
|
|
158
|
-
[3.0.1]: https://github.com/ProjectBabbler/ebird-api/compare/3.0.0...3.0.1
|
|
159
|
-
[2.1.1]: https://github.com/ProjectBabbler/ebird-api/compare/2.0.1...2.1.1
|
|
160
|
-
[2.1.0]: https://github.com/ProjectBabbler/ebird-api/compare/2.0.0...2.1.0
|
|
161
|
-
[2.0.0]: https://github.com/ProjectBabbler/ebird-api/compare/1.0.2...2.0.0
|
|
162
|
-
[1.0.2]: https://github.com/ProjectBabbler/ebird-api/compare/1.0.1...1.0.2
|
|
163
|
-
[1.0.1]: https://github.com/ProjectBabbler/ebird-api/compare/1.0.0...1.0.1
|
|
164
|
-
[1.0.0]: https://github.com/ProjectBabbler/ebird-api/compare/0.2.0...1.0.0
|
|
165
|
-
[0.2.0]: https://github.com/ProjectBabbler/ebird-api/compare/0.1.1...0.2.0
|
|
166
|
-
[0.1.1]: https://github.com/ProjectBabbler/ebird-api/compare/0.1.0...0.1.1
|
|
@@ -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.2.0"
|
|
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.2.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
|
|
@@ -109,7 +109,7 @@ class Client:
|
|
|
109
109
|
category=self.category,
|
|
110
110
|
)
|
|
111
111
|
|
|
112
|
-
def get_historic_observations(self, area, date):
|
|
112
|
+
def get_historic_observations(self, area, date, rank="mrec"):
|
|
113
113
|
"""Get recent observations for a region.
|
|
114
114
|
|
|
115
115
|
:param area: a country, subnational1, subnational2 or location code
|
|
@@ -117,6 +117,9 @@ class Client:
|
|
|
117
117
|
|
|
118
118
|
:param date: the date, since Jan 1st 1800.
|
|
119
119
|
|
|
120
|
+
:param rank: for each species, return the latest observation on the
|
|
121
|
+
date, 'mrec' (the default), or the first observation, 'create'.
|
|
122
|
+
|
|
120
123
|
:return: the list of observations in simple format.
|
|
121
124
|
|
|
122
125
|
"""
|
|
@@ -130,6 +133,7 @@ class Client:
|
|
|
130
133
|
hotspot=self.hotspot,
|
|
131
134
|
detail=self.detail,
|
|
132
135
|
category=self.category,
|
|
136
|
+
rank=rank,
|
|
133
137
|
)
|
|
134
138
|
|
|
135
139
|
def get_nearby_observations(self, lat, lng, dist=25):
|
|
@@ -7,6 +7,7 @@ DEFAULT_HOTSPOTS_ONLY = "false"
|
|
|
7
7
|
DEFAULT_PROVISIONAL = "false"
|
|
8
8
|
DEFAULT_LOCALE = "en"
|
|
9
9
|
DEFAULT_OBSERVATION_ORDER = "date"
|
|
10
|
+
DEFAULT_OBSERVATION_RANK = "mrec"
|
|
10
11
|
DEFAULT_SPECIES_CATEGORY = None
|
|
11
12
|
DEFAULT_TAXONOMY_ORDER = "ebird"
|
|
12
13
|
DEFAULT_MAX_OBSERVATIONS = None
|
|
@@ -102,6 +103,8 @@ REGION_TYPES = ["country", "subnational1", "subnational2"]
|
|
|
102
103
|
|
|
103
104
|
TOP_100_RANK = ["spp", "cl"]
|
|
104
105
|
|
|
106
|
+
OBSERVATION_RANK = ["mrec", "create"]
|
|
107
|
+
|
|
105
108
|
# Just occasionally the connection to eBird freezes. The timeout
|
|
106
109
|
# will raise an error which is preferable to the sitting there
|
|
107
110
|
# waiting for something to happen, when it will not.
|
|
@@ -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
|
|
@@ -14,6 +14,7 @@ from ebird.api.requests.validation import (
|
|
|
14
14
|
clean_lng,
|
|
15
15
|
clean_locale,
|
|
16
16
|
clean_max_observations,
|
|
17
|
+
clean_observation_rank,
|
|
17
18
|
clean_provisional,
|
|
18
19
|
clean_sort,
|
|
19
20
|
)
|
|
@@ -633,6 +634,7 @@ def get_historic_observations(
|
|
|
633
634
|
hotspot=False,
|
|
634
635
|
detail="simple",
|
|
635
636
|
category=None,
|
|
637
|
+
rank="mrec",
|
|
636
638
|
):
|
|
637
639
|
"""Get recent observations for a region.
|
|
638
640
|
|
|
@@ -671,6 +673,9 @@ def get_historic_observations(
|
|
|
671
673
|
More than one value can be given in a comma-separated string. The default
|
|
672
674
|
value is None and records from all categories will be returned.
|
|
673
675
|
|
|
676
|
+
:param rank: for each species, return the latest observation on the
|
|
677
|
+
date, 'mrec' (the default), or the first observation, 'create'.
|
|
678
|
+
|
|
674
679
|
:return: the list of observations in simple format.
|
|
675
680
|
|
|
676
681
|
:raises ValueError: if any of the arguments fail the validation checks.
|
|
@@ -686,7 +691,7 @@ def get_historic_observations(
|
|
|
686
691
|
url = HISTORIC_OBSERVATIONS_URL % (cleaned[0], date.strftime("%Y/%m/%d"))
|
|
687
692
|
|
|
688
693
|
params = {
|
|
689
|
-
"rank":
|
|
694
|
+
"rank": clean_observation_rank(rank),
|
|
690
695
|
"detail": clean_detail(detail),
|
|
691
696
|
"sppLocale": clean_locale(locale),
|
|
692
697
|
"includeProvisional": clean_provisional(provisional),
|
|
@@ -5,7 +5,7 @@ from ebird.api.requests.validation import (
|
|
|
5
5
|
clean_area,
|
|
6
6
|
clean_date,
|
|
7
7
|
clean_max_observers,
|
|
8
|
-
|
|
8
|
+
clean_observer_rank,
|
|
9
9
|
clean_region,
|
|
10
10
|
)
|
|
11
11
|
|
|
@@ -46,7 +46,7 @@ def get_top_100(token, region, date, rank="spp", max_results=100):
|
|
|
46
46
|
|
|
47
47
|
params = {
|
|
48
48
|
"maxObservers": clean_max_observers(max_results),
|
|
49
|
-
"rankedBy":
|
|
49
|
+
"rankedBy": clean_observer_rank(rank),
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
headers = {
|
|
@@ -88,7 +88,7 @@ def get_response(url, params=None, headers=None, max_retries=3, backoff_factor=1
|
|
|
88
88
|
:param headers: the headers to add to the request.
|
|
89
89
|
:type headers: dict
|
|
90
90
|
|
|
91
|
-
:param max_retries: number of times to retry after
|
|
91
|
+
:param max_retries: number of times to retry after an error response.
|
|
92
92
|
:type max_retries: int
|
|
93
93
|
|
|
94
94
|
:param backoff_factor: seconds to wait before the first retry; doubles
|
|
@@ -167,7 +167,7 @@ def call(url, params, headers, max_retries=3, backoff_factor=1.0):
|
|
|
167
167
|
:param headers: the headers to add to the request.
|
|
168
168
|
:type headers: dict
|
|
169
169
|
|
|
170
|
-
:param max_retries: number of times to retry after
|
|
170
|
+
:param max_retries: number of times to retry after an error response.
|
|
171
171
|
:type max_retries: int
|
|
172
172
|
|
|
173
173
|
:param backoff_factor: seconds to wait before the first retry; doubles
|
|
@@ -182,6 +182,8 @@ def call(url, params, headers, max_retries=3, backoff_factor=1.0):
|
|
|
182
182
|
|
|
183
183
|
:raises HTTPError if the eBird API returns an error.
|
|
184
184
|
|
|
185
|
+
:raises JSONDecodeError if the JSON data returned by the API is invalid.
|
|
186
|
+
|
|
185
187
|
"""
|
|
186
188
|
filtered = filter_parameters(params)
|
|
187
189
|
mapped = map_parameters(filtered)
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
"""Functions for validation."""
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
|
+
import warnings
|
|
6
7
|
from datetime import date, datetime
|
|
7
8
|
from enum import Enum
|
|
8
9
|
|
|
9
10
|
from ebird.api.requests import constants
|
|
10
11
|
|
|
11
12
|
_locales = constants.LOCALES.values()
|
|
13
|
+
_observation_rank = ", ".join(constants.OBSERVATION_RANK)
|
|
12
14
|
_region_types = ", ".join(constants.REGION_TYPES)
|
|
13
15
|
_sort_list = ", ".join(constants.SPECIES_SORT)
|
|
14
16
|
_species_categories = ", ".join(constants.SPECIES_CATEGORIES)
|
|
@@ -27,18 +29,18 @@ def is_country(value):
|
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
def is_subnational1(value):
|
|
30
|
-
return re.match(r"^[A-Z]{2}-[A-Z0-9]{
|
|
32
|
+
return re.match(r"^[A-Z]{2}-[A-Z0-9]{1,3}$", value)
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
def is_subnational2(value):
|
|
34
|
-
return re.match(r"^[A-Z]{2}-[A-Z0-9]{
|
|
36
|
+
return re.match(r"^[A-Z]{2}-[A-Z0-9]{1,3}-[A-Z0-9]{2,3}$", value)
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
def is_region(value):
|
|
38
40
|
return (
|
|
39
41
|
re.match(r"^[A-Z]{2}$", value)
|
|
40
|
-
or re.match(r"^[A-Z]{2}-[A-Z0-9]{
|
|
41
|
-
or re.match(r"^[A-Z]{2}-[A-Z0-9]{
|
|
42
|
+
or re.match(r"^[A-Z]{2}-[A-Z0-9]{1,3}$", value)
|
|
43
|
+
or re.match(r"^[A-Z]{2}-[A-Z0-9]{1,3}-[A-Z0-9]{2,3}$", value)
|
|
42
44
|
)
|
|
43
45
|
|
|
44
46
|
|
|
@@ -200,6 +202,15 @@ def clean_detail(value):
|
|
|
200
202
|
return cleaned
|
|
201
203
|
|
|
202
204
|
|
|
205
|
+
def clean_observation_rank(value):
|
|
206
|
+
cleaned = clean_code(value, transform=Transform.LOWER)
|
|
207
|
+
if cleaned not in constants.OBSERVATION_RANK:
|
|
208
|
+
raise ValueError(
|
|
209
|
+
"Value for 'rank', %s, must be one of: %s" % (value, _observation_rank)
|
|
210
|
+
)
|
|
211
|
+
return cleaned
|
|
212
|
+
|
|
213
|
+
|
|
203
214
|
def clean_provisional(value):
|
|
204
215
|
return "true" if bool(value) else "false"
|
|
205
216
|
|
|
@@ -339,10 +350,21 @@ def clean_date(value):
|
|
|
339
350
|
return cleaned.strftime("%Y/%m/%d")
|
|
340
351
|
|
|
341
352
|
|
|
342
|
-
def
|
|
353
|
+
def clean_observer_rank(value):
|
|
343
354
|
cleaned = clean_code(value, transform=Transform.LOWER)
|
|
344
355
|
if cleaned not in constants.TOP_100_RANK:
|
|
345
356
|
raise ValueError(
|
|
346
357
|
"Top 100 rank, %s, must be one or more of : %s" % (value, _top_100_rank)
|
|
347
358
|
)
|
|
348
359
|
return cleaned
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def clean_rank(value):
|
|
363
|
+
# Deprecated in favour of clean_observer_rank(). Remove in 5.0.0.
|
|
364
|
+
warnings.warn(
|
|
365
|
+
"clean_rank() is deprecated and will be removed in 5.0.0. "
|
|
366
|
+
"Use clean_observer_rank() instead.",
|
|
367
|
+
DeprecationWarning,
|
|
368
|
+
stacklevel=2,
|
|
369
|
+
)
|
|
370
|
+
return clean_observer_rank(value)
|
|
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
|