feat: serverless Python automation with pluggable components and generator variables#1
feat: serverless Python automation with pluggable components and generator variables#1Copilot wants to merge 6 commits into
Conversation
|
I see you mentioned me, but I'm not sure what you need. Could you please clarify what you'd like me to do? For example:
Please let me know how I can help! |
PR Summary by QodoAdd serverless automation engine with pluggable components and generator variables
AI Description
Diagram
High-Level Assessment
Files changed (4)
|
Code Review by Qodo
1. Execute exceptions escape registry
|
| def run(self, name: str, input_: ComponentInput) -> ComponentOutput: | ||
| """ | ||
| Execute the component registered as *name*. | ||
|
|
||
| Validates the input first; returns an error output if the component | ||
| is not found or validation fails. | ||
| """ | ||
| component = self._components.get(name) | ||
| if component is None: | ||
| return ComponentOutput( | ||
| component=name, | ||
| result=None, | ||
| success=False, | ||
| error=f"Component '{name}' not registered", | ||
| ) | ||
|
|
||
| valid, reason = component.validate(input_) | ||
| if not valid: | ||
| return ComponentOutput( | ||
| component=name, | ||
| result=None, | ||
| success=False, | ||
| error=f"Validation failed: {reason}", | ||
| ) | ||
|
|
||
| return component.execute(input_) | ||
|
|
There was a problem hiding this comment.
3. Execute exceptions escape registry 🐞 Bug ☼ Reliability
ComponentRegistry.run() calls Component.execute() without try/except, so exceptions from Component subclasses escape and abort execution. In AutomationEngine, these get recorded as a generic "__engine__" failure, obscuring which component actually crashed.
Agent Prompt
## Issue description
`ComponentRegistry.run()` does not guard `component.execute()`; if a custom `Component.execute()` raises, the exception propagates.
- When invoked through `AutomationEngine`, the exception is caught at the engine layer and recorded as a `ComponentOutput(component="__engine__", ...)`, which loses the identity of the failing component.
## Issue Context
- `FunctionComponent.execute()` already catches exceptions and returns an error output, but custom `Component` subclasses are not protected.
- `run_pipeline()` relies on `run()` and expects to stop on failure; an exception is a harder failure mode than a structured `ComponentOutput(success=False)`.
## Fix Focus Areas
- src/automation/components.py[228-254]
- src/automation/engine.py[293-325]
## Suggested fix
Wrap `component.execute(input_)` in `ComponentRegistry.run()` with `try/except` and return a `ComponentOutput` for that component name on exception (ideally including traceback text or at least exception type/message). This preserves per-step attribution and avoids escalating normal component failures into engine-level generic errors.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
Pull request overview
Introduces a new src/automation/ Python package implementing a serverless, call-driven automation subsystem that composes registered “components” into pipelines and injects generator-backed “variables” into run metadata.
Changes:
- Added generator-backed variables (
GeneratorVariable) with composition helpers and a sharedVariableRegistry. - Added pluggable components (
Component,FunctionComponent) and aComponentRegistrywith sequential pipeline execution. - Added an
AutomationEnginethat dispatches trigger events to registered automations, snapshots variables, executes pipelines, and tracks run history/stats.
Reviewed changes
Copilot reviewed 4 out of 8 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/automation/variables.py | Implements generator-backed variables, peeking/resetting, composition helpers, and a variable registry. |
| src/automation/components.py | Defines the component contract and a registry for registering/running components and pipelines. |
| src/automation/engine.py | Implements the automation engine, trigger dispatch, variable snapshotting, run execution, and history/stats. |
| src/automation/init.py | Exposes the public API and provides a package-level quick-start example. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def register(self, name: str, component: Component) -> "ComponentRegistry": | ||
| """ | ||
| Register *component* under *name*. | ||
|
|
||
| Calls :meth:`Component.setup` and returns *self* for chaining. | ||
| """ | ||
| component.name = component.name or name | ||
| component.setup() | ||
| self._components[name] = component | ||
| return self |
| def execute(self, input_: ComponentInput) -> ComponentOutput: | ||
| try: | ||
| result = self._fn(input_) | ||
| return self._ok(result) | ||
| except Exception as exc: | ||
| return self._err(str(exc)) |
| snapshot: Dict[str, Any] = {} | ||
| for name in names: | ||
| var = self.variables.get(name) | ||
| if var is not None: | ||
| snapshot[name] = var.peek() | ||
| return snapshot |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CI Feedback 🧐A test triggered by this PR failed. Here is an AI-generated analysis of the failure:
|
Adds
src/automation/— a pure-Python, event-driven automation subsystem with no runtime dependencies beyond stdlib.Variables (
variables.py)GeneratorVariable: lazy value production via generator factory; idempotentpeek(),reset(), and composition via.map()/.filter()/.take()/.chain()VariableRegistry: named lookup store shared across automation stepsComponents (
components.py)Component(ABC):execute(input_) → ComponentOutputcontract with optionalvalidate/setup/teardownhooksFunctionComponent: wraps any callable as a component without subclassingComponentRegistry: name-based registration,run()with pre-execution validation,run_pipeline()for sequential chainingEngine (
engine.py)AutomationEngine: purely call-driven (no threads, no listener) — safe for FaaS environmentsAutomationDefinitionbinds trigger event types → component pipeline + variable referencesstats()/history()for observability