Skip to content

feat: add generic handler factory and build integration for Flash#130

Merged
deanq merged 10 commits intomainfrom
deanq/ae-1251-handler-mapper
Jan 8, 2026
Merged

feat: add generic handler factory and build integration for Flash#130
deanq merged 10 commits intomainfrom
deanq/ae-1251-handler-mapper

Conversation

@deanq
Copy link
Copy Markdown
Member

@deanq deanq commented Jan 3, 2026

Summary

This PR introduces a generic handler factory architecture for Flash applications and integrates it into the build process. The changes eliminate code duplication in generated handler files while enabling efficient service discovery and cross-endpoint function calls.

See AE-1251 for details

Key Changes

  • Generic Handler Factory (src/tetra_rp/runtime/generic_handler.py): Factory function that creates RunPod serverless handlers, eliminating code duplication across generated handler files
  • Build Utilities: New modules for handler generation, manifest building, and remote function scanning
    • handler_generator.py: Generates lightweight handler_<resource>.py files
    • manifest.py: Creates flash_manifest.json for service discovery
    • scanner.py: AST-based discovery of @remote decorated functions
  • Build Command Integration: Updated flash build to automatically generate handlers and manifests
  • Documentation: Added comprehensive docs on build process and handler architecture

Architecture Benefits

  • Single source of truth for handler logic in generic_handler.py
  • Zero runtime overhead (factory called once at startup)
  • Easier maintenance (bug fixes update one module, not all projects)
  • Support for both function and class execution
  • Cross-endpoint function calls via manifest-based service discovery

Testing

  • 19 unit tests for generic handler factory
  • 7 integration tests for handler generation
  • Test coverage for scanning, manifest building, and handler generation

deanq added 8 commits January 3, 2026 01:22
Implement a factory function that creates RunPod serverless handlers,
eliminating code duplication across generated handler files.

The generic_handler module provides:
- create_handler(function_registry) factory that accepts a dict of
  function/class objects and returns a RunPod-compatible handler
- Automatic serialization/deserialization using cloudpickle + base64
- Support for both function execution and class instantiation + method calls
- Structured error responses with full tracebacks for debugging
- Load manifest for cross-endpoint function discovery

This design centralizes all handler logic in one place, making it easy to:
- Fix bugs once, benefit all handlers
- Add new features without regenerating projects
- Keep deployment packages small (handler files are ~23 lines each)

Implementation:
- deserialize_arguments(): Base64 + cloudpickle decoding
- serialize_result(): Cloudpickle + base64 encoding
- execute_function(): Handles function vs. class execution
- load_manifest(): Loads flash_manifest.json for service discovery
…uild process

Implement the build pipeline components that work together to generate
serverless handlers from @Remote decorated functions.

Three core components:

1. RemoteDecoratorScanner (scanner.py)
   - Uses Python AST to discover all @Remote decorated functions
   - Extracts function metadata: name, module, async status, is_class
   - Groups functions by resource_config for handler generation
   - Handles edge cases like decorated classes and async functions

2. ManifestBuilder (manifest.py)
   - Groups functions by their resource_config
   - Creates flash_manifest.json structure for service discovery
   - Maps functions to their modules and handler files
   - Enables cross-endpoint function routing at runtime

3. HandlerGenerator (handler_generator.py)
   - Creates lightweight handler_*.py files for each resource config
   - Each handler imports functions and registers them in FUNCTION_REGISTRY
   - Handler delegates to create_handler() factory from generic_handler
   - Generated handlers are ~23 lines (vs ~98 with duplication)

Build Pipeline Flow:
1. Scanner discovers @Remote functions
2. ManifestBuilder groups them by resource_config
3. HandlerGenerator creates handler_*.py for each group
4. All files + manifest bundled into archive.tar.gz

This eliminates ~95% duplication across handlers by using the factory pattern
instead of template-based generation.
Implement 19 unit tests covering all major paths through the generic_handler
factory and its helper functions.

