ebird-api 4.1.1__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.
Files changed (23) hide show
  1. {ebird_api-4.1.1 → ebird_api-4.3.0}/CHANGELOG.md +17 -19
  2. {ebird_api-4.1.1/src/ebird_api.egg-info → ebird_api-4.3.0}/PKG-INFO +25 -1
  3. {ebird_api-4.1.1 → ebird_api-4.3.0}/README.md +24 -0
  4. {ebird_api-4.1.1 → ebird_api-4.3.0}/pyproject.toml +2 -2
  5. {ebird_api-4.1.1 → ebird_api-4.3.0}/src/ebird/api/requests/__init__.py +6 -1
  6. {ebird_api-4.1.1 → ebird_api-4.3.0}/src/ebird/api/requests/client.py +5 -1
  7. {ebird_api-4.1.1 → ebird_api-4.3.0}/src/ebird/api/requests/constants.py +3 -0
  8. {ebird_api-4.1.1 → ebird_api-4.3.0}/src/ebird/api/requests/observations.py +6 -1
  9. {ebird_api-4.1.1 → ebird_api-4.3.0}/src/ebird/api/requests/statistics.py +2 -2
  10. {ebird_api-4.1.1 → ebird_api-4.3.0}/src/ebird/api/requests/utils.py +125 -6
  11. {ebird_api-4.1.1 → ebird_api-4.3.0}/src/ebird/api/requests/validation.py +27 -5
  12. {ebird_api-4.1.1 → ebird_api-4.3.0/src/ebird_api.egg-info}/PKG-INFO +25 -1
  13. {ebird_api-4.1.1 → ebird_api-4.3.0}/LICENSE.txt +0 -0
  14. {ebird_api-4.1.1 → ebird_api-4.3.0}/setup.cfg +0 -0
  15. {ebird_api-4.1.1 → ebird_api-4.3.0}/setup.py +0 -0
  16. {ebird_api-4.1.1 → ebird_api-4.3.0}/src/ebird/api/requests/checklists.py +0 -0
  17. {ebird_api-4.1.1 → ebird_api-4.3.0}/src/ebird/api/requests/hotspots.py +0 -0
  18. {ebird_api-4.1.1 → ebird_api-4.3.0}/src/ebird/api/requests/regions.py +0 -0
  19. {ebird_api-4.1.1 → ebird_api-4.3.0}/src/ebird/api/requests/species.py +0 -0
  20. {ebird_api-4.1.1 → ebird_api-4.3.0}/src/ebird/api/requests/taxonomy.py +0 -0
  21. {ebird_api-4.1.1 → ebird_api-4.3.0}/src/ebird_api.egg-info/SOURCES.txt +0 -0
  22. {ebird_api-4.1.1 → ebird_api-4.3.0}/src/ebird_api.egg-info/dependency_links.txt +0 -0
  23. {ebird_api-4.1.1 → ebird_api-4.3.0}/src/ebird_api.egg-info/top_level.txt +0 -0
