nimbio-community-api 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nimbio_community_api-0.1.0/.gitignore +33 -0
- nimbio_community_api-0.1.0/AGENTS.md +130 -0
- nimbio_community_api-0.1.0/CHANGELOG.md +28 -0
- nimbio_community_api-0.1.0/LICENSE +21 -0
- nimbio_community_api-0.1.0/PKG-INFO +288 -0
- nimbio_community_api-0.1.0/README.md +246 -0
- nimbio_community_api-0.1.0/examples/async_quickstart.py +35 -0
- nimbio_community_api-0.1.0/examples/sync_quickstart.py +43 -0
- nimbio_community_api-0.1.0/pyproject.toml +101 -0
- nimbio_community_api-0.1.0/src/nimbio_community_api/__init__.py +79 -0
- nimbio_community_api-0.1.0/src/nimbio_community_api/_async.py +212 -0
- nimbio_community_api-0.1.0/src/nimbio_community_api/_base.py +303 -0
- nimbio_community_api-0.1.0/src/nimbio_community_api/_environments.py +49 -0
- nimbio_community_api-0.1.0/src/nimbio_community_api/_exceptions.py +141 -0
- nimbio_community_api-0.1.0/src/nimbio_community_api/_sync.py +222 -0
- nimbio_community_api-0.1.0/src/nimbio_community_api/_version.py +3 -0
- nimbio_community_api-0.1.0/src/nimbio_community_api/models.py +451 -0
- nimbio_community_api-0.1.0/src/nimbio_community_api/py.typed +0 -0
- nimbio_community_api-0.1.0/tests/conftest.py +15 -0
- nimbio_community_api-0.1.0/tests/test_async_client.py +231 -0
- nimbio_community_api-0.1.0/tests/test_community_endpoints.py +170 -0
- nimbio_community_api-0.1.0/tests/test_config.py +49 -0
- nimbio_community_api-0.1.0/tests/test_exceptions.py +74 -0
- nimbio_community_api-0.1.0/tests/test_models.py +170 -0
- nimbio_community_api-0.1.0/tests/test_sync_client.py +171 -0
- nimbio_community_api-0.1.0/tests/test_transport.py +283 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
*.egg
|
|
6
|
+
.eggs/
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
wheels/
|
|
10
|
+
|
|
11
|
+
# Virtual envs
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
env/
|
|
15
|
+
|
|
16
|
+
# Tooling caches
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.mypy_cache/
|
|
19
|
+
.ruff_cache/
|
|
20
|
+
.tox/
|
|
21
|
+
htmlcov/
|
|
22
|
+
.coverage
|
|
23
|
+
.coverage.*
|
|
24
|
+
|
|
25
|
+
# Editors / OS
|
|
26
|
+
.idea/
|
|
27
|
+
.vscode/
|
|
28
|
+
*.swp
|
|
29
|
+
.DS_Store
|
|
30
|
+
|
|
31
|
+
# Secrets — never commit a real API key
|
|
32
|
+
.env
|
|
33
|
+
*.local.ini
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# AGENTS.md — using `nimbio-community-api` from an LLM/agent
|
|
2
|
+
|
|
3
|
+
This is a compact, copy-pasteable reference for coding agents and quick sessions.
|
|
4
|
+
Everything here is real and current with the package.
|
|
5
|
+
|
|
6
|
+
## Install & import
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install nimbio-community-api
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
from nimbio_community_api import NimbioClient # sync
|
|
14
|
+
from nimbio_community_api import AsyncNimbioClient # async
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Authenticate
|
|
18
|
+
|
|
19
|
+
A key is required. It looks like `nimbio_test_<22 chars>` or `nimbio_live_<22 chars>`.
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
client = NimbioClient("nimbio_test_...") # explicit
|
|
23
|
+
client = NimbioClient() # reads NIMBIO_API_KEY
|
|
24
|
+
client = NimbioClient("nimbio_live_...", environment="dev")
|
|
25
|
+
client = NimbioClient("nimbio_test_...", base_url="http://localhost:8000")
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- `environment`: `"prod"` (default → api.nimbio.com), `"dev"` (→ api.nimbio.dev),
|
|
29
|
+
`"local"` (→ localhost:8000). Or set `base_url` to override.
|
|
30
|
+
- **test vs live is the KEY, not a flag.** A `nimbio_test_*` key never fires a
|
|
31
|
+
gate / sends a real message. Check with `client.mode` → `"test"` | `"live"`.
|
|
32
|
+
- Env vars: `NIMBIO_API_KEY`, `NIMBIO_ENV`, `NIMBIO_BASE_URL`.
|
|
33
|
+
|
|
34
|
+
## The whole API (sync — drop the `with`/use `await` for async)
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
with NimbioClient("nimbio_test_...") as client:
|
|
38
|
+
client.me() # -> Me (account_id, key.usage…)
|
|
39
|
+
client.health() # -> Health (ok, wamp) — never raises on 503
|
|
40
|
+
client.mode # -> "test" | "live" | None (no network)
|
|
41
|
+
|
|
42
|
+
# Reads (community-scoped key required)
|
|
43
|
+
client.community.gate_status() # -> GateStatus (.latches: list[Latch])
|
|
44
|
+
client.community.members() # -> Members (.accepted/.unaccepted/.removed)
|
|
45
|
+
client.community.key_statuses() # -> KeyStatuses (.keys, .hold_opens)
|
|
46
|
+
client.community.keys() # -> list[CommunityKey]
|
|
47
|
+
|
|
48
|
+
# Writes (test key = simulated, live key = real)
|
|
49
|
+
client.community.open("LATCH_ID", note="...", idempotency_key="...") # -> OpenResult
|
|
50
|
+
client.community.message("text") # -> WriteResult
|
|
51
|
+
client.community.add_member("+15551234567", ["KEY_ID"]) # -> WriteResult
|
|
52
|
+
client.community.grant_keys(ACCOUNT_COMMUNITY_ID, ["KEY_ID"]) # -> WriteResult
|
|
53
|
+
client.community.revoke_keys(ACCOUNT_COMMUNITY_ID, ["KEY_ID"],
|
|
54
|
+
remove_member=False) # -> WriteResult
|
|
55
|
+
client.community.set_keys_disabled(ACCOUNT_COMMUNITY_ID, ["KEY_ID"],
|
|
56
|
+
disabled=True) # -> WriteResult
|
|
57
|
+
|
|
58
|
+
# Logs (community must have Access Log History enabled)
|
|
59
|
+
client.community.member_access_logs(ACCOUNT_COMMUNITY_ID, window="last_30") # last_30|30_60|60_90
|
|
60
|
+
client.community.access_log(page=0) # -> AccessLogPage (.logs, .has_more)
|
|
61
|
+
client.community.gate_status_log(page=0) # -> GateStatusLogPage
|
|
62
|
+
for row in client.community.iter_access_log(): # auto-paginates all pages
|
|
63
|
+
...
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Async is identical with `await`, and the iterators are `async for`:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
async with AsyncNimbioClient("nimbio_test_...") as client:
|
|
70
|
+
me = await client.me()
|
|
71
|
+
await client.community.open("LATCH_ID")
|
|
72
|
+
async for row in client.community.iter_access_log():
|
|
73
|
+
...
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## ID vocabulary (important)
|
|
77
|
+
|
|
78
|
+
- **`latch_id`** — from `gate_status().latches[i].latch_id`.
|
|
79
|
+
- **`key_id`** (community key id) — from `keys()[i].id` or `key_statuses()`.
|
|
80
|
+
Used everywhere keys are granted/revoked/disabled.
|
|
81
|
+
- **`account_community_id`** — a member's id, from
|
|
82
|
+
`members().accepted[i].account_community_id`. Used to address a member.
|
|
83
|
+
|
|
84
|
+
## Return values
|
|
85
|
+
|
|
86
|
+
Every model exposes typed attributes **and** the full payload on `.raw`. Writes
|
|
87
|
+
return a `WriteResult` whose `.result` is the outcome string
|
|
88
|
+
(`"member_added"`, `"keys_granted"`, `"sent"`, or `"simulated"`); extra fields
|
|
89
|
+
are reachable with `result["field"]`, `result.get("field")`, or `result.raw`.
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
r = client.community.add_member("+15551234567", ["KEY_ID"])
|
|
93
|
+
r.result # "member_added" (live) or "simulated" (test)
|
|
94
|
+
r.simulated # True on a test key
|
|
95
|
+
r.get("account_community_id")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Errors (always wrap network/side-effecting calls)
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from nimbio_community_api import (
|
|
102
|
+
APIError, AuthenticationError, PermissionDeniedError,
|
|
103
|
+
RateLimitError, GateNotOpenedError,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
client.community.open("LATCH_ID")
|
|
108
|
+
except GateNotOpenedError: # 504 — gate didn't confirm in time
|
|
109
|
+
...
|
|
110
|
+
except PermissionDeniedError as e: # 403 — wrong scope / open denied / not a community key
|
|
111
|
+
print(e.code) # machine code, e.g. "open_denied", "not_community_key"
|
|
112
|
+
except RateLimitError as e: # 429
|
|
113
|
+
print(e.retry_after) # seconds, may be None
|
|
114
|
+
except APIError as e: # any other HTTP >= 400
|
|
115
|
+
print(e.status_code, e.code, e.message, e.request_id)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`APIError` always has `.status_code`, `.code`, `.message`, `.request_id`.
|
|
119
|
+
Config problems (missing key, bad environment) raise `NimbioConfigError`
|
|
120
|
+
*before* any request. Network failures raise `APIConnectionError` /
|
|
121
|
+
`APITimeoutError`.
|
|
122
|
+
|
|
123
|
+
## Safety tips for agents
|
|
124
|
+
|
|
125
|
+
- Default to a **test key** while iterating; `assert client.mode == "test"` to
|
|
126
|
+
hard-stop accidental live opens.
|
|
127
|
+
- `open()` and member writes are **side-effecting** with a live key. Read first
|
|
128
|
+
(`gate_status`, `members`, `keys`) to discover valid ids before writing.
|
|
129
|
+
- The community `open` is **synchronous** and can take ~15–18s; the default
|
|
130
|
+
client timeout (30s) already accounts for this.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `nimbio-community-api` are documented here. This project
|
|
4
|
+
adheres to [Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-06-30
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Initial release.
|
|
12
|
+
- Synchronous `NimbioClient` and asynchronous `AsyncNimbioClient`, sharing one
|
|
13
|
+
request/parse/retry core.
|
|
14
|
+
- `client.me()`, `client.health()`, and the `client.community.*` namespace
|
|
15
|
+
covering gate status, members, key statuses, keys, opens, messages, member
|
|
16
|
+
key management, and access/gate-status logs.
|
|
17
|
+
- Typed, tolerant dataclass response models (`.raw` always retained); ships
|
|
18
|
+
`py.typed`.
|
|
19
|
+
- Environment selection (`prod` / `dev` / `local`) plus `base_url` override;
|
|
20
|
+
test-vs-live mode inferred from the API key (`client.mode`).
|
|
21
|
+
- Configuration via arguments or `NIMBIO_API_KEY` / `NIMBIO_ENV` /
|
|
22
|
+
`NIMBIO_BASE_URL`.
|
|
23
|
+
- Typed exception hierarchy mapping the API error envelope, with automatic
|
|
24
|
+
retries (429 + 5xx, honoring `Retry-After`).
|
|
25
|
+
- Log pagination helpers (`iter_access_log`, `iter_gate_status_log`).
|
|
26
|
+
|
|
27
|
+
[Unreleased]: https://github.com/nimbio-labs/nimbio-python-community-api/compare/v0.1.0...HEAD
|
|
28
|
+
[0.1.0]: https://github.com/nimbio-labs/nimbio-python-community-api/releases/tag/v0.1.0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nimbio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nimbio-community-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python client for the Nimbio community API (api.nimbio.com) — sync + async.
|
|
5
|
+
Project-URL: Homepage, https://api.nimbio.com
|
|
6
|
+
Project-URL: Documentation, https://github.com/nimbio-labs/nimbio-python-community-api#readme
|
|
7
|
+
Project-URL: Source, https://github.com/nimbio-labs/nimbio-python-community-api
|
|
8
|
+
Project-URL: Issues, https://github.com/nimbio-labs/nimbio-python-community-api/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/nimbio-labs/nimbio-python-community-api/blob/main/CHANGELOG.md
|
|
10
|
+
Author: Nimbio
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: access-control,api,client,community,gate,nimbio,sdk
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Requires-Dist: httpx<1,>=0.23
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-cov>=4.1; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
34
|
+
Requires-Dist: respx>=0.20; extra == 'dev'
|
|
35
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
36
|
+
Provides-Extra: test
|
|
37
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'test'
|
|
38
|
+
Requires-Dist: pytest-cov>=4.1; extra == 'test'
|
|
39
|
+
Requires-Dist: pytest>=7.4; extra == 'test'
|
|
40
|
+
Requires-Dist: respx>=0.20; extra == 'test'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# nimbio-community-api
|
|
44
|
+
|
|
45
|
+
Official Python client for the **Nimbio community API** ([api.nimbio.com](https://api.nimbio.com)).
|
|
46
|
+
|
|
47
|
+
Manage a Nimbio community programmatically: read gate status, open gates, add and
|
|
48
|
+
manage members and their keys, send community messages, and pull access logs —
|
|
49
|
+
from sync **or** async Python, with full type hints.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install nimbio-community-api
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- ✅ **Sync and async** — `NimbioClient` for any script, `AsyncNimbioClient` for asyncio.
|
|
56
|
+
- ✅ **Typed** — dataclass response models with autocomplete; ships `py.typed`.
|
|
57
|
+
- ✅ **Test vs live** — inferred automatically from your API key.
|
|
58
|
+
- ✅ **One dependency** — just [`httpx`](https://www.python-httpx.org/).
|
|
59
|
+
- ✅ **Built-in retries**, a clean exception hierarchy, and log pagination helpers.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Quickstart
|
|
64
|
+
|
|
65
|
+
### Sync
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from nimbio_community_api import NimbioClient
|
|
69
|
+
|
|
70
|
+
with NimbioClient("nimbio_test_your_key_here") as client:
|
|
71
|
+
print(client.me().account_id)
|
|
72
|
+
|
|
73
|
+
for latch in client.community.gate_status().latches:
|
|
74
|
+
print(latch.latch_name, "->", latch.status)
|
|
75
|
+
|
|
76
|
+
# Open a gate. A test key simulates; a live key fires the gate.
|
|
77
|
+
result = client.community.open("latch-id-123", note="front gate")
|
|
78
|
+
print(result.result) # "simulated" (test key) or "opened" (live key)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Async
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import asyncio
|
|
85
|
+
from nimbio_community_api import AsyncNimbioClient
|
|
86
|
+
|
|
87
|
+
async def main():
|
|
88
|
+
async with AsyncNimbioClient("nimbio_live_your_key_here", environment="dev") as client:
|
|
89
|
+
me = await client.me()
|
|
90
|
+
print(me.account_id)
|
|
91
|
+
await client.community.open("latch-id-123")
|
|
92
|
+
|
|
93
|
+
asyncio.run(main())
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The two clients have an **identical method surface** — the async version just
|
|
97
|
+
returns awaitables and exposes async iterators.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Configuration
|
|
102
|
+
|
|
103
|
+
You can configure the client with arguments or environment variables. Precedence
|
|
104
|
+
is **arguments > environment variables > defaults**.
|
|
105
|
+
|
|
106
|
+
| Argument | Env var | Default | Notes |
|
|
107
|
+
|---|---|---|---|
|
|
108
|
+
| `api_key` | `NIMBIO_API_KEY` | — (required) | `nimbio_test_…` or `nimbio_live_…` |
|
|
109
|
+
| `environment` | `NIMBIO_ENV` | `"prod"` | `"prod"`, `"dev"`, or `"local"` |
|
|
110
|
+
| `base_url` | `NIMBIO_BASE_URL` | — | Overrides `environment` entirely |
|
|
111
|
+
| `timeout` | — | `30.0` | Seconds; the community open is synchronous (~15–18s) |
|
|
112
|
+
| `max_retries` | — | `2` | Retries 429 + 5xx with backoff, honoring `Retry-After` |
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
# Picks up NIMBIO_API_KEY and NIMBIO_ENV from the environment:
|
|
116
|
+
client = NimbioClient()
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Environments vs. test/live
|
|
120
|
+
|
|
121
|
+
These are **two independent axes**:
|
|
122
|
+
|
|
123
|
+
- **Environment** = *which server* you talk to (`prod` → `api.nimbio.com`,
|
|
124
|
+
`dev` → `api.nimbio.dev`, `local` → `localhost:8000`).
|
|
125
|
+
- **Test vs live** = *what the key does*, determined by the key itself. A
|
|
126
|
+
`nimbio_test_*` key runs the full pipeline (auth, rate limits, scope checks,
|
|
127
|
+
validation) but never fires a gate or sends a real message; a `nimbio_live_*`
|
|
128
|
+
key performs the action. Check it without a network call via `client.mode`.
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
client = NimbioClient("nimbio_test_...")
|
|
132
|
+
assert client.mode == "test" # great as a guard before destructive calls
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## API reference
|
|
138
|
+
|
|
139
|
+
### Top level
|
|
140
|
+
|
|
141
|
+
| Method | Returns | Description |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| `client.me()` | `Me` | Key metadata + live usage counters |
|
|
144
|
+
| `client.health()` | `Health` | Backend reachability (unauthenticated; never raises on 503) |
|
|
145
|
+
| `client.mode` | `"test"`/`"live"`/`None` | Key mode, derived locally |
|
|
146
|
+
| `client.close()` / `await client.aclose()` | — | Close the underlying HTTP client |
|
|
147
|
+
|
|
148
|
+
### `client.community` — reads
|
|
149
|
+
|
|
150
|
+
| Method | Returns |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `gate_status()` | `GateStatus` — latest sensed state per latch |
|
|
153
|
+
| `members()` | `Members` — accepted / unaccepted / removed |
|
|
154
|
+
| `key_statuses()` | `KeyStatuses` — live key + latch state, hold-opens |
|
|
155
|
+
| `keys()` | `list[CommunityKey]` — keys with their access restrictions |
|
|
156
|
+
|
|
157
|
+
### `client.community` — writes
|
|
158
|
+
|
|
159
|
+
| Method | Returns |
|
|
160
|
+
|---|---|
|
|
161
|
+
| `open(latch_id, *, note=None, idempotency_key=None)` | `OpenResult` |
|
|
162
|
+
| `message(message)` | `WriteResult` |
|
|
163
|
+
| `add_member(phone_number, key_ids)` | `WriteResult` |
|
|
164
|
+
| `grant_keys(account_community_id, key_ids)` | `WriteResult` |
|
|
165
|
+
| `revoke_keys(account_community_id, key_ids, *, remove_member=False)` | `WriteResult` |
|
|
166
|
+
| `set_keys_disabled(account_community_id, key_ids, disabled)` | `WriteResult` |
|
|
167
|
+
|
|
168
|
+
### `client.community` — logs
|
|
169
|
+
|
|
170
|
+
| Method | Returns |
|
|
171
|
+
|---|---|
|
|
172
|
+
| `member_access_logs(account_community_id, *, window="last_30")` | `MemberAccessLogPage` |
|
|
173
|
+
| `access_log(*, page=0)` | `AccessLogPage` |
|
|
174
|
+
| `gate_status_log(*, page=0)` | `GateStatusLogPage` |
|
|
175
|
+
| `iter_access_log(*, start_page=0)` | iterator of `AccessLogEntry` (walks pages) |
|
|
176
|
+
| `iter_gate_status_log(*, start_page=0)` | iterator of `GateStatusLogEntry` |
|
|
177
|
+
|
|
178
|
+
`window` is one of `"last_30"`, `"30_60"`, `"60_90"`. The community-wide log
|
|
179
|
+
methods are paginated 1000 rows per page; the `iter_*` helpers walk every page
|
|
180
|
+
for you (and are `async for` iterators on the async client).
|
|
181
|
+
|
|
182
|
+
Every response object keeps the full decoded JSON on `.raw`, so any field not
|
|
183
|
+
yet surfaced as a typed attribute is still available.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Error handling
|
|
188
|
+
|
|
189
|
+
Every non-2xx response raises a typed exception carrying the API's error
|
|
190
|
+
envelope (`code`, `message`, `request_id`, `status_code`).
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from nimbio_community_api import (
|
|
194
|
+
NimbioClient, RateLimitError, PermissionDeniedError, GateNotOpenedError,
|
|
195
|
+
APIError,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
with NimbioClient("nimbio_live_...") as client:
|
|
199
|
+
try:
|
|
200
|
+
client.community.open("latch-id-123")
|
|
201
|
+
except GateNotOpenedError:
|
|
202
|
+
print("Gate did not confirm the open in time (504).")
|
|
203
|
+
except PermissionDeniedError as e:
|
|
204
|
+
print("Not allowed:", e.code) # e.g. "open_denied"
|
|
205
|
+
except RateLimitError as e:
|
|
206
|
+
print("Slow down, retry after", e.retry_after, "s")
|
|
207
|
+
except APIError as e:
|
|
208
|
+
print(e.status_code, e.code, e.message, e.request_id)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Exception hierarchy:
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
NimbioError
|
|
215
|
+
├── NimbioConfigError # missing key / bad environment (no request made)
|
|
216
|
+
├── APIConnectionError # DNS/TCP/TLS failure
|
|
217
|
+
│ └── APITimeoutError # request timed out
|
|
218
|
+
└── APIError # any HTTP >= 400 (has .status_code, .code, .request_id)
|
|
219
|
+
├── BadRequestError # 400
|
|
220
|
+
├── AuthenticationError # 401
|
|
221
|
+
├── PermissionDeniedError # 403 (wrong scope, open denied, ...)
|
|
222
|
+
├── NotFoundError # 404
|
|
223
|
+
├── RateLimitError # 429 (has .retry_after)
|
|
224
|
+
├── GateNotOpenedError # 504 did_not_open
|
|
225
|
+
├── UpstreamError # 502 / 503
|
|
226
|
+
└── ServerError # other 5xx
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Bring your own HTTP client
|
|
232
|
+
|
|
233
|
+
Pass an existing `httpx` client to share connection pools, proxies, or custom
|
|
234
|
+
transports (useful for testing and advanced deployments):
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
import httpx
|
|
238
|
+
from nimbio_community_api import NimbioClient
|
|
239
|
+
|
|
240
|
+
http = httpx.Client(timeout=10, proxies="http://localhost:8888")
|
|
241
|
+
client = NimbioClient("nimbio_test_...", http_client=http)
|
|
242
|
+
# You own `http`'s lifecycle when you pass it in; client.close() won't close it.
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Development
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
python -m venv .venv && source .venv/bin/activate
|
|
251
|
+
pip install -e '.[dev]'
|
|
252
|
+
pytest # respx-mocked, no network
|
|
253
|
+
pytest --cov=nimbio_community_api --cov-report=term-missing # with coverage
|
|
254
|
+
ruff check .
|
|
255
|
+
mypy
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Common tasks are wrapped in a `Makefile` (run `make help` to list them):
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
make install # pip install -e '.[dev]'
|
|
262
|
+
make test # run the suite
|
|
263
|
+
make check # lint + type-check + coverage (what CI runs)
|
|
264
|
+
make build # build sdist + wheel
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
To run the suite across every installed Python (3.9–3.13) in isolated envs, use
|
|
268
|
+
[`tox`](https://tox.wiki):
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
tox # all interpreters + lint + type
|
|
272
|
+
tox -e py311 # a single interpreter
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
The test suite is fully mocked with [`respx`](https://lundberg.github.io/respx/)
|
|
276
|
+
— it never touches the network — and covers every endpoint, the model parsers,
|
|
277
|
+
the error mapping, retries, and transport edge cases (100% line + branch
|
|
278
|
+
coverage; CI enforces a 95% floor).
|
|
279
|
+
|
|
280
|
+
> Dependency extras: `pip install -e '.[test]'` installs just the test runner;
|
|
281
|
+
> `'.[dev]'` adds ruff + mypy on top.
|
|
282
|
+
|
|
283
|
+
See [`AGENTS.md`](AGENTS.md) for an LLM/agent-oriented usage cheat sheet and
|
|
284
|
+
[`examples/`](examples/) for runnable scripts.
|
|
285
|
+
|
|
286
|
+
## License
|
|
287
|
+
|
|
288
|
+
MIT — see [LICENSE](LICENSE).
|