GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — fix: accept the Transform Platform API URL as server_url (0.46.2) by paulkarayan · Pull Request #352 · Unstructured-IO/unstructured-python-client Conversation
|
server_url |
main |
this branch | |
|---|---|---|---|
https://platform-api.transform.unstructured.io/api/v1 |
https://platform-api.transform.unstructured.io/api/v1 |
https://platform-api.transform.unstructured.io |
changed |
http://platform-api.transform.unstructured.io/api/v1 |
http://platform-api.transform.unstructured.io/api/v1 |
https://platform-api.transform.unstructured.io |
changed |
platform-api.transform.unstructured.io/api/v1 |
http://platform-api.transform.unstructured.io/api/v1 |
https://platform-api.transform.unstructured.io |
changed |
platform-api.transform.unstructured.io |
http://platform-api.transform.unstructured.io |
https://platform-api.transform.unstructured.io |
changed |
https://platform-api.unstructured.io/api/v1 |
https://platform-api.unstructured.io/api/v1 |
https://platform-api.unstructured.io |
changed |
http://unstructuredapp.io.example.com/api/v1 |
https://unstructuredapp.io.example.com |
http://unstructuredapp.io.example.com/api/v1 |
changed |
http://myunstructuredapp.io/api/v1 |
https://myunstructuredapp.io |
http://myunstructuredapp.io/api/v1 |
changed |
https://platform-api.transform.unstructured.io |
https://platform-api.transform.unstructured.io |
same | |
https://platform.unstructuredapp.io/api/v1 |
https://platform.unstructuredapp.io |
same | |
https://api.unstructuredapp.io/general/v0/general |
https://api.unstructuredapp.io |
same | |
unstructured-000mock.api.unstructuredapp.io/general/v0/general |
https://unstructured-000mock.api.unstructuredapp.io |
same | |
http://localhost:8000 |
http://localhost:8000 |
same | |
localhost:8000 |
http://localhost:8000 |
same | |
http://localhost:8000/my/endpoint/ |
http://localhost:8000/my/endpoint |
same | |
localhost:8000/general/v0/general |
http://localhost:8000/general/v0/general |
same | |
https://unstructured.example.com/api/v1 |
https://unstructured.example.com/api/v1 |
same | |
http://not-unstructured.io/api/v1 |
http://not-unstructured.io/api/v1 |
same |
The first five changed rows are the reported bug. The last two are the substring-match fix: those hosts are not ours, so they keep their path and their scheme.
Customers: Anyone using the Python SDK against the Transform Platform can now paste the API URL shown in the app, or set it from the documented UNSTRUCTURED_API_URL, and have jobs, workflows, sources, destinations and templates calls work. Today that exact value 404s on every call. Users who already worked around it by passing the bare host are unaffected; that keeps working. Users on unstructuredapp.io are unaffected; their URLs were already cleaned.
Internal (devs / ops / other teams): The docs can stop steering readers away from the URL the product displays. No service imports this code; it is a client library published to PyPI.
Wire contract / clients: No request or response shape changes. The only behavior change is which URL a request is sent to, and only for base URLs that were previously producing a doubled path. The one case where a user could notice a difference is a self-hosted deployment on a host under unstructuredapp.io or unstructured.io that genuinely serves the API beneath a subpath; that path is now stripped. Hosts outside those domains keep their path exactly as before, which the existing localhost subpath tests cover.
Deployment target considerations: This is a PyPI client library, not a deployed service, so SaaS / DI / in-VPC / on-prem / SND deploys are unaffected. Air-gapped users pointing the SDK at their own hostname keep the existing keep-the-path behavior, since their host is not under an Unstructured domain.
The last commit is ruff format over the files this change touches, plus seven noqa directives for pre-existing lint that cannot be auto-fixed without changing behaviour. It is formatting only and carries no behaviour change, so reading the first two commits on their own gives you the whole fix. Two of the noqas are worth knowing about: raise err in basesdk.py re-raises whatever an after-error hook returned, which is not always the active exception, so ruff's suggested bare raise would be a real bug.
Low. Small changes to URL normalization plus a .genignore entry, revert-safe, no migration and no flag.
Ran the unit suite on Python 3.11, 3.12 and 3.13 and the contract suite, plus pylint (10.00/10) and mypy, all green, matching what CI runs. uv.lock is unchanged, so the UV_LOCKED=1 install holds. Reproduced the bug and then the fix against the live Transform Platform API without an API key, which is enough to tell the two apart: a route that exists answers 401, a route that does not answers 404. Not exercised with a real API key end to end, and not exercised against a self-hosted deployment.
Repro, against the live API, before the fix:
$ curl -s -o /dev/null -w '%{http_code}\n' https://platform-api.transform.unstructured.io/api/v1/jobs/
401
$ curl -s -o /dev/null -w '%{http_code}\n' https://platform-api.transform.unstructured.io/api/v1/api/v1/jobs/
404
Through the SDK, before the fix:
server_url='https://platform-api.transform.unstructured.io'
request sent: https://platform-api.transform.unstructured.io/api/v1/jobs/
status: 401
server_url='https://platform-api.transform.unstructured.io/api/v1'
request sent: https://platform-api.transform.unstructured.io/api/v1/api/v1/jobs/
status: 404
Failing tests at HEAD before the fix, _test_unstructured_client/unit/test_server_urls.py::test_platform_request_url_has_a_single_api_prefix plus the hook tests:
FAILED test_custom_hooks.py::test_unit_clean_server_url_fixes_malformed_transform_platform_url[https://platform-api.transform.unstructured.io/api/v1]
FAILED test_custom_hooks.py::test_unit_clean_server_url_fixes_malformed_transform_platform_url[http://platform-api.transform.unstructured.io/api/v1]
FAILED test_custom_hooks.py::test_unit_clean_server_url_fixes_malformed_transform_platform_url[platform-api.transform.unstructured.io/api/v1]
FAILED test_custom_hooks.py::test_unit_clean_server_url_fixes_malformed_transform_platform_url[platform-api.transform.unstructured.io]
FAILED test_custom_hooks.py::test_unit_clean_server_url_leaves_lookalike_domains_alone[http://unstructuredapp.io.example.com/api/v1]
E Failed: transform platform ... Expected https://platform-api.transform.unstructured.io, got https://platform-api.transform.unstructured.io/api/v1
After the fix, the same live check across both ways of passing the URL:
client-level, bare host
sent: https://platform-api.transform.unstructured.io/api/v1/jobs/
status: 401
client-level, URL from the app (/api/v1)
sent: https://platform-api.transform.unstructured.io/api/v1/jobs/
status: 401
operation-level, bare host
sent: https://platform-api.transform.unstructured.io/api/v1/jobs/
status: 401
operation-level, URL from the app (/api/v1)
sent: https://platform-api.transform.unstructured.io/api/v1/jobs/
status: 401
Every case now reaches the real route. Suites after the fix: unit and contract both pass, pylint 10.00/10, mypy clean.
none
from unstructured_client import UnstructuredClient # The value the app's API Keys page gives you, pasted as-is. client = UnstructuredClient( api_key_auth="YOUR_KEY", server_url="https://platform-api.transform.unstructured.io/api/v1", ) client.jobs.list_jobs(request={}) # before: GET https://platform-api.transform.unstructured.io/api/v1/api/v1/jobs/ -> 404 {"detail":"Not Found"} # after: GET https://platform-api.transform.unstructured.io/api/v1/jobs/, the real route
Bumped to 0.46.2 with CHANGELOG and RELEASES entries.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
The Transform Platform's API Keys page, and the docs, hand you https://platform-api.transform.unstructured.io/api/v1. That works with curl and 404s every Platform call in this SDK: clean_server_url only stripped a path for unstructuredapp.io hosts, so the /api/v1 survived and the operation's own /api/v1/jobs/ was appended on top, producing /api/v1/api/v1/jobs/, which matches no route. Recognize unstructured.io hosts too, matched on domain boundaries rather than by substring, so a lookalike host such as unstructuredapp.io.example.com keeps its path and scheme. Clean the base URL in BaseSDK._get_url as well. An operation-level server_url= override bypasses the SDK-init hook, so client.jobs.list_jobs(request={}, server_url=...) was uncleaned and 404d the same way even after the domain fix. Co-Authored-By: Claude Opus 5 <[email protected]>
The base URL is now cleaned in BaseSDK._get_url, which every operation passes through, so a self-hosted deployment behind a subpath is the regression this change could cause. The existing server-url cases mock _build_request and so cannot see it; assert on the request that is actually sent instead. Co-Authored-By: Claude Opus 5 <[email protected]>
Formatting only, plus seven noqa directives for pre-existing lint that cannot be auto-fixed without changing behaviour. Two of them matter: `raise err` in basesdk re-raises the exception an after-error hook returned, which is not always the active one, so ruff's suggestion of a bare `raise` would be wrong. No behaviour change. pylint 10.00/10 and mypy stay clean, and the unit and contract suites pass on 3.11, 3.12 and 3.13. Co-Authored-By: Claude Opus 5 <[email protected]>
Repro-first proofBugfix — server_url copied from the Transform Platform API Keys page (.../api/v1) doubled the path and 404'd every Platform SDK call Reproduced the broken state
Failing test (red)
Fix
Proof it's resolved
Auto-generated from this branch's |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
All reported issues were addressed across 7 files
Shadow auto-approve: would not auto-approve because issues were found.
Re-trigger cubic
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
…alified host Two problems cubic caught on the PR, both real. basesdk.py is generated and was not in .genignore, so a Speakeasy run would drop the _get_url cleaning and an operation-level server_url= would double its /api/v1 prefix again. Added the entry, following the same pattern as general.py, with a regeneration guard test to match the existing ones. Freezing the file also freezes the generated request plumbing, so the entry documents how to un-freeze it for a regen. is_unstructured_domain also missed a fully qualified host carrying the terminal root dot. That was a regression this branch introduced: the old substring test matched api.unstructuredapp.io. and stripped its path, the domain-boundary test did not. The path is stripped again; the host keeps the dot exactly as the caller wrote it, since it changes the Host header and SNI and is a deliberate choice. Co-Authored-By: Claude Opus 5 <[email protected]>
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
0 issues found across 4 files (changes from recent commits).
Shadow auto-approve: would not auto-approve. Auto-approval blocked by 1 unresolved issue from previous reviews.
Re-trigger cubic
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| def test_basesdk_keeps_cleaning_the_base_url(): | ||
| """`_get_url` is the only point every operation's base URL passes through. | ||
|
|
||
| An operation-level `server_url=` override bypasses the SDK-init hook, so the |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
We can drop this comment. We're no longer autogenerating, so we can change anything we need in the code. In fact, this whole file may be able to go away.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| # that normalizes the client-level URL, so clean here too -- this is the one point | ||
| # every operation's base URL passes through. Cleaning an already-clean URL is a | ||
| # no-op, so the client-level case is unaffected. | ||
| return clean_server_url(utils.template_url(base_url, url_variables)) |
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Per the last comment, I would just do a quick test and see if this one line transforms the url whether it was passed in the operation or the base client. If so, we don't even need the hook file anymore.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| # - Bring back the ignore line and commit | ||
| src/unstructured_client/general.py | ||
|
|
||
| # Ignore basesdk.py so _get_url keeps cleaning the base URL. |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Unused file, if you don't mind deleting it while you're in here.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
LGTM! All of my comments are just about cleanup now that we own the codebase in full, no need to step around auto-generated files. I don't want to block the fix, though. So feel free to merge with a fast follow.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
@awalker4 |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
The client-level and operation-level server_url are both cleaned at request time in BaseSDK._get_url, so the CleanServerUrlSDKInitHook is redundant; remove the hook class and its registration. The clean_server_url function stays (basesdk.py and general.py import it). Retarget the six clean_server_url unit tests onto the function directly, since the stored sdk_configuration.server_url is no longer normalized at init (request URLs are unaffected). Delete .genignore and test_regeneration_guards.py: they were insurance for Speakeasy regeneration, which is retired. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01P1fwUQxYtk4khxLHEixYza
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.