Python client for the Wildbook v3 API
Project description
pywildbook
A Python client library for interacting with the Wildbook v3 API. This package provides an interface for authenticating with Wildbook instances and searching for wildlife encounters, individuals, and other data.
Installation
Using uv (recommended)
cd pywildbook
uv sync
Using pip
pip install -e .
Quick Start
from pywildbook import WildbookClient
from pywildbook.queries import match_all
import os
# Create a client instance
# The base URL can also be set via the WILDBOOK_URL environment variable.
client = WildbookClient(os.environ.get('WILDBOOK_URL', 'http://localhost:8080'))
# Login
# Credentials can be passed directly or sourced from WILDBOOK_USERNAME and WILDBOOK_PASSWORD environment variables.
client.login()
# Search for encounters
results = client.search_encounters(match_all(), size=10)
# Print results
for encounter in results['hits']:
print(f"{encounter['id']}: {encounter['genus']} {encounter.get('specificEpithet', '')}")
# Logout when done
client.logout()
Authentication
The client uses session-based authentication. After logging in, the session cookie is automatically managed for all subsequent requests. For security, it is highly recommended to use environment variables for sensitive credentials.
from pywildbook import WildbookClient
import os
# The base URL can also be set via the WILDBOOK_URL environment variable.
client = WildbookClient(os.environ.get('WILDBOOK_URL', 'http://localhost:8080'))
# Login
# Credentials can be passed directly or sourced from WILDBOOK_USERNAME and WILDBOOK_PASSWORD environment variables.
user_info = client.login()
print(f"Logged in as: {user_info['username']}")
# Check authentication status
if client.is_authenticated():
print("✓ Authenticated")
# Get current user info
user = client.get_current_user()
print(f"User ID: {user['id']}")
# Logout
client.logout()
Using Context Manager (Recommended)
The client can be used as a context manager to ensure automatic logout:
import os
from pywildbook import WildbookClient
from pywildbook.queries import match_all
# The base URL can also be set via the WILDBOOK_URL environment variable.
with WildbookClient(os.environ.get('WILDBOOK_URL', 'http://localhost:8080')) as client:
# Credentials can be passed directly or sourced from WILDBOOK_USERNAME and WILDBOOK_PASSWORD environment variables.
client.login()
results = client.search_encounters(match_all())
# ... do work ...
# logout() is called automatically when exiting the context
Searching Encounters
Basic Search
from pywildbook.queries import match_all
# Get all encounters
results = client.search_encounters(match_all(), size=50)
# With pagination
results = client.search_encounters(
match_all(),
from_=0, # offset
size=20, # page size
sort='date', # sort field
sort_order='desc'
)
My Encounters
from pywildbook.queries import filter_by_species, combine_queries
# Get my 10 most recent encounters
my_encounters = client.search_encounters(
client.filter_current_user(),
size=10,
sort='date',
sort_order='desc'
)
# Combine with other filters: my encounters of a specific species
query = combine_queries(
client.filter_current_user(),
filter_by_species('Megaptera novaeangliae'),
operator='must'
)
results = client.search_encounters(query)
Filtering by Species
from pywildbook.queries import filter_by_species
# Search by species
query = filter_by_species('novaeangliae')
results = client.search_encounters(query)
# Search by genus and species
query = filter_by_species('Megaptera novaeangliae')
results = client.search_encounters(query)
Filtering by Date Range
from pywildbook.queries import filter_by_date_range
# Encounters since 1 November 2025
query = filter_by_date_range(start_date='2025-11-01')
results = client.search_encounters(query)
# Encounters between two dates
query = filter_by_date_range(start_date='2025-11-01', end_date='2025-12-01')
results = client.search_encounters(query)
Filtering by Location
from pywildbook.queries import filter_by_location
# Filter by country
query = filter_by_location(country='Kenya')
results = client.search_encounters(query)
# Filter by bounding box
query = filter_by_location(
min_lat=-5.0,
max_lat=5.0,
min_lon=35.0,
max_lon=42.0
)
results = client.search_encounters(query)
Combining Multiple Filters
from pywildbook.queries import (
filter_by_species,
filter_by_sex,
filter_by_year_range,
combine_queries
)
# Female humpback whales from 2020-2023
species = filter_by_species('Megaptera novaeangliae')
sex = filter_by_sex('female')
years = filter_by_year_range(2020, 2023)
query = combine_queries(species, sex, years, operator='must')
results = client.search_encounters(query)
Finding Unassigned Encounters
from pywildbook.queries import field_missing
# Encounters without an assigned individual
query = field_missing('individualId')
unassigned = client.search_encounters(query)
Text Search
from pywildbook.queries import text_search
# Search for "beach" in locality descriptions
query = text_search('verbatimLocality', 'beach', fuzzy=True)
results = client.search_encounters(query)
Searching Individuals
from pywildbook.queries import field_exists
# Find individuals with encounters
query = field_exists('encounters')
results = client.search_individuals(query, size=20)
for individual in results['hits']:
print(f"{individual['id']}: {individual.get('displayName', 'Unnamed')}")
Getting Specific Records
# Get a specific encounter by UUID
encounter = client.get_encounter('123e4567-e89b-12d3-a456-426614174000')
print(encounter)
# Get a specific individual by UUID
individual = client.get_individual('987fcdeb-51a2-43f7-9876-543210fedcba')
print(individual)
User Dashboard
# Get dashboard data for the current user
dashboard = client.get_user_home()
print(f"Latest encounters: {dashboard['latestEncounters']}")
print(f"Projects: {dashboard['projects']}")
print(f"Latest bulk import: {dashboard.get('latestBulkImportTask')}")
Available Query Helpers
The pywildbook.queries module provides these helper functions:
match_all()- Match all documentsfilter_by_sex(sex)- Filter by sexfilter_by_species(species, genus=None)- Filter by speciesfilter_by_year_range(start_year, end_year)- Filter by year rangefilter_by_date_range(start_date, end_date)- Filter by date range (ISO 8601)filter_by_location(country, location_id, min_lat, max_lat, min_lon, max_lon)- Filter by locationfilter_by_individual(individual_id)- Find encounters for an individualfilter_by_submitter(submitter_id)- Filter by submittertext_search(field, text, fuzzy=False)- Text search in a fieldfield_exists(field)- Find documents where field existsfield_missing(field)- Find documents where field is missingcombine_queries(*queries, operator='must')- Combine multiple queries with AND/OR/NOT logic
Custom Queries
For advanced use cases, you can construct your own OpenSearch/Elasticsearch queries:
# Custom query using Elasticsearch DSL
custom_query = {
'bool': {
'must': [
{'term': {'genus': 'Tursiops'}},
{'range': {'year': {'gte': 2020, 'lte': 2023}}}
],
'must_not': [
{'term': {'sex': 'unknown'}}
]
}
}
results = client.search_encounters(custom_query)
Error Handling
The client provides specific exceptions for different error scenarios:
from pywildbook import (
WildbookClient,
AuthenticationError,
NotAuthenticatedError,
NotFoundError,
BadRequestError,
ForbiddenError,
APIError
)
client = WildbookClient('http://localhost:8080')
try:
client.login('user@example.com', 'wrong_password')
except AuthenticationError as e:
print(f"Login failed: {e}")
try:
# Trying to search without logging in
results = client.search_encounters(match_all())
except NotAuthenticatedError as e:
print(f"Not authenticated: {e}")
try:
encounter = client.get_encounter('invalid-uuid')
except NotFoundError as e:
print(f"Encounter not found: {e}")
Examples
See the examples/ directory for complete examples:
basic_usage.py- Basic login, search, and logoutadvanced_search.py- Complex queries, pagination, and filteringindividual_statistics.ipynb- Analyze individual encounter patterns and statistics
Run examples:
# Set environment variables
export WILDBOOK_URL="http://localhost:8080"
export WILDBOOK_USERNAME="your@email.com"
export WILDBOOK_PASSWORD="yourpassword"
# Run basic example
uv run python examples/basic_usage.py
# Run advanced example
uv run python examples/advanced_search.py
# Install notebook dependencies, then open a notebook
uv sync --extra notebook
uv run jupyter notebook examples/individual_statistics.ipynb
Development
See CONTRIBUTING.md for contributor workflow, coding conventions, tests, and pull request guidance.
Setting up the development environment
# Clone the repository
git clone <repo-url>
cd pywildbook
# Initialize with uv
uv sync
# Run tests
uv run pytest
Notebook output stripping
nbstripout is configured to automatically strip cell outputs from .ipynb files before they are staged. After cloning, activate the git filter once:
uv run nbstripout install
After that, git add on any notebook will silently strip outputs before staging. Your local working copy keeps its outputs for interactive use; only clean notebooks are committed.
Project Structure
pywildbook/
├── src/
│ └── pywildbook/
│ ├── __init__.py # Package exports
│ ├── client.py # Main client class
│ ├── exceptions.py # Custom exceptions
│ └── queries.py # Query helper functions
├── examples/
│ ├── basic_usage.py
│ └── advanced_search.py
├── pyproject.toml # Package configuration
└── README.md
API Reference
WildbookClient
Main client class for interacting with Wildbook.
Methods
__init__(base_url: str = None)- Create a new client instance (falls back to WILDBOOK_URL env var)login(username: str, password: str) -> Dict- Authenticate userlogout() -> bool- End sessionis_authenticated() -> bool- Check authentication statusget_current_user() -> Dict- Get current user infoget_user_home() -> Dict- Get user dashboard datasearch_encounters(query, from_=0, size=10, sort=None, sort_order=None) -> Dict- Search encountersget_encounter(encounter_id: str) -> Dict- Get specific encountersearch_individuals(query, from_=0, size=10, sort=None, sort_order=None) -> Dict- Search individualsget_individual(individual_id: str) -> Dict- Get specific individualfilter_current_user() -> Dict- Query for encounters assigned to the logged-in user
Requirements
- Python >= 3.11
- requests >= 2.31.0
Optional: notebook extras
The encounter_map.ipynb and individual_statistics.ipynb examples require additional dependencies. Install with:
uv sync --extra notebook
This adds ipykernel, ipyleaflet, and python-dotenv.
License
Support
For issues, questions, and more information:
Related Projects
Versioning
pywildbook and RWildbook follow a shared versioning convention to make feature equivalence explicit:
- Major and minor versions are synchronised across both libraries.
pywildbook 1.2.xandRWildbook 1.2.xexpose the same API surface. - Patch versions are independent. Bug fixes, dependency updates, and other library-specific changes do not require a coordinated release.
- Minor bumps are coordinated. When new features are added they land in both libraries together, then both get the minor bump.
This project follows Semantic Versioning.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pywildbook-1.0.0.tar.gz.
File metadata
- Download URL: pywildbook-1.0.0.tar.gz
- Upload date:
- Size: 11.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3b730ebe87a6b92529edf0961f5463611271aceabb71d617fefb270e7d92e8df
|
|
| MD5 |
c608f0d384501a8e672d1bbaf86f0fee
|
|
| BLAKE2b-256 |
57508e29164e5b02df524913058d768a34d019f1ec460f6c2133f88d7ed35d73
|
Provenance
The following attestation bundles were made for pywildbook-1.0.0.tar.gz:
Publisher:
publish.yml on WildMeOrg/pywildbook
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pywildbook-1.0.0.tar.gz -
Subject digest:
3b730ebe87a6b92529edf0961f5463611271aceabb71d617fefb270e7d92e8df - Sigstore transparency entry: 1420433521
- Sigstore integration time:
-
Permalink:
WildMeOrg/pywildbook@6ddbb0468d09146766641cd0968d6d41c434d5d4 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/WildMeOrg
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6ddbb0468d09146766641cd0968d6d41c434d5d4 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file pywildbook-1.0.0-py3-none-any.whl.
File metadata
- Download URL: pywildbook-1.0.0-py3-none-any.whl
- Upload date:
- Size: 13.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9491ad412b8a34c88c21c422c705e7b82f8024cd14de604bda5c23cdc004d92
|
|
| MD5 |
4fba84d81d949aa129a8cb58f8a3cd6f
|
|
| BLAKE2b-256 |
4b1305f43e6f82303612046066ae1bc1b3708649abe641097075efdfd858f04e
|
Provenance
The following attestation bundles were made for pywildbook-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on WildMeOrg/pywildbook
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pywildbook-1.0.0-py3-none-any.whl -
Subject digest:
e9491ad412b8a34c88c21c422c705e7b82f8024cd14de604bda5c23cdc004d92 - Sigstore transparency entry: 1420433594
- Sigstore integration time:
-
Permalink:
WildMeOrg/pywildbook@6ddbb0468d09146766641cd0968d6d41c434d5d4 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/WildMeOrg
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6ddbb0468d09146766641cd0968d6d41c434d5d4 -
Trigger Event:
workflow_dispatch
-
Statement type: