This guide covers setting up a development environment for asyncgateway.
- Python 3.10 or higher (dev environment pinned to 3.11 via
.python-version) - Git
- uv package manager
-
Clone the repository:
git clone <repository-url> cd asyncgateway
-
Install uv (if not already installed):
# On macOS and Linux curl -LsSf https://astral.sh/uv/install.sh | sh # On Windows powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
-
Set up the development environment:
uv sync --group dev
- Add a dependency:
uv add <package> - Remove a dependency:
uv remove <package> - Sync dependencies:
uv sync --group dev
- Run all tests:
uv run pytest - Run tests with verbose output:
uv run pytest -v - Run tests with coverage:
uv run pytest -v --cov - Run specific test pattern:
uv run pytest -k <pattern>
No live IAG instance is required — all tests mock ipsdk.
Tests run across Python 3.10–3.13 via tox:
tox # all versions
tox -e py312 # single version- Lint:
uv run ruff check src tests - Format:
uv run ruff format src tests - Type check:
uv run mypy src/ - Security scan:
uv run bandit -r src/asyncgateway --configfile pyproject.toml
Run the complete CI suite (lint + typecheck + security + tests) with:
make ci- Build distributions:
uv build
asyncgateway/
├── src/asyncgateway/
│ ├── __init__.py # Public API: client, logging
│ ├── client.py # Client class with service/resource discovery
│ ├── exceptions.py # Exception hierarchy (AsyncGatewayError, ValidationError)
│ ├── logging.py # Logging with sensitive data filtering
│ ├── serdes.py # JSON / YAML / TOML serialization utilities
│ ├── services/ # 23 thin async wrappers, one per IAG API tag group
│ └── resources/ # 22 declarative resource abstractions
├── tests/ # Test files (mock ipsdk, no live IAG needed)
├── docs/ # Documentation
└── pyproject.toml # Project configuration and tool settings
The core library requires only ipsdk. Additional serialization formats can
be unlocked by installing optional packages:
| Format | Operation | Package | Python version |
|---|---|---|---|
| YAML | read & write | PyYAML |
any |
| TOML | read | (stdlib tomllib) |
3.11+ |
| TOML | read | tomli |
3.10 only |
| TOML | write | tomli-w |
any |
Install a single optional format:
uv add PyYAML # YAML support
uv add tomli # TOML read on Python 3.10
uv add tomli-w # TOML write (any version)Install all optional serialization extras at once:
uv add PyYAML tomli-w # tomli only needed on Python 3.10You can inspect runtime availability from Python:
from asyncgateway.serdes import YAML_AVAILABLE, TOML_AVAILABLE, TOML_WRITE_AVAILABLECalling a function whose dependency is absent raises ValidationError (not
ImportError), so callers do not need to guard imports.
- Services and resources are filesystem-discovered at client init. Adding a file with a
ServiceorResourceclass and anameattribute is sufficient for registration — no other wiring required. Operation(MERGE,REPLACE,OVERWRITE) is a plain string class, not an enum. It lives inservices/__init__.pyand is re-exported fromresources/__init__.py.- Logging is silent by default (level
NONE = 100). Enable withasyncgateway.logging.set_level(asyncgateway.logging.DEBUG). client.load(path, op)reads files from a directory tree and dispatches toservice.load().client.resources.devices.load(data, op)takes an in-memory list. These are separate code paths.playbooks.get_all()uses a manual pagination loop and does not call_get_all()— the playbooks endpoint usesmeta["count"]rather thanmeta["total_count"].
- Make your changes following the existing code style.
- Run the full CI suite before committing:
make ci
- Ensure all tests pass and coverage remains high.
- Raise typed exceptions from
exceptions.py. Never raise bareException. - Services must be thin — one method per API operation, return
res.json(), no logic. - Resources catch broad
Exceptionfor "not found" detection (ipsdk exception types are not part of the public contract).
- If you encounter import errors, ensure all dependencies are installed with
uv sync --group dev. - For test failures, run with
-vfor more detailed output. - If tests mock
os.listdir, note thatClient.__init__calls it twice — once forservices/and once forresources/. Useside_effect=[service_files, resource_files]. - YAML support is optional. If
PyYAMLis not installed, YAML paths raiseValidationErrorrather thanImportError. - TOML read support is optional on Python 3.10 (install
tomli); on 3.11+ it uses the stdlibtomllib. TOML write always requirestomli-w.