Repository navigation
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The ZIP loader needs failed-import cache cleanup and regression coverage for the new compatibility path.
2 open issues
What changed in this PR
Adds Python 3.15 compatibility to the external-function server and extends CI coverage to the new interpreter.
Changes:
- Replaces ZIP environment loading with
find_spec()/exec_module(), retaining the Python 3.9 fallback. - Uses the running event loop to create the uvicorn startup future.
- Adds Python 3.15 CI jobs and upgrades cibuildwheel, raising the Linux wheel requirement to glibc 2.28.
| File | Description |
|---|---|
singlestoredb/functions/ext/asgi.py |
Updates ZIP environment loading for Python 3.15. |
singlestoredb/apps/_uvicorn_util.py |
Creates the startup future on the running loop. |
.github/workflows/smoke-test.yml |
Adds Python 3.15 smoke coverage. |
.github/workflows/publish.yml |
Upgrades cibuildwheel and selects Python 3.15 for wheel testing. |
.github/workflows/pre-commit.yml |
Adds Python 3.15 to the matrix. |
Review effort: Balanced
kesmit13
left a comment
There was a problem hiding this comment.
I'd like to see this split into two PRs. One for 3.15 support and a separate one for the update of cibuildwheel. The 3.15 changes seem straightforward, but we need to evaluate any side-effects of cibuildwheel updates more thoroughly.
|
Can you rebase this? We've done some refactoring recently and there is an issue with resource cleanup in PRs. I pushed a fix for it so that the code checks should run cleanly now. Sorry for the inconvenience. |
Python 3.15 removed zipimport.zipimporter.load_module(), which the external function server used to load a zipped environment file. Load it with find_spec()/exec_module() instead. Those methods only exist from Python 3.10, so 3.9 keeps using load_module(); that branch can be removed when 3.9 support is dropped. Use asyncio.get_running_loop() in AwaitableUvicornServer rather than get_event_loop(). Both callers already create it inside a running event loop, so behaviour is unchanged. Add 3.15 to the smoke-test and pre-commit matrices. The cibuildwheel upgrade needed to build and test wheels on 3.15 is in singlestore-labs#147. Tested locally on 3.15.0 against SingleStore 9.1.1 (management tests excluded): 967 passed, 19 skipped, 1 failed, the same results as 3.14. The failure, TestServerProcessMode::test_process_mode_worker_handles_request, also fails on 3.14 on macOS and is unrelated to this change.
If the module in a zipped environment file raised while importing, main() left the half-initialized module in sys.modules, and a later import in the same process would return it without retrying. Remove the entry and re-raise, as load_module() did. Add test_asgi_env_file, which runs a zip environment through main() with uvicorn mocked out and checks that its functions are found, and that a failed import leaves nothing in sys.modules. The smoke-test job runs it on every Python version, 3.9 through 3.15, since the loader takes a different path on 3.9 than on 3.10+.
281c47e to
6ccbc33
Compare
|
@kesmit13 No prob! Rebased onto main |
|
|
||
| def __init__(self, config: 'uvicorn.Config') -> None: | ||
| super().__init__(config) | ||
| self._startup_future = asyncio.get_event_loop().create_future() |
There was a problem hiding this comment.
We have another PR that is going to refactor this part of the code, so I'm holding off on approving this PR until that is done. get_event_loop is no longer used in the refactored code.

Python 3.15 removed
zipimport.zipimporter.load_module(), which the external function server used to load a zipped environment file. It's now loaded withfind_spec()/exec_module()instead. Those methods only exist from Python 3.10, so 3.9 keeps usingload_module(); that branch can be removed when 3.9 support is dropped. If the module raises while importing, itssys.modulesentry is removed before the error is re-raised, asload_module()did.AwaitableUvicornServernow usesasyncio.get_running_loop()rather thanget_event_loop(). Both callers already create it inside a running event loop, so behaviour is unchanged.CI
smoke-testandpre-commitmatrices.test_asgi_env_fileon every Python version from 3.9 to 3.15. It loads a zip environment throughmain()with uvicorn mocked, and checks both a successful import and a failed one.The cibuildwheel upgrade needed to build and test wheels on 3.15 is in a separate PR: #NNN.
Testing
Full suite run locally on 3.15.0 against SingleStore 9.1.1, with the management tests excluded:
The one failure,
TestServerProcessMode::test_process_mode_worker_handles_request, also fails on 3.14 on macOS and is unrelated to this change.test_asgi_env_filepasses on 3.9, 3.14 and 3.15. Onmainit fails on 3.15 with theload_moduleerror, which confirms it catches the original bug.Note
Medium Risk
Changes how zipped external-function environment files are imported—a deployment path—with version-specific branches, though behavior is covered by new tests.
Overview
Adds Python 3.15 to pre-commit and smoke-test CI matrices and removes the old note that 3.15 was intentionally excluded.
External function zip environment loading in
asgi.main()no longer calls removedzipimporter.load_module()on 3.10+; it usesfind_spec()/exec_module()instead, with 3.9 still onload_module(). Failed imports clearsys.moduleslike the old API.AwaitableUvicornServercreates its startup future withasyncio.get_running_loop()instead ofget_event_loop().New
test_asgi_env_fileexercises zipped env import throughmain()(uvicorn mocked) and runs on every smoke-test Python version alongsidetest_basics.Reviewed by Cursor Bugbot for commit ac40b8e. Bugbot is set up for automated code reviews on this repo. Configure here.