puffinflow 2.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. puffinflow/__init__.py +132 -0
  2. puffinflow/core/__init__.py +110 -0
  3. puffinflow/core/agent/__init__.py +320 -0
  4. puffinflow/core/agent/base.py +1635 -0
  5. puffinflow/core/agent/checkpoint.py +50 -0
  6. puffinflow/core/agent/context.py +521 -0
  7. puffinflow/core/agent/decorators/__init__.py +90 -0
  8. puffinflow/core/agent/decorators/builder.py +454 -0
  9. puffinflow/core/agent/decorators/flexible.py +714 -0
  10. puffinflow/core/agent/decorators/inspection.py +144 -0
  11. puffinflow/core/agent/dependencies.py +57 -0
  12. puffinflow/core/agent/scheduling/__init__.py +21 -0
  13. puffinflow/core/agent/scheduling/builder.py +160 -0
  14. puffinflow/core/agent/scheduling/exceptions.py +35 -0
  15. puffinflow/core/agent/scheduling/inputs.py +137 -0
  16. puffinflow/core/agent/scheduling/parser.py +209 -0
  17. puffinflow/core/agent/scheduling/scheduler.py +413 -0
  18. puffinflow/core/agent/state.py +141 -0
  19. puffinflow/core/config.py +62 -0
  20. puffinflow/core/coordination/__init__.py +137 -0
  21. puffinflow/core/coordination/agent_group.py +359 -0
  22. puffinflow/core/coordination/agent_pool.py +629 -0
  23. puffinflow/core/coordination/agent_team.py +577 -0
  24. puffinflow/core/coordination/coordinator.py +720 -0
  25. puffinflow/core/coordination/deadlock.py +1759 -0
  26. puffinflow/core/coordination/fluent_api.py +421 -0
  27. puffinflow/core/coordination/primitives.py +478 -0
  28. puffinflow/core/coordination/rate_limiter.py +520 -0
  29. puffinflow/core/observability/__init__.py +47 -0
  30. puffinflow/core/observability/agent.py +139 -0
  31. puffinflow/core/observability/alerting.py +73 -0
  32. puffinflow/core/observability/config.py +127 -0
  33. puffinflow/core/observability/context.py +88 -0
  34. puffinflow/core/observability/core.py +147 -0
  35. puffinflow/core/observability/decorators.py +105 -0
  36. puffinflow/core/observability/events.py +71 -0
  37. puffinflow/core/observability/interfaces.py +196 -0
  38. puffinflow/core/observability/metrics.py +137 -0
  39. puffinflow/core/observability/tracing.py +209 -0
  40. puffinflow/core/reliability/__init__.py +27 -0
  41. puffinflow/core/reliability/bulkhead.py +96 -0
  42. puffinflow/core/reliability/circuit_breaker.py +149 -0
  43. puffinflow/core/reliability/leak_detector.py +122 -0
  44. puffinflow/core/resources/__init__.py +77 -0
  45. puffinflow/core/resources/allocation.py +790 -0
  46. puffinflow/core/resources/pool.py +645 -0
  47. puffinflow/core/resources/quotas.py +567 -0
  48. puffinflow/core/resources/requirements.py +217 -0
  49. puffinflow/version.py +21 -0
  50. puffinflow-2.dev0.dist-info/METADATA +334 -0
  51. puffinflow-2.dev0.dist-info/RECORD +55 -0
  52. puffinflow-2.dev0.dist-info/WHEEL +5 -0
  53. puffinflow-2.dev0.dist-info/entry_points.txt +3 -0
  54. puffinflow-2.dev0.dist-info/licenses/LICENSE +21 -0
  55. puffinflow-2.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,144 @@
