Metadata-Version: 2.5
Name: aiogeodrops
Version: 0.4.1
Summary: Async client for GeoDrops soil-probe readings in Google BigQuery
Project-URL: Homepage, https://github.com/ajbaldwin/aiogeodrops
Project-URL: Issues, https://github.com/ajbaldwin/aiogeodrops/issues
Project-URL: Changelog, https://github.com/ajbaldwin/aiogeodrops/blob/main/CHANGELOG.md
Author: Adam Baldwin
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Home Automation
Classifier: Typing :: Typed
Requires-Python: >=3.13
Requires-Dist: aiohttp>=3.9
Requires-Dist: pyjwt[crypto]>=2.8
Provides-Extra: dev
Requires-Dist: mypy>=1.15; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.25; extra == 'dev'
Requires-Dist: pytest-cov>=6; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.11; extra == 'dev'
Description-Content-Type: text/markdown

# aiogeodrops

Async Python client for [GeoDrops](https://geodrops.io/) soil-probe readings.

GeoDrops publishes every probe's readings to a public Google BigQuery table.
This library queries it over BigQuery's REST API with a Google service
account. It is built on `aiohttp` and `PyJWT` only (no Google Cloud SDK, no
gRPC), takes your `aiohttp.ClientSession`, and is fully typed.

It is the library behind the
[GeoDrops Home Assistant integration](https://github.com/ajbaldwin/ha-geodrops-hacs).

## Install

```bash
pip install aiogeodrops
```

## Prerequisites

A Google Cloud project of your own (queries run and are billed there, well
within BigQuery's free tier for a handful of probes), with the BigQuery API
enabled, and a service account that has **BigQuery Job User** on that
project. Download a JSON key for the service account.

## Usage

```python
import asyncio
from pathlib import Path

import aiohttp

from aiogeodrops import GeoDropsClient


async def main() -> None:
    key = Path("service-account.json").read_text()
    async with aiohttp.ClientSession() as session:
        client = GeoDropsClient(session, "my-gcp-project", key)

        await client.validate_access()

        # Find a probe's device id from the serial shown in the GeoDrops app.
        reading = await client.lookup_serial("AAA111", lookback_hours=12)
        if reading is None:
            print("No reading in the last 12 hours")
            return

        latest = await client.fetch_latest([reading.device_id], lookback_hours=12)
        probe = latest[reading.device_id]
        print(probe.moisture_pct, probe.temp_surface, probe.read_at)


asyncio.run(main())
```

`fetch_latest` makes one query for all the device ids you pass. Each query
gives up after 60 seconds (`query_timeout=` to change).

`fetch_battery_history(device_ids, days)` returns each probe's battery voltage
readings over the last `days` days, oldest first, as `BatteryPoint(read_at,
battery_mv)`: about 48 a day per probe, in one query. Use it to judge how fast
a battery is draining.

### When GeoDrops changes its table

GeoDrops' table is theirs to change, and columns have been renamed or dropped
before. Before querying, the client reads which columns exist (a free metadata
call, repeated at most every 6 hours) and selects only those:

- A missing column leaves the reading fields it fed as `None` (or
  `UNCLASSIFIED`). `client.missing_columns` lists them, and one warning is
  logged.
- If a query still names an unknown column, the columns are re-read and the
  query retried once.
- `GeoDropsSchemaError` is raised only when a column the query can't run
  without is gone: `deviceId`, `date`, `createdAtOrigin`, `mfgSn` for
  `lookup_serial`, and `deviceBattMV` for `fetch_battery_history`.

`await client.table_columns()` lists every column GeoDrops publishes.

## Errors

All errors derive from `GeoDropsError`:

| Exception | Meaning | What to do |
| --- | --- | --- |
| `GeoDropsCredentialsError` | The key isn't a usable service-account key | Fix the key; raised by the constructor |
| `GeoDropsAuthError` | Google rejected the key (deleted, revoked, account disabled) | Create a new key |
| `GeoDropsAccessDeniedError` | 403: missing BigQuery Job User role, or BigQuery API not enabled | Fix it in Google Cloud; then retry |
| `GeoDropsSchemaError` | GeoDrops' table lost a column the query can't run without | Update aiogeodrops |
| `GeoDropsQueryError` | BigQuery rejected the query (parent of `GeoDropsAccessDeniedError` and `GeoDropsSchemaError`) | Check the project id |
| `GeoDropsConnectionError` | Network error, timeout, Google outage, rate or quota limit | Retry later |

## Development

```bash
pip install -e ".[dev]"
ruff check . && ruff format --check .
mypy
pytest --cov=aiogeodrops
```

Releases are published to PyPI by GitHub Actions when a GitHub release is
published, using PyPI trusted publishing (no stored token) with attestations.

## License

[MIT](LICENSE)
