Metadata-Version: 2.4
Name: pybirdbuddy
Version: 0.1.0
Summary: A library to query data about a Bird Buddy smart bird feeder
Author-email: jhansche <madcoder@gmail.com>
License-Expression: MIT-0
Project-URL: Homepage, https://github.com/jhansche/pybirdbuddy
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: python-graphql-client
Requires-Dist: langcodes
Requires-Dist: typing_extensions>=4.5
Provides-Extra: dev
Requires-Dist: pytest~=9.1; extra == "dev"
Requires-Dist: pytest-asyncio~=1.4; extra == "dev"
Requires-Dist: pytest-aiohttp~=1.1; extra == "dev"
Requires-Dist: pytest-cov~=7.1; extra == "dev"
Requires-Dist: coverage~=7.15; extra == "dev"
Requires-Dist: graphql-core~=3.2; extra == "dev"
Requires-Dist: python-dotenv~=1.2; extra == "dev"
Requires-Dist: ruff~=0.16.3; extra == "dev"
Requires-Dist: pyright~=1.1.411; extra == "dev"
Requires-Dist: build~=1.5; extra == "dev"
Requires-Dist: twine<8.0,>=6.2; extra == "dev"
Dynamic: license-file

# pybirdbuddy

[![Build Status][build-status-shield]][build-status]
![Maintenance][maintenance-shield]
[![GitHub Release][releases-shield]][releases]
[![PyPI Version][pypi-shield]][pypi]
[![License][license-shield]](LICENSE)

`pybirdbuddy` is an asynchronous Python client for the undocumented GraphQL
API behind the Bird Buddy smart bird feeder. Sign in with a Bird Buddy account
to read your feeders and their state, browse your collections and media, and
finish the "postcard" sightings the feeder captures.

It is an unofficial client for an undocumented API that can change without
notice, and is not affiliated with or endorsed by Bird Buddy.

## Installation

```bash
pip install pybirdbuddy
```

Python 3.10–3.14 is supported.

## Usage

```python
import asyncio
import pprint

from birdbuddy.client import BirdBuddy

bb = BirdBuddy("user@email.com", "Pa$$w0rd")

# Using coroutines with async/await:
async def async_test():
    await bb.refresh()
    pprint.pprint(bb.feeders)

# Without async/await, including from a top-level module:
result = asyncio.run(bb.refresh())
pprint.pprint(result)
pprint.pprint(bb.feeders)
```

Note: only password login is supported currently. Google and other SSOs are
not supported. If you've already set up your Bird Buddy with SSO, one option
could be to register a new account with a password, and then redeem an invite
code to your Bird Buddy under the new account. Some fields will be missing
(such as firmware versions and off-grid status).

The `feeders` property will be an array of feeders with the following fields:

```graphql
fragment ListFeederFields on FeederForPrivate {
  battery {
    charging    # Boolean
    percentage  # Int (93)
    state       # String (enum: "HIGH")
  }
  food {
    state       # String (enum: "LOW")
  }
  id            # String (UUID)
  name          # String
  signal {
    state       # String (enum: "HIGH")
    value       # Int (rssi: -41)
  }
  state         # String (enum: "READY_TO_STREAM")
  temperature {
    value       # Int
  }
}
```

New postcards arrive in the feed. Identify a postcard's visitor without
collecting it, or collect it into your account:

```python
from birdbuddy.client import BirdBuddy


async def main():
    bb = BirdBuddy("user@email.com", "Pa$$w0rd")

    postcards = await bb.new_postcards()
    postcard = postcards[0]

    # Preview the recognized species and media, without collecting:
    analysis = await bb.identify_postcard(postcard)
    print([species.name for species in analysis.species])

    # Collect it into your account. collect_postcard reanalyzes internally
    # (idempotent), so calling identify_postcard beforehand is not required.
    collected = await bb.collect_postcard(postcard)
    print(collected.species)
```

## Translations

API responses can return translated strings by setting the client's
`language_code` property. Language codes are parsed using
[`langcodes`][langcodes].

```python
from birdbuddy.client import BirdBuddy

async def main():
    bb = BirdBuddy("user@email.com", "Pa$$w0rd")
    bb.language_code = "de"

    collections = await bb.refresh_collections()
    birds = [c.species.name for c in collections.values()]
    print(birds)
```

## Deprecations

Deprecated methods keep working and emit a `DeprecationWarning`, so upgrading
does not break existing callers. Prefer the replacement:

- `latest_collections()` — deprecated in 0.0.22; use `refresh_collections()`.
  The old method referenced an undefined query and raised `AttributeError` on
  every call, so it never returned data. It now delegates to
  `refresh_collections()`, which returns the account's bird collections.
- `reanalyze_postcard()` — deprecated in 0.0.22; use `identify_postcard()`,
  which returns a `PostcardAnalysis` (recognized species and media) instead of
  the raw payload.
- `sighting_from_postcard()`, `finish_postcard()`, `sighting_choose_species()`,
  and `sighting_choose_mystery()` — deprecated in 0.0.22; the report-token flow
  is superseded by `identify_postcard()` and `collect_postcard()`.
- `sighting_create()` and `sighting_create_check_progress()` — deprecated in
  0.0.22; the API removed the underlying mutations, so these raise
  `NotImplementedError`.

## Development

Install [pyenv] and the pinned interpreter, then use the Makefile — every
target runs inside the project venv automatically:

```bash
pyenv install 3.10.20   # matches .python-version
make deps               # create the venv and install the [dev] extra

make test     # ruff + ruff format --check + markdownlint + pyright + pytest
make check    # alias for `make test`
make format   # auto-fix ruff issues and reformat
make schema   # refresh schema.json from the live API
```

Alternatively, install the tooling into an existing environment with
`pip install -e '.[dev]'`.

## Releasing

The package is published to [PyPI][pypi]. To cut a release:

1. Bump `version` in `pyproject.toml` (if needed).
2. `make build` — build the sdist and wheel into `dist/`.
3. `make publish` — rebuild, run `twine check`, then upload to PyPI.

`make publish` uses [Twine], which reads credentials from `~/.pypirc` or the
`TWINE_USERNAME` / `TWINE_PASSWORD` environment variables; use `__token__` as
the username and a PyPI API token (the value includes its `pypi-` prefix) as
the password.

## License

Released under the [MIT No Attribution](LICENSE) license (MIT-0).

[build-status]: https://github.com/jhansche/pybirdbuddy/actions/workflows/python-package.yml?query=branch%3Amain
[build-status-shield]: https://img.shields.io/github/actions/workflow/status/jhansche/pybirdbuddy/python-package.yml?branch=main&style=for-the-badge
[langcodes]: https://pypi.org/project/langcodes/
[license-shield]: https://img.shields.io/github/license/jhansche/pybirdbuddy.svg?style=for-the-badge
[maintenance-shield]: https://img.shields.io/maintenance/yes/2026?style=for-the-badge
[pyenv]: https://github.com/pyenv/pyenv
[pypi]: https://pypi.org/project/pybirdbuddy/
[pypi-shield]: https://img.shields.io/pypi/v/pybirdbuddy?style=for-the-badge
[releases]: https://github.com/jhansche/pybirdbuddy/releases
[releases-shield]: https://img.shields.io/github/v/release/jhansche/pybirdbuddy.svg?style=for-the-badge
[twine]: https://twine.readthedocs.io/
