isa-sdk 1.0.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.
- isa_sdk-1.0.0/.gitignore +9 -0
- isa_sdk-1.0.0/CHANGELOG.md +86 -0
- isa_sdk-1.0.0/MIGRATION.md +140 -0
- isa_sdk-1.0.0/PKG-INFO +328 -0
- isa_sdk-1.0.0/PUBLIC_API.txt +1141 -0
- isa_sdk-1.0.0/README.md +294 -0
- isa_sdk-1.0.0/pyproject.toml +86 -0
- isa_sdk-1.0.0/scripts/gen_catalog.py +882 -0
- isa_sdk-1.0.0/src/isa_sdk/__init__.py +60 -0
- isa_sdk-1.0.0/src/isa_sdk/account.py +5 -0
- isa_sdk-1.0.0/src/isa_sdk/catalog.py +5 -0
- isa_sdk-1.0.0/src/isa_sdk/core.py +5 -0
- isa_sdk-1.0.0/src/isa_sdk/proxy.py +5 -0
- isa_sdk-1.0.0/src/isa_sdk/py.typed +0 -0
- isa_sdk-1.0.0/src/isa_sdk/rapidsign.py +5 -0
- isa_sdk-1.0.0/src/isa_sdk/webhooks.py +5 -0
- isa_sdk-1.0.0/src/isa_sdk/zyins.py +50 -0
- isa_sdk-1.0.0/src/sah_sdk/__contract__.py +257 -0
- isa_sdk-1.0.0/src/sah_sdk/__init__.py +133 -0
- isa_sdk-1.0.0/src/sah_sdk/_codemod/__init__.py +25 -0
- isa_sdk-1.0.0/src/sah_sdk/_codemod/__main__.py +45 -0
- isa_sdk-1.0.0/src/sah_sdk/_codemod/rewriter.py +179 -0
- isa_sdk-1.0.0/src/sah_sdk/account/__init__.py +142 -0
- isa_sdk-1.0.0/src/sah_sdk/account/_op.py +86 -0
- isa_sdk-1.0.0/src/sah_sdk/account/branding.py +107 -0
- isa_sdk-1.0.0/src/sah_sdk/account/cases.py +173 -0
- isa_sdk-1.0.0/src/sah_sdk/account/email.py +92 -0
- isa_sdk-1.0.0/src/sah_sdk/account/preferences.py +102 -0
- isa_sdk-1.0.0/src/sah_sdk/account/reference_data.py +111 -0
- isa_sdk-1.0.0/src/sah_sdk/catalog/__init__.py +36 -0
- isa_sdk-1.0.0/src/sah_sdk/catalog/carriers.py +107 -0
- isa_sdk-1.0.0/src/sah_sdk/catalog/conditions.py +44 -0
- isa_sdk-1.0.0/src/sah_sdk/catalog/errors.py +89 -0
- isa_sdk-1.0.0/src/sah_sdk/catalog/medications.py +1909 -0
- isa_sdk-1.0.0/src/sah_sdk/catalog/products.py +312 -0
- isa_sdk-1.0.0/src/sah_sdk/catalog/scopes.py +35 -0
- isa_sdk-1.0.0/src/sah_sdk/catalog/sign_events.py +24 -0
- isa_sdk-1.0.0/src/sah_sdk/catalog/states.py +236 -0
- isa_sdk-1.0.0/src/sah_sdk/core/__init__.py +56 -0
- isa_sdk-1.0.0/src/sah_sdk/core/auth.py +95 -0
- isa_sdk-1.0.0/src/sah_sdk/core/bootstrap.py +135 -0
- isa_sdk-1.0.0/src/sah_sdk/core/constants.py +117 -0
- isa_sdk-1.0.0/src/sah_sdk/core/credential_store.py +96 -0
- isa_sdk-1.0.0/src/sah_sdk/core/debug.py +157 -0
- isa_sdk-1.0.0/src/sah_sdk/core/env.py +73 -0
- isa_sdk-1.0.0/src/sah_sdk/core/envelope.py +93 -0
- isa_sdk-1.0.0/src/sah_sdk/core/errors.py +388 -0
- isa_sdk-1.0.0/src/sah_sdk/core/idempotency.py +16 -0
- isa_sdk-1.0.0/src/sah_sdk/core/json_response.py +40 -0
- isa_sdk-1.0.0/src/sah_sdk/core/license_hmac.py +134 -0
- isa_sdk-1.0.0/src/sah_sdk/core/session.py +336 -0
- isa_sdk-1.0.0/src/sah_sdk/core/sign_request.py +133 -0
- isa_sdk-1.0.0/src/sah_sdk/core/transport.py +102 -0
- isa_sdk-1.0.0/src/sah_sdk/core/value_types.py +96 -0
- isa_sdk-1.0.0/src/sah_sdk/core/wire.py +70 -0
- isa_sdk-1.0.0/src/sah_sdk/isa.py +1635 -0
- isa_sdk-1.0.0/src/sah_sdk/proxy/__init__.py +88 -0
- isa_sdk-1.0.0/src/sah_sdk/proxy/call.py +302 -0
- isa_sdk-1.0.0/src/sah_sdk/py.typed +0 -0
- isa_sdk-1.0.0/src/sah_sdk/rapidsign/__init__.py +30 -0
- isa_sdk-1.0.0/src/sah_sdk/webhooks.py +29 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/__init__.py +210 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/account_namespaces.py +162 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/applicant.py +180 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/branding.py +75 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/bundled_api_versions.py +72 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/cases/__init__.py +254 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/cases/storage.py +104 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/cases/zero_knowledge.py +269 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/cases.py +141 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/cases_storage.py +19 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/client.py +431 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/coverage.py +111 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/credential_state.py +151 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/datasets.py +35 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/datasets_v3.py +764 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/health.py +47 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/isa_options.py +335 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/license.py +46 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/licenses.py +122 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/licenses_facade.py +227 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/logos.py +147 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/measurements.py +279 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/plan_info_label.py +128 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/preferences.py +74 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/prequalify.py +282 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/prequalify_legacy_blob.py +44 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/prequalify_v3.py +904 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/product.py +216 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/products.py +54 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/quote.py +80 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/quote_v3.py +153 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/reference/__init__.py +435 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/reference/_make_key.py +39 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/reference/autocomplete_algorithm.py +406 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/reference/autocorrector.py +325 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/reference/concept.py +173 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/reference/index.py +260 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/reference/match_algorithm.py +111 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/reference/reference_index.py +215 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/reference/sort.py +31 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/reference/suggestion.py +77 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/reference_data.py +42 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/reference_v3.py +394 -0
- isa_sdk-1.0.0/src/sah_sdk/zyins/usage.py +32 -0
- isa_sdk-1.0.0/tests/__init__.py +0 -0
- isa_sdk-1.0.0/tests/account/__init__.py +0 -0
- isa_sdk-1.0.0/tests/account/helpers.py +63 -0
- isa_sdk-1.0.0/tests/account/test_branding.py +65 -0
- isa_sdk-1.0.0/tests/account/test_cases.py +170 -0
- isa_sdk-1.0.0/tests/account/test_email.py +67 -0
- isa_sdk-1.0.0/tests/account/test_preferences.py +48 -0
- isa_sdk-1.0.0/tests/account/test_reference_data.py +57 -0
- isa_sdk-1.0.0/tests/catalog/__init__.py +0 -0
- isa_sdk-1.0.0/tests/catalog/test_catalog.py +96 -0
- isa_sdk-1.0.0/tests/core/__init__.py +0 -0
- isa_sdk-1.0.0/tests/core/test_bootstrap_conformance.py +135 -0
- isa_sdk-1.0.0/tests/core/test_sign_request.py +212 -0
- isa_sdk-1.0.0/tests/proxy/__init__.py +0 -0
- isa_sdk-1.0.0/tests/proxy/test_call.py +218 -0
- isa_sdk-1.0.0/tests/test_canonical_aliases.py +101 -0
- isa_sdk-1.0.0/tests/test_conformance_scenarios.py +108 -0
- isa_sdk-1.0.0/tests/test_session_interceptor.py +380 -0
- isa_sdk-1.0.0/tests/test_top_level_exports.py +89 -0
- isa_sdk-1.0.0/tests/zyins/test_account_namespaces.py +255 -0
- isa_sdk-1.0.0/tests/zyins/test_api_version_map_and_case_storage.py +215 -0
- isa_sdk-1.0.0/tests/zyins/test_bundleless_match_and_cases_save_recall.py +366 -0
- isa_sdk-1.0.0/tests/zyins/test_client.py +200 -0
- isa_sdk-1.0.0/tests/zyins/test_concurrency.py +113 -0
- isa_sdk-1.0.0/tests/zyins/test_datasets.py +145 -0
- isa_sdk-1.0.0/tests/zyins/test_datasets_v3.py +359 -0
- isa_sdk-1.0.0/tests/zyins/test_datasets_v3_parse_conformance.py +71 -0
- isa_sdk-1.0.0/tests/zyins/test_debug_logging.py +164 -0
- isa_sdk-1.0.0/tests/zyins/test_envelope_and_raw_response.py +168 -0
- isa_sdk-1.0.0/tests/zyins/test_errors.py +142 -0
- isa_sdk-1.0.0/tests/zyins/test_health.py +114 -0
- isa_sdk-1.0.0/tests/zyins/test_idempotency_conflict.py +91 -0
- isa_sdk-1.0.0/tests/zyins/test_isa_factories.py +125 -0
- isa_sdk-1.0.0/tests/zyins/test_isa_options.py +338 -0
- isa_sdk-1.0.0/tests/zyins/test_isa_reference_wiring.py +137 -0
- isa_sdk-1.0.0/tests/zyins/test_licenses.py +120 -0
- isa_sdk-1.0.0/tests/zyins/test_licenses_facade.py +307 -0
- isa_sdk-1.0.0/tests/zyins/test_logos.py +89 -0
- isa_sdk-1.0.0/tests/zyins/test_measurements.py +170 -0
- isa_sdk-1.0.0/tests/zyins/test_plan_info_label.py +165 -0
- isa_sdk-1.0.0/tests/zyins/test_prequalify.py +448 -0
- isa_sdk-1.0.0/tests/zyins/test_prequalify_kwargs.py +217 -0
- isa_sdk-1.0.0/tests/zyins/test_prequalify_legacy_blob.py +93 -0
- isa_sdk-1.0.0/tests/zyins/test_prequalify_v2_fields.py +201 -0
- isa_sdk-1.0.0/tests/zyins/test_prequalify_v3.py +291 -0
- isa_sdk-1.0.0/tests/zyins/test_prequalify_v3_multi_amount.py +202 -0
- isa_sdk-1.0.0/tests/zyins/test_prequalify_v3_wire_shape.py +368 -0
- isa_sdk-1.0.0/tests/zyins/test_quote.py +64 -0
- isa_sdk-1.0.0/tests/zyins/test_quote_v3.py +240 -0
- isa_sdk-1.0.0/tests/zyins/test_reference_adapters.py +830 -0
- isa_sdk-1.0.0/tests/zyins/test_reference_namespace.py +473 -0
- isa_sdk-1.0.0/tests/zyins/test_reference_v3_conformance.py +184 -0
- isa_sdk-1.0.0/tests/zyins/test_v3_facade_routing.py +211 -0
- isa_sdk-1.0.0/tests/zyins/test_zero_knowledge_get_not_found.py +54 -0
- isa_sdk-1.0.0/uv.lock +868 -0
isa_sdk-1.0.0/.gitignore
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Changelog — sah-sdk
|
|
2
|
+
|
|
3
|
+
All notable changes to the unified Python SDK. Until v1.0.0 ships to
|
|
4
|
+
public PyPI, every release is a pre-release and the API may change in
|
|
5
|
+
incompatible ways.
|
|
6
|
+
|
|
7
|
+
## 1.0.0rc2 — 2026-05-30
|
|
8
|
+
|
|
9
|
+
Reference-facade consumer-gap fixes so bpp2.0 can drop its bypasses. The
|
|
10
|
+
underlying release tag is `sdk/v1.0.0-rc.2`.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`products_by_family` skips empty-id rows.** A row whose `id` is an
|
|
15
|
+
empty string is now dropped (was kept), matching the Go/PHP/C# mirrors.
|
|
16
|
+
- **`discontinued_products` accepts integer-valued float epochs.** The
|
|
17
|
+
strict `isinstance(value, int)` check is replaced by an integer-epoch
|
|
18
|
+
coercion that keeps integer-valued numbers in any JSON notation
|
|
19
|
+
(`1700000000.0`) and drops genuine fractionals (`1700000000.5`).
|
|
20
|
+
|
|
21
|
+
## 1.0.0rc1 — 2026-05-29
|
|
22
|
+
|
|
23
|
+
First release candidate for the public 1.0 SDK. **Internal channel only**
|
|
24
|
+
(GitHub Packages / Test PyPI). The committed metadata uses PEP 440
|
|
25
|
+
normalized form `1.0.0rc1`; the underlying release tag is
|
|
26
|
+
`sdk/v1.0.0-rc.1`.
|
|
27
|
+
|
|
28
|
+
### Highlights
|
|
29
|
+
|
|
30
|
+
- **Per-surface `api_version` map (no global default).**
|
|
31
|
+
`Isa.with_keycode(api_version={"prequalify": "2026-05-14", ...})`.
|
|
32
|
+
Surfaces missing from the map fall back to `BUNDLED_API_VERSIONS`.
|
|
33
|
+
- **`BUNDLED_API_VERSIONS` exported.** The full per-surface default
|
|
34
|
+
table is a public symbol; downstream tooling and conformance
|
|
35
|
+
scenarios pin against it byte-identically across all 5 languages.
|
|
36
|
+
- **Bundleless top-level `isa.zyins.reference.match(text)`.** Fetches
|
|
37
|
+
and caches the v3 datasets index transparently. No prior
|
|
38
|
+
`datasets.get_v3()` required. `refresh_reference_index()`
|
|
39
|
+
invalidates.
|
|
40
|
+
- **`cases.save` / `cases.recall` via injectable `CaseStorage`.**
|
|
41
|
+
Default `ZeroKnowledgeCaseStorage` envelopes the payload before
|
|
42
|
+
writing through `/v1/case`. Carrier adapters implement the same
|
|
43
|
+
protocol to redirect persistence.
|
|
44
|
+
- **v3 wire-shape end-to-end.** `prequalify` / `quote` accept the
|
|
45
|
+
nested `applicant` + `coverage` + `products` envelope; `pricing[]`
|
|
46
|
+
table iteration on offers; ULID-aware conditions/medications;
|
|
47
|
+
`face_amount_cents` singular.
|
|
48
|
+
- **Idempotency: strict UUID v4** on `/v3/*` mutations. SDK auto-mints
|
|
49
|
+
per call; consumer override via `idempotency_key=` validated at
|
|
50
|
+
the transport layer.
|
|
51
|
+
|
|
52
|
+
### Migration
|
|
53
|
+
|
|
54
|
+
See [`MIGRATION.md`](./MIGRATION.md) for the 0.x → 1.0 cut and
|
|
55
|
+
[../../MIGRATION.md](../../MIGRATION.md) for the cross-language guide.
|
|
56
|
+
|
|
57
|
+
### Known drift (conformance gate YELLOW — non-blocking)
|
|
58
|
+
|
|
59
|
+
The Python conformance runner is PENDING; nine known drift items from
|
|
60
|
+
the TypeScript canonical runner are tracked in PR #387:
|
|
61
|
+
|
|
62
|
+
1. Bearer auth doesn't reach product methods on the locked surface;
|
|
63
|
+
`isa.zyins.prequalify` requires `Isa.with_keycode()`.
|
|
64
|
+
2. `applicant.height_inches` (number) vs `applicant.height` (Height
|
|
65
|
+
object): v3 serializer crashes on scenarios 01,02,03,04,05,12.
|
|
66
|
+
3. Scenario 12 routes through v2, not v3 (no `api_version` map).
|
|
67
|
+
4. `reference.concepts.match` returns `is_known=False` when the v3
|
|
68
|
+
datasets bundle hasn't been primed (scenarios 03, 07, 09).
|
|
69
|
+
5. `cases.save({applicant, state, note})` rejected; locked
|
|
70
|
+
`ZeroKnowledgeCaseStorage` requires `{product, payload}`.
|
|
71
|
+
6. `cases.recall(id)` without `recall_token` throws by design;
|
|
72
|
+
scenario 10 assumes token-optional recall.
|
|
73
|
+
7. `prequalify(req, idempotency_key=...)` not supported on locked
|
|
74
|
+
signature (scenario 12).
|
|
75
|
+
8. `reference.match` (scenario) vs `reference.concepts.match` (locked).
|
|
76
|
+
9. `reference.conditions_for(id=, sort=)` does not exist; traversal
|
|
77
|
+
is on the concept handle.
|
|
78
|
+
|
|
79
|
+
Each is either an SDK fix, a scenario rewrite, or "by design" — the
|
|
80
|
+
gate is YELLOW pending triage.
|
|
81
|
+
|
|
82
|
+
### Links
|
|
83
|
+
|
|
84
|
+
- Docs: <https://docs.isaapi.com>
|
|
85
|
+
- Guides: [`api/guides/`](../../api/guides/)
|
|
86
|
+
- Migration: [`MIGRATION.md`](./MIGRATION.md)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Migration — 0.x → 1.0.0rc1 (Python)
|
|
2
|
+
|
|
3
|
+
The cross-language guide at [`../../MIGRATION.md`](../../MIGRATION.md)
|
|
4
|
+
covers the full cut (constructor rename, per-surface `api_version`,
|
|
5
|
+
v3 wire-shape, CaseStorage adapter, bundleless `reference.match`).
|
|
6
|
+
Python-specific notes:
|
|
7
|
+
|
|
8
|
+
- **Install (rc.1, internal channel):**
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# Test PyPI:
|
|
12
|
+
pip install --index-url https://test.pypi.org/simple/ \
|
|
13
|
+
--extra-index-url https://pypi.org/simple/ \
|
|
14
|
+
sah-sdk==1.0.0rc1
|
|
15
|
+
|
|
16
|
+
# OR GitHub Packages for Python (when configured):
|
|
17
|
+
pip install --index-url https://<token>@pypi.pkg.github.com/Software-Automation-Holdings-LLC/ \
|
|
18
|
+
sah-sdk==1.0.0rc1
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
PEP 440 normalization: the release tag is `sdk/v1.0.0-rc.1`; the
|
|
22
|
+
installable package version is `1.0.0rc1`.
|
|
23
|
+
|
|
24
|
+
- **Constructor:** `Isa.create(...)` → `Isa.with_keycode(...)`. The
|
|
25
|
+
`device_id` parameter is removed (internal SDK detail).
|
|
26
|
+
- **api_version:** `str` → `dict[str, str]` (per-surface map). Use
|
|
27
|
+
`BUNDLED_API_VERSIONS` for defaults.
|
|
28
|
+
- **Cases:** `isa.case.save(...)` → `isa.zyins.cases.save(product=...,
|
|
29
|
+
payload=...)`. Default storage is `ZeroKnowledgeCaseStorage`.
|
|
30
|
+
- **Reference:** `datasets.get_v3().match(...)` →
|
|
31
|
+
`isa.zyins.reference.match(text)`. Cache primes on first call;
|
|
32
|
+
`isa.zyins.reference.refresh()` invalidates.
|
|
33
|
+
- **Type checking:** mypy strict against the SDK now enforces the
|
|
34
|
+
locked surface contract.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
# Historical: `isa-sdk-zyins` 0.2.x → `sah-sdk` 0.3.0
|
|
39
|
+
|
|
40
|
+
## What changed
|
|
41
|
+
|
|
42
|
+
The Python SDK has consolidated from a per-product package into a single
|
|
43
|
+
unified package. Per `SDK_DESIGN.md` §0 (2026-05-18), there is now **one
|
|
44
|
+
package per language**, with each product mounted under a sub-namespace:
|
|
45
|
+
|
|
46
|
+
| Before (0.2.x) | After (0.3.0) |
|
|
47
|
+
|-------------------------------|----------------------------------------|
|
|
48
|
+
| `isa-sdk-zyins` (PyPI name) | `sah-sdk` |
|
|
49
|
+
| `isa_sdk.zyins` (import root) | `sah_sdk.zyins` |
|
|
50
|
+
| `isa_sdk.zyins.transport` | `sah_sdk.core.transport` |
|
|
51
|
+
| `isa_sdk.zyins.errors` | `sah_sdk.core.errors` |
|
|
52
|
+
| `isa_sdk.zyins.envelope` | `sah_sdk.core.envelope` |
|
|
53
|
+
| `isa_sdk.zyins.debug` | `sah_sdk.core.debug` |
|
|
54
|
+
| `isa_sdk.zyins.auth` | `sah_sdk.core.auth` |
|
|
55
|
+
| (n/a — was zyins-only) | `sah_sdk.rapidsign` (scaffold) |
|
|
56
|
+
| (n/a — was zyins-only) | `sah_sdk.proxy` (scaffold) |
|
|
57
|
+
|
|
58
|
+
All public re-exports remain available at the namespace root —
|
|
59
|
+
`from sah_sdk.zyins import Isa, QuoteInput, PrequalifyResult` still
|
|
60
|
+
works, and `Isa` is additionally exported from the top-level
|
|
61
|
+
(`from sah_sdk import Isa`). Domain types (`Applicant`, `Coverage`,
|
|
62
|
+
`Product`, `Medication`, `Condition`, …) are unchanged.
|
|
63
|
+
|
|
64
|
+
## Why
|
|
65
|
+
|
|
66
|
+
- Per-product splits added 20 publish targets (4 products × 5 languages)
|
|
67
|
+
without realized benefit — bundle size, licensing separation, and
|
|
68
|
+
release cadence are all addressable at other layers.
|
|
69
|
+
- One mental model: consumers learn `Isa.with_bearer()` once and use the
|
|
70
|
+
same client for `isa.zyins.*`, `isa.rapidsign.*`, `isa.proxy.*`.
|
|
71
|
+
- Cross-product primitives (auth strategies, error hierarchy, envelope,
|
|
72
|
+
transport) live in one place and stay in sync.
|
|
73
|
+
|
|
74
|
+
## Mechanical migration
|
|
75
|
+
|
|
76
|
+
For most projects, the migration is two find-and-replace operations:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# In your project root:
|
|
80
|
+
grep -rl 'from isa_sdk.zyins' src/ | xargs sed -i '' \
|
|
81
|
+
's/from isa_sdk\.zyins/from sah_sdk.zyins/g'
|
|
82
|
+
|
|
83
|
+
grep -rl 'isa_sdk\.zyins\.\(transport\|errors\|envelope\|debug\|auth\)' src/ | xargs sed -i '' \
|
|
84
|
+
-e 's/isa_sdk\.zyins\.transport/sah_sdk.core.transport/g' \
|
|
85
|
+
-e 's/isa_sdk\.zyins\.errors/sah_sdk.core.errors/g' \
|
|
86
|
+
-e 's/isa_sdk\.zyins\.envelope/sah_sdk.core.envelope/g' \
|
|
87
|
+
-e 's/isa_sdk\.zyins\.debug/sah_sdk.core.debug/g' \
|
|
88
|
+
-e 's/isa_sdk\.zyins\.auth/sah_sdk.core.auth/g'
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
For projects with non-trivial imports (e.g. that re-export SDK symbols
|
|
92
|
+
from internal facades, or that use the SDK across many packages), use
|
|
93
|
+
the bundled `libcst`-based codemod:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install 'sah-sdk[dev]'
|
|
97
|
+
python -m sah_sdk._codemod path/to/your/src
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The codemod is idempotent — running it twice on already-migrated code
|
|
101
|
+
is a no-op. It only rewrites known import patterns; ambiguous cases are
|
|
102
|
+
left alone with a diagnostic for human review.
|
|
103
|
+
|
|
104
|
+
## Public surface (0.3.0)
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from sah_sdk import (
|
|
108
|
+
Isa,
|
|
109
|
+
# Auth strategies (carry credentials; transport wires them later)
|
|
110
|
+
BearerAuth, LicenseAuth, SessionAuth,
|
|
111
|
+
# Errors
|
|
112
|
+
IsaApiError, IsaIdempotencyConflictError, IsaPermissionError,
|
|
113
|
+
IsaTransportError, IsaConfigError,
|
|
114
|
+
# Envelope and value types
|
|
115
|
+
Envelope, RawResponse, Money, Email, Url,
|
|
116
|
+
# Constants
|
|
117
|
+
Product, ProductLabels, UsState, ErrorCode,
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Pinning during transition
|
|
122
|
+
|
|
123
|
+
If you must support both during transition, pin to the legacy package
|
|
124
|
+
in one place and the new package in another, then migrate file by file:
|
|
125
|
+
|
|
126
|
+
```toml
|
|
127
|
+
# pyproject.toml
|
|
128
|
+
dependencies = [
|
|
129
|
+
"isa-sdk-zyins==0.2.*; python_version < '3.10'", # legacy callers
|
|
130
|
+
"sah-sdk>=0.3.0", # everything else
|
|
131
|
+
]
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The legacy package will receive security backports for six months past
|
|
135
|
+
the 0.3.0 GA date, then be archived.
|
|
136
|
+
|
|
137
|
+
## Questions / issues
|
|
138
|
+
|
|
139
|
+
File an issue at <https://github.com/Software-Automation-Holdings-LLC/isa-platform/issues>
|
|
140
|
+
with the `sdk-python` label.
|
isa_sdk-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: isa-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Unified Python SDK for ISA APIs (zyins, rapidsign, proxy).
|
|
5
|
+
Project-URL: Homepage, https://github.com/Software-Automation-Holdings-LLC/isa-platform
|
|
6
|
+
Project-URL: Source, https://github.com/Software-Automation-Holdings-LLC/isa-platform
|
|
7
|
+
Author: Software Automation Holdings, LLC
|
|
8
|
+
License: Apache-2.0
|
|
9
|
+
Keywords: insurance,isa,rapidsign,sdk,zyins
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: cryptography>=42
|
|
22
|
+
Requires-Dist: httpx>=0.27
|
|
23
|
+
Requires-Dist: libcst>=1.4
|
|
24
|
+
Requires-Dist: pydantic>=2.6
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
27
|
+
Requires-Dist: libcst>=1.4; extra == 'dev'
|
|
28
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# sah-sdk
|
|
36
|
+
|
|
37
|
+
Python SDK for the [Best Plan Pro API](https://docs.isaapi.com) — powered by the ZyINS engine. Mirrors the
|
|
38
|
+
canonical TypeScript SDK at `packages/zyins/js/` with Python-idiomatic
|
|
39
|
+
naming (`snake_case`) and pydantic v2 models.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install sah-sdk
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
> **GitHub Packages fallback.** Until `sah-sdk` is fully published on PyPI, install directly from the source repository:
|
|
48
|
+
>
|
|
49
|
+
> ```bash
|
|
50
|
+
> pip install "git+https://github.com/Software-Automation-Holdings-LLC/sdk.git@sdk/v0.5.0#subdirectory=packages/python"
|
|
51
|
+
> ```
|
|
52
|
+
>
|
|
53
|
+
> The wire surface is identical either way; only the install command changes.
|
|
54
|
+
|
|
55
|
+
## Quick start
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import os
|
|
59
|
+
from sah_sdk.zyins import Isa, Applicant, Coverage, PrequalifyInput, Sex
|
|
60
|
+
|
|
61
|
+
# Reads ISA_TOKEN from the environment — no explicit token needed.
|
|
62
|
+
isa = Isa.with_bearer()
|
|
63
|
+
|
|
64
|
+
result = isa.zyins.prequalify(PrequalifyInput(
|
|
65
|
+
applicant=Applicant(
|
|
66
|
+
dob="1962-04-18",
|
|
67
|
+
sex=Sex.MALE,
|
|
68
|
+
height_inches=70,
|
|
69
|
+
weight_pounds=195,
|
|
70
|
+
state="NC",
|
|
71
|
+
nicotine_use="none",
|
|
72
|
+
),
|
|
73
|
+
coverage=Coverage.face_value(100_000),
|
|
74
|
+
products="colonial-penn.final-expense",
|
|
75
|
+
))
|
|
76
|
+
|
|
77
|
+
for plan in result.data.plans:
|
|
78
|
+
print(plan.brand, plan.tier, plan.monthly_premium)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`Isa.with_bearer()` reads `ISA_TOKEN` from the environment. `Authorization: Bearer <token>`,
|
|
82
|
+
`Idempotency-Key`, and the date-pinned `Version` header are set automatically.
|
|
83
|
+
|
|
84
|
+
## First call in <15 lines
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from sah_sdk.zyins import Isa, Coverage
|
|
88
|
+
|
|
89
|
+
isa = Isa.with_keycode(
|
|
90
|
+
keycode="SDV-HWH-WDD",
|
|
91
|
+
email="john.doe@acme-agency.com",
|
|
92
|
+
)
|
|
93
|
+
result = isa.zyins.prequalify_v2(
|
|
94
|
+
applicant={"dob": "1962-04-18", "sex": "male", "state": "NC"},
|
|
95
|
+
coverage=Coverage.face_value(25_000),
|
|
96
|
+
products="colonial-penn.final-expense",
|
|
97
|
+
)
|
|
98
|
+
print(result.data.plans[0].monthly_premium)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Per-surface API versions
|
|
102
|
+
|
|
103
|
+
The ISA API is a federation of independently versioned surfaces. Every SDK
|
|
104
|
+
release exports a frozen `BUNDLED_API_VERSIONS` mapping recording which
|
|
105
|
+
`/vN` each surface targets:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from sah_sdk.zyins import BUNDLED_API_VERSIONS
|
|
109
|
+
|
|
110
|
+
print(BUNDLED_API_VERSIONS)
|
|
111
|
+
# {
|
|
112
|
+
# "prequalify": "v2",
|
|
113
|
+
# "quote": "v2",
|
|
114
|
+
# "datasets": "v2",
|
|
115
|
+
# "reference": "v2",
|
|
116
|
+
# "sessions": "v1",
|
|
117
|
+
# "branding": "v1",
|
|
118
|
+
# "cases": "v1",
|
|
119
|
+
# }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Pin individual surfaces with a per-surface `api_version` map. There is **no**
|
|
123
|
+
`default` key and **no** string shorthand — resolution is
|
|
124
|
+
`api_version.get(surface, BUNDLED_API_VERSIONS[surface])`:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
isa = await Isa.with_keycode(
|
|
128
|
+
keycode="SDV-HWH-WDD",
|
|
129
|
+
email="john.doe@acme-agency.com",
|
|
130
|
+
api_version={"quote": "v2"}, # pin only quote; everything else bundled
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The release that retargets `prequalify` / `quote` / `datasets` / `reference`
|
|
135
|
+
to `v3` will bump those entries. See [SDK syntax proposal §2.7][syntax-27].
|
|
136
|
+
|
|
137
|
+
[syntax-27]: ../../docs/sdk-syntax-proposal.md#27-versioning--per-surface-not-global
|
|
138
|
+
|
|
139
|
+
## Reference data — `.match()`
|
|
140
|
+
|
|
141
|
+
The unversioned `isa.zyins.reference` namespace canonicalizes free-text
|
|
142
|
+
medication and condition input. Unknown text never rejects — it returns a
|
|
143
|
+
structured envelope so the final canonicalization fires server-side at
|
|
144
|
+
`/vN/prequalify`:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
ds = await isa.zyins.datasets.get(include=["conditions", "medications"])
|
|
148
|
+
|
|
149
|
+
insulin = isa.zyins.medications.match("insulin")
|
|
150
|
+
print(insulin.id, insulin.name, insulin.is_known)
|
|
151
|
+
# med_01KSR2WVAGC05ZGR6FA4QYEB12 INSULIN True
|
|
152
|
+
|
|
153
|
+
# Symmetric traversal — what conditions is insulin used for?
|
|
154
|
+
used_for = insulin.conditions(isa.zyins.reference.Sort.MOST_COMMON_FIRST)
|
|
155
|
+
# frequency-ordered list; cond_01KSR2WVAGC05ZGR6FA4QYEA8X first
|
|
156
|
+
|
|
157
|
+
novel = isa.zyins.medications.match("NewExperimental XR 2026")
|
|
158
|
+
# → {"is_known": False, "input_text": "NewExperimental XR 2026", ...}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`Sort.MOST_COMMON_FIRST` and `Sort.ALPHABETICAL` are the two supported
|
|
162
|
+
orderings.
|
|
163
|
+
|
|
164
|
+
## Case storage — bring your own
|
|
165
|
+
|
|
166
|
+
`isa.zyins.cases.*` routes through a `CaseStorage` adapter. The default is
|
|
167
|
+
the zero-knowledge store — ISA's servers only hold ciphertext and an opaque
|
|
168
|
+
ID. To plug a carrier-controlled store, pass your adapter at construction:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
isa = await Isa.with_keycode(
|
|
172
|
+
keycode=..., email=...,
|
|
173
|
+
case_storage=CarrierCaseStorage(), # optional; default = ZeroKnowledgeCaseStorage
|
|
174
|
+
)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
See [cases guide](https://docs.isaapi.com/docs/cases) for the full
|
|
178
|
+
bring-your-own pattern.
|
|
179
|
+
|
|
180
|
+
## Auth deviation from the TS SDK
|
|
181
|
+
|
|
182
|
+
The TypeScript SDK in this monorepo still carries the pre-#286 HMAC device
|
|
183
|
+
signing surface (`AuthContext` with `licenseKey + orderId + email + deviceId`).
|
|
184
|
+
The Python SDK is built against the post-#286 wire contract: a single
|
|
185
|
+
bearer token (`isa_live_*` / `isa_test_*`) is the entire auth surface.
|
|
186
|
+
This is the intentional simplification called out in the platform's
|
|
187
|
+
`platform_v1_architecture` notes.
|
|
188
|
+
|
|
189
|
+
## Surface
|
|
190
|
+
|
|
191
|
+
| TypeScript | Python |
|
|
192
|
+
| ----------------------------------------- | ----------------------------------- |
|
|
193
|
+
| `client.prequalify(req)` | `client.prequalify.run(input)` |
|
|
194
|
+
| `client.license.activate/deactivate/check`| `client.license.*` (mirrored) |
|
|
195
|
+
| `client.case.email(req)` | `client.case.email(input)` |
|
|
196
|
+
| (new) | `client.quote.run(input)` |
|
|
197
|
+
| (new) | `client.datasets.list/get` |
|
|
198
|
+
| (new) | `client.reference_data.get(kind)` |
|
|
199
|
+
| (new) | `client.usage.summary(period)` |
|
|
200
|
+
|
|
201
|
+
Errors mirror the TS hierarchy: `ISAError` (alias `ZyInsError`, also
|
|
202
|
+
exported as `IsaApiError`) → `LicenseError`, `PrequalifyError`,
|
|
203
|
+
`ValidationError`, `RateLimitError`, `AuthError`,
|
|
204
|
+
`IsaIdempotencyConflictError`.
|
|
205
|
+
|
|
206
|
+
## `Isa` factory client
|
|
207
|
+
|
|
208
|
+
Per [SDK_DESIGN.md §3](https://github.com/Software-Automation-Holdings-LLC/isa-platform/blob/main/docs/SDK_DESIGN.md),
|
|
209
|
+
the recommended entry point is the `Isa` class with three named factories:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
from sah_sdk.zyins import Isa
|
|
213
|
+
|
|
214
|
+
# Reads ISA_TOKEN from the environment.
|
|
215
|
+
isa = Isa.with_bearer()
|
|
216
|
+
env = isa.zyins.prequalify(req)
|
|
217
|
+
print(env.data, env.request_id, env.idempotency_key, env.retry_attempts)
|
|
218
|
+
|
|
219
|
+
# Or pass the token explicitly.
|
|
220
|
+
isa = Isa.with_bearer("isa_live_…")
|
|
221
|
+
|
|
222
|
+
# License factory — reads ISA_LICENSE_KEYCODE / ISA_LICENSE_EMAIL.
|
|
223
|
+
isa = Isa.with_license()
|
|
224
|
+
|
|
225
|
+
# Session factory — reads ISA_SESSION_ID / ISA_SESSION_SECRET.
|
|
226
|
+
isa = Isa.with_session()
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Each factory raises `IsaConfigError` with a clear, actionable message if
|
|
230
|
+
the required env vars are unset and no explicit arguments are supplied.
|
|
231
|
+
|
|
232
|
+
### Raw HTTP access
|
|
233
|
+
|
|
234
|
+
Every method has a `.with_raw_response()` variant returning both the
|
|
235
|
+
parsed envelope and the underlying HTTP metadata:
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
env, raw = isa.zyins.prequalify.with_raw_response(req)
|
|
239
|
+
raw.status # int
|
|
240
|
+
raw.url # str
|
|
241
|
+
raw.headers # read-only mapping
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Debug logging
|
|
245
|
+
|
|
246
|
+
Set `ISA_LOG=debug` to dump every request and response to **stderr** —
|
|
247
|
+
never stdout, so parent processes piping the consumer's JSON output stay
|
|
248
|
+
clean. Credential headers (`Authorization`, `X-Device-Signature`,
|
|
249
|
+
`X-Session-Signature`) and PII body fields (`email`, `dob`, `ssn`,
|
|
250
|
+
`phone`) are redacted automatically.
|
|
251
|
+
|
|
252
|
+
### Idempotency conflicts
|
|
253
|
+
|
|
254
|
+
When the same `Idempotency-Key` is replayed with a different body the
|
|
255
|
+
server returns 409 `idempotency_conflict`. The SDK raises
|
|
256
|
+
`IsaIdempotencyConflictError` with `.key` and `.first_seen_at` so the
|
|
257
|
+
caller can audit the queued-write bug class:
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
from sah_sdk.zyins import IsaIdempotencyConflictError
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
isa.zyins.prequalify(req, idempotency_key="case-42")
|
|
264
|
+
except IsaIdempotencyConflictError as e:
|
|
265
|
+
log.error("key %s first seen at %s", e.key, e.first_seen_at)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Concurrency
|
|
269
|
+
|
|
270
|
+
The `Isa` client is safe for use with `asyncio.gather` and
|
|
271
|
+
`concurrent.futures` — every request mints a fresh request-id and
|
|
272
|
+
idempotency key, and shared client state (auth, base URL, debug logger)
|
|
273
|
+
is read-only after construction. Reuse a single `Isa` instance across
|
|
274
|
+
all concurrent requests; the underlying HTTP transport pools connections
|
|
275
|
+
for you.
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
import asyncio
|
|
279
|
+
from sah_sdk.zyins import Isa
|
|
280
|
+
|
|
281
|
+
isa = Isa.with_bearer()
|
|
282
|
+
|
|
283
|
+
async def one(req):
|
|
284
|
+
return isa.zyins.prequalify(req)
|
|
285
|
+
|
|
286
|
+
results = await asyncio.gather(*(one(r) for r in batch))
|
|
287
|
+
# Each result.request_id is distinct.
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Development
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
hatch run test # pytest
|
|
294
|
+
hatch run lint # ruff + mypy --strict
|
|
295
|
+
hatch build # wheel + sdist
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Live-integration tests run only when `ZYINS_TEST_TOKEN` is set:
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
ZYINS_TEST_TOKEN=isa_test_... hatch run test -- -m integration
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Licenses and Ready
|
|
305
|
+
|
|
306
|
+
The Python SDK exposes the public BPP license-lifecycle surface and the
|
|
307
|
+
platform readiness probe on every `ZyInsClient`:
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
from sah_sdk.zyins import LicenseCheckInput, ZyInsClient
|
|
311
|
+
|
|
312
|
+
client = ZyInsClient("isa_live_...")
|
|
313
|
+
|
|
314
|
+
result = client.license.check(
|
|
315
|
+
LicenseCheckInput(
|
|
316
|
+
email="john.doe@acme-agency.com",
|
|
317
|
+
keycode="ABC-123-XYZ",
|
|
318
|
+
device_id="dev_01HZK2N5GQR9T8X4B6FJW3Y1AS",
|
|
319
|
+
)
|
|
320
|
+
)
|
|
321
|
+
# result.status: "valid" | "invalid" | "inactive"
|
|
322
|
+
|
|
323
|
+
ready = client.health.get_readiness()
|
|
324
|
+
# ready.ready: True on every required probe = "serving"
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
The pre-existing `client.license` (singular) sub-client targets the
|
|
328
|
+
authenticated `/v1/license/*` self-status endpoints and is untouched.
|