Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
275095b
Add asyncio client support
kvz May 20, 2026
346818b
Harden asyncio support
kvz May 20, 2026
25f498c
Improve async coverage
kvz May 20, 2026
85697ba
Fix async retry and response edge cases
kvz May 20, 2026
37e4cc5
Handle plain-text async assembly responses
kvz May 20, 2026
fb1bcfd
Gate async TUS upload on successful create
kvz May 20, 2026
def57f6
Harden async retry rewinds
kvz May 20, 2026
559b875
Fix resumable async wait and rewind retries
kvz May 20, 2026
f8d3482
Refine async retries and upload metadata
kvz May 20, 2026
83ea871
Polish async retries and uploads
kvz May 20, 2026
3cb1bb4
Improve async coverage and retry safety
kvz May 20, 2026
924fffc
Address async council review findings
kvz May 20, 2026
bb58520
Add async E2E coverage
kvz May 21, 2026
1722c93
Harden async upload edge cases
kvz May 21, 2026
7f8fc8c
Harden request URL and upload handling
kvz May 21, 2026
88807ad
Harden async retry and service URLs
kvz May 21, 2026
d19832a
Add E2E coverage for resumable uploads and templates
kvz May 21, 2026
dc77820
Add runnable SDK examples
kvz May 21, 2026
51fdac5
Run quickstart examples in CI
kvz May 21, 2026
86afdcc
Add async template lifecycle example
kvz May 21, 2026
017fefc
Fix council review edge cases
kvz May 21, 2026
bb04389
Note endpoint coverage TODO
kvz May 21, 2026
a779f9e
Add generated low-level endpoint methods
kvz May 23, 2026
3cd48ac
Mark generated endpoint method blocks
kvz May 23, 2026
8090d14
Harden sync assembly retries and generated docs
kvz May 24, 2026
0c4c974
Regenerate endpoint methods from API2 contracts
kvz May 24, 2026
43e999e
Harden async SDK edge cases
kvz May 25, 2026
d80ba14
Mark generated blocks as contract-owned
kvz May 26, 2026
7341ccf
Add generated wait for assembly helpers
kvz May 26, 2026
24738de
Regenerate endpoint params
kvz Jun 1, 2026
70e8ba9
Generate TUS Assembly helper
kvz Jun 1, 2026
27aa93c
Add devdock TUS Assembly example
kvz Jun 1, 2026
fcb4535
Let API2 assert devdock example result
kvz Jun 1, 2026
c02c48f
Add devdock template lifecycle example
kvz Jun 2, 2026
75f0049
Fix devdock template content payload
kvz Jun 2, 2026
41484c2
Read TUS scenario steps generically
kvz Jun 2, 2026
67407bf
Generate TUS assembly upload helper
kvz Jun 2, 2026
9737ecc
Use header-derived TUS offset variable
kvz Jun 2, 2026
315774a
Read TUS example input from SDK feature call
kvz Jun 3, 2026
9406940
Read SDK example input projection
kvz Jun 3, 2026
51c5439
Regenerate required feature value guards
kvz Jun 5, 2026
19186b1
Add Assembly lifecycle devdock example
kvz Jun 10, 2026
bcf056e
Use SSL Assembly URL for TUS metadata
kvz Jun 10, 2026
6e22acd
Regenerate with snake_case locals
kvz Jun 10, 2026
be626e0
Move generated TUS imports to module headers
kvz Jun 10, 2026
552fed8
Prove TUS resume upload via generated SDK method
kvz Jun 11, 2026
846f1b7
Refresh generated endpoint documentation
kvz Jul 11, 2026
7d0d02e
Generate bearer token issuance
kvz Jul 11, 2026
91fdb4b
Harden generated bearer token endpoints
kvz Jul 11, 2026
4873622
Generate invoice bill endpoint
kvz Jul 11, 2026
e679c33
Generate exact-one-file TUS helper
kvz Jul 11, 2026
9ca0ce4
Harden generated Assembly URL requests
kvz Jul 11, 2026
4720079
Merge remote-tracking branch 'origin/main' into asyncio-v2
kvz Jul 12, 2026
6c015fe
Allow HTTPS polling on configured hosts
kvz Jul 12, 2026
4c15f0c
Reject dot path segments
kvz Jul 12, 2026
79bfb19
Merge current Python 2.0 base into asyncio branch
kvz Oct 10, 2026
399aa1f
Record API contract coverage and 2.0 scope choices
kvz Oct 10, 2026
d6361c9
Generate full sync and async API surface for Python 2.0
kvz Oct 10, 2026
fda95a7
Fix upload cancellation and transport configuration after review
kvz Oct 10, 2026
d3b16ec
Isolate Assembly credentials and preserve upload and polling compatib…
kvz Oct 10, 2026
34d15b5
Preserve async shutdown, proxy configuration and text upload lengths
kvz Oct 10, 2026
456047b
Match async proxy policy and preserve multipart filenames
kvz Oct 10, 2026
8faf570
Clarify multipart quoting compatibility after final review
kvz Oct 10, 2026
a63d7d4
Keep Python 2.0 on handwritten async client surface
kvz Oct 11, 2026
7164895
Align async responses and clarify Python 2.0 upgrades
kvz Oct 11, 2026
3c0a3a3
Preserve polling recovery details and query encoding
kvz Oct 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ jobs:
exit 1
fi

