Skip to content

feat: bundle local (non-pip) modules for serverless endpoints#352

Open
deanq wants to merge 13 commits into
mainfrom
deanquinanola/sls-360-local-module-bundling
Open

feat: bundle local (non-pip) modules for serverless endpoints#352
deanq wants to merge 13 commits into
mainfrom
deanquinanola/sls-360-local-module-bundling

Conversation

@deanq

@deanq deanq commented Jul 6, 2026

Copy link
Copy Markdown
Member

Summary

Flash could not reliably include local (non-pip) Python modules an endpoint imports — a sibling utils.py, or a helpers/ package next to the endpoint, imported via import utils / from helpers import x. This adds first-class support across both code paths that ship user code to a worker.

Resolves SLS-360.

  • Live serverless (@Endpoint(...).run(...), flash dev LB dispatch): the endpoint's transitive local-import closure is now shipped inline alongside the function source.
  • Build/deploy (flash build / flash deploy): local modules an endpoint imports are force-included in the artifact even when the ignore filter would drop them, and the build fails loudly (clean error, not a traceback) if an endpoint's local import can't be resolved.

What changed

  • FunctionRequest.modules: dict[str, str] — additive protocol field (POSIX relative path → source text), default empty. Backward compatible: old workers ignore it, old SDKs send nothing. Shared contract with the worker repo — see the companion worker PR: feat: SLS-360 import shipped local modules before executing user code runpod-workers/flash#100.
  • stubs/local_modules.py — a shared, pure resolve_local_modules(...) that walks the transitive local-import closure (absolute, relative, and importlib.import_module("literal") imports, at module level and inside function bodies), classifies local vs stdlib/installed, pulls in package __init__.py, terminates on cycles, warns on non-literal dynamic imports, and raises LocalModuleResolutionError on unresolved relative imports / out-of-root files.
  • Live sendersstubs/live_serverless.py and stubs/load_balancer_sls.py populate modules from the resolved closure, with an 8 MiB inline cap raising LocalModulePayloadTooLargeError (points users to flash deploy for larger local dependencies).
  • Worker side (this repo)runtime/module_loader.py::materialized_modules writes shipped modules to a temp dir on sys.path and cleans up; wired into runtime/lb_handler.py's /execute so the temp dir stays live through the function call (not just exec).
  • Build pathaugment_with_local_modules in cli/commands/build.py force-includes resolved local files past the ignore filter. Strictness is scoped to endpoint files (@Endpoint/@remote): an endpoint with an unresolved local import fails the build loudly; an incidental non-endpoint file that fails resolution is warned and skipped (no regression on unrelated files).

Test plan

  • Unit: resolver (transitive closure, relative/dynamic/star imports, cycles, package inits, stdlib/external classification), materializer (writes files, restores sys.path on exception, no-op when empty), both live senders populate modules, LB /execute def-now/call-later import regression, build force-include + endpoint-only-strict A/B.
  • make quality-check green (86%+ coverage).
  • Deploy path verified end-to-end with real flash build: an ignored test_*.py sibling imported by an endpoint is force-included into artifact.tar.gz; an endpoint with an unresolvable relative import fails the build with a clean error (exit 1, no traceback).

Coordinated release

