possible issue with some endpoints
svshepherd opened this issue · 7 comments
The problem
Doing simple tasks with the get_observation endpoint I am running into trouble. At first the query just returned no response, but for most of today
local_ids = inat.v1.identifications.get_identifications(page=0)
local_ids
results in:
MaxRetryError: HTTPSConnectionPool(host='api.inaturalist.org', port=443): Max retries exceeded with url: /v1/identifications?page=0 (Caused by ResponseError('too many 500 error responses'))
Curiously, other contexts ( inat.get_observations(user_id=[uname], d1=start_date, page='all') ) seem to still work.
Environment
- Win10, Python
packages in environment at C:\Users\svs2\anaconda3\envs\base2023geonat:
Name Version Build Channel
aiofiles 22.1.0 py311haa95532_0
aiosqlite 0.18.0 py311haa95532_0
anyio 3.5.0 py311haa95532_0
argon2-cffi 21.3.0 pyhd3eb1b0_0
argon2-cffi-bindings 21.2.0 py311h2bbff1b_0
arrow 1.3.0 pypi_0 pypi
asttokens 2.0.5 pyhd3eb1b0_0
async-lru 2.0.4 py311haa95532_0
attrs 23.1.0 py311haa95532_0
babel 2.11.0 py311haa95532_0
backcall 0.2.0 pyhd3eb1b0_0
beautifulsoup4 4.12.2 py311haa95532_0
blas 1.0 mkl
bleach 4.1.0 pyhd3eb1b0_0
bottleneck 1.3.5 py311h5bb9823_0
brotli 1.0.9 h2bbff1b_7
brotli-bin 1.0.9 h2bbff1b_7
brotli-python 1.0.9 py311hd77b12b_7
bzip2 1.0.8 he774522_0
ca-certificates 2023.12.12 haa95532_0
cattrs 23.1.2 py311haa95532_0
certifi 2023.11.17 pyhd8ed1ab_0 conda-forge
cffi 1.16.0 py311h2bbff1b_0
charset-normalizer 2.0.4 pyhd3eb1b0_0
colorama 0.4.6 py311haa95532_0
comm 0.1.2 py311haa95532_0
contourpy 1.2.0 py311h59b6b97_0
cryptography 41.0.7 py311h89fc84f_0
cycler 0.11.0 pyhd3eb1b0_0
debugpy 1.6.7 py311hd77b12b_0
decorator 5.1.1 pyhd3eb1b0_0
defusedxml 0.7.1 pyhd3eb1b0_0
dill 0.3.7 py311haa95532_0
entrypoints 0.4 py311haa95532_0
exceptiongroup 1.1.3 pyhd8ed1ab_0 conda-forge
executing 0.8.3 pyhd3eb1b0_0
fonttools 4.25.0 pyhd3eb1b0_0
fqdn 1.5.1 pypi_0 pypi
freetype 2.12.1 ha860e81_0
giflib 5.2.1 h8cc25b3_3
icc_rt 2022.1.0 h6049295_2
icu 73.1 h6c2663c_0
idna 3.4 py311haa95532_0
importlib-metadata 6.8.0 pyha770c72_0 conda-forge
importlib_metadata 6.8.0 hd8ed1ab_0 conda-forge
intel-openmp 2023.1.0 h59b6b97_46320
ipykernel 6.25.0 py311h746a85d_0
ipython 8.15.0 py311haa95532_0
ipython_genutils 0.2.0 pyhd3eb1b0_1
isoduration 20.11.0 pypi_0 pypi
itsdangerous 2.1.2 pyhd8ed1ab_0 conda-forge
jaraco.classes 3.3.0 pyhd8ed1ab_0 conda-forge
jedi 0.18.1 py311haa95532_1
jinja2 3.1.2 py311haa95532_0
jpeg 9e h2bbff1b_1
json5 0.9.6 pyhd3eb1b0_0
jsonpointer 2.4 pypi_0 pypi
jsonschema 4.19.2 py311haa95532_0
jsonschema-specifications 2023.7.1 py311haa95532_0
jupyter-lsp 2.2.0 py311haa95532_0
jupyter_client 8.6.0 py311haa95532_0
jupyter_core 5.5.0 py311haa95532_0
jupyter_events 0.8.0 py311haa95532_0
jupyter_server 2.10.0 py311haa95532_0
jupyter_server_fileid 0.9.0 py311haa95532_0
jupyter_server_terminals 0.4.4 py311haa95532_1
jupyter_server_ydoc 0.8.0 py311haa95532_1
jupyter_ydoc 0.2.4 py311haa95532_0
jupyterlab 4.0.8 py311haa95532_0
jupyterlab-execute-time 3.1.0 pypi_0 pypi
jupyterlab_pygments 0.1.2 py_0
jupyterlab_server 2.25.1 py311haa95532_0
keyring 24.2.0 py311h1ea47a8_1 conda-forge
kiwisolver 1.4.4 py311hd77b12b_0
krb5 1.20.1 h5b6d351_0
lerc 3.0 hd77b12b_0
libbrotlicommon 1.0.9 h2bbff1b_7
libbrotlidec 1.0.9 h2bbff1b_7
libbrotlienc 1.0.9 h2bbff1b_7
libclang 14.0.6 default_hb5a9fac_1
libclang13 14.0.6 default_h8e68704_1
libdeflate 1.17 h2bbff1b_1
libffi 3.4.4 hd77b12b_0
libpng 1.6.39 h8cc25b3_0
libpq 12.15 h906ac69_1
libsodium 1.0.18 h62dcd97_0
libtiff 4.5.1 hd77b12b_0
libwebp 1.3.2 hbc33d0d_0
libwebp-base 1.3.2 h2bbff1b_0
lz4-c 1.9.4 h2bbff1b_0
markdown-it-py 3.0.0 pyhd8ed1ab_0 conda-forge
markupsafe 2.1.1 py311h2bbff1b_0
matplotlib 3.8.0 py311haa95532_0
matplotlib-base 3.8.0 py311hf62ec03_0
matplotlib-inline 0.1.6 py311haa95532_0
mdurl 0.1.0 py311haa95532_0
mistune 2.0.4 py311haa95532_0
mkl 2023.1.0 h6b88ed4_46358
mkl-service 2.4.0 py311h2bbff1b_1
mkl_fft 1.3.8 py311h2bbff1b_0
mkl_random 1.2.4 py311h59b6b97_0
more-itertools 10.1.0 py311haa95532_0
munkres 1.1.4 py_0
nbclassic 1.0.0 py311haa95532_0
nbclient 0.8.0 py311haa95532_0
nbconvert 7.10.0 py311haa95532_0
nbconvert-core 7.10.0 pyhd8ed1ab_0 conda-forge
nbformat 5.9.2 py311haa95532_0
nest-asyncio 1.5.6 py311haa95532_0
nodejs 18.18.2 haa95532_0
notebook 7.0.6 py311haa95532_0
notebook-shim 0.2.3 py311haa95532_0
numexpr 2.8.7 py311h1fcbade_0
numpy 1.26.2 py311hdab7c0b_0
numpy-base 1.26.2 py311hd01c5d8_0
openjpeg 2.4.0 h4fc8c34_0
openssl 3.2.0 hcfcfb64_1 conda-forge
overrides 7.4.0 py311haa95532_0
packaging 23.1 py311haa95532_0
pandas 2.1.4 py311hf62ec03_0
pandocfilters 1.5.0 pyhd3eb1b0_0
parso 0.8.3 pyhd3eb1b0_0
pickleshare 0.7.5 pyhd3eb1b0_1003
pillow 10.0.1 py311h045eedc_0
pip 23.3.1 py311haa95532_0
platformdirs 3.10.0 py311haa95532_0
plotly 5.9.0 py311haa95532_0
ply 3.11 py311haa95532_0
prometheus_client 0.14.1 py311haa95532_0
prompt-toolkit 3.0.36 py311haa95532_0
psutil 5.9.0 py311h2bbff1b_0
pure_eval 0.2.2 pyhd3eb1b0_0
pycparser 2.21 pyhd3eb1b0_0
pygments 2.15.1 py311haa95532_1
pyinaturalist 0.19.0 pyhd8ed1ab_0 conda-forge
pyopenssl 23.2.0 py311haa95532_0
pyparsing 3.0.9 py311haa95532_0
pyqt 5.15.10 py311hd77b12b_0
pyqt5-sip 12.13.0 py311h2bbff1b_0
pyrate-limiter 2.10.0 pyhd8ed1ab_0 conda-forge
pysocks 1.7.1 py311haa95532_0
python 3.11.5 he1021f5_0
python-dateutil 2.8.2 pyhd3eb1b0_0
python-fastjsonschema 2.16.2 py311haa95532_0
python-forge 18.6.0 pyhd8ed1ab_0 conda-forge
python-json-logger 2.0.7 py311haa95532_0
python-tzdata 2023.3 pyhd3eb1b0_0
python_abi 3.11 2_cp311 conda-forge
pytz 2023.3.post1 py311haa95532_0
pywin32 305 py311h2bbff1b_0
pywin32-ctypes 0.2.2 py311h1ea47a8_1 conda-forge
pywinpty 2.0.10 py311h5da7b33_0
pyyaml 6.0.1 py311h2bbff1b_0
pyzmq 25.1.0 py311hd77b12b_0
qt-main 5.15.2 h19c9488_10
referencing 0.30.2 py311haa95532_0
requests 2.31.0 py311haa95532_0
requests-cache 1.1.1 pyhd8ed1ab_0 conda-forge
requests-ratelimiter 0.4.1 pyhd8ed1ab_0 conda-forge
rfc3339-validator 0.1.4 py311haa95532_0
rfc3986-validator 0.1.1 py311haa95532_0
rich 13.6.0 pyhd8ed1ab_0 conda-forge
rpds-py 0.10.6 py311h062c2fa_0
scipy 1.11.4 py311hc1ccb85_0
send2trash 1.8.2 py311haa95532_0
setuptools 68.2.2 py311haa95532_0
sip 6.7.12 py311hd77b12b_0
six 1.16.0 pyhd3eb1b0_1
sniffio 1.2.0 py311haa95532_1
soupsieve 2.5 py311haa95532_0
sqlite 3.41.2 h2bbff1b_0
stack_data 0.2.0 pyhd3eb1b0_0
tbb 2021.8.0 h59b6b97_0
tenacity 8.2.2 py311haa95532_0
terminado 0.17.1 py311haa95532_0
tinycss2 1.2.1 py311haa95532_0
tk 8.6.12 h2bbff1b_0
tornado 6.3.3 py311h2bbff1b_0
traitlets 5.7.1 py311haa95532_0
types-python-dateutil 2.8.19.14 pypi_0 pypi
typing-extensions 4.7.1 py311haa95532_0
typing_extensions 4.7.1 py311haa95532_0
tzdata 2023c h04d1e81_0
ucrt 10.0.22621.0 h57928b3_0 conda-forge
ujson 5.8.0 py311h12c1d0e_0 conda-forge
uri-template 1.3.0 pypi_0 pypi
url-normalize 1.4.3 pyhd8ed1ab_0 conda-forge
urllib3 1.26.18 py311haa95532_0
vc 14.2 h21ff451_1
vc14_runtime 14.38.33130 h82b7239_18 conda-forge
vs2015_runtime 14.38.33130 hcb4865c_18 conda-forge
wcwidth 0.2.5 pyhd3eb1b0_0
webcolors 1.13 pypi_0 pypi
webencodings 0.5.1 py311haa95532_1
websocket-client 0.58.0 py311haa95532_4
wheel 0.41.2 py311haa95532_0
win_inet_pton 1.1.0 py311haa95532_0
winpty 0.4.3 4
xz 5.4.5 h8cc25b3_0
y-py 0.5.9 py311hb6bf4ef_0
yaml 0.2.5 he774522_0
ypy-websocket 0.8.2 py311haa95532_0
zeromq 4.3.4 hd77b12b_0
zipp 3.17.0 pyhd8ed1ab_0 conda-forge
zlib 1.2.13 h8cc25b3_0
zstd 1.5.5 hd43e919_0
I noticed the web-based platform was acting wonky yesterday, specifically not always returning lists of observations when there were too many filter parameters. That makes me think the issue you experienced is more an iNat server side issue than anything to do with pyinaturalist.
Hope this helps!
That's very helpful. I started to file a bug report on the main site, but realized I don't know the best practice for converting pyinaturalist requests into standard API requests. Is there an straightforward command for troubleshooting? (I realize I can dig a bit and probably figure it out, but if there's an existing tool... well, I'm lazy.) Thanks everybody for your work on this and your help.
I think @willkuhn is right. Your traceback shows a 500 error, which is on the iNat server's side, and probably a temporary problem. Are you still seeing these errors?
There are instructions for enabling logging here, and that will show the complete API request details.
Thank you! Shall I report it in the iNat forum? Here's the traceback with logging:
INFO:pyinaturalist:Request:
GET https://api.inaturalist.org/v1/identifications?page=0
User-Agent: python-requests/2.31.0 pyinaturalist/0.19.0
Accept-Encoding: gzip, deflate, br
Accept: application/json
Connection: keep-alive
MaxRetryError Traceback (most recent call last)
File ~\anaconda3\envs\base2023geonat\Lib\site-packages\requests\adapters.py:486, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
485 try:
--> 486 resp = conn.urlopen(
487 method=request.method,
488 url=url,
489 body=request.body,
490 headers=request.headers,
491 redirect=False,
492 assert_same_host=False,
493 preload_content=False,
494 decode_content=False,
495 retries=self.max_retries,
496 timeout=timeout,
497 chunked=chunked,
498 )
500 except (ProtocolError, OSError) as err:
File ~\anaconda3\envs\base2023geonat\Lib\site-packages\urllib3\connectionpool.py:894, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)
893 log.debug("Retry: %s", url)
--> 894 return self.urlopen(
895 method,
896 url,
897 body,
898 headers,
899 retries=retries,
900 redirect=redirect,
901 assert_same_host=assert_same_host,
902 timeout=timeout,
903 pool_timeout=pool_timeout,
904 release_conn=release_conn,
905 chunked=chunked,
906 body_pos=body_pos,
907 **response_kw
908 )
910 return response
File ~\anaconda3\envs\base2023geonat\Lib\site-packages\urllib3\connectionpool.py:894, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)
893 log.debug("Retry: %s", url)
--> 894 return self.urlopen(
895 method,
896 url,
897 body,
898 headers,
899 retries=retries,
900 redirect=redirect,
901 assert_same_host=assert_same_host,
902 timeout=timeout,
903 pool_timeout=pool_timeout,
904 release_conn=release_conn,
905 chunked=chunked,
906 body_pos=body_pos,
907 **response_kw
908 )
910 return response
[... skipping similar frames: HTTPConnectionPool.urlopen at line 894 (2 times)]
File ~\anaconda3\envs\base2023geonat\Lib\site-packages\urllib3\connectionpool.py:894, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)
893 log.debug("Retry: %s", url)
--> 894 return self.urlopen(
895 method,
896 url,
897 body,
898 headers,
899 retries=retries,
900 redirect=redirect,
901 assert_same_host=assert_same_host,
902 timeout=timeout,
903 pool_timeout=pool_timeout,
904 release_conn=release_conn,
905 chunked=chunked,
906 body_pos=body_pos,
907 **response_kw
908 )
910 return response
File ~\anaconda3\envs\base2023geonat\Lib\site-packages\urllib3\connectionpool.py:884, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)
883 try:
--> 884 retries = retries.increment(method, url, response=response, _pool=self)
885 except MaxRetryError:
File ~\anaconda3\envs\base2023geonat\Lib\site-packages\urllib3\util\retry.py:592, in Retry.increment(self, method, url, response, error, _pool, _stacktrace)
591 if new_retry.is_exhausted():
--> 592 raise MaxRetryError(_pool, url, error or ResponseError(cause))
594 log.debug("Incremented Retry for (url='%s'): %r", url, new_retry)
MaxRetryError: HTTPSConnectionPool(host='api.inaturalist.org', port=443): Max retries exceeded with url: /v1/identifications?page=0 (Caused by ResponseError('too many 500 error responses'))
During handling of the above exception, another exception occurred:
RetryError Traceback (most recent call last)
Cell In[4], line 6
3 logging.getLogger('pyinaturalist').setLevel('INFO')
5 # local_ids = inat.v1.identifications.get_identifications(page=0)
----> 6 local_ids = inat.get_identifications(page=0)
7 local_ids
File ~\anaconda3\envs\base2023geonat\Lib\site-packages\forge_revision.py:328, in Revision.call..inner(*args, **kwargs)
324 @functools.wraps(callable) # type: ignore
325 def inner(*args, **kwargs):
326 # pylint: disable=E1102, not-callable
327 mapped = inner.mapper(*args, **kwargs)
--> 328 return callable(*mapped.args, **mapped.kwargs)
File ~\anaconda3\envs\base2023geonat\Lib\site-packages\pyinaturalist\v1\identifications.py:69, in get_identifications(**params)
67 identifications = paginate_all(get, f'{API_V1}/identifications', **params)
68 else:
---> 69 identifications = get(f'{API_V1}/identifications', **params).json()
71 identifications['results'] = convert_all_timestamps(identifications['results'])
72 return identifications
File ~\anaconda3\envs\base2023geonat\Lib\site-packages\pyinaturalist\session.py:370, in get(url, session, **kwargs)
368 """Wrapper around :py:func:requests.get
with additional options specific to iNat API requests"""
369 session = session or get_local_session()
--> 370 return session.request('GET', url, **kwargs)
File ~\anaconda3\envs\base2023geonat\Lib\site-packages\pyinaturalist\session.py:256, in ClientSession.request(self, method, url, headers, json, access_token, allow_redirects, allow_str_ids, dry_run, expire_after, files, ids, only_if_cached, raise_for_status, refresh, stream, timeout, verify, **params)
220 """Wrapper around :py:func:requests.request
with additional options specific to iNat API requests
221
222 Args:
(...)
242 API response
243 """
244 request = self.prepare_inat_request(
245 method=method,
246 url=url,
(...)
253 params=params,
254 )
--> 256 response = self.send(
257 request,
258 dry_run=dry_run,
259 expire_after=expire_after,
260 only_if_cached=only_if_cached,
261 refresh=refresh,
262 timeout=timeout,
263 allow_redirects=allow_redirects,
264 stream=stream,
265 verify=verify,
266 )
268 # Raise an exception if the request failed (after retries are exceeded)
269 if raise_for_status:
File ~\anaconda3\envs\base2023geonat\Lib\site-packages\pyinaturalist\session.py:305, in ClientSession.send(self, request, dry_run, expire_after, refresh, retries, timeout, **kwargs)
303 # Otherwise, send the request
304 read_timeout = timeout or self.timeout
--> 305 response = super().send(
306 request,
307 expire_after=expire_after,
308 refresh=refresh,
309 timeout=(CONNECT_TIMEOUT, read_timeout),
310 **kwargs,
311 )
312 response = self._validate_json(
313 request,
314 response,
(...)
318 **kwargs,
319 )
321 logger.debug(format_response(response))
File ~\anaconda3\envs\base2023geonat\Lib\site-packages\requests_cache\session.py:205, in CacheMixin.send(self, request, expire_after, only_if_cached, refresh, force_refresh, **kwargs)
203 response = self._resend(request, actions, cached_response, **kwargs) # type: ignore
204 elif actions.send_request:
--> 205 response = self._send_and_cache(request, actions, cached_response, **kwargs)
206 else:
207 response = cached_response # type: ignore # Guaranteed to be non-None by this point
File ~\anaconda3\envs\base2023geonat\Lib\site-packages\requests_cache\session.py:229, in CacheMixin._send_and_cache(self, request, actions, cached_response, **kwargs)
225 """Send a request and cache the response, unless disabled by settings or headers.
226 If applicable, also handle conditional requests.
227 """
228 request = actions.update_request(request)
--> 229 response = super().send(request, **kwargs)
230 actions.update_from_response(response)
232 if not actions.skip_write:
File ~\anaconda3\envs\base2023geonat\Lib\site-packages\requests_ratelimiter\requests_ratelimiter.py:87, in LimiterMixin.send(self, request, **kwargs)
77 """Send a request with rate-limiting.
78
79 Raises:
80 :py:exc:.BucketFullException
if this request would result in a delay longer than max_delay
81 """
82 with self.limiter.ratelimit(
83 self._bucket_name(request),
84 delay=True,
85 max_delay=self.max_delay,
86 ):
---> 87 response = super().send(request, **kwargs)
88 if response.status_code in self.limit_statuses:
89 self._fill_bucket(request)
File ~\anaconda3\envs\base2023geonat\Lib\site-packages\requests\sessions.py:703, in Session.send(self, request, **kwargs)
700 start = preferred_clock()
702 # Send the request
--> 703 r = adapter.send(request, **kwargs)
705 # Total elapsed time of the request (approximately)
706 elapsed = preferred_clock() - start
File ~\anaconda3\envs\base2023geonat\Lib\site-packages\requests\adapters.py:510, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
507 raise ConnectTimeout(e, request=request)
509 if isinstance(e.reason, ResponseError):
--> 510 raise RetryError(e, request=request)
512 if isinstance(e.reason, _ProxyError):
513 raise ProxyError(e, request=request)
RetryError: HTTPSConnectionPool(host='api.inaturalist.org', port=443): Max retries exceeded with url: /v1/identifications?page=0 (Caused by ResponseError('too many 500 error responses'))
Oh, I just noticed that you're passing page=0
. Page indexes start at 1, so if you remove the page
parameter or use page=1
, it will work as expected.
Most endpoints will handle page=0
and return the first page, but this one appears to pass its request parameters to a different storage backend (for full text search) that doesn't handle this case:
curl 'https://api.inaturalist.org/v1/identifications?page=0'
{"error":"Elasticsearch error, if this persists please contact the iNaturalist development team.","status":500}
This likely won't be a high priority for them to fix, but it might be worth making a bug report for reference.
On this end, I can add a check to catch page=0
and return a friendlier error message. I also noticed that error retries are making that traceback a lot more verbose than necessary, so I'll try to clean that up a bit.
Thanks! Turned out I had two issues: In my original code, I was also entering places as dictionary keys, where the values were the place names. This worked when I first made my notebook, but they now need to be explicitly turned into a list. Appreciate the help!
Do you know what version of python your notebook was using? The behavior for dict.keys() and other view types may be slightly different based on the python version. Lists or tuples are the best options for params that take multiple values.