Commit 9e231b8
authored
Release v2: Async client, TLS enforcement, retries/backoff, and CI upgrades
## Summary
This major release delivers a production-ready async client with robust TLS handling, resilient request logic (timeouts, retries, rate limits), and a fully wired CI pipeline (lint, type-check, tests, coverage). It replaces the legacy synchronous v1 client and prepares the project for Home Assistant and broader integration.
## Highlights
- **Async client**
- New `FmdClient` with an async factory (`await FmdClient.create(...)`) and async context manager (`async with ...`).
- Clean session lifecycle management and connection pooling controls.
- **TLS/SSL**
- HTTPS-only `base_url` enforcement (rejects plain HTTP).
- Configurable validation: `ssl=False` (dev only) or pass a custom `ssl.SSLContext`.
- Certificate pinning example included in docs.
- **Reliability**
- Timeouts applied across all HTTP requests.
- Retries with exponential backoff and optional jitter for 5xx and connection errors.
- 429 rate-limit handling with Retry-After support.
- Safe behavior for command POSTs (no unsafe retries).
- **Features**
- Client-side export to ZIP (locations + pictures).
- Device helper with convenience actions (e.g., request location, play sound, take picture).
- **Safety and ergonomics**
- Sanitized logging (no sensitive payloads); token masking helper.
- Typed package (`py.typed`) and mypy-clean.
- **CI/CD**
- GitHub Actions: lint (flake8), type-check (mypy), unit tests matrix (Ubuntu/Windows; Py 3.8–3.12).
- Coverage with branch analysis and Codecov upload + badges.
- Publish workflows for TestPyPI (non-main) and PyPI (main or Release).
## Breaking changes
- API surface moved to async:
- Before (v1, sync): `client = FmdApi(...); client.authenticate(...); client.get_all_locations()`
- Now (v2, async):
- `client = await FmdClient.create(base_url, fmd_id, password, ...)`
- `await client.get_locations(...)`, `await client.get_pictures(...)`, `await client.send_command(...)`
- Prefer: `async with FmdClient.create(...) as client: ...`
- Transport requirements:
- `base_url` must be HTTPS; plain HTTP raises `ValueError`.
- Python versions:
- Targets Python 3.8+ (3.7 removed from classifiers).
## Migration guide
- Replace `FmdApi` usage with the async `FmdClient`:
- Use `await FmdClient.create(...)` and `async with` for safe resource management.
- Update all calls to await the async methods.
- TLS and self-signed setups:
- For dev-only scenarios: pass `ssl=False`.
- For proper self-signed use: pass a custom `ssl.SSLContext`.
- For high-security setups: consider the certificate pinning example in README.
- Connection tuning:
- Optional: `conn_limit`, `conn_limit_per_host`, `keepalive_timeout` on the TCPConnector via client init.
## Error handling and semantics
- 401 triggers one automatic re-authenticate then retries the request.
- 429 honors Retry-After, otherwise uses exponential backoff with jitter.
- Transient 5xx/connection errors are retried (except unsafe command POST replays).
- Exceptions are normalized to `FmdApiException` where appropriate; messages mask sensitive data.
## Documentation and examples
- README: TLS/self-signed guidance, warnings, and certificate pinning example.
- Debugging: `pin_cert_example.py` demonstrates secure pinning and avoids CLI secrets.
## CI/CD and release automation
- Tests: unit suite expanded; functional tests run when credentials are available.
- Coverage: ~83% overall; XML + branch coverage uploaded to Codecov (badges included).
- Workflows:
- `test.yml`: runs on push/PR for all branches (lint, mypy, unit tests matrix, coverage, optional functional tests).
- `publish.yml`: builds on push/releases; publishes to TestPyPI for non-main pushes, PyPI on main or release.
## Checklist
- [x] All unit tests pass
- [x] Flake8 clean
- [x] Mypy clean
- [x] Coverage collected and uploaded
- [x] README/docs updated (TLS, pinning, badges)
- [x] Packaging: sdist and wheel built; publish workflows configured
## Notes
- Use `ssl=False` sparingly and never in production.
- Consider adding Dependabot and security scanning in a follow-up.
- A `CHANGELOG.md` entry for 2.0.0 is recommended if not already included.
> BREAKING CHANGE: v2 switches to an async client, enforces HTTPS, and drops Python 3.7; synchronous v1 usage must be migrated as noted above.File tree
56 files changed
+5550
-2766
lines changed- .github/workflows
- debugging
- docs
- release
- fmd_api
- tests
- functional
- unit
- utils
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
56 files changed
+5550
-2766
lines changedBinary file not shown.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
4 | 6 | | |
| 7 | + | |
| 8 | + | |
5 | 9 | | |
6 | 10 | | |
7 | 11 | | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
8 | 16 | | |
9 | 17 | | |
10 | 18 | | |
11 | 19 | | |
| 20 | + | |
| 21 | + | |
12 | 22 | | |
13 | 23 | | |
14 | 24 | | |
| |||
18 | 28 | | |
19 | 29 | | |
20 | 30 | | |
21 | | - | |
22 | | - | |
23 | | - | |
| 31 | + | |
| 32 | + | |
24 | 33 | | |
25 | | - | |
26 | 34 | | |
27 | | - | |
| 35 | + | |
28 | 36 | | |
29 | | - | |
30 | 37 | | |
| 38 | + | |
31 | 39 | | |
32 | 40 | | |
33 | 41 | | |
34 | 42 | | |
35 | 43 | | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
40 | 47 | | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | | - | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
48 | 55 | | |
49 | | - | |
50 | | - | |
| 56 | + | |
| 57 | + | |
51 | 58 | | |
52 | 59 | | |
53 | | - | |
| 60 | + | |
54 | 61 | | |
55 | 62 | | |
56 | 63 | | |
57 | 64 | | |
58 | 65 | | |
59 | | - | |
60 | | - | |
| 66 | + | |
61 | 67 | | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
67 | | - | |
68 | | - | |
69 | | - | |
70 | | - | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
71 | 71 | | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
72 | 84 | | |
73 | 85 | | |
74 | 86 | | |
75 | 87 | | |
76 | 88 | | |
77 | | - | |
| 89 | + | |
78 | 90 | | |
79 | 91 | | |
80 | 92 | | |
81 | 93 | | |
82 | | - | |
83 | | - | |
84 | | - | |
85 | | - | |
86 | | - | |
87 | | - | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
6 | 12 | | |
7 | 13 | | |
8 | 14 | | |
| |||
39 | 45 | | |
40 | 46 | | |
41 | 47 | | |
42 | | - | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
0 commit comments