You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Upload Files in threads, not processes; test on Python 3.14 - #58
Bug:convertapi.convert(..., {'Files': ['a.docx', 'b.docx']}) with local files fails with TypeError: can only concatenate str (not "NoneType") to str on macOS, and on Linux with Python 3.14+. The parallel uploads ran in a multiprocessing.Pool. Under the spawn start method (macOS default) and forkserver (Linux default since 3.14), each child process re-imports convertapi, so it loses api_credentials and anything else the caller configured. Linux on 3.13 and older forks, which is why CI never saw it. I reproduced it against staging on Python 3.14/macOS.
Fix:utils.map_in_parallel now uses a ThreadPoolExecutor. Uploads are network-bound, so threads fit, and they share the caller's configuration. max_parallel_uploads keeps its meaning.
Tests: a new mocked test uploads two local files with max_parallel_uploads 1 and 10 and checks that every upload carries the token. It fails on the old code and passes now. All 12 live tests pass against stag-v2. Python 3.14 is added to the CI matrix and the classifiers.
Parallel uploads ran in a multiprocessing.Pool. Under the spawn (macOS) and forkserver (Linux,
Python 3.14+) start methods the child processes re-import convertapi and lose api_credentials, so
convert(..., {'Files': [local paths]}) failed with a TypeError. A ThreadPoolExecutor keeps the
caller's configuration and suits network-bound uploads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Concurrent reuse of an uninitialized UploadIO can consume the same stream twice, producing an empty or incomplete upload.
No open issues 🎉
1 previously missed issue
In code that hasn't changed since last review
Concurrent uploads race when the same uninitialized UploadIO is reused
convertapi/utils.py:10
With max_parallel_uploads >= 2, passing the same uninitialized UploadIO twice in Files lets both workers see _result is None and upload its shared stream concurrently. A synchronized upload stub reproduces one complete body and one empty body, so conversion can fail or receive incomplete input. Synchronize lazy initialization per UploadIO, or resolve repeated references once before dispatch, and add a regression test confirming both entries reuse the same uploaded file ID.
Review effort: Balanced
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug:
convertapi.convert(..., {'Files': ['a.docx', 'b.docx']})with local files fails withTypeError: can only concatenate str (not "NoneType") to stron macOS, and on Linux with Python 3.14+. The parallel uploads ran in amultiprocessing.Pool. Under thespawnstart method (macOS default) andforkserver(Linux default since 3.14), each child process re-importsconvertapi, so it losesapi_credentialsand anything else the caller configured. Linux on 3.13 and older forks, which is why CI never saw it. I reproduced it against staging on Python 3.14/macOS.Fix:
utils.map_in_parallelnow uses aThreadPoolExecutor. Uploads are network-bound, so threads fit, and they share the caller's configuration.max_parallel_uploadskeeps its meaning.Tests: a new mocked test uploads two local files with
max_parallel_uploads1 and 10 and checks that every upload carries the token. It fails on the old code and passes now. All 12livetests pass against stag-v2. Python 3.14 is added to the CI matrix and the classifiers.CHANGELOG: under 2.0.1 / Fixed.
🤖 Generated with Claude Code