pyinaturalist¶

Python client for the iNaturalist APIs.
Status¶
Work in progress: features are implemented one by one, as time allows and as the authors needs them.
That being said, many things are already possible (searching observations, creating a new observation, …) and contributions are welcome!
Python 3 only.
Examples¶
Search all observations matching a criteria:¶
from pyinaturalist.node_api import get_all_observations
obs = get_all_observations(params={'user_id': 'niconoe'})
see available parameters.
For authenticated API calls, you first need to obtain a token for the user:¶
from pyinaturalist.rest_api import get_access_token
token = get_access_token(username='<your_inaturalist_username>', password='<your_inaturalist_password>',
app_id='<your_inaturalist_app_id>',
app_secret=<your_inaturalist_app_secret>)
Note: you’ll need to create an iNaturalist app.
Create a new observation:¶
from pyinaturalist.rest_api import create_observations
params = {'observation':
{'taxon_id': 54327, # Vespa Crabro
'observed_on_string': datetime.datetime.now().isoformat(),
'time_zone': 'Brussels',
'description': 'This is a free text comment for the observation',
'tag_list': 'wasp, Belgium',
'latitude': 50.647143,
'longitude': 4.360216,
'positional_accuracy': 50, # meters,
# sets vespawatch_id (an observation field whose ID is 9613) to the value '100'.
'observation_field_values_attributes':
[{'observation_field_id': 9613,'value': 100}],
},
}
r = create_observations(params=params, access_token=token)
new_observation_id = r[0]['id']
Upload a picture for this observation:¶
from pyinaturalist.rest_api import add_photo_to_observation
r = add_photo_to_observation(observation_id=new_observation_id,
file_object=open('/Users/nicolasnoe/vespa.jpg', 'rb'),
access_token=token)
Update an existing observation of yours:¶
from pyinaturalist.rest_api import update_observation
p = {'ignore_photos': 1, # Otherwise existing pictures will be deleted
'observation': {'description': 'updated description !'}}
r = update_observation(observation_id=17932425, params=p, access_token=token)
Get a list of all (globally available) observation fields:¶
from pyinaturalist.rest_api import get_all_observation_fields
r = get_all_observation_fields(search_query="DNA")
Sets an observation field value to an existing observation:¶
from pyinaturalist.rest_api import put_observation_field_values
put_observation_field_values(observation_id=7345179,
observation_field_id=9613,
value=250,
access_token=token)
Contents:¶
Installation¶
Simply use pip:
$ pip install pyinaturalist
Or if you prefer using the development version:
$ pip install git+https://github.com/niconoe/pyinaturalist.git
Or, to set up for local development (preferably in a new virtualenv):
$ git clone https://github.com/niconoe/pyinaturalist.git
$ cd pyinaturalist
$ pip install -Ue ".[dev]"
Reference¶
iNaturalist actually provides two APIs:
the REST API that they also use internally: it is very complete and provides read/write access, but is rather slow and sometimes inconsistent.
the Node-based API allows searching and returning core data, is faster and provides more consistent returned results than the REST API, but has less features.
Pyinaturalist provides functions to use both of those APIs.
Note
While not mandatory, it is considered good practice in the iNaturalist community to set a custom user-agent header to your API calls. That allows iNaturalist to identify “who’s doing what” with their APIs, and maybe contact you back in case they want to start a discussion about how you use them.
It is recommended to set this user-agent field to either something that identifies the project (MyCoolAndroidApp/2.0) or its contact person (Jane Doe, iNat user XXXXXX, jane@doe.net).
Pyinaturalist therefore provides a couple of features to make that easy:
import pyinaturalist
from pyinaturalist.node_api import get_observation
pyinaturalist.user_agent = "MyCoolAndroidApp/2.0 (using Pyinaturalist)"
# From now on, all API calls will use this user-agent.
t = get_access_token('username', 'password', 'app_id', 'app_secret')
do_something_else()
get_observation(observation_id=1234)
...
In the rare cases where you want to use multiple user agents in your script, you can configure it per call:
get_observation(observation_id=16227955, user_agent='AnotherUserAgent')
(All functions that communicate with the API accept the user_agent optional parameter).
If you don’t configure the user agent, Pyinaturalist/<VERSION> will be used.
REST API¶
-
pyinaturalist.rest_api.
add_photo_to_observation
(observation_id, file_object, access_token, user_agent=None)[source]¶ Upload a picture and assign it to an existing observation.
- Parameters
observation_id (
int
) – the ID of the observationfile_object (
BinaryIO
) – a file-like object for the picture. Example: open(‘/Users/nicolasnoe/vespa.jpg’, ‘rb’)access_token (
str
) – the access token, as returned byget_access_token()
user_agent (
Optional
[str
]) – a user-agent string that will be passed to iNaturalist.
-
pyinaturalist.rest_api.
create_observations
(params, access_token, user_agent=None)[source]¶ Create a single or several (if passed an array) observations).
- Parameters
params (
Dict
[str
,Dict
[str
,Any
]]) –access_token (
str
) – the access token, as returned byget_access_token()
user_agent (
Optional
[str
]) – a user-agent string that will be passed to iNaturalist.
- Return type
List
[Dict
[str
,Any
]]- Returns
iNaturalist’s JSON response, as a Python object
- Raise
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 give details about the errors.
allowed params: see https://www.inaturalist.org/pages/api+reference#post-observations
Example:
- params = {‘observation’:
{‘species_guess’: ‘Pieris rapae’},
}
TODO investigate: according to the doc, we should be able to pass multiple observations (in an array, and in renaming observation to observations, but as far as I saw they are not created (while a status of 200 is returned)
-
pyinaturalist.rest_api.
delete_observation
(observation_id, access_token, user_agent=None)[source]¶ Delete an observation.
- Parameters
observation_id (
int
) –access_token (
str
) –user_agent (
Optional
[str
]) – a user-agent string that will be passed to iNaturalist.
- Return type
List
[Dict
[str
,Any
]]- Returns
iNaturalist’s JSON response, as a Python object (currently raise a JSONDecodeError because of an iNaturalist bug
- Raise
ObservationNotFound if the requested observation doesn’t exists, requests.HTTPError (403) if the observation belongs to another user
-
pyinaturalist.rest_api.
get_access_token
(username, password, app_id, app_secret, user_agent=None)[source]¶ Get an access token using the user’s iNaturalist username and password.
(you still need an iNaturalist app to do this)
- Parameters
username (
str
) –password (
str
) –app_id (
str
) –app_secret (
str
) –user_agent (
Optional
[str
]) – a user-agent string that will be passed to iNaturalist.
- Return type
str
- Returns
the access token, example use: headers = {“Authorization”: “Bearer %s” % access_token}
-
pyinaturalist.rest_api.
get_all_observation_fields
(search_query='', user_agent=None)[source]¶ Like get_observation_fields(), but handles pagination for you.
- Parameters
search_query (
str
) – a string to searchuser_agent (
Optional
[str
]) – a user-agent string that will be passed to iNaturalist.
- Return type
List
[Dict
[str
,Any
]]
-
pyinaturalist.rest_api.
get_observation_fields
(search_query='', page=1, user_agent=None)[source]¶ Search the (globally available) observation
- Parameters
search_query (
str
) –page (
int
) –user_agent (
Optional
[str
]) – a user-agent string that will be passed to iNaturalist.
- Return type
List
[Dict
[str
,Any
]]- Returns
-
pyinaturalist.rest_api.
put_observation_field_values
(observation_id, observation_field_id, value, access_token, user_agent=None)[source]¶ Sets an observation field (value) on an observation.
- Parameters
observation_id (
int
) –observation_field_id (
int
) –value (
Any
) –access_token (
str
) – access_token: the access token, as returned byget_access_token()
user_agent (
Optional
[str
]) – a user-agent string that will be passed to iNaturalist.
- Return type
Dict
[str
,Any
]- Returns
iNaturalist’s response as a dict, for example: {‘id’: 31,
’observation_id’: 18166477, ‘observation_field_id’: 31, ‘value’: ‘fouraging’, ‘created_at’: ‘2012-09-29T11:05:44.935+02:00’, ‘updated_at’: ‘2018-11-13T10:49:47.985+01:00’, ‘user_id’: 1, ‘updater_id’: 1263313, ‘uuid’: ‘b404b654-1bf0-4299-9288-52eeda7ac0db’, ‘created_at_utc’: ‘2012-09-29T09:05:44.935Z’, ‘updated_at_utc’: ‘2018-11-13T09:49:47.985Z’}
Will fail if this observation_field is already set for this observation.
-
pyinaturalist.rest_api.
update_observation
(observation_id, params, access_token, user_agent=None)[source]¶ Update a single observation. See https://www.inaturalist.org/pages/api+reference#put-observations-id
- Parameters
observation_id (
int
) – the ID of the observation to updateparams (
Dict
[str
,Any
]) – to be passed to iNaturalist APIaccess_token (
str
) – the access token, as returned byget_access_token()
user_agent (
Optional
[str
]) – a user-agent string that will be passed to iNaturalist.
- Return type
List
[Dict
[str
,Any
]]- Returns
iNaturalist’s JSON response, as a Python object
- Raise
requests.HTTPError, if the call is not successful. iNaturalist returns an error 410 if the observation doesn’t exists or belongs to another user (as of November 2018).
Node-based API¶
-
pyinaturalist.node_api.
get_all_observations
(params, user_agent=None)[source]¶ Like get_observations() but handles pagination so you get all the results in one shot.
Some params will be overwritten: order_by, order, per_page, id_above (do NOT specify page when using this).
Returns a list of dicts (one entry per observation)
- Return type
List
[Dict
[str
,Any
]]
-
pyinaturalist.node_api.
get_observation
(observation_id, user_agent=None)[source]¶ Get details about an observation.
- Parameters
observation_id (
int
) –user_agent (
Optional
[str
]) – a user-agent string that will be passed to iNaturalist.
- Return type
Dict
[str
,Any
]- Returns
a dict with details on the observation
- Raises
ObservationNotFound
-
pyinaturalist.node_api.
get_observations
(params, user_agent=None)[source]¶ Search observations, see: http://api.inaturalist.org/v1/docs/#!/Observations/get_observations.
Returns the parsed JSON returned by iNaturalist (observations in r[‘results’], a list of dicts)
- Return type
Dict
[str
,Any
]
-
pyinaturalist.node_api.
get_rank_range
(min_rank=None, max_rank=None)[source]¶ Translate min and/or max rank into a list of ranks
- Return type
List
[str
]
-
pyinaturalist.node_api.
get_taxa
(user_agent=None, min_rank=None, max_rank=None, **params)[source]¶ Given zero to many of following parameters, returns taxa matching the search criteria. See https://api.inaturalist.org/v1/docs/#!/Taxa/get_taxa
- Parameters
q – Name must begin with this value
is_active – Taxon is active
taxon_id – Only show taxa with this ID, or its descendants
parent_id – Taxon’s parent must have this ID
rank – Taxon must have this exact rank
min_rank (
Optional
[str
]) – Taxon must have this rank or higher; overridesrank
max_rank (
Optional
[str
]) – Taxon must have this rank or lower; overridesrank
rank_level – Taxon must have this rank level. Some example values are 70 (kingdom), 60 (phylum), 50 (class), 40 (order), 30 (family), 20 (genus), 10 (species), 5 (subspecies)
id_above – Must have an ID above this value
id_below – Must have an ID below this value
per_page – Number of results to return in a page. The maximum value is generally 200 unless otherwise noted
locale – Locale preference for taxon common names
preferred_place_id – Place preference for regional taxon common names
only_id – Return only the record IDs
all_names – Include all taxon names in the response
- Return type
Dict
[str
,Any
]- Returns
A list of dicts containing taxa results
-
pyinaturalist.node_api.
get_taxa_autocomplete
(user_agent=None, **params)[source]¶ Given a query string, returns taxa with names starting with the search term See: https://api.inaturalist.org/v1/docs/#!/Taxa/get_taxa_autocomplete
- Parameters
q – Name must begin with this value
is_active – Taxon is active
taxon_id – Only show taxa with this ID, or its descendants
rank – Taxon must have this rank
rank_level – Taxon must have this rank level. Some example values are 70 (kingdom), 60 (phylum), 50 (class), 40 (order), 30 (family), 20 (genus), 10 (species), 5 (subspecies)
per_page – Number of results to return in a page. The maximum value is generally 200 unless otherwise noted
locale – Locale preference for taxon common names
preferred_place_id – Place preference for regional taxon common names
all_names – Include all taxon names in the response
- Return type
Dict
[str
,Any
]- Returns
A list of dicts containing taxa results
-
pyinaturalist.node_api.
get_taxa_by_id
(taxon_id, user_agent=None)[source]¶ Get one or more taxa by ID. See: https://api.inaturalist.org/v1/docs/#!/Taxa/get_taxa_id
- Param
taxon_id: Get taxa with this ID. Multiple values are allowed.
- Return type
Dict
[str
,Any
]- Returns
A list of dicts containing taxa results
-
pyinaturalist.node_api.
make_inaturalist_api_get_call
(endpoint, params, user_agent=None, **kwargs)[source]¶ Make an API call to iNaturalist.
endpoint is a string such as ‘observations’ method: ‘GET’, ‘HEAD’, ‘POST’, ‘PUT’, ‘PATCH’, ‘DELETE’ kwargs are passed to requests.request Returns a requests.Response object
- Return type
Response
Contributing¶
Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.
You can contribute in many ways:
Types of Contributions¶
Report Bugs¶
Report bugs at https://github.com/niconoe/pyinaturalist/issues.
If you are reporting a bug, please include:
Your operating system name and version.
Any details about your local setup that might be helpful in troubleshooting.
Detailed steps to reproduce the bug.
Fix Bugs¶
Look through the GitHub issues for bugs. Anything tagged with “bug” is open to whoever wants to implement it.
Implement Features¶
Look through the GitHub issues for features. Anything tagged with “feature” is open to whoever wants to implement it.
Write Documentation¶
pyinaturalist could always use more documentation, whether as part of the official pyinaturalist docs, in docstrings, or even on the web in blog posts, articles, and such.
Submit Feedback¶
The best way to send feedback is to file an issue at https://github.com/niconoe/pyinaturalist/issues.
If you are proposing a feature:
Explain in detail how it would work.
Keep the scope as narrow as possible, to make it easier to implement.
Remember that this is a volunteer-driven project, and that contributions are welcome :)
Infrastructure¶
Documentation¶
We use Sphinx, and the references page is automatically generated thanks to
sphinx.ext.autodoc
and sphinx_autodoc_typehints
extensions. All functions / methods / classes should have a
proper docstring.
To build the doc locally:
$ tox -e docs
To preview:
# MacOS:
$ open docs/_build/index.html
# Linux:
$ xdg-open docs/_build/index.html
Hosted documentation (https://pyinaturalist.readthedocs.io/) is automatically updated when code gets pushed to GitHub.
Testing¶
We use the pytest framework.
To run locally:
$ pytest
It is however always good to run tox
frequently, to run the tests against multiple Python versions, as well as some
style and type annotations checks:
$ tox
Travis-CI is run when code is pushed to GitHub.
Type annotations¶
All functions / methods should have parameters and return value type annotations. Those type annotations are checked by
MyPy (tox -e mypy
) and will appear in the documentation.
Releasing at PyPI¶
Release checklist:
Make sure the code is tested, annotated and documented.
Update version in HISTORY.rst, setup.py and pyinaturalist/__init__.py
Create the distributions:
python setup.py sdist bdist_wheel
Use twine to upload the package to PyPI:
twine upload dist/*
Push a vX.Y.Z tag to GitHub:
git tag vX.Y.Z && git push origin --tags
Credits¶
Development Lead¶
Nicolas Noé <nicolas@niconoe.eu>
Contributors¶
Jordan Cook
Peter Desmet
Stijn Van Hoey
History¶
0.8.0 (2019-07-11)¶
all functions now take an optional user-agent parameter in order to identify yourself to iNaturalist. If not set, Pyinaturalist/<VERSION> will be used.
0.7.0 (2019-05-08)¶
rest_api.delete_observation() now raises ObservationNotFound if the observation doesn’t exists
minor dependencies update for security reasons
0.6.0 (2018-11-15)¶
New function: rest_api.delete_observation()
0.5.0 (2018-11-05)¶
New function: node_api.get_observation()
0.4.0 (2018-11-05)¶
create_observation() now raises exceptions in case of errors.
0.3.0 (2018-11-05)¶
update_observation() now raises exceptions in case of errors.
0.2.0 (2018-10-31)¶
Better infrastructure (type annotations, documentation, …)
Dropped support for Python 2.
New function: update_observation()
rest_api.AuthenticationError is now exceptions.AuthenticationError
0.1.0 (2018-10-10)¶
First release on PyPI.
Feedback¶
If you have any suggestions or questions about pyinaturalist feel free to email me at nicolas@niconoe.eu.
If you encounter any errors or problems with pyinaturalist, please let me know! Open an Issue at the GitHub http://github.com/niconoe/pyinaturalist main repository.