- name: Run E2E upload test
- name: Run E2E tests and examples
env:
TEST_NODE_PARITY: 0
run: |
poetry run pytest tests/test_e2e_upload.py -q --maxfail=1 --no-cov
poetry run pytest tests/test_e2e_upload.py tests/test_examples.py -q --maxfail=1 --no-cov
12 changes: 3 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
### 2.0.0 / Unreleased ###
* **Breaking Change**: Python 3.9, 3.10, and 3.11 are no longer supported. Installing 2.0 requires Python 3.12 or newer; upgrade the interpreter before upgrading the SDK. Applications that must stay on those older interpreters can pin `pytransloadit<2` (the latest 1.x release is 1.0.4).
* **Breaking Change**: Runtime dependencies now require `requests>=2.33,<3` and `urllib3>=2.7,<3`. Remove or update older pins in application requirements/constraints and regenerate the application lockfile; incompatible pins will prevent installation.
* The synchronous client API, imports, Assembly creation/polling, uploads, and Smart CDN signing remain compatible with 1.0.4 on supported interpreters. Asyncio support is tracked separately in #57 and is not part of 2.0.
* The dependency refresh already released in 1.0.4 remains available to 1.x users; 2.0 removes the old-interpreter lockfile variants and requires the newer HTTP stack on every supported runtime.
* Updated development and documentation tooling, including `pytest` 9.0.3, `Sphinx` 9.1, `sphinx-autobuild` 2025.8, `coverage` 7.14, `tox` 4.54, and `requests-mock` 1.12.
* Updated CI and local Docker test coverage to a representative Python 3.12, 3.13, and 3.14 matrix.
* Migrated package metadata to the modern `[project]` format used by Poetry 2.
* Source contributors need Poetry 2.4.1 for the documented Docker/CI toolchain; isolated package builds require `poetry-core>=2.2,<3`. Normal `pip` installation installs the build backend automatically when needed.
* Refreshed GitHub Actions, release documentation, and Sphinx docs that still referenced older runtime/tooling assumptions.
* Added `AsyncTransloadit` with asyncio support for the existing synchronous client's Assembly, Template and billing methods, plus Smart CDN URL signing. Polling uses `asyncio.sleep`; resumable uploads run tuspy in worker threads.
* **Breaking Change**: Python 3.12 or newer is required. Upgrade development, CI and deployment interpreters before installing 2.0. Applications that need Python 3.9–3.11 can pin `pytransloadit<2` (latest 1.x: 1.0.4).
* **Upgrade notes**: Update application constraints for `requests>=2.33,<3` and `urllib3>=2.7,<3`, then regenerate your lockfile. Existing synchronous imports and methods remain available; asyncio applications can use `async with AsyncTransloadit(...)` and await network operations. See README for request-handling corrections, including explicit polling retry exhaustion, trusted Assembly URLs, redirect handling and Smart CDN validation/boolean serialization. Keep file context managers open around awaited uploads; cancellation waits for active file reads and resumable workers to finish. Resumable transfers do not inherit an injected aiohttp session's proxy/TLS settings. Source contributors use Poetry 2.4.1 with `[project]` metadata; isolated source builds require `poetry-core>=2.2,<3`.

