def get_taxa_by_id(taxon_id: MultiInt, **params) -> JsonResponse: """Get one or more taxa by ID .. rubric:: Notes * API reference: :v1:`GET /taxa/{id} <Taxa/get_taxa_id>` Example: >>> response = get_taxa_by_id(343248) >>> basic_fields = ['preferred_common_name', 'observations_count', 'wikipedia_url', 'wikipedia_summary'] >>> print({f: response['results'][0][f] for f in basic_fields}) { 'preferred_common_name': 'Paper Wasps', 'observations_count': 69728, 'wikipedia_url': 'http://en.wikipedia.org/wiki/Polistinae', 'wikipedia_summary': 'The Polistinae are eusocial wasps closely related to yellow jackets...', } .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_taxa_by_id.py Args: taxon_id: Get taxa with this ID. Multiple values are allowed. Returns: Response dict containing taxon records """ response = get_v1('taxa', ids=taxon_id, **params) taxa = response.json() taxa['results'] = convert_all_timestamps(taxa['results']) return taxa
def get_observation_popular_field_values(**params) -> JsonResponse: """Get controlled terms values and a monthly histogram of observations matching the search .. rubric:: Notes * API reference: :v1:`GET /observations/popular_field_values <Observations/get_observations_popular_field_values>` Example: >>> response = get_observation_popular_field_values( ... species_name='Danaus plexippus', place_id=24, ... ) .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_observation_popular_field_values.py Returns: Response dict. Each record contains a ``count``, a ``month_of_year`` histogram, a ``controlled_attribute``, and a ``controlled_value``. """ response_json = get_v1('observations/popular_field_values', **params).json() for r in response_json['results']: r['month_of_year'] = convert_histogram(r['month_of_year'], interval='month_of_year') return response_json
def get_users_autocomplete(q: str, **params) -> JsonResponse: """Given a query string, return users with names or logins starting with the search term .. rubric:: Notes * API reference: :v1:`GET /users/autocomplete <Users/get_users_autocomplete>` * Pagination is supported; default page size is 6, and max is 100. Example: >>> response = get_taxa_autocomplete(q='my_userna') >>> pprint(response) [1234] my_username [12345] my_username_2 .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_users_autocomplete.py Returns: Response dict containing user records """ response = get_v1('users/autocomplete', q=q, **params) users = response.json() users['results'] = convert_all_timestamps(users['results']) return users
def get_taxa(**params) -> JsonResponse: """Search taxa .. rubric:: Notes * API reference: :v1:`GET /taxa <Taxa/get_taxa>` Example: >>> response = get_taxa(q='vespi', rank=['genus', 'family']) >>> pprint(response) [52747] Family: Vespidae (Hornets, Paper Wasps, Potter Wasps, and Allies) [92786] Genus: Vespicula [646195] Genus: Vespiodes ... .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_taxa.json :language: JSON Returns: Response dict containing taxon records """ params = convert_rank_range(params) if params.get('page') == 'all': taxa = paginate_all(get_v1, 'taxa', **params) else: taxa = get_v1('taxa', **params).json() taxa['results'] = convert_all_timestamps(taxa['results']) return taxa
def get_places_autocomplete(q: str = None, **params) -> JsonResponse: """Given a query string, get places with names starting with the search term .. rubric:: Notes * API reference: :v1:`GET /places/autocomplete <Places/get_places_autocomplete>` * This endpoint accepts a ``per_page`` param, up to a max of 20 (default 10) * Pages beyond the first page cannot be retrieved. Use ``page=all`` to attempt to retrieve additional results. See :py:func:`.paginate_autocomplete` for more info. Example: >>> response = get_places_autocomplete('Irkutsk') >>> pprint(response) [11803] Irkutsk [41854] Irkutskiy rayon [166186] Irkutsk Oblast - ADD [163077] Irkutsk agglomeration .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_places_autocomplete.py Returns: Response dict containing place records """ if params.get('page') == 'all': pagninator = PlaceAutocompletePaginator(q=q, **params) places = pagninator.all() else: places = get_v1('places/autocomplete', q=q, **params).json() places['results'] = convert_all_coordinates(places['results']) return places
def get_user_by_id(user_id: IntOrStr, **params) -> JsonResponse: """Get a user by ID .. rubric:: Notes * API reference: :v1:`GET /users/{id} <Users/get_users_id>` Args: user_id: Get the user with this ID. Only a single ID is allowed per request. Example: >>> response = get_user_by_id(123456) >>> pprint(response) [1234] my_username .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_user_by_id.py Returns: Response dict containing user record """ response = get_v1('users', ids=user_id, allow_str_ids=True, **params) results = response.json()['results'] if not results: return {} return convert_generic_timestamps(results[0])
def get_messages(**params) -> JsonResponse: """Get messages from the user's inbox .. rubric:: Notes * :fa:`lock` :ref:`Requires authentication <auth>` * API reference: :v1:`GET /messages <Messages/get_messages>` Example: >>> response = get_messages() >>> pprint(response) .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_messages.json Returns: Response dict containing user record """ # `threads` is not compatible with `q` param, and includes totals from both inbox and sent if params.get('threads') is True: params['box'] = 'any' params['q'] = None response = get_v1('messages', **params) messages = response.json() messages['results'] = convert_all_timestamps(messages['results']) return messages
def get_identifications(**params) -> JsonResponse: """Search identifications .. rubric:: Notes * API reference: :v1:`GET /identifications <Identifications/get_identifications>` Example: Get all of your own species-level identifications: >>> response = get_identifications(user_login='******', rank='species') >>> print([f"{i['user']['login']}: {i['taxon_id']} ({i['category']})" for i in response['results']]) [155043569] Species: 76465 (leading) added on 2021-02-15 10:46:27-06:00 by jkcook [153668189] Species: 76465 (supporting) added on 2021-02-06 17:43:37+00:00 by jkcook [147500725] Species: 1163860 (improving) added on 2020-12-24 23:52:30+00:00 by jkcook ... .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_identifications.py Returns: Response dict containing identification records """ params = convert_rank_range(params) if params.get('page') == 'all': identifications = paginate_all(get_v1, 'identifications', **params) else: identifications = get_v1('identifications', **params).json() identifications['results'] = convert_all_timestamps( identifications['results']) return identifications
def get_observation_taxonomy(user_id: IntOrStr = None, **params) -> JsonResponse: """Get observation counts for all taxa in a full taxonomic tree. In the web UI, these are used for life lists. Args: user_id: iNaturalist user ID or username Example: >>> response = get_observation_taxonomy(user_id='my_username') .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_observation_taxonomy.json :language: JSON Returns: Response dict containing taxon records with counts """ if params.get('page') == 'all': return paginate_all(get_v1, 'observations/taxonomy', user_id=user_id, **params) else: return get_v1('observations/taxonomy', user_id=user_id, **params).json()
def get_identifications_by_id(identification_id: MultiInt, **params) -> JsonResponse: """Get one or more identification records by ID .. rubric:: Notes * API reference: :v1:`GET /identifications/{id} <Identifications/get_identifications_id>` Example: >>> get_identifications_by_id(155554373) .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_identifications.py Args: identification_id: Get taxa with this ID. Multiple values are allowed. Returns: Response dict containing identification records """ response = get_v1('identifications', ids=identification_id, **params) identifications = response.json() identifications['results'] = convert_all_timestamps( identifications['results']) return identifications
def get_observation_taxon_summary(observation_id: int, **params) -> JsonResponse: """Get information about an observation's taxon, within the context of the observation's location .. rubric:: Notes * API reference: :v1:`GET /observations/{id}/taxon_summary <Observations/get_observations_id_taxon_summary>` Args: observation_id: Observation ID to get taxon summary for Example: >>> response = get_observation_taxon_summary(7849808) .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_observation_taxon_summary.py Returns: Response dict containing taxon summary, optionally with conservation status and listed taxon """ results = get_v1(f'observations/{observation_id}/taxon_summary', **params).json() results['conservation_status'] == convert_generic_timestamps( results['conservation_status']) results['listed_taxon'] == convert_generic_timestamps( results['listed_taxon']) return results
def get_observation_species_counts(**params) -> JsonResponse: """Get all species (or other 'leaf taxa') associated with observations matching the search criteria, and the count of observations they are associated with. **Leaf taxa** are the leaves of the taxonomic tree, e.g., species, subspecies, variety, etc. .. rubric:: Notes * API reference: :v1:`GET /observations/species_counts <Observations/get_observations_species_counts>` Example: >>> response = get_observation_species_counts(user_login='******', quality_grade='research') >>> pprint(response) [62060] Species: Palomena prasina (Green Shield Bug): 10 [84804] Species: Graphosoma italicum (European Striped Shield Bug): 8 [55727] Species: Cymbalaria muralis (Ivy-leaved toadflax): 3 ... .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_observation_species_counts.py Returns: Response dict containing taxon records with counts """ if params.get('page') == 'all': return paginate_all(get_v1, 'observations/species_counts', **params) else: return get_v1('observations/species_counts', **params).json()
def get_places_by_id(place_id: MultiInt, **params) -> JsonResponse: """Get one or more places by ID .. rubric:: Notes * API reference: :v1:`GET /places/{id} <Places/get_places_id>` Example: >>> response = get_places_by_id([67591, 89191]) >>> pprint(response) [89191] Conservation Area Riversdale [67591] Riversdale Beach .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_places_by_id.py Args: place_id: Get a place with this ID. Multiple values are allowed. Returns: Response dict containing place records """ response = get_v1('places', ids=place_id, **params) # Convert coordinates to floats places = response.json() places['results'] = convert_all_coordinates(places['results']) return places
def get_observation_observers(**params) -> JsonResponse: """Get observers of observations matching the search criteria and the count of observations and distinct taxa of rank species they have observed. .. rubric:: Notes * API reference: :v1:`GET /observations/observers <Observations/get_observations_observers>` * Options for ``order_by`` are 'observation_count' (default) or 'species_count' * This endpoint will only return up to 500 results * See this issue for more details: https://github.com/inaturalist/iNaturalistAPI/issues/235 Example: >>> response = get_observation_observers(place_id=72645, order_by='species_count') >>> pprint(response) [1566366 ] fossa1211 [674557 ] schurchin [5813 ] fluffberger (Fluff Berger) .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_observation_observers_ex_results.json :language: JSON Returns: Response dict of observers """ params.setdefault('per_page', 500) response = get_v1('observations/observers', **params) return response.json()
def get_observation_identifiers(**params) -> JsonResponse: """Get identifiers of observations matching the search criteria and the count of observations they have identified. By default, results are sorted by ID count in descending. .. rubric:: Notes * API reference: :v1:`GET /observations/identifiers <Observations/get_observations_identifiers>` * This endpoint will only return up to 500 results. Example: >>> response = get_observation_identifiers(place_id=72645) >>> pprint(response) [409010 ] jdoe42 (Jane Doe) [691216 ] jbrown252 (James Brown) [3959037 ] tnsparkleberry .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_observation_identifiers_ex_results.json :language: JSON Returns: Response dict of identifiers """ params.setdefault('per_page', 500) response = get_v1('observations/identifiers', **params) return response.json()
def search(q: str, **params) -> JsonResponse: """A unified search endpoint for places, projects, taxa, and/or users .. rubric:: Notes * API reference: :v1:`GET /search <Search/get_search>` Example: >>> response = search(q='odonat') >>> pprint(response) [Taxon ] [47792 ] Order: Odonata (Dragonflies and Damselflies) [Place ] [113562 ] Odonates of Peninsular India and Sri Lanka [Project] [9978 ] Ohio Dragonfly Survey (Ohio Odonata Survey) [User ] [113886 ] odonatanb (Gilles Belliveau) .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_search.py Returns: Response dict containing search results """ response = get_v1('search', q=q, **params) search_results = response.json() search_results['results'] = convert_all_timestamps( search_results['results']) search_results['results'] = convert_all_coordinates( search_results['results']) return search_results
def get_observation_histogram(**params) -> HistogramResponse: """Search observations and return histogram data for the given time interval .. rubric:: Notes * API reference: :v1:`GET /observations/histogram <Observations/get_observations_histogram>` * Search parameters are the same as :py:func:`~pyinaturalist.v1.observations.get_observations()`, with the addition of ``date_field`` and ``interval``. * ``date_field`` may be either 'observed' (default) or 'created'. * Observed date ranges can be filtered by parameters ``d1`` and ``d2`` * Created date ranges can be filtered by parameters ``created_d1`` and ``created_d2`` * ``interval`` may be one of: 'year', 'month', 'week', 'day', 'hour', 'month_of_year', or 'week_of_year'; spaces are also allowed instead of underscores, e.g. 'month of year'. * The year, month, week, day, and hour interval options will set default values for ``d1`` and ``created_d1``, to limit the number of groups returned. You can override those values if you want data from a longer or shorter time span. * The 'hour' interval only works with ``date_field='created'`` Example: Get observations per month during 2020 in Austria (place ID 8057) >>> response = get_observation_histogram( >>> interval='month', >>> d1='2020-01-01', >>> d2='2020-12-31', >>> place_id=8057, >>> ) .. admonition:: Example Response (observations per month of year) :class: toggle .. literalinclude:: ../sample_data/get_observation_histogram_month_of_year.py .. admonition:: Example Response (observations per month) :class: toggle .. literalinclude:: ../sample_data/get_observation_histogram_month.py .. admonition:: Example Response (observations per day) :class: toggle .. literalinclude:: ../sample_data/get_observation_histogram_day.py Returns: Dict of ``{time_key: observation_count}``. Keys are ints for 'month of year' and\ 'week of year' intervals, and :py:class:`~datetime.datetime` objects for all other intervals. """ response = get_v1('observations/histogram', **params) return convert_observation_histogram(response.json())
def get_observations(**params) -> JsonResponse: """Search observations .. rubric:: Notes * API reference: :v1:`GET /observations <Observations/get_observations>` Examples: Get observations of Monarch butterflies with photos + public location info, on a specific date in the provice of Saskatchewan, CA (place ID 7953): >>> response = get_observations( >>> taxon_name='Danaus plexippus', >>> created_on='2020-08-27', >>> photos=True, >>> geo=True, >>> geoprivacy='open', >>> place_id=7953, >>> ) Get basic info for observations in response: >>> pprint(response) '[57754375] Species: Danaus plexippus (Monarch) observed by samroom on 2020-08-27 at Railway Ave, Wilcox, SK' '[57707611] Species: Danaus plexippus (Monarch) observed by ingridt3 on 2020-08-26 at Michener Dr, Regina, SK' .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_observations_node.py Returns: Response dict containing observation records """ validate_multiple_choice_param(params, 'order_by', V1_OBS_ORDER_BY_PROPERTIES) if params.get('page') == 'all': observations = paginate_all(get_v1, 'observations', method='id', **params) else: observations = get_v1('observations', **params).json() observations['results'] = convert_all_coordinates(observations['results']) observations['results'] = convert_all_timestamps(observations['results']) return observations
def get_taxa_autocomplete(**params) -> JsonResponse: """Given a query string, return taxa with names starting with the search term .. rubric:: Notes * API reference: :v1:`GET /taxa/autocomplete <Taxa/get_taxa_autocomplete>` * There appears to currently be a bug in the API that causes ``per_page`` to not have any effect. Example: Get just the name of the first matching taxon: >>> response = get_taxa_autocomplete(q='vespi') >>> print(response['results'][0]['name']) 'Vespidae' Get basic info for taxa in response: >>> pprint(response) [52747 ] Family: Vespidae (Hornets, Paper Wasps, Potter Wasps, and Allies) [84738 ] Subfamily: Vespinae (Hornets and Yellowjackets) [131878 ] Species: Nicrophorus vespillo (Vespillo Burying Beetle) If you get unexpected matches, the search likely matched a synonym, either in the form of a common name or an alternative classification. Check the ``matched_term`` property for more info. For example: >>> first_result = get_taxa_autocomplete(q='zygoca')['results'][0] >>> first_result["name"] "Schlumbergera truncata" # This doesn't look like our search term! >>> first_result["matched_term"] "Zygocactus truncatus" # ...Because it matched an older synonym for Schlumbergera .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_taxa_autocomplete.py .. admonition:: Example Response (formatted) :class: toggle .. literalinclude:: ../sample_data/get_taxa_autocomplete_minified.py Returns: Response dict containing taxon records """ params = convert_rank_range(params) response = get_v1('taxa/autocomplete', **params) return response.json()
def get_controlled_terms(taxon_id: int = None, **params) -> JsonResponse: """List controlled terms and their possible values .. rubric:: Notes * 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 2: Adult 3: Teneral 4: Pupa ... .. admonition:: Example Response (all terms) :class: toggle .. literalinclude:: ../sample_data/get_controlled_terms.json :language: JSON .. admonition:: Example Response (for a specific taxon) :class: toggle .. literalinclude:: ../sample_data/get_controlled_terms_for_taxon.json :language: JSON Args: taxon_id: ID of taxon to get controlled terms for Returns: A dict containing details on controlled terms and their values Raises: :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' response = get_v1(endpoint, taxon_id=taxon_id, **params) # controlled_terms/for_taxon returns a 422 if the specified taxon does not exist if response.status_code in (404, 422): raise TaxonNotFound return response.json()
def get_places_nearby(nelat: float, nelng: float, swlat: float, swlng: float, **params) -> JsonResponse: """Search for places near a given location .. rubric:: Notes * API reference: :v1:`GET /places/nearby <get_places_nearby>` Example: >>> bounding_box = (150.0, -50.0, -149.999, -49.999) >>> response = get_places_nearby(*bounding_box) Response is split into standard (curated) places and community (non-curated) places: >>> print(len(response['results']['standard'])) 10 >>> print(len(response['results']['community'])) 10 Show basic info for all places in response: >>> pprint(response) [97394] North America [97395] Asia [97393] Oceania ... .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_places_nearby.py Returns: Response dict containing place records, divided into 'standard' and 'community' places. """ response = get_v1('places/nearby', nelat=nelat, nelng=nelng, swlat=swlat, swlng=swlng, **params) return convert_all_place_coordinates(response.json())
def get_taxa_map_layers(taxon_id: int, **params) -> JsonResponse: """Get some additional taxon metadata, including: * GBIF taxon ID and URL * Whether the taxon has range data and/or listed places Example: >>> response = get_taxa_map_layers(343248) .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_taxa_map_layers.json :language: JSON Args: taxon_id: iNaturalist taxon ID. Only one value is allowed. """ response = get_v1(f'taxa/{taxon_id}/map_layers', **params).json() response[ 'gbif_url'] = f'https://www.gbif.org/species/{response["gbif_id"]}' return response
def get_unread_meassage_count(**params) -> int: """Get the number of unread messages in the user's inbox .. rubric:: Notes * :fa:`lock` :ref:`Requires authentication <auth>` * API reference: :v1:`GET /messages/unread <Messages/get_messages_unread>` Example: >>> get_unread_meassage_count() 12 Returns: Unread message count """ response = get_v1('messages/unread', **params) try: return int(response.json()['count']) except (KeyError, TypeError, ValueError): logger.error(f'Failed to get unread message count: {response.text}', exc_info=True) return 0
def get_posts(**params) -> ListResponse: """Search posts .. rubric:: Notes * API reference: :v1:`GET /posts <Posts/get_posts>` Example: Get journal posts from user 'username' >>> response = get_posts(login='******') Returns: List containing journal posts from the iNaturalist site """ response = get_v1('posts', **params) posts = response.json() posts = convert_all_coordinates(posts) posts = convert_all_timestamps(posts) return posts
def get_message_by_id(message_id: MultiInt, **params) -> JsonResponse: """Get a message by ID .. rubric:: Notes * :fa:`lock` :ref:`Requires authentication <auth>` * API reference: :v1:`GET /messages/{id} <Messages/get_messages_id>` Example: >>> response = get_messages(123456) >>> pprint(response) .. admonition:: Example Response :class: toggle .. literalinclude:: ../sample_data/get_messages.json Returns: Response dict containing user record """ response = get_v1('messages', ids=message_id, **params) messages = response.json() messages['results'] = convert_all_timestamps(messages['results']) return messages
def reqeuest_function(**request_kwargs): return get_v1('places/autocomplete', **request_kwargs)