labyrinthe-connect 1.2.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.
- labyrinthe_connect-1.2.0/.gitignore +10 -0
- labyrinthe_connect-1.2.0/CHANGELOG.md +102 -0
- labyrinthe_connect-1.2.0/LICENSE +21 -0
- labyrinthe_connect-1.2.0/PKG-INFO +720 -0
- labyrinthe_connect-1.2.0/README.fr.md +690 -0
- labyrinthe_connect-1.2.0/README.md +679 -0
- labyrinthe_connect-1.2.0/examples/async_send.py +37 -0
- labyrinthe_connect-1.2.0/examples/otp_login.py +51 -0
- labyrinthe_connect-1.2.0/examples/send_sms.py +37 -0
- labyrinthe_connect-1.2.0/examples/webhook_fastapi.py +41 -0
- labyrinthe_connect-1.2.0/examples/webhook_flask.py +41 -0
- labyrinthe_connect-1.2.0/pyproject.toml +122 -0
- labyrinthe_connect-1.2.0/requirements-dev.in +18 -0
- labyrinthe_connect-1.2.0/requirements-dev.txt +129 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/__init__.py +145 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/_client.py +208 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/_data.py +187 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/_redact.py +39 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/_transport.py +524 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/_version.py +1 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/contrib/__init__.py +29 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/contrib/django.py +114 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/contrib/fastapi.py +86 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/contrib/flask.py +73 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/errors.py +459 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/pagination.py +151 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/phone.py +93 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/py.typed +0 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/resources/__init__.py +16 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/resources/account.py +76 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/resources/otp.py +217 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/resources/sms.py +467 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/types.py +476 -0
- labyrinthe_connect-1.2.0/src/labyrinthe_connect/webhooks.py +327 -0
- labyrinthe_connect-1.2.0/tests/__init__.py +0 -0
- labyrinthe_connect-1.2.0/tests/conftest.py +25 -0
- labyrinthe_connect-1.2.0/tests/helpers.py +190 -0
- labyrinthe_connect-1.2.0/tests/test_api_1_2.py +512 -0
- labyrinthe_connect-1.2.0/tests/test_client.py +175 -0
- labyrinthe_connect-1.2.0/tests/test_contract.py +528 -0
- labyrinthe_connect-1.2.0/tests/test_contrib.py +384 -0
- labyrinthe_connect-1.2.0/tests/test_errors.py +284 -0
- labyrinthe_connect-1.2.0/tests/test_integration.py +267 -0
- labyrinthe_connect-1.2.0/tests/test_otp.py +117 -0
- labyrinthe_connect-1.2.0/tests/test_phone.py +93 -0
- labyrinthe_connect-1.2.0/tests/test_retry.py +408 -0
- labyrinthe_connect-1.2.0/tests/test_security.py +170 -0
- labyrinthe_connect-1.2.0/tests/test_sms.py +326 -0
- labyrinthe_connect-1.2.0/tests/test_webhooks.py +219 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `labyrinthe-connect` (Python) are documented here. This project follows
|
|
4
|
+
[Semantic Versioning](https://semver.org/) and the format of [Keep a Changelog](https://keepachangelog.com/).
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [1.2.0] - 2026-11-06
|
|
9
|
+
|
|
10
|
+
Built against the API specification 1.3.0. Backwards compatible with 1.1.x.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `FeatureDisabledError` (503 `FEATURE_DISABLED`, any operation), a subclass of `ServiceUnavailableError`: the
|
|
14
|
+
platform switched off the whole API (`blocked_by == "api"`) or one function (`api.sms_send`, `api.otp_send`,
|
|
15
|
+
`api.otp_verify`); nothing was done. Properties `feature`, `blocked_by`, `admin_message` (`details.message`),
|
|
16
|
+
`until` (aware `datetime`, `None` when unknown or unreadable) and `retry_after` (`Retry-After` seconds, `None` when
|
|
17
|
+
no re-enable is scheduled). Exported from `labyrinthe_connect`.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- 503 `OTP_SERVICE_DISABLED` (`otp.send`) is now raised as `FeatureDisabledError` (still a `ServiceUnavailableError`),
|
|
21
|
+
with the same details (`feature` and `blocked_by` = `otp`).
|
|
22
|
+
- Retries: `FEATURE_DISABLED` and `OTP_SERVICE_DISABLED` are never retried with an exponential backoff. They are
|
|
23
|
+
retried for every operation, `otp.verify` included (refused before processing), only when `Retry-After` is present
|
|
24
|
+
and not longer than `max_retry_wait`, after exactly that delay; otherwise the error is raised at once. Other 5xx
|
|
25
|
+
are unchanged.
|
|
26
|
+
|
|
27
|
+
## [1.1.1] - 2026-11-06
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- `IdempotencyKeyUnresolvedError` (409 `IDEMPOTENCY_KEY_UNRESOLVED`, `sms.send`, `sms.send_many` and `otp.send`): the
|
|
31
|
+
original request with this `Idempotency-Key` failed unexpectedly and its outcome is unknown (an SMS may have been
|
|
32
|
+
sent). Never retried: check (`sms.list(reference=...)` or with the user), then send again with a new key.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
- Phone numbers are now sent to the API in E.164 with their `+` (`recipient`, `recipients`, the OTP `phone` and the
|
|
36
|
+
`recipient` filter of `sms.list()`). Without it, the API read a short foreign number (10 digits or fewer, e.g.
|
|
37
|
+
`+2481234567`, Seychelles) as a local DR Congo number and sent it to `2432481234567`. `phone.normalize()` still
|
|
38
|
+
returns digits without `+` (the format of the API responses).
|
|
39
|
+
|
|
40
|
+
## [1.1.0] - 2026-11-06
|
|
41
|
+
|
|
42
|
+
Built against the API specification 1.2.0. Backwards compatible with 1.0.0.
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
- `request_id` (`X-Request-Id`) on every error (`LabyrintheError.request_id`; for a network error, the id you sent)
|
|
46
|
+
and on every returned object (`Message`, `MessageBatch`, `Estimate`, `Balance`, `SenderId`, `OtpSendResult`,
|
|
47
|
+
`OtpVerifyResult`, pages), left out of equality and `repr()`. `ApiError.api_version` (`X-Api-Version`).
|
|
48
|
+
- `request_id=` on every method: sent as `X-Request-Id` on every attempt (checked locally against the spec pattern).
|
|
49
|
+
- `IdempotencyRequestInProgressError` (409 `IDEMPOTENCY_REQUEST_IN_PROGRESS`, `retry_after`): retried automatically
|
|
50
|
+
after `Retry-After` with the same `Idempotency-Key` for `sms.send`, `sms.send_many` and `otp.send`.
|
|
51
|
+
- `AccountSuspendedError` (403 `ACCOUNT_SUSPENDED`), a subclass of `PermissionError`.
|
|
52
|
+
- `otp.send()`: automatic `Idempotency-Key` (`uuid4`, or `idempotency_key=`) reused across retries; `web_otp_domain`
|
|
53
|
+
and `strip_accents` parameters; `OtpSendResult.code_length`, `code_type` (`OtpCodeType`: `numeric`,
|
|
54
|
+
`alphanumeric`, `unknown`), `resend_after_seconds`, `replayed`, `idempotency_key`.
|
|
55
|
+
- `OtpVerifyResult.attempts_remaining`.
|
|
56
|
+
- `strip_accents` on `sms.send()`, `sms.send_many()` and `sms.estimate()`.
|
|
57
|
+
- Webhooks: `MessageStatusChanged.data.reference`, `error_code`, `error_message`; new events `OtpVerified`
|
|
58
|
+
(`otp.verified`) and `OtpFailed` (`otp.failed`) with `OtpWebhookData` and `OtpVerificationStatus`; Django signals
|
|
59
|
+
`otp_verified` and `otp_failed`; FastAPI and Flask examples updated.
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
- `otp.send()` is now retried, with the same `Idempotency-Key`, after a network error, a 5xx, a 429
|
|
63
|
+
`TOO_MANY_REQUESTS` or a 409 `IDEMPOTENCY_REQUEST_IN_PROGRESS` (never after `OTP_RESEND_TOO_SOON` or
|
|
64
|
+
`OTP_RATE_LIMITED`). `otp.verify()` is still retried only on 429 `TOO_MANY_REQUESTS`.
|
|
65
|
+
- Phone normalisation aligned with the API: without `+`, 11 digits or more not starting with 0 are already
|
|
66
|
+
international (`33612345678` is no longer prefixed with 243). DR Congo formats are unchanged.
|
|
67
|
+
- `OtpVerifyResult` equality now includes `attempts_remaining`.
|
|
68
|
+
- `retry_after` of `OtpRateLimitedError` and `OtpResendTooSoonError` reads the `Retry-After` header now sent by the API.
|
|
69
|
+
- Fields added by the API 1.2.0 are read leniently (`None` / `UNKNOWN` when an older backend omits them); `[]`
|
|
70
|
+
error details and `Z` / fractional dates are still accepted.
|
|
71
|
+
|
|
72
|
+
## [1.0.0] - 2026-11-06
|
|
73
|
+
|
|
74
|
+
First stable release, built against the API specification 1.1.0.
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
- `Labyrinthe` (synchronous) and `AsyncLabyrinthe` (asyncio) clients with the same surface:
|
|
78
|
+
`api_key`, `base_url`, `timeout`, `max_retries`, `max_retry_wait`, `default_country_code`, `default_sender`,
|
|
79
|
+
`http_client`; `LABYRINTHE_API_KEY` / `LABYRINTHE_BASE_URL` read from the environment; context managers;
|
|
80
|
+
`is_test_mode` / `is_live_mode`.
|
|
81
|
+
- SMS: `send()`, `send_many()` (all or nothing, `replayed`), `estimate()`, `get()`, `list()` with filters and
|
|
82
|
+
pagination (`auto_paging_iter()`, `for` / `async for`).
|
|
83
|
+
- Balance and approved Sender IDs.
|
|
84
|
+
- OTP: `send()` (with `test_code` in test mode, hidden from `repr()` and dropped when pickled) and `verify()`.
|
|
85
|
+
- Automatic `Idempotency-Key` (`uuid4`) on every SMS send, reused across retries.
|
|
86
|
+
- Retries with exponential backoff and jitter, only when safe (GET, estimate, SMS send; OTP only on
|
|
87
|
+
429 `TOO_MANY_REQUESTS`); `Retry-After` support capped by `max_retry_wait`; no redirect following.
|
|
88
|
+
- Typed errors for every documented error code (`LabyrintheError` → `ApiError` → …), `APIConnectionError`,
|
|
89
|
+
`APITimeoutError`, `InvalidArgumentError`; the original `httpx` exception is never chained.
|
|
90
|
+
- `webhooks.construct_event()`: constant-time HMAC-SHA256 verification on the raw body, timestamp tolerance, typed
|
|
91
|
+
frozen events (`MessageStatusChanged`, `CampaignCompleted`, `BalanceLow`, `TestPing`, `UnknownEvent`);
|
|
92
|
+
`sign()` and `verify_signature()`.
|
|
93
|
+
- Optional integrations: `contrib.django` (view + signals), `contrib.fastapi` (dependency + router),
|
|
94
|
+
`contrib.flask` (blueprint + signal); extras `[django]`, `[fastapi]`, `[flask]`.
|
|
95
|
+
- `phone` helpers (normalisation identical to the API, validation, masking); `logging` logger
|
|
96
|
+
`labyrinthe_connect` (no key, code, body or full number); fully typed (`py.typed`); only dependency: `httpx`.
|
|
97
|
+
|
|
98
|
+
[Unreleased]: https://github.com/Labyrinthe-Team/labyrinthe-connect-sdks/compare/python-v1.2.0...HEAD
|
|
99
|
+
[1.2.0]: https://github.com/Labyrinthe-Team/labyrinthe-connect-sdks/compare/python-v1.1.1...python-v1.2.0
|
|
100
|
+
[1.1.1]: https://github.com/Labyrinthe-Team/labyrinthe-connect-sdks/compare/python-v1.1.0...python-v1.1.1
|
|
101
|
+
[1.1.0]: https://github.com/Labyrinthe-Team/labyrinthe-connect-sdks/compare/python-v1.0.0...python-v1.1.0
|
|
102
|
+
[1.0.0]: https://github.com/Labyrinthe-Team/labyrinthe-connect-sdks/releases/tag/python-v1.0.0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Labyrinthe Connect
|
|
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.
|