### 1.0.4 / 2026-05-20 ###
* Refreshed locked runtime and development dependencies, including `aiohttp` 3.13.5, `idna` 3.15, `pygments` 2.20.0, Python-version-specific `requests` updates, and `tuspy` 1.1.0.
Expand Down
77 changes: 67 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,22 @@ application pins or constraints and regenerate your lockfile before upgrading:
python -m pip install --upgrade 'pytransloadit>=2,<3'
```

The synchronous client API and imports remain compatible with 1.0.4 on supported
interpreters. Asyncio support is tracked separately in
[#57](https://github.com/transloadit/python-sdk/pull/57) and can land in a later
2.x minor release. The dependency refresh shipped in 1.0.4 does not require
upgrading to 2.0.
The synchronous imports and methods remain available on supported interpreters.
SDK 2.0 adds the separate `AsyncTransloadit` client for asyncio
applications. The dependency refresh shipped in 1.0.4 does not require upgrading
to 2.0.

Request-handling changes to account for when upgrading:

- Smart CDN boolean parameters use `true`/`false` to match the reference CLI. Workspace slugs must be DNS-safe; `auth_key`, `exp` and `sig` are reserved query keys.
- Assembly status/cancellation uses credential-free requests to trusted Assembly URLs. Default Transloadit clients reject foreign destinations; explicitly configured services retain their worker hosts.
- HTTP redirects are returned to the caller instead of followed automatically. Invalid service URLs and empty/dot-segment Template IDs raise `ValueError`.
- Template creation sends steps inside the `template` object. When combining a `template` option with `add_step`, supply an object rather than a serialized JSON string.
- Non-JSON responses return text or bytes in `Response.data`; empty bodies return an empty string. With `wait=True`, exhausting consecutive status rate-limit retries raises `AssemblyPollingError` (a `RuntimeError` subclass). The Assembly may still be running: use the error's `assembly_url` to resume polling or cancel, and inspect `assembly_response`/`last_response` for creation and rate-limit details. Normal API error responses still need an `error` check.

Source contributors use Poetry 2.4.1 with the new `[project]` metadata; isolated
source builds require `poetry-core>=2.2,<3`. Normal pip installation manages the
build backend automatically.

## Usage

Expand All @@ -56,9 +67,55 @@ print(assembly_response.data.get('assembly_id'))
print(assembly_response.data['assembly_id'])
```

## Example
## Async usage

```python
import asyncio
import os
from transloadit.async_client import AsyncTransloadit

async def main():
async with AsyncTransloadit(os.environ["TRANSLOADIT_KEY"], os.environ["TRANSLOADIT_SECRET"]) as tl:
assembly = tl.new_assembly()
assembly.add_step("resize", "/image/resize", {"width": 70, "height": 70})
with open("PATH/TO/FILE.jpg", "rb") as upload:
assembly.add_file(upload)
response = await assembly.create(wait=True, resumable=False)
print(response.data["ok"])

asyncio.run(main())
```

The async client keeps polling on `asyncio.sleep`. Resumable uploads still use the existing TUS client, but are offloaded to worker threads so the event loop stays responsive.