Test Coverage:

Serialization/Deserialization (7 tests):
- serialize_result() with simple values, dicts, lists
- deserialize_arguments() with empty, args-only, kwargs-only, mixed inputs
- Round-trip encoding/decoding of cloudpickle + base64

Function Execution (4 tests):
- Simple function execution with positional and keyword arguments
- Keyword argument handling
- Class instantiation and method calls
- Argument passing to instance methods

Handler Factory (8 tests):
- create_handler() returns callable RunPod handler
- Handler with simple function registry
- Missing function error handling (returns error response, not exception)
- Function exceptions caught with traceback included
- Multiple functions in single registry
- Complex Python objects (classes, lambdas, closures)
- Empty registry edge case
- Default execution_type parameter
- None return values
- Correct RunPod response format (success, result/error, traceback)

Test Strategy:
- Arrange-Act-Assert pattern for clarity
- Isolated unit tests (no external dependencies)
- Tests verify behavior, not implementation
- Error cases tested for proper error handling
- All serialization tested for round-trip correctness

All tests passing, 83% coverage on generic_handler.py
…canning

Implement integration tests validating the build pipeline components work
correctly together.

Test Coverage:

HandlerGenerator Tests:
- Handler files created with correct names (handler_<resource_name>.py)
- Generated files import required functions from workers
- FUNCTION_REGISTRY properly formatted
- create_handler() imported from generic_handler
- Handler creation via factory
- RunPod start call present and correct
- Multiple handlers generated for multiple resource configs

ManifestBuilder Tests:
- Manifest structure with correct version and metadata
- Resources grouped by resource_config
- Handler file paths correct
- Function metadata preserved (name, module, is_async, is_class)
- Function registry mapping complete

ScannerTests:
- @Remote decorated functions discovered via AST
- Function metadata extracted correctly
- Module paths resolved properly
- Async functions detected
- Class methods detected
- Edge cases handled (multiple decorators, nested classes)

Test Strategy:
- Integration tests verify components work together
- Tests verify generated files are syntactically correct
- Tests validate data structures match expected schemas
- No external dependencies in build process

Validates that the entire build pipeline:
1. Discovers functions correctly
2. Groups them appropriately
3. Generates valid Python handler files
4. Creates correct manifest structure
Add comprehensive architecture documentation explaining why the factory
pattern was chosen and how it works.

Documentation includes:

Overview & Context:
- Problem statement: Handler files had 95% duplication
- Design decision: Use factory function instead of templates
- Benefits: Single source of truth, easier maintenance, consistency

Architecture Diagrams (MermaidJS):
- High-level flow: @Remote functions → Scanner → Manifest → Handlers → Factory
- Component relationships: HandlerGenerator, GeneratedHandler, generic_handler
- Function registry pattern: Discovery → Grouping → Registration → Factory

Implementation Details:
- create_handler(function_registry) signature and behavior
- deserialize_arguments(): Base64 + cloudpickle decoding
- serialize_result(): Cloudpickle + base64 encoding
- execute_function(): Function vs. class execution
- load_manifest(): Service discovery via flash_manifest.json

Design Decisions (with rationale):
- Factory Pattern over Inheritance: Simpler, less coupling, easier to test
- CloudPickle + Base64: Handles arbitrary objects, safe JSON transmission
- Manifest in Generic Handler: Runtime service discovery requirement
- Structured Error Responses: Debugging aid, functional error handling
- Both Execution Types: Supports stateful classes and pure functions

Usage Examples:
- Simple function handler
- Class execution with methods
- Multiple functions in one handler

Build Process Integration:
- 4-phase pipeline: Scanner → Grouping → Generation → Packaging
- Manifest structure and contents
- Generated handler structure (~23 lines)

Testing Strategy:
- 19 unit tests covering all major paths
- 7 integration tests verifying handler generation
- Manual testing with example applications

