Skip to content

Commit

Permalink
Merge pull request #44 from JWCook/dev
Browse files Browse the repository at this point in the history
Add Observation species counts endpoint from Node API
  • Loading branch information
JWCook committed Jul 11, 2020
2 parents 884a28a + 616f763 commit 6fc2dba
Show file tree
Hide file tree
Showing 11 changed files with 1,052 additions and 51 deletions.
2 changes: 2 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ History
* Added new functions for Places endpoints: ``get_places_by_id()``, ``get_places_nearby()``, ``get_places_autocomplete()``
* Updated ``get_taxa_by_id()`` to accept multiple IDs
* Updated ``rest_api.get_observations()`` to convert coordinate strings to floats
* Added new function for an additional Observations endpoint: ``node_api.get_observation_species_counts()``
* Added parameter validation for multiple-choice request parameters

0.10.0 (2020-06-16)
^^^^^^^^^^^^^^^^^^^
Expand Down
65 changes: 40 additions & 25 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ Pyinaturalist is under active development. Currently, a handful of the most rele
are implemented, including:

* Searching, creating, and updating observations and observation fields
* Searching for species
* Searching for places, species, and species counts
* Text search autocompletion for species and places

See below for some examples,
see `Reference <https://pyinaturalist.readthedocs.io/en/latest/reference.html>`_ for a complete list, and
Expand All @@ -66,27 +67,29 @@ Examples
Observations
^^^^^^^^^^^^

Search all observations matching a criteria:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Search observations:
~~~~~~~~~~~~~~~~~~~~
.. code-block:: python
from pyinaturalist.node_api import get_all_observations
obs = get_all_observations(params={'user_id': 'niconoe'})
See `available parameters <https://api.inaturalist.org/v1/docs/#!/Observations/get_observations/>`_.

For authenticated API calls, you first need to obtain a token for the user:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Get an access token:
~~~~~~~~~~~~~~~~~~~~
For authenticated API calls (creating/updating/deleting data), you first need to obtain an access token.
This requires creating an `iNaturalist app <https://www.inaturalist.org/oauth/applications/new>`_.

.. code-block:: python
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 <https://www.inaturalist.org/oauth/applications/new>`_.
token = get_access_token(
username='<your_inaturalist_username>',
password='<your_inaturalist_password>',
app_id='<your_inaturalist_app_id>',
app_secret='<your_inaturalist_app_secret>',
)
Create a new observation:
~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -130,23 +133,24 @@ Update an existing observation of yours:
'observation': {'description': 'updated description !'}}
r = update_observation(observation_id=17932425, params=p, access_token=token)
Get a list of all (globally available) observation fields:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: python
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:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set an observation field value on an existing observation:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: python
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)
put_observation_field_values(
observation_id=7345179,
observation_field_id=9613,
value=250,
access_token=token,
)
Get observation data in alternative formats:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand All @@ -159,11 +163,23 @@ A separate endpoint can provide other data formats, including Darwin Core, KML,
See `available parameters and formats <https://www.inaturalist.org/pages/api+reference#get-observations>`_.

Get observation species counts:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There is an additional endpoint to get counts of observations by species.
On the iNaturalist web UI, this information can be found on the 'Species' tab of search results.
For example, to get the counts of all your own research-grade observations:

.. code-block:: python
from pyinaturalist.node_api import get_observation_species_counts
obs_counts = get_observation_species_counts(user_id='my_username', quality_grade='research')
Taxonomy
^^^^^^^^

Search for all taxa matching some criteria:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Search species and other taxa:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Let's say you partially remember either a genus or family name that started with **'vespi'**-something:

.. code-block:: python
Expand All @@ -173,7 +189,6 @@ Let's say you partially remember either a genus or family name that started with
>>> print({taxon["id"]: taxon["name"] for taxon in response["results"]})
{52747: "Vespidae", 84737: "Vespina", 92786: "Vespicula", 646195: "Vespiodes", ...}
Oh, that's right, it was **'Vespidae'**! Now let's find all of its subfamilies using its taxon ID
from the results above:

Expand All @@ -183,8 +198,8 @@ from the results above:
>>> print({taxon["id"]: taxon["name"] for taxon in response["results"]})
{343248: "Polistinae", 84738: "Vespinae", 119344: "Eumeninae", 121511: "Masarinae", ...}
Get a specific taxon by ID:
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Get a species by ID:
~~~~~~~~~~~~~~~~~~~~
Let's find out more about this 'Polistinae' genus. We could search for it by name or by ID,
but since we already know the ID from the previous search, let's use that:

Expand Down Expand Up @@ -246,7 +261,7 @@ If you get unexpected matches, the search likely matched a synonym, either in th
common name or an alternative classification. Check the ``matched_term`` property for more
info. For example:

.. code-block:: python
.. code-block:: python
>>> first_result = get_taxa_autocomplete(q='zygoca')['results'][0]
>>> first_result["name"]
Expand Down
3 changes: 2 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# General information about the project.
project = "pyinaturalist"
copyright = "2020, Nicolas Noé"
needs_sphinx = "3.0" # Minimum Sphinx version; needed for latest version of autodoc type hints
needs_sphinx = "3.0"
master_doc = "index"
source_suffix = ".rst"
version = release = __version__
Expand All @@ -29,6 +29,7 @@
"sphinx.ext.intersphinx",
"sphinx.ext.napoleon",
"sphinx.ext.viewcode",
"sphinx_autodoc_typehints",
"sphinxcontrib.apidoc",
]

Expand Down
45 changes: 44 additions & 1 deletion pyinaturalist/constants.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from datetime import date, datetime
from typing import Any, Dict, List, Union

INAT_NODE_API_BASE_URL = "https://api.inaturalist.org/v1/"
INAT_BASE_URL = "https://www.inaturalist.org"

Expand All @@ -9,6 +12,14 @@
DRY_RUN_WRITE_ONLY = False # Only mock 'write' requests
WRITE_HTTP_METHODS = ["PATCH", "POST", "PUT", "DELETE"]

# Type aliases
Date = Union[date, datetime, str]
DateTime = Union[date, datetime, str]
IntOrStr = Union[int, str]
JsonResponse = Dict[str, Any]
MultiInt = Union[int, List[int]]
MultiStr = Union[str, List[str]]

# Basic observation attributes to include by default in geojson responses
DEFAULT_OBSERVATION_ATTRS = [
"id",
Expand Down Expand Up @@ -45,14 +56,39 @@
# Reponse formats supported by GET /observations endpoint
OBSERVATION_FORMATS = ["atom", "csv", "dwc", "json", "kml", "widget"]

# Taxonomic ranks from Node API Swagger spec
# Creative Commons license codes
CC_LICENSES = ["CC-BY", "CC-BY-NC", "CC-BY-ND", "CC-BY-SA", "CC-BY-NC-ND", "CC-BY-NC-SA", "CC0"]

# IUCN Conservation status codes; for more info, see: https://www.iucnredlist.org
CONSERVATION_STATUSES = ["LC", "NT", "VU", "EN", "CR", "EW", "EX"]

# Main taxa "categories" that can be filtered on
ICONIC_TAXA = {
0: "Unknown",
1: "Animalia",
3: "Aves",
20978: "Amphibia",
26036: "Reptilia",
40151: "Mammalia",
47178: "Actinopterygii",
47115: "Mollusca",
47119: "Arachnida",
47158: "Insecta",
47126: "Plantae",
47170: "Fungi",
48222: "Chromista",
47686: "Protozoa",
}

# Taxonomic ranks that can be filtered on
RANKS = [
"form",
"variety",
"subspecies",
"hybrid",
"species",
"genushybrid",
"subgenus",
"genus",
"subtribe",
"tribe",
Expand All @@ -65,10 +101,17 @@
"suborder",
"order",
"superorder",
"infraclass",
"subclass",
"class",
"superclass",
"subphylum",
"phylum",
"kingdom",
]

# Additional options for multiple-choice search filters, used for validation
COMMUNITY_ID_STATUSES = ["most_agree", "most_disagree", "some_agree"]
GEOPRIVACY_LEVELS = ["obscured", "obscured_private", "open", "private"]
SEARCH_PROPERTIES = ["names", "tags", "description", "place"]
QUALITY_GRADES = ["casual", "needs_id", "research"]
115 changes: 115 additions & 0 deletions pyinaturalist/docstrings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
""" Reusable portions of API documentation, to help reduce verbosity of particularly long lists of
request parameter descriptions.
"""
from typing import Callable, List
from pyinaturalist.request_params import MULTIPLE_CHOICE_PARAMS


def append(func: Callable, doc_chunks: List[str]):
""" Append information to a function's docstring """
func.__doc__ = func.__doc__ or "" # Makes type checker happy
for chunk in doc_chunks:
func.__doc__ += chunk


def _format_param_choices():
return "\n".join(
[" * {}: {}".format(param, choices) for param, choices in MULTIPLE_CHOICE_PARAMS.items()]
)


MULTIPLE_CHOICE_PARAM_DOCS = "**Multiple-Choice Parameters:**\n" + _format_param_choices()

GET_OBSERVATIONS = """
Args:
acc: Whether or not positional accuracy / coordinate uncertainty has been specified
captive: Captive or cultivated observations
endemic: Observations whose taxa are endemic to their location
geo: Observations that are georeferenced
id_please: Observations with the **deprecated** "ID, Please!" flag.
Note that this will return observations, but that this attribute is no longer used.
identified: Observations that have community identifications
introduced: Observations whose taxa are introduced in their location
mappable: Observations that show on map tiles
native: Observations whose taxa are native to their location
out_of_range: Observations whose taxa are outside their known ranges
pcid: Observations identified by the curator of a project. If the project_id parameter
is also specified, this will only consider observations identified by curators of the
specified project(s)
photos: Observations with photos
popular: Observations that have been favorited by at least one user
sounds: Observations with sounds
taxon_is_active: Observations of active taxon concepts
threatened: Observations whose taxa are threatened in their location
verifiable: Observations with a quality_grade of either needs_id or research. Equivalent to quality_grade=needs_id,research
id: Must have this ID
not_id: Must not have this ID
license: Observation must have this license
ofv_datatype: Must have an observation field value with this datatype
photo_license: Must have at least one photo with this license
place_id: Must be observed within the place with this ID
project_id: Must be added to the project this ID or slug
rank: Taxon must have this rank
site_id: Must be affiliated with the iNaturalist network website with this ID
sound_license: Must have at least one sound with this license
taxon_id: Only show observations of these taxa and their descendants
without_taxon_id: Exclude observations of these taxa and their descendants
taxon_name: Taxon must have a scientific or common name matching this string
user_id: User must have this ID or login
user_login: User must have this login
day: Must be observed within this day of the month
month: Must be observed within this month
year: Must be observed within this year
term_id: Must have an annotation using this controlled term ID
term_value_id: Must have an annotation using this controlled value ID.
Must be combined with the term_id parameter
without_term_value_id: Exclude observations with annotations using this controlled value ID.
Must be combined with the term_id parameter
acc_above: Must have an positional accuracy above this value (meters)
acc_below: Must have an positional accuracy below this value (meters)
d1: Must be observed on or after this date
d2: Must be observed on or before this date
created_d1: Must be created at or after this time
created_d2: Must be created at or before this time
created_on: Must be created on this date
observed_on: Must be observed on this date
unobserved_by_user_id: Must not be of a taxon previously observed by this user
apply_project_rules_for: Must match the rules of the project with this ID or slug
cs: Taxon must have this conservation status code. If the place_id parameter is also
specified, this will only consider statuses specific to that place
csa: Taxon must have a conservation status from this authority. If the place_id parameter is
also specified, this will only consider statuses specific to that place
csi: Taxon must have this IUCN conservation status. If the place_id parameter is also
specified, this will only consider statuses specific to that place
geoprivacy: Must have this geoprivacy setting
taxon_geoprivacy: Filter observations by the most conservative geoprivacy applied by a
conservation status associated with one of the taxa proposed in the current
identifications.
hrank: Taxon must have this rank or lower
lrank: Taxon must have this rank or higher
iconic_taxa: Taxon must by within this iconic taxon
id_above: Must have an ID above this value
id_below: Must have an ID below this value
identifications: Identifications must meet these criteria
lat: Must be within a ``radius`` kilometer circle around this lat/lng (lat, lng, radius)
lng: Must be within a ``radius`` kilometer circle around this lat/lng (lat, lng, radius)
radius: Must be within a {radius} kilometer circle around this lat/lng (lat, lng, radius)
nelat: NE latitude of bounding box
nelng: NE longitude of bounding box
swlat: SW latitude of bounding box
swlng: SW longitude of bounding box
list_id: Taxon must be in the list with this ID
not_in_project: Must not be in the project with this ID or slug
not_matching_project_rules_for: Must not match the rules of the project with this ID or slug
q: Search observation properties. Can be combined with search_on
search_on: Properties to search on, when combined with q. Searches across all properties by
default
quality_grade: Must have this quality grade
updated_since: Must be updated since this time
viewer_id: See reviewed
reviewed: Observations have been reviewed by the user with ID equal to the value of the
``viewer_id`` parameter
locale: Locale preference for taxon common names
preferred_place_id: Place preference for regional taxon common names
ttl: Set the ``Cache-Control`` HTTP header with this value as ``max-age``, in seconds
"""

0 comments on commit 6fc2dba

Please sign in to comment.