An injected aiohttp session configures Assembly creation and polling. Resumable
file transfers use tuspy's separate Requests transport and do not inherit that
session's explicit proxy or TLS connector settings; configure the Requests
environment for those transfers.

Cancellation of a resumable upload waits for the tuspy upload batch, including
retries, to finish before releasing your files. Multipart cancellation waits for
any active file read. Timeout and shutdown cleanup can therefore exceed the
requested deadline; keep file context managers open around the awaited call.

If you do not use `async with`, call `await tl.aclose()` when you are done with the session.

## Client features

`AsyncTransloadit` mirrors the existing synchronous client: Assembly creation,
retrieval, listing and cancellation; Template creation, retrieval, listing,
updates and deletion; monthly billing; and Smart CDN URL signing. Use the
Assembly helpers for multipart or resumable uploads and completion polling.
Await the async client's network methods; local factories and URL signing stay
synchronous.

## Examples

For copy/paste runnable examples, take a look at
[`examples/`](https://github.com/transloadit/python-sdk/tree/HEAD/examples).

For fully working examples, take a look at [`examples/`](https://github.com/transloadit/python-sdk/tree/HEAD/examples).
The examples cover sync uploads, async uploads, resumable uploads, Template usage,
sync and async Template lifecycle management, and Smart CDN URL signing.

## Documentation

Expand All @@ -80,17 +137,17 @@ This script will:
- install Poetry, Node.js 24, and the Transloadit CLI
- pass credentials from `.env` (if present) so end-to-end tests can run against real Transloadit accounts

Signature parity tests use `npx transloadit smart_sig` under the hood, matching the reference implementation used by our other SDKs. Our GitHub Actions workflow also runs the E2E upload against Python 3.14 on every push/PR using a dedicated Transloadit test account (wired through the `TRANSLOADIT_KEY` and `TRANSLOADIT_SECRET` secrets).
Signature parity tests use `npx transloadit smart_sig` under the hood, matching the reference implementation used by our other SDKs. Our GitHub Actions workflow also runs the E2E upload and quickstart examples against Python 3.14 on every push/PR using a dedicated Transloadit test account (wired through the `TRANSLOADIT_KEY` and `TRANSLOADIT_SECRET` secrets).

Pass `--python 3.14` (or set `PYTHON_VERSIONS`) to restrict the matrix, or append a custom command after `--`, for example `scripts/test-in-docker.sh -- pytest -k smartcdn`.

To exercise the optional end-to-end upload against a real Transloadit account, provide `TRANSLOADIT_KEY` and `TRANSLOADIT_SECRET` (via environment variables or `.env`) and set `PYTHON_SDK_E2E=1`:

```bash
PYTHON_SDK_E2E=1 scripts/test-in-docker.sh --python 3.14 -- pytest tests/test_e2e_upload.py
PYTHON_SDK_E2E=1 scripts/test-in-docker.sh --python 3.14 -- pytest tests/test_e2e_upload.py tests/test_examples.py
```

The test uploads `chameleon.jpg`, resizes it, and asserts on the live assembly results.
The tests upload `chameleon.jpg`, run the copy/paste quickstart examples, and assert on the live assembly results.

If you have a global installation of `poetry`, you can run the tests with:

Expand Down
72 changes: 64 additions & 8 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,22 @@ application pins or constraints and regenerate your application lockfile.

python -m pip install --upgrade 'pytransloadit>=2,<3'

The synchronous client API and imports remain compatible with 1.0.4 on supported
interpreters. Asyncio support is tracked separately in
`#57 <https://github.com/transloadit/python-sdk/pull/57>`_ and can land in a later
2.x minor release. The dependency refresh shipped in 1.0.4 does not require
upgrading to 2.0.
The synchronous imports and methods remain available on supported interpreters.
SDK 2.0 adds the separate ``AsyncTransloadit`` client for asyncio
applications. The dependency refresh shipped in 1.0.4 does not require upgrading
to 2.0.

Request-handling changes to account for when upgrading:

- Smart CDN boolean parameters use ``true``/``false`` to match the reference CLI. Workspace slugs must be DNS-safe; ``auth_key``, ``exp`` and ``sig`` are reserved query keys.
- Assembly status/cancellation uses credential-free requests to trusted Assembly URLs. Default Transloadit clients reject foreign destinations; explicitly configured services retain their worker hosts.
- HTTP redirects are returned to the caller instead of followed automatically. Invalid service URLs and empty/dot-segment Template IDs raise ``ValueError``.
- Template creation sends steps inside the ``template`` object. When combining a ``template`` option with ``add_step``, supply an object rather than a serialized JSON string.
- Non-JSON responses return text or bytes in ``Response.data``; empty bodies return an empty string. With ``wait=True``, exhausting consecutive status rate-limit retries raises ``AssemblyPollingError`` (a ``RuntimeError`` subclass). The Assembly may still be running: use the error's ``assembly_url`` to resume polling or cancel, and inspect ``assembly_response``/``last_response`` for creation and rate-limit details. Normal API error responses still need an ``error`` check.

Source contributors use Poetry 2.4.1 with the new ``[project]`` metadata; isolated
source builds require ``poetry-core>=2.2,<3``. Normal pip installation manages the
build backend automatically.

Usage
-----
Expand All @@ -87,9 +98,54 @@ Usage
# or
print(assembly_response.data['assembly_id'])

Example
-------
Async usage
-----------

.. code:: python

For fully working examples, take a look at `examples/`_.
import asyncio
import os
from transloadit.async_client import AsyncTransloadit

async def main():
async with AsyncTransloadit(os.environ['TRANSLOADIT_KEY'], os.environ['TRANSLOADIT_SECRET']) as tl:
assembly = tl.new_assembly()
assembly.add_step('resize', '/image/resize', {'width': 70, 'height': 70})
with open('PATH/TO/FILE.jpg', 'rb') as upload:
assembly.add_file(upload)
response = await assembly.create(wait=True, resumable=False)
print(response.data['ok'])

asyncio.run(main())

If you do not use ``async with``, call ``await tl.aclose()`` when you are done with the session.
Resumable uploads through tuspy run in a worker thread; polling uses ``asyncio.sleep``.
An injected aiohttp session configures Assembly creation and polling. Resumable
file transfers use tuspy's separate Requests transport and do not inherit that
session's explicit proxy or TLS connector settings; configure the Requests
environment for those transfers.

Cancellation waits for that upload batch, including retries, to finish before
releasing caller-owned files. Multipart cancellation drains active file reads.
Timeout and shutdown cleanup can therefore exceed the requested deadline; keep
file context managers open around the awaited call.

Client features
---------------

``AsyncTransloadit`` mirrors the existing synchronous client: Assembly creation,
retrieval, listing and cancellation; Template creation, retrieval, listing,
updates and deletion; monthly billing; and Smart CDN URL signing. Use the
Assembly helpers for multipart or resumable uploads and completion polling.
Await the async client's network methods; local factories and URL signing stay
synchronous.

Examples
--------

For copy/paste runnable examples, take a look at `examples/`_.

The examples cover sync uploads, async uploads, resumable uploads, Template usage,
sync and async Template lifecycle management, and Smart CDN URL signing.

.. _examples/: https://github.com/transloadit/python-sdk/tree/HEAD/examples
33 changes: 32 additions & 1 deletion docs/source/transloadit.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,38 @@ transloadit.request module
:undoc-members:
:show-inheritance:

transloadit.async_client module
-------------------------------

.. automodule:: transloadit.async_client
:members:
:undoc-members:
:show-inheritance:

transloadit.async_assembly module
----------------------------------

.. automodule:: transloadit.async_assembly
:members:
:undoc-members:
:show-inheritance:

transloadit.async_template module
----------------------------------

.. automodule:: transloadit.async_template
:members:
:undoc-members:
:show-inheritance:

transloadit.async_request module
--------------------------------

.. automodule:: transloadit.async_request
:members:
:undoc-members:
:show-inheritance:

transloadit.response module
---------------------------

Expand All @@ -57,4 +89,3 @@ transloadit.response module
:undoc-members:
:show-inheritance:


41 changes: 41 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Transloadit Python SDK Examples

Run the examples from the repository root after installing the project:

```bash
poetry install
export TRANSLOADIT_KEY="YOUR_TRANSLOADIT_KEY"
export TRANSLOADIT_SECRET="YOUR_TRANSLOADIT_SECRET"
```

## Quickstart Examples

```bash
poetry run python examples/image_resize.py
poetry run python examples/async_image_resize.py
poetry run python examples/resumable_upload.py
poetry run python examples/assembly_with_template.py
poetry run python examples/template_lifecycle.py
poetry run python examples/async_template_lifecycle.py
poetry run python examples/smart_cdn_url.py
```

`smart_cdn_url.py` only signs a URL locally. The other quickstart examples contact
Transloadit and may create temporary Assemblies or Templates in your account.

These quickstart examples run in CI against a dedicated Transloadit test account, so they
are kept in sync with the SDK and API.

## Advanced Examples

These examples require pre-created Templates and, depending on your Template, third-party
provider configuration:

```bash
export TRANSLOADIT_TTS_TEMPLATE_ID="YOUR_TEMPLATE_ID"
poetry run python examples/file_to_tts.py

export TRANSLOADIT_TRANSCRIBE_TEMPLATE_ID="YOUR_TRANSCRIBE_TEMPLATE_ID"
export TRANSLOADIT_TRANSLATE_TEMPLATE_ID="YOUR_TRANSLATE_TEMPLATE_ID"
poetry run python examples/video_translator.py
```
78 changes: 78 additions & 0 deletions examples/assembly_with_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""Create a temporary Template and use it to process an uploaded image.

Run from the repository root:

TRANSLOADIT_KEY=xxx TRANSLOADIT_SECRET=yyy poetry run python examples/assembly_with_template.py
"""

import os
from pathlib import Path
from uuid import uuid4

from transloadit.client import Transloadit


def get_credentials():
key = os.getenv("TRANSLOADIT_KEY")
secret = os.getenv("TRANSLOADIT_SECRET")
if not key or not secret:
raise RuntimeError("Please set TRANSLOADIT_KEY and TRANSLOADIT_SECRET.")
return key, secret


def get_example_image_path():
return Path(__file__).resolve().parent / "fixtures" / "lol_cat.jpg"


def extract_template_id(response_data):
template_id = response_data.get("id")
if not template_id:
raise RuntimeError(f"Template response did not contain an id: {response_data}")
return template_id


def first_result_url(response_data, step_name):
results = (response_data.get("results") or {}).get(step_name) or []
if not results:
raise RuntimeError(f"No results found for step {step_name!r}: {response_data}")
url = results[0].get("ssl_url")
if not url:
raise RuntimeError(f"No result URL found for step {step_name!r}: {response_data}")
return url


def create_resize_template(client):
template = client.new_template(f"python-sdk-template-example-{uuid4().hex[:12]}")
template.add_step(
"resize",
"/image/resize",
{
"use": ":original",
"width": 120,
"height": 120,
"resize_strategy": "fit",
"format": "png",
},
)
return extract_template_id(template.create().data)


def main():
key, secret = get_credentials()
client = Transloadit(key, secret)
template_id = create_resize_template(client)

try:
assembly = client.new_assembly({"template_id": template_id})
with get_example_image_path().open("rb") as upload:
assembly.add_file(upload, "image")
response = assembly.create(wait=True, resumable=False)

print("Assembly:", response.data["assembly_ssl_url"])
print("Template result:", first_result_url(response.data, "resize"))
finally:
client.delete_template(template_id)


if __name__ == "__main__":
main()
Loading
Loading