Commit d4f0962
refactor: fold the platform client into a shared facade base
PR #29 gave the platform client its own copy of the transport: the pool,
the close/reopen handling and the httpx-to-requests exception translation
were duplicated from APIDeploymentsClient, and the copy had no retry policy
at all. Two copies of that code drift; only one of them was getting fixes.
Both clients now inherit `_HttpxFacade`, which owns the lazily built pooled
transport, `close`/context-manager support, the exception translation and the
retry-with-Retry-After policy. A client subclass supplies its error class and
its methods, nothing else. That drops ~184 duplicated lines and gives the
platform operations the retry behaviour the README already promised.
Three defects fall out of sharing the code:
- The transport pool is now built inside the lock. `AuthenticatedClient
.get_httpx_client()` builds lazily and unsynchronised, so publishing the
client before warming it let two threads build two pools.
- A close during flight no longer escapes untranslated. httpx answers a send
on a closed client with a bare `RuntimeError`, which is not in the subtree
`_translate_transport_errors` covers, so it reached callers catching the
documented `requests` types. It is translated at the send.
- `list_deployments` no longer sends `workflow=None`. The generated builder
renders that parameter with `str()` before it filters `None` out, so the
literal string "None" went on the wire as a filter matching no workflow on
every otherwise unfiltered call. Unset filters are omitted instead, which
also holds if the generator special-cases another parameter later.
Exceptions get a hierarchy. `APIDeploymentsClientException` never worked --
its `__init__` nested three more `def`s that were never bound to the class,
so `message` was dropped and `Exception.__init__` was never called, leaving
`str(e)` empty and the documented `error_message()` non-existent. It is now
an alias of a new `UnstractError` base, with `APIDeploymentError` and
`PlatformClientError` beneath it. Catching the old name still catches both
clients, including anything added later.
Also here:
- The generated models are re-exported, so callers who want typing can
`WhoAmIResponse.from_dict(...)` instead of us hand-writing a mirror of the
spec that regeneration would not update. Facade methods keep returning
`dict[str, Any]`.
- The platform client gets its own logger. Both clients shared the module
logger, so levelling one re-levelled the other, switching a live sibling's
debug output -- which includes response bodies -- on or off as a side effect.
- A 2xx body that is unreadable, or JSON that is not an object, is now an
error naming what arrived rather than an `AttributeError` downstream. The
ERROR log for it is bounded to the same excerpt the exception carries.
- An `org_id` that is empty or blank is refused before the request, and a path
on `base_url` is warned about rather than silently discarded by `urljoin`.
- `.claude/skills/spec-upgrade/SKILL.md` step 5 gains the recipe for adding an
operation: which class it belongs to, the method shape, and why it builds
from `_get_kwargs` rather than `sync_detailed`.
No runtime breaking change. The one visible shift is `type(e).__name__`, which
becomes `APIDeploymentError` where it was `APIDeploymentsClientException`;
`except APIDeploymentsClientException` is unaffected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CnhmFYFRBrK376tZyDtwUM1 parent 67e0197 commit d4f0962
5 files changed
Lines changed: 705 additions & 344 deletions
File tree
- .claude/skills/spec-upgrade
- src/unstract/api_deployments
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
71 | 71 | | |
72 | 72 | | |
73 | 73 | | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
74 | 99 | | |
75 | 100 | | |
76 | 101 | | |
| |||
87 | 112 | | |
88 | 113 | | |
89 | 114 | | |
90 | | - | |
91 | | - | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
92 | 119 | | |
93 | 120 | | |
94 | 121 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
113 | 113 | | |
114 | 114 | | |
115 | 115 | | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
116 | 132 | | |
117 | 133 | | |
118 | 134 | | |
119 | 135 | | |
120 | | - | |
121 | | - | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
122 | 140 | | |
123 | 141 | | |
124 | 142 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
3 | 13 | | |
4 | | - | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
5 | 20 | | |
6 | 21 | | |
7 | 22 | | |
| |||
0 commit comments