@@ -6,7 +6,23 @@ 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, [Semantic Versioning](http://semver.org/).
10
+
11
+ ## [4.3.0] - 2026-10-12
12
+ - Added set_rate_limit() to set a minimum interval between requests. The
13
+ interval is doubled, up to 60 seconds, if the API responds with HTTP 429
14
+ Too Many Requests. Rate limiting is disabled by default.
15
+ - Added RateLimitError, a subclass of HTTPError, which is raised when the
16
+ API still responds with HTTP 429 after all the retries have been used.
17
+ - Fixed the handling of a Retry-After header containing a date instead of
18
+ a number of seconds.
19
+
20
+ ## [4.2.0] - 2026-10-12
21
+ - Changed get_historic_observations() so observations can be returned by
22
+ date created or date last modified.
23
+ - Renamed clean_rank() to clean_observer_rank(). clean_rank() is deprecated
24
+ and will be removed in 5.0.0.
25
+ - Allow single-character subnational1 codes, e.g. IE-L.
10
26
 
11
27
  ## [4.1.1] - 2026-10-10
12
28
  - Changed get_location() to handle an API change where an empty response
@@ -150,21 +166,3 @@ avoid any issues with representation.
150
166
  ## [0.1.0] - 2017-02-13
151
167
  ### Added
152
168
  - Core functions for accessing end-points for fetching observations.
153
-
154
- [Unreleased]: https://github.com/ProjectBabbler/ebird-api/compare/3.0.6...HEAD
155
- [3.0.6]: https://github.com/ProjectBabbler/ebird-api/compare/3.0.5...3.0.6
156
- [3.0.5]: https://github.com/ProjectBabbler/ebird-api/compare/3.0.5...3.0.5
157
- [3.0.5]: https://github.com/ProjectBabbler/ebird-api/compare/3.0.4...3.0.5
158
- [3.0.4]: https://github.com/ProjectBabbler/ebird-api/compare/3.0.3...3.0.4
159
- [3.0.3]: https://github.com/ProjectBabbler/ebird-api/compare/3.0.3...3.0.3
160
- [3.0.3]: https://github.com/ProjectBabbler/ebird-api/compare/3.0.2...3.0.3
161
- [3.0.2]: https://github.com/ProjectBabbler/ebird-api/compare/3.0.1...3.0.2
162
- [3.0.1]: https://github.com/ProjectBabbler/ebird-api/compare/3.0.0...3.0.1
163
- [2.1.1]: https://github.com/ProjectBabbler/ebird-api/compare/2.0.1...2.1.1
164
- [2.1.0]: https://github.com/ProjectBabbler/ebird-api/compare/2.0.0...2.1.0
165
- [2.0.0]: https://github.com/ProjectBabbler/ebird-api/compare/1.0.2...2.0.0
166
- [1.0.2]: https://github.com/ProjectBabbler/ebird-api/compare/1.0.1...1.0.2
167
- [1.0.1]: https://github.com/ProjectBabbler/ebird-api/compare/1.0.0...1.0.1
168
- [1.0.0]: https://github.com/ProjectBabbler/ebird-api/compare/0.2.0...1.0.0
169
- [0.2.0]: https://github.com/ProjectBabbler/ebird-api/compare/0.1.1...0.2.0
170
- [0.1.1]: https://github.com/ProjectBabbler/ebird-api/compare/0.1.0...0.1.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ebird-api
3
- Version: 4.1.1
3
+ Version: 4.3.0
4
4
  Summary: Wrapper for accessing the eBird API
5
5
  Author-email: Project Babbler <projectbabbler@gmail.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"
30
30
  readme = "README.md"
31
31
  requires-python = ">= 3.10"
32
- version = "4.1.1"
32
+ version = "4.3.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.1.1"
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.1.1"
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
+ )
@@ -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.
@@ -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": "mrec",
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
- clean_rank,
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": clean_rank(rank),
49
+ "rankedBy": clean_observer_rank(rank),
50
50
  }
51
51
 
52
52
  headers = {
@@ -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,
@@ -88,7 +197,7 @@ def get_response(url, params=None, headers=None, max_retries=3, backoff_factor=1
88
197
  :param headers: the headers to add to the request.
89
198
  :type headers: dict
90
199
 
91
- :param max_retries: number of times to retry after a 429 response.
200
+ :param max_retries: number of times to retry after an error response.
92
201
  :type max_retries: int
93
202
 
94
203
  :param backoff_factor: seconds to wait before the first retry; doubles
@@ -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
- if attempt == max_retries:
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
- 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)
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):
@@ -167,7 +286,7 @@ def call(url, params, headers, max_retries=3, backoff_factor=1.0):
167
286
  :param headers: the headers to add to the request.
168
287
  :type headers: dict
169
288
 
170
- :param max_retries: number of times to retry after a 429 response.
289
+ :param max_retries: number of times to retry after an error response.
171
290
  :type max_retries: int
172
291
 
173
292
  :param backoff_factor: seconds to wait before the first retry; doubles
@@ -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]{2,3}$", value)
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]{2,3}-[A-Z0-9]{2,3}$", value)
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]{2,3}$", value)
41
- or re.match(r"^[A-Z]{2}-[A-Z0-9]{2,3}-[A-Z0-9]{2,3}$", value)
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 clean_rank(value):
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ebird-api
3
- Version: 4.1.1
3
+ Version: 4.3.0
4
4
  Summary: Wrapper for accessing the eBird API
5
5
  Author-email: Project Babbler <projectbabbler@gmail.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