The worker-side materializer requires the companion worker PR (runpod-workers/flash#100) to be released for the live inline-shipping path to work end-to-end. The deploy path is independent (bundled files sit on the worker filesystem regardless of image version). Live-path E2E against real endpoints is a gating step once the worker image is rebuilt/published.

Follow-ups (non-blocking)

  • Live path silently omits parent-directory absolute imports (only relative imports fail loudly) — documented constraint; endpoint should sit at/above its local deps or use flash deploy.
  • sys.path materialization assumes one invocation at a time per worker; isolate per-invocation if concurrent execution is enabled.
  • Endpoint detection in the build path handles decorator forms; the ep = Endpoint(...) assignment form is not yet detected (fails safe toward warn).

@capy-ai

capy-ai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Capy auto-review is paused for this organization because the usage-cycle auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews.

@promptless

promptless Bot commented Jul 6, 2026

Copy link
Copy Markdown

Promptless prepared a documentation update related to this change.

Triggered by PR #352: feat: bundle local (non-pip) modules for serverless endpoints

Documents Flash's new first-class support for bundling local (non-pip) Python modules that endpoints import: a new "Import local modules" section in the Flash endpoints guide (supported import forms, transitive resolution, the 8 MiB live-execution cap, and the parent-directory absolute-import constraint), a force-include note in the build CLI reference, and two troubleshooting entries for the new resolution and payload-size errors.

Review: Document Flash local (non-pip) module bundling for endpoints

@deanq deanq force-pushed the deanquinanola/sls-360-local-module-bundling branch from 724bc98 to 0c8c90c Compare July 8, 2026 15:14
@deanq deanq requested a review from Copilot July 11, 2026 06:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class support for bundling/shipping local (non-pip) Python modules that serverless endpoints import, across both live execution (inline module shipping) and build/deploy (force-include local imports in artifacts), with new protocol support via FunctionRequest.modules.

Changes:

  • Added a shared local-import resolver (resolve_local_modules) to compute a transitive closure of local dependencies for endpoint code.
  • Live execution paths now populate modules (subject to an 8 MiB cap) and worker runtime materializes shipped modules onto sys.path for the duration of the function call.
  • Build path now force-includes locally imported modules that would otherwise be ignored, and fails builds for unresolved endpoint-local imports.

Reviewed changes

Copilot reviewed 20 out of 22 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/unit/test_stub_live_serverless.py Verifies live stub request includes sibling local modules.
tests/unit/test_load_balancer_sls_stub.py Verifies LB stub request includes sibling local modules.
tests/unit/stubs/test_local_modules_resolve.py Unit tests for transitive local module resolution behavior and error cases.
tests/unit/stubs/test_local_modules_classify.py Unit tests for stdlib detection, local path resolution, and size cap constant.
tests/unit/stubs/test_live_serverless_modules.py Tests build_modules_map behavior and size cap enforcement.
tests/unit/runtime/test_module_loader.py Tests temp materialization behavior and sys.path restoration.
tests/unit/runtime/test_lb_handler_extended.py Regression test ensuring shipped modules remain importable during function execution.
tests/unit/protos/test_remote_execution.py Verifies FunctionRequest.modules defaults and round-trips.
tests/unit/core/test_exceptions.py Tests new exception types’ message behavior.
tests/unit/cli/commands/test_build_local_modules.py Tests build-time force-include and endpoint-only strictness behavior.
tests/unit/_live_serverless_prepare_request_sibling.py Sibling module fixture for live-serverless stub tests.
tests/unit/_lb_prepare_request_sibling.py Sibling module fixture for LB stub tests.
src/runpod_flash/stubs/local_modules.py New AST-based resolver for local module import closure and warnings/errors.
src/runpod_flash/stubs/load_balancer_sls.py Populates modules in LB request using build_modules_map.
src/runpod_flash/stubs/live_serverless.py Adds build_modules_map and populates modules in live request with cap enforcement.
src/runpod_flash/runtime/module_loader.py New temp-dir module materializer that prepends to sys.path during execution.
src/runpod_flash/runtime/lb_handler.py Keeps module materialization active through exec + function call/await.
src/runpod_flash/protos/remote_execution.py Adds FunctionRequest.modules protocol field with default empty dict.
src/runpod_flash/core/exceptions.py Adds LocalModuleResolutionError and LocalModulePayloadTooLargeError.
src/runpod_flash/cli/commands/build.py Adds endpoint detection and build-time augmentation to force-include local imports.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +42 to +47
tmpdir = tempfile.mkdtemp(prefix="flash_modules_")
root = Path(tmpdir)
for rel_path, source in modules.items():
dest = root / rel_path
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_text(source, encoding="utf-8")
Comment on lines +95 to +101
except (LocalModuleResolutionError, UnicodeDecodeError, SyntaxError) as e:
if _defines_endpoint(py_file):
raise
print_warning(
console, f"Skipping local-module resolution for {py_file}: {e}"
)
continue
deanq added a commit to runpod-workers/flash that referenced this pull request Jul 11, 2026
Worker uses runpod_flash.runtime.module_loader, which is not in a published
runpod-flash yet. Temporary git pin so CI installs a flash with the module.
Revert to a released constraint once runpod/flash#352 ships.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants