|
| 1 | +# Rate Limiting & Default Timeouts (Backwards-Compatible) |
| 2 | + |
| 3 | +This fork adds a **client-side write rate limiter** and **default HTTP timeouts** without breaking existing callers. |
| 4 | + |
| 5 | +- **Backwards compatible:** Existing code that calls `Frigidaire(email, password)` keeps working. |
| 6 | +- **Write call smoothing:** Mutating HTTP verbs (`POST`, `PUT`, `PATCH`, `DELETE`) are spaced out by default to avoid server lockouts. |
| 7 | +- **Retry-After aware:** `429/423` responses back off, honoring `Retry-After` (capped). |
| 8 | +- **Default HTTP timeout:** If a request doesn’t pass `timeout=...`, a library default is applied (`15s` by default). |
| 9 | + |
| 10 | +## Quickstart |
| 11 | + |
| 12 | +```python |
| 13 | +from frigidaire import Frigidaire |
| 14 | +# If your build doesn't auto-enable autowrap at import time, uncomment one of these: |
| 15 | +# import frigidaire.rl_autowrap as _ |
| 16 | +# from frigidaire.rl_autowrap import enable_autowrap; enable_autowrap() |
| 17 | + |
| 18 | +api = Frigidaire("email", "password") |
| 19 | +devices = api.get_appliances() |
| 20 | +print(devices) |
| 21 | +``` |
| 22 | + |
| 23 | +## Customization (optional) |
| 24 | + |
| 25 | +You can pass kwargs to `Frigidaire(...)` to tune behavior. All are optional: |
| 26 | + |
| 27 | +- `http_timeout`: float or `(connect, read)` tuple; default `15.0` seconds |
| 28 | +- `rate_limit_min_interval`: seconds between write requests; default `1.25` |
| 29 | +- `rate_limit_jitter`: random jitter added to spacing; default `0.25` |
| 30 | +- `rate_limit_methods`: set of verbs to limit; default `{"POST","PUT","PATCH","DELETE"}` |
| 31 | +- `rate_limit_scope_key`: share a limiter across instances by key (default: the account email) |
| 32 | +- `max_retry_after`: cap for honoring server `Retry-After`; default `60.0` |
| 33 | +- `max_retries_on_429`: max retries for `429/423`; default `4` |
| 34 | + |
| 35 | +**Example:** |
| 36 | + |
| 37 | +```python |
| 38 | +api = Frigidaire( |
| 39 | + "email", "password", |
| 40 | + http_timeout=20.0, # or (5, 30) |
| 41 | + rate_limit_min_interval=1.5, # seconds between writes |
| 42 | + rate_limit_jitter=0.3, # smooth bursts |
| 43 | + max_retry_after=90.0, |
| 44 | + max_retries_on_429=5, |
| 45 | + # rate_limit_methods={"POST","PUT","PATCH","DELETE"}, |
| 46 | + # rate_limit_scope_key="household-42", |
| 47 | +) |
| 48 | +``` |
| 49 | + |
| 50 | +## CLI Smoke Tests |
| 51 | + |
| 52 | +We provide a small script in `scripts/smoke_test_frigidaire.py` that exercises the new features without changing device state. |
| 53 | + |
| 54 | +```bash |
| 55 | +python3 -m venv .venv |
| 56 | +source .venv/bin/activate |
| 57 | +pip install -e . |
| 58 | + |
| 59 | +export FRIGIDAIRE_EMAIL="you@example.com" |
| 60 | +export FRIGIDAIRE_PASSWORD="••••••••" |
| 61 | + |
| 62 | +# Optional tunables for the test run |
| 63 | +export MIN_INTERVAL=1.5 |
| 64 | +export JITTER=0.0 |
| 65 | +export HTTP_TIMEOUT=15.0 |
| 66 | + |
| 67 | +python scripts/smoke_test_frigidaire.py --min-interval "${MIN_INTERVAL:-1.5}" --jitter "${JITTER:-0.0}" --http-timeout "${HTTP_TIMEOUT:-15.0}" |
| 68 | +``` |
| 69 | + |
| 70 | +What it checks: |
| 71 | + |
| 72 | +1. **Auth + list devices** (read-only) |
| 73 | +2. **Default timeout** (~15s) using a delayed URL when no per-request timeout is passed |
| 74 | +3. **Rate-limit spacing** using harmless POSTs to prove gaps between writes |
| 75 | +4. **Retry-After** handling — tries a live 429, then falls back to a local simulation if the live check is blocked |
| 76 | + |
| 77 | +## Opt-in vs. auto-enable |
| 78 | + |
| 79 | +If your package enables autowrap at import time (via a small block in `frigidaire/__init__.py`), no changes are needed by callers. |
| 80 | +Otherwise, add one of these once at startup: |
| 81 | + |
| 82 | +```python |
| 83 | +import frigidaire.rl_autowrap as _ |
| 84 | +# or |
| 85 | +from frigidaire.rl_autowrap import enable_autowrap |
| 86 | +enable_autowrap() |
| 87 | +``` |
| 88 | + |
| 89 | +## Notes |
| 90 | + |
| 91 | +- The rate limiter is **in-process** and keyed by `rate_limit_scope_key` (defaults to the account email). |
| 92 | +- Reads (`GET`) are not rate-limited by default. |
| 93 | +- Timeouts apply **only** when a given request doesn’t already supply `timeout=`. |
| 94 | +- For Home Assistant users, no changes are required if the library is imported normally and autowrap is enabled. |
0 commit comments