pyinaturalist 1.0.0.dev1__tar.gz → 1.0.0.dev2__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.
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/PKG-INFO +2 -2
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/client/oauth.py +32 -12
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/client/session.py +5 -4
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/constants.py +11 -1
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/controllers/search_controller.py +1 -1
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyproject.toml +2 -2
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/.gitignore +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/LICENSE +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/README.md +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinat/__init__.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/__init__.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/client/__init__.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/client/client.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/client/oauth_callback.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/client/paginator.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/controllers/__init__.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/controllers/annotation_controller.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/controllers/base_controller.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/controllers/identification_controller.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/controllers/observation_controller.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/controllers/observation_field_controller.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/controllers/place_controller.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/controllers/project_controller.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/controllers/taxon_controller.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/controllers/user_controller.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/converters.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/docs/__init__.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/docs/docstrings.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/docs/emoji.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/docs/model_docs.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/docs/signatures.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/docs/templates.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/exceptions.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/formatters.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/__init__.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/base.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/checklist.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/conservation_status.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/controlled_term.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/identification.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/lazy_property.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/media.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/message.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/observation.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/observation_field.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/place.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/project.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/search.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/taxon.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/user.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/py.typed +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/request_params.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v0/__init__.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v0/observation_fields.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v0/observations.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v1/__init__.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v1/controlled_terms.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v1/identifications.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v1/messages.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v1/observation_fields.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v1/observations.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v1/places.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v1/posts.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v1/projects.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v1/search.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v1/taxa.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v1/users.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v2/__init__.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v2/observations.py +0 -0
- {pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v2/taxa.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: pyinaturalist
|
|
3
|
-
Version: 1.0.0.
|
|
3
|
+
Version: 1.0.0.dev2
|
|
4
4
|
Summary: iNaturalist API client for python
|
|
5
5
|
Project-URL: homepage, https://github.com/pyinat/pyinaturalist
|
|
6
6
|
Project-URL: repository, https://github.com/pyinat/pyinaturalist
|
|
@@ -40,7 +40,15 @@ def _decode_jwt_exp(token: str) -> datetime | None:
|
|
|
40
40
|
payload = json.loads(base64.urlsafe_b64decode(payload_b64))
|
|
41
41
|
exp = payload.get('exp')
|
|
42
42
|
return datetime.fromtimestamp(exp, tz=timezone.utc) if exp else None
|
|
43
|
-
except (
|
|
43
|
+
except (
|
|
44
|
+
ValueError,
|
|
45
|
+
KeyError,
|
|
46
|
+
AttributeError,
|
|
47
|
+
OverflowError,
|
|
48
|
+
OSError,
|
|
49
|
+
TypeError,
|
|
50
|
+
binascii.Error,
|
|
51
|
+
):
|
|
44
52
|
return None
|
|
45
53
|
|
|
46
54
|
|
|
@@ -97,9 +105,10 @@ def get_access_token(
|
|
|
97
105
|
|
|
98
106
|
Raises:
|
|
99
107
|
:py:exc:`requests.HTTPError`: (401) if credentials are invalid
|
|
108
|
+
:py:exc:`.AuthenticationError`: if required credentials are missing
|
|
100
109
|
"""
|
|
101
110
|
session, cached = _get_cached_jwt(refresh)
|
|
102
|
-
if cached:
|
|
111
|
+
if cached and jwt:
|
|
103
112
|
return cached
|
|
104
113
|
|
|
105
114
|
# Otherwise check for credentials in either args or environment variables
|
|
@@ -113,7 +122,7 @@ def get_access_token(
|
|
|
113
122
|
|
|
114
123
|
# If any fields were missing, then check the keyring
|
|
115
124
|
if not all(payload.values()):
|
|
116
|
-
payload.
|
|
125
|
+
payload |= {k: v for k, v in get_keyring_credentials().items() if not payload.get(k)}
|
|
117
126
|
if all(payload.values()):
|
|
118
127
|
_logger.info('Retrieved credentials from keyring')
|
|
119
128
|
else:
|
|
@@ -121,12 +130,11 @@ def get_access_token(
|
|
|
121
130
|
|
|
122
131
|
# Get OAuth access token
|
|
123
132
|
response = session.post(f'{API_V0}/oauth/token', json=payload)
|
|
124
|
-
response.raise_for_status()
|
|
125
133
|
access_token = response.json()['access_token']
|
|
126
134
|
|
|
127
135
|
# If specified, use OAuth token to get (and cache) a JWT
|
|
128
136
|
if jwt:
|
|
129
|
-
response = _get_jwt(session, access_token)
|
|
137
|
+
response = _get_jwt(session, access_token, refresh=refresh)
|
|
130
138
|
response.raise_for_status()
|
|
131
139
|
access_token = response.json()['api_token']
|
|
132
140
|
return access_token
|
|
@@ -205,7 +213,7 @@ def get_access_token_via_auth_code(
|
|
|
205
213
|
authorize in time, or the token exchange fails.
|
|
206
214
|
"""
|
|
207
215
|
session, cached = _get_cached_jwt(refresh)
|
|
208
|
-
if cached:
|
|
216
|
+
if cached and jwt:
|
|
209
217
|
return cached
|
|
210
218
|
|
|
211
219
|
app_id, app_secret = _resolve_auth_code_creds(app_id, app_secret, use_pkce)
|
|
@@ -235,12 +243,11 @@ def get_access_token_via_auth_code(
|
|
|
235
243
|
app_id, auth_code, redirect_uri, code_verifier, app_secret, use_pkce
|
|
236
244
|
)
|
|
237
245
|
response = session.post(f'{API_V0}/oauth/token', json=payload)
|
|
238
|
-
response.raise_for_status()
|
|
239
246
|
access_token = response.json()['access_token']
|
|
240
247
|
|
|
241
248
|
# If specified, use OAuth token to get (and cache) a JWT
|
|
242
249
|
if jwt:
|
|
243
|
-
response = _get_jwt(session, access_token)
|
|
250
|
+
response = _get_jwt(session, access_token, refresh=refresh)
|
|
244
251
|
response.raise_for_status()
|
|
245
252
|
access_token = response.json()['api_token']
|
|
246
253
|
return access_token
|
|
@@ -257,13 +264,22 @@ def _get_cached_jwt(refresh: bool) -> tuple[ClientSession, str | None]:
|
|
|
257
264
|
|
|
258
265
|
|
|
259
266
|
def validate_token(access_token: str) -> bool:
|
|
260
|
-
"""Determine if an access token is valid
|
|
267
|
+
"""Determine if an access token is valid.
|
|
268
|
+
|
|
269
|
+
Returns ``False`` when the server confirms the token is invalid (401);
|
|
270
|
+
other failures are not re-raised.
|
|
271
|
+
|
|
272
|
+
Raises:
|
|
273
|
+
:py:exc:`requests.HTTPError`: for any non-401 error response
|
|
274
|
+
"""
|
|
261
275
|
session = get_local_session()
|
|
262
276
|
try:
|
|
263
277
|
session.request('GET', f'{API_V1}/users/me', access_token=access_token)
|
|
264
278
|
return True
|
|
265
|
-
except HTTPError:
|
|
266
|
-
|
|
279
|
+
except HTTPError as e:
|
|
280
|
+
if e.response is not None and e.response.status_code == 401:
|
|
281
|
+
return False
|
|
282
|
+
raise
|
|
267
283
|
|
|
268
284
|
|
|
269
285
|
def get_keyring_credentials() -> dict[str, str | None]:
|
|
@@ -306,12 +322,16 @@ def set_keyring_credentials(
|
|
|
306
322
|
|
|
307
323
|
|
|
308
324
|
def _get_jwt(
|
|
309
|
-
session: ClientSession,
|
|
325
|
+
session: ClientSession,
|
|
326
|
+
access_token: str | None = None,
|
|
327
|
+
only_if_cached: bool = False,
|
|
328
|
+
refresh: bool = False,
|
|
310
329
|
) -> Response:
|
|
311
330
|
headers = {'Authorization': f'Bearer {access_token}'} if access_token else {}
|
|
312
331
|
return session.get(
|
|
313
332
|
f'{API_V0}/users/api_token',
|
|
314
333
|
headers=headers,
|
|
315
334
|
only_if_cached=only_if_cached, # If True, will return a 504 if not cached
|
|
335
|
+
force_refresh=refresh,
|
|
316
336
|
raise_for_status=False, # type: ignore
|
|
317
337
|
)
|
|
@@ -49,6 +49,7 @@ from pyinaturalist.constants import (
|
|
|
49
49
|
REQUESTS_PER_SECOND,
|
|
50
50
|
RETRY_BACKOFF,
|
|
51
51
|
RETRY_STATUSES,
|
|
52
|
+
RETRYABLE_CONNECTION_ERRORS,
|
|
52
53
|
WRITE_HTTP_METHODS,
|
|
53
54
|
WRITE_TIMEOUT,
|
|
54
55
|
FileOrPath,
|
|
@@ -356,12 +357,12 @@ class ClientSession(CacheMixin, LimiterMixin, Session):
|
|
|
356
357
|
timeout=timeout, # type: ignore[arg-type]
|
|
357
358
|
**kwargs,
|
|
358
359
|
)
|
|
359
|
-
# Handle
|
|
360
|
+
# Handle connection errors not captured by urllib3 retry handling (write timeouts, remote disconnects);
|
|
360
361
|
except ConnectionError as e:
|
|
361
|
-
if
|
|
362
|
+
if not any(msg in str(e).lower() for msg in RETRYABLE_CONNECTION_ERRORS):
|
|
362
363
|
raise
|
|
363
|
-
_logger.debug('
|
|
364
|
-
_logger.warning('
|
|
364
|
+
_logger.debug('Connection error:', exc_info=True)
|
|
365
|
+
_logger.warning('Connection error; retrying...')
|
|
365
366
|
|
|
366
367
|
# Reuse the same retry object to share retry state and limits
|
|
367
368
|
retries = retries or self.retries
|
|
@@ -42,6 +42,10 @@ REQUEST_TIMEOUT = 20
|
|
|
42
42
|
REQUEST_RETRIES = 5 # Maximum number of retries for a failed request
|
|
43
43
|
RETRY_BACKOFF = 0.5 # Exponential backoff factor for retries
|
|
44
44
|
RETRY_STATUSES = (500, 502, 503, 504)
|
|
45
|
+
RETRYABLE_CONNECTION_ERRORS = (
|
|
46
|
+
'write operation timed out',
|
|
47
|
+
'remote end closed connection without response',
|
|
48
|
+
)
|
|
45
49
|
WRITE_TIMEOUT = 60
|
|
46
50
|
|
|
47
51
|
# HTTP methods that apply to write-only dry-run mode
|
|
@@ -152,6 +156,13 @@ RANK_LEVELS = {
|
|
|
152
156
|
}
|
|
153
157
|
RANKS = list(RANK_LEVELS.keys())[:-2]
|
|
154
158
|
|
|
159
|
+
# When there are multiple ranks for a given level, this maps to the most representative/common one
|
|
160
|
+
RANK_LEVEL_REPRESENTATIVE_RANKS = {
|
|
161
|
+
5: 'subspecies',
|
|
162
|
+
10: 'species',
|
|
163
|
+
20: 'genus',
|
|
164
|
+
}
|
|
165
|
+
|
|
155
166
|
# The main 7 taxonomic ranks, used for condensed display
|
|
156
167
|
COMMON_RANKS = [
|
|
157
168
|
'species',
|
|
@@ -176,7 +187,6 @@ RANK_EQUIVALENTS = {
|
|
|
176
187
|
'var': 'variety',
|
|
177
188
|
}
|
|
178
189
|
|
|
179
|
-
|
|
180
190
|
# Type aliases
|
|
181
191
|
# Multiple-choice parameter type aliases
|
|
182
192
|
Inbox = Literal['inbox', 'sent', 'any']
|
{pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/controllers/search_controller.py
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from pyinaturalist.constants import MultiInt, MultiStr
|
|
2
2
|
from pyinaturalist.controllers import BaseController
|
|
3
3
|
from pyinaturalist.models import SearchResult
|
|
4
|
-
from pyinaturalist.v1 import search
|
|
4
|
+
from pyinaturalist.v1.search import search
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class SearchController(BaseController):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = 'pyinaturalist'
|
|
3
|
-
version = "1.0.0.
|
|
3
|
+
version = "1.0.0.dev2"
|
|
4
4
|
description = 'iNaturalist API client for python'
|
|
5
5
|
authors = [{ name = 'Jordan Cook' }]
|
|
6
6
|
license = 'MIT'
|
|
@@ -136,7 +136,7 @@ fix = true
|
|
|
136
136
|
unsafe-fixes = true
|
|
137
137
|
line-length = 100
|
|
138
138
|
output-format = 'grouped'
|
|
139
|
-
exclude = ['examples/', 'scripts/specs/', 'test/sample_data/']
|
|
139
|
+
exclude = ['README.md', 'examples/', 'scripts/specs/', 'test/sample_data/']
|
|
140
140
|
|
|
141
141
|
[tool.ruff.format]
|
|
142
142
|
quote-style = 'single'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/client/oauth_callback.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/controllers/base_controller.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/controllers/place_controller.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/controllers/taxon_controller.py
RENAMED
|
File without changes
|
{pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/controllers/user_controller.py
RENAMED
|
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
|
{pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/conservation_status.py
RENAMED
|
File without changes
|
{pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/controlled_term.py
RENAMED
|
File without changes
|
{pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/identification.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/models/observation_field.py
RENAMED
|
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
|
{pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v0/observation_fields.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyinaturalist-1.0.0.dev1 → pyinaturalist-1.0.0.dev2}/pyinaturalist/v1/observation_fields.py
RENAMED
|
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
|