1
+ """Utilities for inspecting decorated states."""
2
+
3
+ from typing import Any, Callable, Optional
4
+
5
+ from ...coordination.rate_limiter import RateLimitStrategy
6
+ from ...resources.requirements import ResourceRequirements
7
+ from ..state import Priority
8
+
9
+
10
+ def is_puffinflow_state(func: Callable) -> bool:
11
+ """Check if a function is a PuffinFlow state."""
12
+ return getattr(func, "_puffinflow_state", False)
13
+
14
+
15
+ def get_state_config(func: Callable) -> Optional[dict[str, Any]]:
16
+ """Get the configuration of a PuffinFlow state."""
17
+ return getattr(func, "_state_config", None)
18
+
19
+
20
+ def get_state_requirements(func: Callable) -> Optional[ResourceRequirements]:
21
+ """Get the resource requirements of a PuffinFlow state."""
22
+ return getattr(func, "_resource_requirements", None)
23
+
24
+
25
+ def get_state_rate_limit(func: Callable) -> Optional[dict[str, Any]]:
26
+ """Get the rate limiting configuration of a PuffinFlow state."""
27
+ if not hasattr(func, "_rate_limit"):
28
+ return None
29
+
30
+ strategy = getattr(func, "_rate_strategy", RateLimitStrategy.TOKEN_BUCKET)
31
+
32
+ return {
33
+ "rate": func._rate_limit,
34
+ "burst": getattr(func, "_burst_limit", None),
35
+ "strategy": strategy,
36
+ }
37
+
38
+
39
+ def get_state_coordination(func: Callable) -> Optional[dict[str, Any]]:
40
+ """Get the coordination configuration of a PuffinFlow state."""
41
+ primitive = getattr(func, "_coordination_primitive", None)
42
+ if primitive is None:
43
+ return None
44
+
45
+ return {"type": primitive, "config": getattr(func, "_coordination_config", {})}
46
+
47
+
48
+ def list_state_metadata(func: Callable) -> dict[str, Any]:
49
+ """Get all metadata for a PuffinFlow state."""
50
+ if not is_puffinflow_state(func):
51
+ return {}
52
+
53
+ # Get description, falling back to docstring if not set
54
+ description = getattr(func, "_state_description", "")
55
+ if not description and func.__doc__:
56
+ description = func.__doc__.strip()
57
+
58
+ # If still no description, use fallback
59
+ if not description:
60
+ description = f"State: {func.__name__}"
61
+
62
+ return {
63
+ "name": getattr(func, "_state_name", func.__name__),
64
+ "description": description,
65
+ "tags": getattr(func, "_state_tags", {}),
66
+ "priority": getattr(func, "_priority", Priority.NORMAL),
67
+ "requirements": get_state_requirements(func),
68
+ "rate_limit": get_state_rate_limit(func),
69
+ "coordination": get_state_coordination(func),
70
+ "dependencies": getattr(func, "_dependency_configs", {}),
71
+ "preemptible": getattr(func, "_preemptible", False),
72
+ "checkpoint_interval": getattr(func, "_checkpoint_interval", None),
73
+ "cleanup_on_failure": getattr(func, "_cleanup_on_failure", True),
74
+ }
75
+
76
+
77
+ def compare_states(func1: Callable, func2: Callable) -> dict[str, Any]:
78
+ """Compare two state configurations."""
79
+ config1 = get_state_config(func1) or {}
80
+ config2 = get_state_config(func2) or {}
81
+
82
+ differences = {}
83
+ all_keys = set(config1.keys()) | set(config2.keys())
84
+
85
+ for key in all_keys:
86
+ val1 = config1.get(key)
87
+ val2 = config2.get(key)
88
+ if val1 != val2:
89
+ differences[key] = {"func1": val1, "func2": val2}
90
+
91
+ return differences
92
+
93
+
94
+ def get_state_summary(func: Callable) -> str:
95
+ """Get a human-readable summary of state configuration."""
96
+ if not is_puffinflow_state(func):
97
+ return f"{func.__name__}: Not a PuffinFlow state"
98
+
99
+ config = get_state_config(func)
100
+ if config is None or not isinstance(config, dict):
101
+ return f"{func.__name__}: No configuration found"
102
+
103
+ summary_parts = [f"{func.__name__}:"]
104
+
105
+ # Resources
106
+ resources = []
107
+ cpu = config.get("cpu", 0)
108
+ if cpu is not None and cpu > 0:
109
+ resources.append(f"CPU={cpu}")
110
+ memory = config.get("memory", 0)
111
+ if memory is not None and memory > 0:
112
+ resources.append(f"Memory={memory}MB")
113
+ gpu = config.get("gpu", 0)
114
+ if gpu is not None and gpu > 0:
115
+ resources.append(f"GPU={gpu}")
116
+
117
+ if resources:
118
+ summary_parts.append(f" Resources: {', '.join(resources)}")
119
+
120
+ # Priority
121
+ priority = config.get("priority")
122
+ if priority and priority != Priority.NORMAL:
123
+ summary_parts.append(f" Priority: {priority.name}")
124
+
125
+ # Coordination
126
+ coord_info = []
127
+ if config.get("mutex"):
128
+ coord_info.append("Mutex")
129
+ if config.get("semaphore"):
130
+ coord_info.append(f"Semaphore({config['semaphore']})")
131
+ if config.get("barrier"):
132
+ coord_info.append(f"Barrier({config['barrier']})")
133
+ if config.get("rate_limit"):
134
+ coord_info.append(f"RateLimit({config['rate_limit']}/s)")
135
+
136
+ if coord_info:
137
+ summary_parts.append(f" Coordination: {', '.join(coord_info)}")
138
+
139
+ # Dependencies
140
+ deps = config.get("depends_on")
141
+ if deps:
142
+ summary_parts.append(f" Dependencies: {', '.join(deps)}")
143
+
144
+ return "\n".join(summary_parts)
@@ -0,0 +1,57 @@
1
+ """Dependency management types."""
2
+
3
+ from dataclasses import dataclass
4
+ from enum import Enum
5
+ from typing import TYPE_CHECKING, Any, Callable, Optional
6
+
7
+ if TYPE_CHECKING:
8
+ from .base import Agent
9
+
10
+
11
+ class DependencyType(Enum):
12
+ """Types of dependencies between states."""
13
+
14
+ REQUIRED = "required" # Must complete before state can run
15
+ OPTIONAL = "optional" # Will wait if running, otherwise skips
16
+ PARALLEL = "parallel" # Can run in parallel with dependency
17
+ SEQUENTIAL = "sequential" # Must run after dependency completes
18
+ CONDITIONAL = "conditional" # Depends on condition function
19
+ TIMEOUT = "timeout" # Wait for max time then continue
20
+ XOR = "xor" # Only one dependency needs to be satisfied
21
+ AND = "and" # All dependencies must be satisfied
22
+ OR = "or" # At least one dependency must be satisfied
23
+
24
+ def __str__(self) -> str:
25
+ return f"DependencyType.{self.name}"
26
+
27
+ def __repr__(self) -> str:
28
+ return f"DependencyType.{self.name}"
29
+
30
+
31
+ class DependencyLifecycle(Enum):
32
+ """Lifecycle management for dependencies."""
33
+
34
+ ONCE = "once" # Dependency only needs to be satisfied once
35
+ ALWAYS = "always" # Dependency must be satisfied every time
36
+ SESSION = "session" # Dependency valid for current run() execution
37
+ TEMPORARY = "temporary" # Dependency expires after specified time
38
+ PERIODIC = "periodic" # Must be re-satisfied after specified interval
39
+
40
+ def __str__(self) -> str:
41
+ return f"DependencyLifecycle.{self.name}"
42
+
43
+ def __repr__(self) -> str:
44
+ return f"DependencyLifecycle.{self.name}"
45
+
46
+
47
+ @dataclass
48
+ class DependencyConfig:
49
+ """Configuration for state dependencies."""
50
+
51
+ type: DependencyType
52
+ lifecycle: DependencyLifecycle = DependencyLifecycle.ALWAYS
53
+ condition: Optional[Callable[["Agent"], bool]] = None
54
+ expiry: Optional[float] = None
55
+ interval: Optional[float] = None
56
+ timeout: Optional[float] = None
57
+ retry_policy: Optional[dict[str, Any]] = None
@@ -0,0 +1,21 @@
1
+ """Agent scheduling module for PuffinFlow."""
2
+
3
+ from .builder import ScheduleBuilder
4
+ from .exceptions import InvalidInputTypeError, InvalidScheduleError, SchedulingError
5
+ from .inputs import InputType, ScheduledInput, parse_magic_prefix
6
+ from .parser import ScheduleParser, parse_schedule_string
7
+ from .scheduler import GlobalScheduler, ScheduledAgent
8
+
9
+ __all__ = [
10
+ "GlobalScheduler",
11
+ "InputType",
12
+ "InvalidInputTypeError",
13
+ "InvalidScheduleError",
14
+ "ScheduleBuilder",
15
+ "ScheduleParser",
16
+ "ScheduledAgent",
17
+ "ScheduledInput",
18
+ "SchedulingError",
19
+ "parse_magic_prefix",
20
+ "parse_schedule_string",
21
+ ]
@@ -0,0 +1,160 @@
1
+ """Fluent API builder for agent scheduling."""
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from .inputs import ScheduledInput, parse_inputs
6
+
7
+ if TYPE_CHECKING:
8
+ from ..base import Agent
9
+ from .scheduler import ScheduledAgent
10
+
11
+
12
+ class ScheduleBuilder:
13
+ """Fluent API builder for scheduling agents."""
14
+
15
+ def __init__(self, agent: "Agent", schedule_string: str):
16
+ self._agent = agent
17
+ self._schedule_string = schedule_string
18
+ self._inputs: dict[str, ScheduledInput] = {}
19
+
20
+ def with_inputs(self, **inputs: Any) -> "ScheduleBuilder":
21
+ """Add regular variable inputs.
22
+
23
+ Args:
24
+ **inputs: Input key-value pairs
25
+
26
+ Returns:
27
+ Self for chaining
28
+ """
29
+ parsed = parse_inputs(**inputs)
30
+ self._inputs.update(parsed)
31
+ return self
32
+
33
+ def with_secrets(self, **secrets: Any) -> "ScheduleBuilder":
34
+ """Add secret inputs.
35
+
36
+ Args:
37
+ **secrets: Secret key-value pairs
38
+
39
+ Returns:
40
+ Self for chaining
41
+ """
42
+ for key, value in secrets.items():
43
+ prefixed_value = f"secret:{value}"
44
+ parsed = parse_inputs(**{key: prefixed_value})
45
+ self._inputs.update(parsed)
46
+ return self
47
+
48
+ def with_constants(self, **constants: Any) -> "ScheduleBuilder":
49
+ """Add constant inputs.
50
+
51
+ Args:
52
+ **constants: Constant key-value pairs
53
+
54
+ Returns:
55
+ Self for chaining
56
+ """
57
+ for key, value in constants.items():
58
+ prefixed_value = f"const:{value}"
59
+ parsed = parse_inputs(**{key: prefixed_value})
60
+ self._inputs.update(parsed)
61
+ return self
62
+
63
+ def with_cache(self, ttl: int, **cached_inputs: Any) -> "ScheduleBuilder":
64
+ """Add cached inputs with TTL.
65
+
66
+ Args:
67
+ ttl: Time to live in seconds
68
+ **cached_inputs: Cached input key-value pairs
69
+
70
+ Returns:
71
+ Self for chaining
72
+ """
73
+ for key, value in cached_inputs.items():
74
+ # Convert value to string for cache prefix
75
+ if isinstance(value, (dict, list)):
76
+ import json
77
+
78
+ value_str = json.dumps(value)
79
+ else:
80
+ value_str = str(value)
81
+ prefixed_value = f"cache:{ttl}:{value_str}"
82
+ parsed = parse_inputs(**{key: prefixed_value})
83
+ self._inputs.update(parsed)
84
+ return self
85
+
86
+ def with_typed(self, **typed_inputs: Any) -> "ScheduleBuilder":
87
+ """Add typed inputs.
88
+
89
+ Args:
90
+ **typed_inputs: Typed input key-value pairs
91
+
92
+ Returns:
93
+ Self for chaining
94
+ """
95
+ for key, value in typed_inputs.items():
96
+ # Convert value to string for typed prefix
97
+ if isinstance(value, (dict, list)):
98
+ import json
99
+
100
+ value_str = json.dumps(value)
101
+ else:
102
+ value_str = str(value)
103
+ prefixed_value = f"typed:{value_str}"
104
+ parsed = parse_inputs(**{key: prefixed_value})
105
+ self._inputs.update(parsed)
106
+ return self
107
+
108
+ def with_outputs(self, **outputs: Any) -> "ScheduleBuilder":
109
+ """Add pre-set outputs.
110
+
111
+ Args:
112
+ **outputs: Output key-value pairs
113
+
114
+ Returns:
115
+ Self for chaining
116
+ """
117
+ for key, value in outputs.items():
118
+ prefixed_value = f"output:{value}"
119
+ parsed = parse_inputs(**{key: prefixed_value})
120
+ self._inputs.update(parsed)
121
+ return self
122
+
123
+ def run(self) -> "ScheduledAgent":
124
+ """Execute the scheduling with configured inputs.
125
+
126
+ Returns:
127
+ ScheduledAgent instance
128
+ """
129
+ # Convert ScheduledInput objects back to input format for schedule method
130
+ input_kwargs = {}
131
+ for key, scheduled_input in self._inputs.items():
132
+ if scheduled_input.input_type.value == "secret":
133
+ input_kwargs[key] = f"secret:{scheduled_input.value}"
134
+ elif scheduled_input.input_type.value == "const":
135
+ input_kwargs[key] = f"const:{scheduled_input.value}"
136
+ elif scheduled_input.input_type.value == "cache":
137
+ input_kwargs[
138
+ key
139
+ ] = f"cache:{scheduled_input.ttl}:{scheduled_input.value}"
140
+ elif scheduled_input.input_type.value == "typed":
141
+ input_kwargs[key] = f"typed:{scheduled_input.value}"
142
+ elif scheduled_input.input_type.value == "output":
143
+ input_kwargs[key] = f"output:{scheduled_input.value}"
144
+ else: # variable
145
+ input_kwargs[key] = scheduled_input.value
146
+
147
+ return self._agent.schedule(self._schedule_string, **input_kwargs)
148
+
149
+
150
+ def create_schedule_builder(agent: "Agent", schedule_string: str) -> ScheduleBuilder:
151
+ """Create a schedule builder for fluent API.
152
+
153
+ Args:
154
+ agent: Agent to schedule
155
+ schedule_string: Schedule string
156
+
157
+ Returns:
158
+ ScheduleBuilder instance
159
+ """
160
+ return ScheduleBuilder(agent, schedule_string)
@@ -0,0 +1,35 @@
1
+ """Scheduling exceptions."""
2
+
3
+ from typing import Optional
4
+
5
+
6
+ class SchedulingError(Exception):
7
+ """Base exception for scheduling errors."""
8
+
9
+ pass
10
+
11
+
12
+ class InvalidScheduleError(SchedulingError):
13
+ """Raised when a schedule string is invalid."""
14
+
15
+ def __init__(self, schedule: str, message: Optional[str] = None):
16
+ self.schedule = schedule
17
+ if message is None:
18
+ message = (
19
+ f"Invalid schedule '{schedule}'. "
20
+ "Try: 'daily', 'hourly', 'every 5 minutes', or cron expression."
21
+ )
22
+ super().__init__(message)
23
+
24
+
25
+ class InvalidInputTypeError(SchedulingError):
26
+ """Raised when an input type prefix is invalid."""
27
+
28
+ def __init__(self, prefix: str, message: Optional[str] = None):
29
+ self.prefix = prefix
30
+ if message is None:
31
+ message = (
32
+ f"Unknown input type '{prefix}'. "
33
+ "Supported: secret:, const:, cache:TTL:, typed:, output:"
34
+ )
35
+ super().__init__(message)
@@ -0,0 +1,137 @@
1
+ """Input types and magic prefix parsing for scheduled agents."""
2
+
3
+ import json
4
+ from dataclasses import dataclass
5
+ from enum import Enum
6
+ from typing import Any, Optional
7
+
8
+ from .exceptions import InvalidInputTypeError
9
+
10
+
11
+ class InputType(Enum):
12
+ """Types of inputs for scheduled agents."""
13
+
14
+ VARIABLE = "variable" # Regular variables (no prefix)
15
+ SECRET = "secret" # secret:value
16
+ CONSTANT = "const" # const:value
17
+ CACHED = "cache" # cache:TTL:value
18
+ TYPED = "typed" # typed:value
19
+ OUTPUT = "output" # output:value
20
+
21
+
22
+ @dataclass
23
+ class ScheduledInput:
24
+ """Configuration for a scheduled input."""
25
+
26
+ key: str
27
+ value: Any
28
+ input_type: InputType
29
+ ttl: Optional[int] = None # For cached inputs
30
+
31
+ def apply_to_context(self, context: Any) -> None:
32
+ """Apply this input to a context."""
33
+ if self.input_type == InputType.SECRET:
34
+ context.set_secret(self.key, self.value)
35
+ elif self.input_type == InputType.CONSTANT:
36
+ context.set_constant(self.key, self.value)
37
+ elif self.input_type == InputType.CACHED:
38
+ context.set_cached(self.key, self.value, self.ttl)
39
+ elif self.input_type == InputType.TYPED:
40
+ context.set_typed_variable(self.key, self.value)
41
+ elif self.input_type == InputType.OUTPUT:
42
+ context.set_output(self.key, self.value)
43
+ else: # VARIABLE
44
+ context.set_variable(self.key, self.value)
45
+
46
+
47
+ def parse_magic_prefix(key: str, value: Any) -> ScheduledInput:
48
+ """Parse magic prefix from input value and return ScheduledInput.
49
+
50
+ Args:
51
+ key: The input key name
52
+ value: The input value, potentially with magic prefix
53
+
54
+ Returns:
55
+ ScheduledInput with parsed type and value
56
+
57
+ Raises:
58
+ InvalidInputTypeError: If prefix is invalid
59
+ """
60
+ if not isinstance(value, str):
61
+ # Non-string values are treated as regular variables
62
+ return ScheduledInput(key, value, InputType.VARIABLE)
63
+
64
+ # Check for magic prefixes
65
+ if ":" not in value:
66
+ # No prefix, regular variable
67
+ return ScheduledInput(key, value, InputType.VARIABLE)
68
+
69
+ parts = value.split(":", 1)
70
+ prefix = parts[0].lower()
71
+
72
+ if prefix == "secret":
73
+ if len(parts) != 2 or not parts[1]:
74
+ raise InvalidInputTypeError(prefix, "Secret format: secret:value")
75
+ return ScheduledInput(key, parts[1], InputType.SECRET)
76
+
77
+ elif prefix == "const":
78
+ if len(parts) != 2 or not parts[1]:
79
+ raise InvalidInputTypeError(prefix, "Constant format: const:value")
80
+ return ScheduledInput(key, parts[1], InputType.CONSTANT)
81
+
82
+ elif prefix == "cache":
83
+ # Format: cache:TTL:value
84
+ cache_parts = value.split(":", 2)
85
+ if len(cache_parts) != 3:
86
+ raise InvalidInputTypeError(prefix, "Cache format: cache:TTL:value")
87
+
88
+ try:
89
+ ttl = int(cache_parts[1])
90
+ except ValueError as e:
91
+ raise InvalidInputTypeError(prefix, "Cache TTL must be an integer") from e
92
+
93
+ # Try to parse value as JSON, fall back to string
94
+ raw_value = cache_parts[2]
95
+ try:
96
+ parsed_value = json.loads(raw_value)
97
+ except json.JSONDecodeError:
98
+ parsed_value = raw_value
99
+
100
+ return ScheduledInput(key, parsed_value, InputType.CACHED, ttl=ttl)
101
+
102
+ elif prefix == "typed":
103
+ if len(parts) != 2 or not parts[1]:
104
+ raise InvalidInputTypeError(prefix, "Typed format: typed:value")
105
+
106
+ # Try to parse value as JSON for complex types
107
+ raw_value = parts[1]
108
+ try:
109
+ parsed_value = json.loads(raw_value)
110
+ except json.JSONDecodeError:
111
+ parsed_value = raw_value
112
+
113
+ return ScheduledInput(key, parsed_value, InputType.TYPED)
114
+
115
+ elif prefix == "output":
116
+ if len(parts) != 2 or not parts[1]:
117
+ raise InvalidInputTypeError(prefix, "Output format: output:value")
118
+ return ScheduledInput(key, parts[1], InputType.OUTPUT)
119
+
120
+ else:
121
+ # Unknown prefix, treat as regular variable
122
+ return ScheduledInput(key, value, InputType.VARIABLE)
123
+
124
+
125
+ def parse_inputs(**inputs: Any) -> dict[str, ScheduledInput]:
126
+ """Parse all inputs with magic prefixes.
127
+
128
+ Args:
129
+ **inputs: Input key-value pairs
130
+
131
+ Returns:
132
+ Dictionary mapping keys to ScheduledInput objects
133
+ """
134
+ parsed_inputs = {}
135
+ for key, value in inputs.items():
136
+ parsed_inputs[key] = parse_magic_prefix(key, value)
137
+ return parsed_inputs