Performance:
- Zero runtime penalty (factory called once at startup)
- No additional indirection in request path
Document the flash build command and update CLI README to include it.

New Documentation:

flash-build.md includes:

Usage & Options:
- Command syntax: flash build [OPTIONS]
- --no-deps: Skip transitive dependencies (faster, smaller archives)
- --keep-build: Keep build directory for inspection/debugging
- --output, -o: Custom archive name (default: archive.tar.gz)

What It Does (5-step process):
1. Discovery: Scan for @Remote decorated functions
2. Grouping: Group functions by resource_config
3. Handler Generation: Create lightweight handler files
4. Manifest Creation: Generate flash_manifest.json
5. Packaging: Create archive.tar.gz for deployment

Build Artifacts:
- .flash/archive.tar.gz: Deployment package (ready for RunPod)
- .flash/flash_manifest.json: Service discovery configuration
- .flash/.build/: Temporary build directory

Handler Generation:
- Explains factory pattern and minimal handler files
- Links to Runtime_Generic_Handler.md for details

Dependency Management:
- Default behavior: Install all dependencies including transitive
- --no-deps: Only direct dependencies (when base image has transitive)
- Trade-offs explained

Cross-Endpoint Function Calls:
- Example showing GPU and CPU endpoints
- Manifest enables routing automatically

Output & Troubleshooting:
- Sample build output with progress indicators
- Common failure scenarios and solutions
- How to debug with --keep-build

Next Steps:
- Test locally with flash run
- Deploy to RunPod
- Monitor with flash undeploy list

Updated CLI README.md:
- Added flash build to command list in sequence
- Links to full flash-build.md documentation
Add a new section explaining how the build system works and why the
factory pattern reduces code duplication.

New Section: Build Process and Handler Generation

Explains:

How Flash Builds Your Application (5-step pipeline):
1. Discovery: Scans code for @Remote decorated functions
2. Grouping: Groups functions by resource_config
3. Handler Generation: Creates lightweight handler files
4. Manifest Creation: Generates flash_manifest.json for service discovery
5. Packaging: Bundles everything into archive.tar.gz

Handler Architecture (with code example):
- Shows generated handler using factory pattern
- Single source of truth: All handler logic in one place
- Easier maintenance: Bug fixes don't require rebuilding projects

Cross-Endpoint Function Calls:
- Example of GPU and CPU endpoints calling each other
- Manifest and runtime wrapper handle service discovery

Build Artifacts:
- .flash/.build/: Temporary build directory
- .flash/archive.tar.gz: Deployment package
- .flash/flash_manifest.json: Service configuration

Links to detailed documentation:
- docs/Runtime_Generic_Handler.md for architecture details
- src/tetra_rp/cli/docs/flash-build.md for CLI reference

This section bridges the main README and detailed documentation,
providing entry point for new users discovering the build system.
Wire up the handler generator, manifest builder, and scanner into the
actual flash build command implementation.

Changes to build.py:

1. Integration:
   - Import RemoteDecoratorScanner for function discovery
   - Import ManifestBuilder for manifest creation
   - Import HandlerGenerator for handler file creation
   - Call these in sequence during the build process

2. Build Pipeline:
   - After copying project files, scan for @Remote functions
   - Build manifest from discovered functions
   - Generate handler files for each resource config
   - Write manifest to build directory
   - Progress indicators show what's being generated

3. Fixes:
   - Change .tetra directory references to .flash
   - Uncomment actual build logic (was showing "Coming Soon" message)
   - Fix progress messages to show actual file counts

4. Error Handling:
   - Try/catch around handler generation
   - Warning shown if generation fails but build continues
   - User can debug with --keep-build flag

Build Flow Now:
1. Load ignore patterns
2. Collect project files
3. Create build directory
4. Copy files to build directory
5. [NEW] Scan for @Remote functions
6. [NEW] Build and write manifest
7. [NEW] Generate handler files
8. Install dependencies
9. Create archive
10. Clean up build directory (unless --keep-build)

Dependencies:
- Updated uv.lock with all required dependencies
@deanq deanq changed the title feat: Add generic handler factory and build integration for Flash feat: add generic handler factory and build integration for Flash Jan 3, 2026
…handling

**Critical Fixes:**
- Remove "Coming Soon" message blocking build command execution
- Fix build directory to use .flash/.build/ directly (no app_name subdirectory)
- Fix tarball to extract with flat structure using arcname="."
- Fix cleanup to remove correct build directory

**Error Handling & Validation:**
- Add specific exception handling (ImportError, SyntaxError, ValueError)
- Add import validation to generated handlers
- Add duplicate function name detection across resources
- Add proper error logging throughout build process

**Resource Type Tracking:**
- Add resource_type field to RemoteFunctionMetadata
- Track actual resource types (LiveServerless, CpuLiveServerless)
- Use actual types in manifest instead of hardcoding

**Robustness Improvements:**
- Add handler import validation post-generation
- Add manifest path fallback search (cwd, module dir, legacy location)
- Add resource name sanitization for safe filenames
- Add specific exception logging in scanner (UnicodeDecodeError, SyntaxError)

**User Experience:**
- Add troubleshooting section to README
- Update manifest path documentation in docs
- Change "Zero Runtime Penalty" to "Minimal Runtime Overhead"
- Mark future enhancements as "Not Yet Implemented"
- Improve build success message with next steps

Fixes all 20 issues identified in code review (issues #1-13, #19-22)
@deanq deanq requested a review from Copilot January 3, 2026 22:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a generic handler factory architecture for Flash applications and integrates it into the build process. The changes eliminate code duplication by using a factory pattern where all handler logic resides in a single source (generic_handler.py), and generated handler files become lightweight wrappers that import functions and delegate execution to the factory.

Key Changes:

  • Added generic handler factory (generic_handler.py) with serialization, deserialization, and execution logic
  • Implemented build utilities (scanner, manifest builder, handler generator) for automated handler generation
  • Integrated handler and manifest generation into the flash build command

Reviewed changes

Copilot reviewed 18 out of 19 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/tetra_rp/runtime/generic_handler.py Core factory function that creates RunPod serverless handlers, eliminating code duplication
src/tetra_rp/cli/commands/build_utils/scanner.py AST-based scanner for discovering @remote decorated functions
src/tetra_rp/cli/commands/build_utils/manifest.py Builds service discovery manifest grouping functions by resource config
src/tetra_rp/cli/commands/build_utils/handler_generator.py Generates lightweight handler files that delegate to generic handler factory
src/tetra_rp/cli/commands/build.py Updated build command to generate handlers and manifest during build process
tests/unit/runtime/test_generic_handler.py Comprehensive unit tests for generic handler factory (19 tests)
tests/unit/cli/commands/build_utils/test_*.py Integration tests for scanner, manifest, and handler generation
src/tetra_rp/cli/docs/flash-build.md Documentation for build process and handler architecture
docs/Runtime_Generic_Handler.md Detailed architecture documentation for generic handler pattern

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

…improvements

- Add 6 new scanner tests for directory filtering (.venv, .flash, .runpod exclusion)
- Add test for resource type validation to prevent false positives
- Add test for fallback behavior when resource name extraction fails
- Add test for handling resource names with special characters
- Update existing tests to reflect new dynamic import format and resource name extraction

These tests guarantee that improvements to the scanner (resource type validation,
directory filtering, fallback behavior) and handler generator (dynamic imports for
invalid Python identifiers) won't regress in future changes.
@deanq deanq merged commit 8c1e6b8 into main Jan 8, 2026
7 checks passed
@deanq deanq deleted the deanq/ae-1251-handler-mapper branch January 8, 2026 01:40
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.

3 participants