pyinaturalist

repository·main·Indexed 41 days ago

https://github.com/pyinat/pyinaturalist

A Python client for the iNaturalist API that provides access to biodiversity data including observations, taxa, places, and users. It supports v0, v1, and v2 API endpoints, handles pagination, and provides typed Python objects for responses. The library includes the iNatClient for read/write operations and supports custom HTTP control via ClientSession for managing caching, timeouts, and rate limiting.

Tokens
18.3K
Snippets
77
Records
82
Agent score
52%

What's inside pyinaturalist

  1. Explore related iNaturalist Python projects

    main

    The pyinat organization maintains several other tools for working with iNaturalist data:

    • naturtag: A desktop application for tagging image files with iNaturalist taxonomy and observation metadata.
    • pyinaturalist-convert: Tools for converting observation data between various formats.
    • pyinaturalist-notebook: A Docker image containing a Jupyter notebook environment pre-configured with pyinaturalist.
    • dronefly: A Discord bot with iNaturalist integration.
  2. Configure and manage API caching

    main

    By default, requests are cached based on Cache-Control headers. You can manage this via ClientSession or individual requests.

    Session-level configuration:

    • expire_after: Set a datetime.timedelta for how long to cache responses.
    • cache_file: Specify a path to a database file for the cache.
    • cache.disabled: Set to True to fully disable caching for the session.

    Request-level control:

    • force_refresh=True: Bypasses the cache to fetch a new response.
    • refresh=True: Performs a "soft-refresh" (revalidation).

    Manual cache management:

    • Use session.cache.clear() to clear the cache for a specific session.
    • Use pyinaturalist.clear_cache() to clear the global cache.
    from datetime import timedelta
    from pyinaturalist import ClientSession, get_taxa
    
    # Cache all requests for 1 day
    session = ClientSession(expire_after=timedelta(days=1))
    # Use a specific cache file
    session = ClientSession(cache_file='~/data/api_requests.db')
    
    # Bypassing cache in a single request
    get_observations(id=12345, force_refresh=True)
    
    # Clearing cache
    from pyinaturalist import clear_cache
    clear_cache()
  3. Understand the supported iNaturalist API versions in pyinaturalist

    main

    The pyinaturalist library provides interfaces for different versions of the iNaturalist API:

    • v0 (Rails-based API): The original API. It is expected to be deprecated in the future.
    • v1 (Node-based API): Generally faster and more consistent than v0. It allows both read and write access to most iNaturalist resources.
    • v2 API: Currently in development and intended to eventually replace both v0 and v1. pyinaturalist currently offers partial support for v2.

    Functions for interacting with these APIs are organized into specific modules within the package.

  4. Use Data Models for typed Python objects

    main

    The pyinaturalist.models module provides typed Python objects for all API response types. Using models is recommended over raw JSON because they provide:

    • Complete type annotations and autocompletion.
    • Condensed print formats (compatible with pprint).
    • Minimal performance overhead.

    Converting JSON to Models: Use .from_json() for a single object or .from_json_list() for a list of objects.

    from pyinaturalist import Observation, get_observations
    
    # 1. Get raw JSON response
    response = get_observations(user_id='my_username')
    
    # 2. Convert list of JSON dicts to list of Observation models
    observations = Observation.from_json_list(response)
    
    # 3. Convert back to JSON dicts if needed
    json_observations = [obs.to_dict() for obs in observations]

    Note: The iNatClient interface is fully integrated with these models and is the recommended way to interact with the API to receive typed objects automatically.

    from pyinaturalist import Observation, get_observations
    response = get_observations(user_id='my_username')
    observations = Observation.from_json_list(response)
  5. Use Dry-run mode for testing

    main

    Dry-run mode mocks HTTP requests so you don't accidentally modify real data. You must enable at least INFO level logging to see the mocked requests.

    Individual requests: Pass dry_run=True to any API function.

    Global dry-run (all requests): Set the DRY_RUN_ENABLED environment variable to true.

    Write-only dry-run (mock POST/PUT/DELETE, allow GET): Set the DRY_RUN_WRITE_ONLY environment variable to true.

    # Individual request dry-run
    from pyinaturalist import get_taxa
    get_taxa(q='warbler', locale='en', dry_run=True)
    
    # Global dry-run via environment variables
    # Python
    import os
    os.environ['DRY_RUN_ENABLED'] = 'true'
    
    # Unix/MacOS
    export DRY_RUN_ENABLED=true
    
    # Windows CMD
    set DRY_RUN_ENABLED="true"
    
    # PowerShell
    $Env:DRY_RUN_ENABLED="true"
  6. Paginate large API responses with Paginator

    main

    Most client methods return a Paginator object. This allows you to fetch data lazily (page by page) or retrieve specific subsets of results to avoid over-fetching.

    Key Paginator methods:

    • Iteration: Iterate directly over the object to fetch pages as needed.
    • .all(): Fetches all results at once.
    • .limit(n): Fetches up to n results.
    • .one(): Fetches only the first result.
    • .count(): Returns the total number of results without fetching the data records.
    # Iterate lazily
    for obs in client.observations.search(user_id='my_username', taxon_name='Danaus plexippus'):
        print(obs)
    
    # Get all results
    observations = client.observations.search(user_id='my_username').all()
    
    # Limit results
    observations = client.observations.search(user_id='my_username').limit(500)
    
    # Get only the first result
    observation = client.observations.search(user_id='my_username').one()
    
    # Get total count
    count = client.observations.search(user_id='my_username').count()
  7. Authenticate API requests with an access token

    main

    For endpoints that create, update, or delete data, you must provide an access_token. Read-only requests generally do not require authentication, but an access token is required to access private data (e.g., obscured coordinates) visible only to your user. Tokens are typically valid for 24 hours.

    Pass the token using the access_token argument in any API request function.

    from pyinaturalist import create_observation
    
    create_observation(
        ...,
        access_token='my_access_token',
    )
  8. Use the Authorization Code flow with PKCE

    main

    The recommended method for CLI and desktop applications. It is more secure as it avoids sharing your password directly with the code. It uses a browser-based authorization step.

    Standard PKCE (Local Browser)

    Opens a browser and starts a local server on port 8080 to receive the callback. Ensure your application's Redirect URI is set to http://127.0.0.1:8080.

    OOB Mode (Headless/Remote)

    For environments without a browser, use use_oob=True. The authorization code will be displayed in the browser for you to paste into the terminal. Ensure your application's Redirect URI is set to urn:ietf:wg:oauth:2.0:oob.

    # Standard PKCE
    from pyinaturalist import get_access_token_via_auth_code
    access_token = get_access_token_via_auth_code(
        app_id='YOUR_APP_ID',
    )
    
    # Headless OOB Mode
    access_token = get_access_token_via_auth_code(
        app_id='YOUR_APP_ID',
        use_oob=True,
    )
  9. Install pyinaturalist

    main

    You can install pyinaturalist using pip or conda.

    Pip (Stable):

    pip install pyinaturalist

    Pip (Pre-release/Development):

    pip install --pre pyinaturalist

    Conda:

    conda install -c conda-forge pyinaturalist

    Python Compatibility: pyinaturalist currently requires Python 3.10+. For older versions, use these specific releases:

    • Python 3.8/3.9: pyinaturalist 0.21
    • Python 3.7: pyinaturalist 0.19
    • Python 3.6: pyinaturalist 0.16
    • Python 3.5: pyinaturalist 0.11
    • Python 3.4: pyinaturalist 0.10
    • Python 2.7: pyinaturalist 0.1
    pip install pyinaturalist
  10. Explore pyinaturalist Jupyter notebooks

    main

    The project provides several Jupyter notebooks for learning how to work with observations, taxa, and data visualizations. You can run these examples directly in your browser using Binder or locally using the pyinaturalist-notebook Docker image.

    Available notebook tutorials include:

    • Tutorial_1_Observations.ipynb
    • Tutorial_2_Taxa.ipynb
    • Tutorial_3_Data_Visualizations.ipynb
    • Specialized visualization notebooks for Regional Activity Reports, Regional Observation Stats, Seaborn, Matplotlib, Choropleth maps, and Geospatial Mapping with Folium.
    https://mybinder.org/v2/gh/pyinat/pyinaturalist/main?filepath=examples
  11. Use iNatClient in async applications

    main

    The iNatClient supports asynchronous usage to prevent blocking the event loop.

    • Async Iteration: Use async for to iterate over a Paginator.
    • Async Fetch All: Use await Paginator.async_all() to retrieve all results at once.
    # Async iteration
    async for obs in client.observations.search(user_id='my_username'):
        print(obs)
    
    # Async fetch all
    query = client.observations.search(user_id='my_username')
    observations = await query.async_all()
  12. Handle pagination in API requests

    main

    Most endpoints support pagination via the following parameters:

    • page: The page number to retrieve.
    • per_page: The number of results per page (default and max vary by endpoint, but is typically 200).
    • count_only=True: A shortcut for per_page=0 that returns only the total number of results without the data.

    To retrieve all pages of results and combine them into a single response, set page='all'. This replaces the legacy get_all_*() functions used in versions $\le$ 0.12.

    # Example of requesting all pages
    get_observations(taxon_id=123, page='all')
    
    # Example of getting only the count
    get_observations(taxon_id=123, count_only=True)