Skip to content

Commit

Permalink
Merge 79371b3 into 9a8eea9
Browse files Browse the repository at this point in the history
  • Loading branch information
JWCook committed Aug 1, 2021
2 parents 9a8eea9 + 79371b3 commit f938a5f
Show file tree
Hide file tree
Showing 15 changed files with 163 additions and 118 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ docs/modules/
.~*
.idea/
.ipynb_checkpoints/
.tmp
.vim/
.vscode/
swagger.json
Expand Down
10 changes: 10 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
'sphinx.ext.autosectionlabel',
'sphinx.ext.extlinks',
'sphinx.ext.intersphinx',
'sphinx.ext.napoleon',
'sphinx_autodoc_typehints',
Expand All @@ -80,10 +81,19 @@

nbsphinx_allow_errors = True

# Prefixes to shorten common links
extlinks = {
'issue': ('https://github.com/niconoe/pyinaturalist/issues/%s', 'issue #%s'),
'v0': ('https://www.inaturalist.org/pages/api+reference#', '%s'),
'v1': ('https://api.inaturalist.org/v1/docs/#/%s', '%s'),
'v2': ('https://api.inaturalist.org/v2/docs/#/%s', '%s'),
}

# Enable automatic links to other projects' Sphinx docs
intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
'requests': ('https://requests.readthedocs.io/en/master/', None),
'urllib3': ('https://urllib3.readthedocs.io/en/stable/', None),
}

# Generate labels in the format <page>:<section>
Expand Down
4 changes: 2 additions & 2 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
nox.options.sessions = ['lint', 'cov']

LIVE_DOCS_PORT = 8181
LIVE_DOCS_IGNORE = ['*.csv', '**/modules/*']
LIVE_DOCS_WATCH = ['pyinaturalist', 'examples/*.py']
LIVE_DOCS_IGNORE = ['*.csv', '*.tmp', '*.pyc', '**/modules/*']
LIVE_DOCS_WATCH = ['pyinaturalist', 'examples']
CLEAN_DIRS = ['dist', 'build', join('docs', '_build'), join('docs', 'models'), join('docs', 'modules')]


Expand Down
2 changes: 1 addition & 1 deletion pyinaturalist/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def get_access_token(
user_agent: A custom user-agent string to provide to the iNaturalist API
Raises:
:py:exc:`requests.HTTPError` (401) if credentials are invalid
:py:exc:`requests.HTTPError`: (401) if credentials are invalid
"""
payload = {
'username': username or getenv('INAT_USERNAME'),
Expand Down
16 changes: 10 additions & 6 deletions pyinaturalist/v0/observation_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ def get_observation_fields(**params) -> JsonResponse:
"""Search observation fields. Observation fields are basically typed data fields that
users can attach to observation.
**API reference:** https://www.inaturalist.org/pages/api+reference#get-observation_fields
.. rubric:: Notes
* API reference: :v0:`GET /observation_fields <get-observation_fields>`
Example:
Expand Down Expand Up @@ -48,13 +50,15 @@ def put_observation_field_values(
# TODO: Return some meaningful exception if it fails because the field is already set.
# TODO: It appears pushing the same value/pair twice in a row (but deleting it meanwhile via the UI)...
# TODO: ...triggers an error 404 the second time (report to iNaturalist?)
"""Set an observation field (value) on an observation.
Will fail if this observation field is already set for this observation.
"""Set an observation field (value) on an observation
To find an `observation_field_id`, either user :py:func:`.get_observation_fields` or search
on iNaturalist: https://www.inaturalist.org/observation_fields
.. rubric:: Notes
**API reference:** https://www.inaturalist.org/pages/api+reference#put-observation_field_values-id
* :fa:`lock` :ref:`Requires authentication <auth>`
* API reference: :v0:`PUT /observation_field_values/{id} <put-observation_field_values-id>`
* The request will fail if this observation field is already set for this observation
* To find an ``observation_field_id``, either user :py:func:`.get_observation_fields` or
`search observation fields on iNaturalist <https://www.inaturalist.org/observation_fields>`_
Example:
>>> # First find an observation field by name, if the ID is unknown
Expand Down
58 changes: 34 additions & 24 deletions pyinaturalist/v0/observations.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@
)
@add_paginate_all(method='page')
def get_observations(**params) -> Union[List, str]:
"""Get observation data, optionally in an alternative format. Also see
:py:func:`.get_geojson_observations` for GeoJSON format (not included here because it wraps
a separate API endpoint).
"""Get observation data, optionally in an alternative format
**API reference:** https://www.inaturalist.org/pages/api+reference#get-observations
.. rubric:: Notes
* API reference: :v0:`GET /observations <get-observations>`
Example:
Expand Down Expand Up @@ -92,9 +92,12 @@ def get_observations(**params) -> Union[List, str]:

@document_request_params(docs._access_token, docs._create_observation)
def create_observation(**params) -> ListResponse:
"""Create a new observation.
"""Create a new observation
.. rubric:: Notes
**API reference:** https://www.inaturalist.org/pages/api+reference#post-observations
* :fa:`lock` :ref:`Requires authentication <auth>`
* API reference: :v0:`POST /observations <post-observations>`
Example:
>>> token = get_access_token()
Expand All @@ -121,10 +124,7 @@ def create_observation(**params) -> ListResponse:
JSON response containing the newly created observation(s)
Raises:
:py:exc:`requests.HTTPError`, if the call is not successful. iNaturalist returns an
error 422 (unprocessable entity) if it rejects the observation data (for example an
observation date in the future or a latitude > 90. In that case the exception's
``response`` attribute gives more details about the errors.
:py:exc:`~urllib3.exceptions.HTTPError`: If the call is not successful. The exception's ``response`` attribute gives more details about the errors.
"""
params, photos, sounds, kwargs = convert_observation_params(params)
response = post(
Expand All @@ -149,10 +149,12 @@ def create_observation(**params) -> ListResponse:
docs._update_observation,
)
def update_observation(observation_id: int, **params) -> ListResponse:
"""
Update a single observation.
"""Update a single observation
**API reference:** https://www.inaturalist.org/pages/api+reference#put-observations-id
.. rubric:: Notes
* :fa:`lock` :ref:`Requires authentication <auth>`
* API reference: :v0:`PUT /observations/{id} <put-observations-id>`
.. note::
Expand Down Expand Up @@ -180,8 +182,7 @@ def update_observation(observation_id: int, **params) -> ListResponse:
JSON response containing the newly updated observation(s)
Raises:
:py:exc:`requests.HTTPError`, if the call is not successful. iNaturalist returns an
error 410 if the observation doesn't exists or belongs to another user.
:py:exc:`~urllib3.exceptions.HTTPError`: if the call is not successful. iNaturalist returns an error 410 if the observation doesn't exists or belongs to another user.
"""
params, photos, sounds, kwargs = convert_observation_params(params)
response = put(
Expand All @@ -198,10 +199,14 @@ def update_observation(observation_id: int, **params) -> ListResponse:


def upload_photos(observation_id: int, photos: MultiFile, **params) -> ListResponse:
"""Upload a local photo and assign it to an existing observation.
"""Upload a local photo and assign it to an existing observation
Example:
.. rubric:: Notes
* :fa:`lock` :ref:`Requires authentication <auth>`
* API reference: :v0:`POST /observation_photos <post-observation_photos>`
Example:
>>> token = get_access_token()
>>> upload_photos(1234, '~/observations/2020_09_01_14003156.jpg', access_token=token)
Expand Down Expand Up @@ -246,10 +251,13 @@ def upload_photos(observation_id: int, photos: MultiFile, **params) -> ListRespo


def upload_sounds(observation_id: int, sounds: MultiFile, **params) -> ListResponse:
"""Upload a local sound file and assign it to an existing observation.
"""Upload a local sound file and assign it to an existing observation
Example:
.. rubric:: Notes
* :fa:`lock` :ref:`Requires authentication <auth>`
Example:
>>> token = get_access_token()
>>> upload_sounds(1234, '~/observations/2020_09_01_14003156.mp3', access_token=token)
Expand Down Expand Up @@ -296,21 +304,23 @@ def upload_sounds(observation_id: int, sounds: MultiFile, **params) -> ListRespo
@document_request_params(docs._observation_id, docs._access_token)
def delete_observation(observation_id: int, **params):
"""
Delete an observation.
Delete an observation
**API reference:** https://www.inaturalist.org/pages/api+reference#delete-observations-id
.. rubric:: Notes
Example:
* :fa:`lock` :ref:`Requires authentication <auth>`
* API reference: :v0:`DELETE /observations/{id} <delete-observations-id>`
Example:
>>> token = get_access_token()
>>> delete_observation(17932425, token)
Returns:
If successful, no response is returned from this endpoint
Raises:
:py:exc:`.ObservationNotFound` if the requested observation doesn't exist
:py:exc:`requests.HTTPError` (403) if the observation belongs to another user
:py:exc:`.ObservationNotFound`: if the requested observation doesn't exist
:py:exc:`~urllib3.exceptions.HTTPError`: 403 if the observation belongs to another user
"""
response = delete(
url=f'{API_V0_BASE_URL}/observations/{observation_id}.json',
Expand Down
15 changes: 7 additions & 8 deletions pyinaturalist/v1/controlled_terms.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@


def get_controlled_terms(taxon_id: int = None, **params) -> JsonResponse:
"""List controlled terms and their possible values.
A taxon ID can optionally be provided to show only terms that are valid for that taxon.
Otherwise, all controlled terms will be returned.
"""List controlled terms and their possible values
**API reference:**
.. rubric:: Notes
* https://api.inaturalist.org/v1/docs/#!/Controlled_Terms/get_controlled_terms
* https://api.inaturalist.org/v1/docs/#!/Controlled_Terms/get_controlled_terms_for_taxon
* API reference: :v1:`GET /controlled_terms <Controlled_Terms/get_controlled_terms>`
* API reference: :v1:`GET /controlled_terms/for_taxon <Controlled_Terms/get_controlled_terms_for_taxon>`
* A taxon ID can optionally be provided to show only terms that are valid for that taxon.
Otherwise, all controlled terms will be returned.
Example:
>>> response = get_controlled_terms()
>>> pprint(response)
1: Life Stage
Expand All @@ -41,7 +40,7 @@ def get_controlled_terms(taxon_id: int = None, **params) -> JsonResponse:
A dict containing details on controlled terms and their values
Raises:
:py:exc:`.TaxonNotFound` If an invalid taxon_id is specified
:py:exc:`.TaxonNotFound`: If an invalid ``taxon_id`` is specified
"""
# This is actually two endpoints, but they are so similar it seems best to combine them
endpoint = 'controlled_terms/for_taxon' if taxon_id else 'controlled_terms'
Expand Down
14 changes: 8 additions & 6 deletions pyinaturalist/v1/identifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@


def get_identifications_by_id(identification_id: MultiInt, **params) -> JsonResponse:
"""Get one or more identification records by ID.
"""Get one or more identification records by ID
**API reference:** https://api.inaturalist.org/v1/docs/#!/Identifications/get_identifications_id
.. rubric:: Notes
Example:
* API reference: :v1:`GET /identifications/{id} <Identifications/get_identifications_id>`
Example:
>>> get_identifications_by_id(155554373)
.. admonition:: Example Response
Expand All @@ -36,12 +37,13 @@ def get_identifications_by_id(identification_id: MultiInt, **params) -> JsonResp
@document_request_params(docs._identification_params, docs._pagination, docs._only_id)
@add_paginate_all(method='page')
def get_identifications(**params) -> JsonResponse:
"""Search identifications.
"""Search identifications
**API reference:** https://api.inaturalist.org/v1/docs/#!/Identifications/get_identifications
.. rubric:: Notes
Example:
* API reference: :v1:`GET /identifications <Identifications/get_identifications>`
Example:
Get all of your own species-level identifications:
>>> response = get_identifications(user_login='my_username', rank='species')
Expand Down
Loading

0 comments on commit f938a5f

Please sign in to comment.