parallelazy 0.0.1__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.
@@ -0,0 +1,158 @@
1
+ """
2
+ Environment Variables Example: Demonstrate shared state management.
3
+
4
+ This example shows how plugins can:
5
+ 1. Set and get environment variables in the manager
6
+ 2. Subscribe to changes in environment variables
7
+ 3. Use environment variables for cross-plugin communication
8
+ """
9
+
10
+ import logging
11
+ import time
12
+ from typing import Any, Dict
13
+
14
+ from parallelazy.plugin import Plugin, PluginDescriptor
15
+ from parallelazy.plugin_worker import PluginWorker
16
+ from parallelazy.plugin_manager import PluginManager
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class ConfigWriter(Plugin):
22
+ """
23
+ Plugin that periodically updates shared configuration.
24
+ Simulates a configuration server updating settings.
25
+ """
26
+
27
+ @classmethod
28
+ def Descriptor(cls, options: Dict[str, Any] = None) -> PluginDescriptor:
29
+ return super().Descriptor(
30
+ version="1.0.0",
31
+ author="ModularNet Team",
32
+ description="Writes configuration to environment variables",
33
+ options=options or {}
34
+ )
35
+
36
+ def __init__(self, worker: PluginWorker, plugin_id: str, options: Dict[str, Any]):
37
+ super().__init__(worker, plugin_id, options)
38
+ self.iteration = 0
39
+
40
+ def on_start(self) -> None:
41
+ logger.info(f"[{self.plugin_id}] ConfigWriter started")
42
+ self.env_set("config.batch_size", 32)
43
+ self.env_set("config.learning_rate", 0.001)
44
+ self.env_set("config.model_type", "resnet50")
45
+
46
+ def main(self):
47
+ self.iteration += 1
48
+ # Periodically update configuration
49
+ if self.iteration % 10 == 0:
50
+ new_lr = 0.001 * (0.9 ** (self.iteration // 10))
51
+ self.env_set("config.learning_rate", new_lr)
52
+ logger.info(f"[{self.plugin_id}] Updated learning rate to {new_lr:.6f}")
53
+
54
+ def on_stop(self) -> None:
55
+ logger.info(f"[{self.plugin_id}] ConfigWriter stopped after {self.iteration} iterations")
56
+
57
+
58
+
59
+ class ConfigReader(Plugin):
60
+ """
61
+ Plugin that subscribes to configuration changes.
62
+ Simulates a worker that reacts to configuration updates.
63
+ """
64
+
65
+ @classmethod
66
+ def Descriptor(cls, options: Dict[str, Any] = None) -> PluginDescriptor:
67
+ return super().Descriptor(
68
+ version="1.0.0",
69
+ author="ModularNet Team",
70
+ description="Reads and reacts to configuration changes",
71
+ options=options or {}
72
+ )
73
+
74
+ def __init__(self, worker: PluginWorker, plugin_id: str, options: Dict[str, Any]):
75
+ super().__init__(worker, plugin_id, options)
76
+ self.current_config = {}
77
+
78
+ def on_start(self) -> None:
79
+ logger.info(f"[{self.plugin_id}] ConfigReader started")
80
+ # Subscribe to all config changes
81
+ self.env_subscribe("config.batch_size", self.on_config_change)
82
+ self.env_subscribe("config.learning_rate", self.on_config_change)
83
+ self.env_subscribe("config.model_type", self.on_config_change)
84
+
85
+ def on_config_change(self, value: Any):
86
+ # The callback receives the new value, not the name
87
+ # (the name is implied by which variable we subscribed to)
88
+ logger.info(f"[{self.plugin_id}] Config changed: {value}")
89
+
90
+ def main(self):
91
+ # Read current config without subscribing
92
+ lr = self.env_get("config.learning_rate", 0.001)
93
+
94
+ def on_stop(self) -> None:
95
+ logger.info(f"[{self.plugin_id}] ConfigReader stopped")
96
+ logger.info(f"[{self.plugin_id}] Final config: {self.current_config}")
97
+
98
+
99
+ def main():
100
+ """Run the environment variables example."""
101
+ manager = PluginManager()
102
+
103
+ # Create and load plugins
104
+ writer_desc = ConfigWriter.Descriptor()
105
+ reader_desc = ConfigReader.Descriptor()
106
+
107
+ manager.load_plugin(writer_desc)
108
+ manager.load_plugin(reader_desc)
109
+
110
+ manager.start_plugin(writer_desc.name)
111
+ manager.start_plugin(reader_desc.name)
112
+
113
+ # Let them run for a bit
114
+ print("Plugins running... Press Ctrl+C to stop.")
115
+ time.sleep(8)
116
+ print("Stopping plugins...")
117
+
118
+ manager.stop_plugin(writer_desc.name)
119
+ manager.stop_plugin(reader_desc.name)
120
+
121
+ # Show final state
122
+ time.sleep(1)
123
+ all_vars = manager.env_get()
124
+ print("\nFinal environment state:")
125
+ for name, value in all_vars.items():
126
+ if name.startswith("config."):
127
+ print(f" {name}: {value}")
128
+
129
+ # VERIFICATION
130
+ print("\n=== VERIFICATION ===")
131
+ print("PREDICTED BEHAVIOR:")
132
+ print("1. ConfigWriter sets initial config (batch_size=32, learning_rate=0.001, model_type=resnet50)")
133
+ print("2. ConfigReader subscribes and receives updates")
134
+ print("3. Learning rate decays over iterations")
135
+ print("4. Both plugins update same environment variables\n")
136
+
137
+ batch_size = all_vars.get("config.batch_size")
138
+ learning_rate = all_vars.get("config.learning_rate")
139
+ model_type = all_vars.get("config.model_type")
140
+
141
+ assert batch_size is not None, "config.batch_size should be set"
142
+ assert learning_rate is not None, "config.learning_rate should be set"
143
+ assert model_type is not None, "config.model_type should be set"
144
+ assert batch_size == 32, f"batch_size should be 32, got {batch_size}"
145
+ assert model_type == "resnet50", f"model_type should be resnet50, got {model_type}"
146
+ assert learning_rate < 0.001, f"learning_rate should decay below 0.001, got {learning_rate}"
147
+
148
+ print(f"✓ config.batch_size: {batch_size} (unchanged)")
149
+ print(f"✓ config.learning_rate: {learning_rate} (decayed from 0.001)")
150
+ print(f"✓ config.model_type: {model_type} (unchanged)")
151
+ print("✓ All assertions passed!")
152
+
153
+ manager.unload_plugin(writer_desc.name)
154
+ manager.unload_plugin(reader_desc.name)
155
+
156
+
157
+ if __name__ == "__main__":
158
+ main()
@@ -0,0 +1,266 @@
1
+ """
2
+ Error Handling Example: Resilient plugins with error recovery.
3
+
4
+ This example shows how plugins can:
5
+ 1. Handle errors gracefully
6
+ 2. Retry operations
7
+ 3. Report errors to manager
8
+ 4. Implement fallback strategies
9
+ """
10
+
11
+ import logging
12
+ import time
13
+ import random
14
+ from typing import Any, Dict
15
+
16
+ from parallelazy.plugin import Plugin, PluginDescriptor
17
+ from parallelazy.plugin_worker import PluginWorker
18
+ from parallelazy.plugin_manager import PluginManager
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class UnreliableService(Plugin):
24
+ """
25
+ Plugin that simulates an unreliable external service.
26
+ Randomly fails to simulate real-world conditions.
27
+ """
28
+
29
+ @classmethod
30
+ def Descriptor(cls, options: Dict[str, Any] = None) -> PluginDescriptor:
31
+ return super().Descriptor(
32
+ version="1.0.0",
33
+ author="ModularNet Team",
34
+ description="Unreliable service simulator",
35
+ options=options or {}
36
+ )
37
+
38
+ def __init__(self, worker: PluginWorker, plugin_id: str, options: Dict[str, Any]):
39
+ super().__init__(worker, plugin_id, options)
40
+ self.success_count = 0
41
+ self.failure_count = 0
42
+ self.failure_rate = 0.3 # 30% failure rate
43
+
44
+ def on_start(self) -> None:
45
+ logger.info(f"[{self.plugin_id}] UnreliableService started")
46
+ self.env_set("service.status", "ready")
47
+ self.env_set("service.success_count", 0)
48
+ self.env_set("service.failure_count", 0)
49
+
50
+ def main(self):
51
+ pass
52
+
53
+ def call_service(self, request_id: int) -> Dict[str, Any]:
54
+ """
55
+ Simulates a service call that may fail.
56
+ """
57
+ if random.random() < self.failure_rate:
58
+ self.failure_count += 1
59
+ logger.error(f"[{self.plugin_id}] Service call #{request_id} FAILED")
60
+ self.env_set("service.failure_count", self.failure_count, wait=False)
61
+ raise Exception(f"Service temporarily unavailable (request #{request_id})")
62
+ else:
63
+ self.success_count += 1
64
+ result = {"request_id": request_id, "data": f"response_{request_id}"}
65
+ logger.info(f"[{self.plugin_id}] Service call #{request_id} SUCCESS")
66
+ self.env_set("service.success_count", self.success_count, wait=False)
67
+ return result
68
+
69
+ def on_stop(self) -> None:
70
+ logger.info(
71
+ f"[{self.plugin_id}] UnreliableService stopped: "
72
+ f"success={self.success_count}, failures={self.failure_count}"
73
+ )
74
+ self.env_set("service.final_status", "stopped")
75
+
76
+
77
+ class ResilientClient(Plugin):
78
+ """
79
+ Plugin that calls unreliable service with retry logic.
80
+ Demonstrates error handling and recovery strategies.
81
+ """
82
+
83
+ @classmethod
84
+ def Descriptor(cls, options: Dict[str, Any] = None) -> PluginDescriptor:
85
+ return super().Descriptor(
86
+ version="1.0.0",
87
+ author="ModularNet Team",
88
+ description="Resilient service client with retry logic",
89
+ options=options or {}
90
+ )
91
+
92
+ def __init__(self, worker: PluginWorker, plugin_id: str, options: Dict[str, Any]):
93
+ super().__init__(worker, plugin_id, options)
94
+ self.requests_sent = 0
95
+ self.requests_succeeded = 0
96
+ self.requests_failed = 0
97
+ self.service_id = "unreliable-service"
98
+
99
+ def on_start(self) -> None:
100
+ logger.info(f"[{self.plugin_id}] ResilientClient started")
101
+ self.env_set("client.requests_sent", 0)
102
+ self.env_set("client.requests_succeeded", 0)
103
+ self.env_set("client.requests_failed", 0)
104
+
105
+ def main(self):
106
+ self.requests_sent += 1
107
+ if self.requests_sent % 3 == 0:
108
+ self._call_with_retry(self.requests_sent)
109
+
110
+ def _call_with_retry(self, request_id: int, max_retries: int = 3):
111
+ """Call service with retry logic."""
112
+ retry_count = 0
113
+ last_error = None
114
+
115
+ while retry_count < max_retries:
116
+ try:
117
+ logger.info(
118
+ f"[{self.plugin_id}] Calling service (request #{request_id}, "
119
+ f"attempt {retry_count + 1}/{max_retries})"
120
+ )
121
+ result = self.plugin_call(self.service_id, "call_service", request_id)
122
+ logger.info(f"[{self.plugin_id}] Request #{request_id} succeeded: {result}")
123
+ self.requests_succeeded += 1
124
+ self.env_set("client.requests_succeeded", self.requests_succeeded, wait=False)
125
+ return result
126
+
127
+ except Exception as e:
128
+ last_error = e
129
+ retry_count += 1
130
+ logger.warning(
131
+ f"[{self.plugin_id}] Request #{request_id} failed (attempt {retry_count}/{max_retries}): {e}"
132
+ )
133
+ if retry_count < max_retries:
134
+ # Brief backoff before retry
135
+ time.sleep(0.1 * retry_count)
136
+
137
+ # All retries exhausted
138
+ logger.error(f"[{self.plugin_id}] Request #{request_id} FAILED after {max_retries} attempts: {last_error}")
139
+ self.requests_failed += 1
140
+ self.env_set("client.requests_failed", self.requests_failed, wait=False)
141
+ return None
142
+
143
+ def on_stop(self) -> None:
144
+ logger.info(
145
+ f"[{self.plugin_id}] ResilientClient stopped: "
146
+ f"sent={self.requests_sent}, succeeded={self.requests_succeeded}, "
147
+ f"failed={self.requests_failed}"
148
+ )
149
+ self.env_set("client.requests_sent", self.requests_sent)
150
+ self.env_set("client.requests_succeeded", self.requests_succeeded)
151
+ self.env_set("client.requests_failed", self.requests_failed)
152
+
153
+
154
+ class HealthMonitor(Plugin):
155
+ """
156
+ Plugin that monitors the health of other plugins.
157
+ """
158
+
159
+ @classmethod
160
+ def Descriptor(cls, options: Dict[str, Any] = None) -> PluginDescriptor:
161
+ return super().Descriptor(
162
+ version="1.0.0",
163
+ author="ModularNet Team",
164
+ description="Health monitor for system resilience",
165
+ options=options or {}
166
+ )
167
+
168
+ def __init__(self, worker: PluginWorker, plugin_id: str, options: Dict[str, Any]):
169
+ super().__init__(worker, plugin_id, options)
170
+ self.checks = 0
171
+
172
+ def on_start(self) -> None:
173
+ logger.info(f"[{self.plugin_id}] HealthMonitor started")
174
+ # Subscribe to service status
175
+ self.env_subscribe("service.status", self.on_service_status_change)
176
+ self.env_set("monitor.checks", 0)
177
+
178
+ def on_service_status_change(self, name: str, value: Any):
179
+ logger.info(f"[{self.plugin_id}] Service status changed: {value}")
180
+
181
+ def main(self):
182
+ self.checks += 1
183
+ if self.checks % 4 == 0:
184
+ # Check service metrics
185
+ success = self.env_get("service.success_count", 0)
186
+ failures = self.env_get("service.failure_count", 0)
187
+ total = success + failures
188
+ if total > 0:
189
+ failure_rate = failures / total
190
+ status = "UNHEALTHY" if failure_rate > 0.5 else "HEALTHY"
191
+ logger.info(
192
+ f"[{self.plugin_id}] Health check: {status} (success_rate={1-failure_rate:.1%})"
193
+ )
194
+
195
+ def on_stop(self) -> None:
196
+ logger.info(f"[{self.plugin_id}] HealthMonitor stopped after {self.checks} checks")
197
+
198
+
199
+ def main():
200
+ """Run the error handling example."""
201
+ manager = PluginManager()
202
+
203
+ # Create plugins
204
+ service_desc = UnreliableService.Descriptor()
205
+ service_desc.name = "unreliable-service"
206
+
207
+ client_desc = ResilientClient.Descriptor()
208
+ monitor_desc = HealthMonitor.Descriptor()
209
+
210
+ manager.load_plugin(service_desc)
211
+ manager.load_plugin(client_desc)
212
+ manager.load_plugin(monitor_desc)
213
+
214
+ manager.start_plugin(service_desc.name)
215
+ manager.start_plugin(client_desc.name)
216
+ manager.start_plugin(monitor_desc.name)
217
+
218
+ print("Error handling example running... Press Ctrl+C to stop.")
219
+ time.sleep(6)
220
+ print("Stopping plugins...")
221
+
222
+ manager.stop_plugin(monitor_desc.name)
223
+ manager.stop_plugin(client_desc.name)
224
+ manager.stop_plugin(service_desc.name)
225
+
226
+ time.sleep(1)
227
+
228
+ # Show final metrics
229
+ print("\nFinal metrics:")
230
+ all_vars = manager.env_get()
231
+ for name, value in sorted(all_vars.items()):
232
+ if "service" in name or "client" in name:
233
+ print(f" {name}: {value}")
234
+
235
+ # VERIFICATION
236
+ print("\n=== VERIFICATION ===")
237
+ print("PREDICTED BEHAVIOR:")
238
+ print("1. UnreliableService fails ~30% of the time")
239
+ print("2. ResilientClient retries up to 3 times (exponential backoff)")
240
+ print("3. Client succeeds on some retries, fails on others")
241
+ print("4. Both success and failure metrics tracked\n")
242
+
243
+ service_success = all_vars.get("service.success_count", 0)
244
+ service_failures = all_vars.get("service.failure_count", 0)
245
+ client_success = all_vars.get("client.requests_succeeded", 0)
246
+ client_failures = all_vars.get("client.requests_failed", 0)
247
+
248
+ assert service_success > 0, "Service should have successes"
249
+ assert service_failures > 0, "Service should have failures (30% rate)"
250
+ assert client_success > 0, "Client should retry and succeed sometimes"
251
+
252
+ service_total = service_success + service_failures
253
+ failure_rate = service_failures / service_total if service_total > 0 else 0
254
+
255
+ print(f"✓ Service stats: success={service_success}, failures={service_failures}, failure_rate={failure_rate:.1%}")
256
+ print(f"✓ Client resilience: succeeded={client_success}, failed={client_failures}")
257
+ print(f"✓ Retry strategy working (some requests succeeded despite service failures)")
258
+ print("✓ All error handling assertions passed!")
259
+
260
+ manager.unload_plugin(service_desc.name)
261
+ manager.unload_plugin(client_desc.name)
262
+ manager.unload_plugin(monitor_desc.name)
263
+
264
+
265
+ if __name__ == "__main__":
266
+ main()
@@ -0,0 +1,139 @@
1
+ """
2
+ Graceful Shutdown Example: Verify clean plugin termination.
3
+
4
+ This minimal example demonstrates:
5
+ 1. Plugin lifecycle (load, start, stop, unload)
6
+ 2. Graceful shutdown with on_stop() callback
7
+ 3. Process cleanup and resource release
8
+ """
9
+
10
+ import logging
11
+ import sys
12
+ import time
13
+ from typing import Any, Dict
14
+
15
+ from parallelazy.plugin import Plugin, PluginDescriptor
16
+ from parallelazy.plugin_worker import PluginWorker
17
+ from parallelazy.plugin_manager import PluginManager
18
+
19
+ # Configure logging for clear shutdown verification
20
+ logging.basicConfig(
21
+ level=logging.DEBUG,
22
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
23
+ handlers=[logging.StreamHandler(sys.stdout)]
24
+ )
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ class SimpleWorker(Plugin):
29
+ """
30
+ Minimal plugin that logs lifecycle events.
31
+ Perfect for verifying graceful shutdown behavior.
32
+ """
33
+
34
+ @classmethod
35
+ def Descriptor(cls, options: Dict[str, Any] = None) -> PluginDescriptor:
36
+ return super().Descriptor(
37
+ version="1.0.0",
38
+ author="ModularNet Team",
39
+ description="Simple worker for testing graceful shutdown",
40
+ options=options or {}
41
+ )
42
+
43
+ def __init__(self, worker: PluginWorker, plugin_id: str, options: Dict[str, Any]):
44
+ super().__init__(worker, plugin_id, options)
45
+ self.iteration = 0
46
+
47
+ def on_start(self) -> None:
48
+ logger.info(f"[{self.plugin_id}] ✓ on_start() called - plugin initialized")
49
+ self.iteration = 0
50
+
51
+ def main(self):
52
+ """Main loop - runs repeatedly until on_stop() is called."""
53
+ self.iteration += 1
54
+ logger.debug(f"[{self.plugin_id}] Iteration {self.iteration}")
55
+ time.sleep(0.1) # Brief sleep to simulate work
56
+
57
+ def on_stop(self) -> None:
58
+ """Graceful shutdown callback - called before plugin terminates."""
59
+ logger.info(f"[{self.plugin_id}] ✓ on_stop() called - cleaning up after {self.iteration} iterations")
60
+ logger.info(f"[{self.plugin_id}] ✓ Graceful shutdown complete")
61
+
62
+
63
+ def main():
64
+ """Run the graceful shutdown verification."""
65
+ print("\n" + "=" * 70)
66
+ print("GRACEFUL SHUTDOWN VERIFICATION")
67
+ print("=" * 70 + "\n")
68
+
69
+ # Step 1: Create manager
70
+ print("[STEP 1] Creating PluginManager...")
71
+ manager = PluginManager(timeout_start=5.0)
72
+ print("✓ Manager created\n")
73
+
74
+ # Step 2: Load plugin
75
+ print("[STEP 2] Loading plugin...")
76
+ descriptor = SimpleWorker.Descriptor()
77
+ if not manager.load_plugin(descriptor):
78
+ logger.error("Failed to load plugin")
79
+ return False
80
+ print("✓ Plugin loaded\n")
81
+
82
+ # Step 3: Start plugin
83
+ print("[STEP 3] Starting plugin...")
84
+ if not manager.start_plugin(descriptor.name):
85
+ logger.error("Failed to start plugin")
86
+ return False
87
+ print("✓ Plugin started\n")
88
+
89
+ # Step 4: Let it run briefly
90
+ print("[STEP 4] Running for 2 seconds...")
91
+ time.sleep(2)
92
+ print("✓ Plugin has been running\n")
93
+
94
+ # Step 5: Stop plugin (graceful shutdown)
95
+ print("[STEP 5] Stopping plugin (graceful shutdown)...")
96
+ if not manager.stop_plugin(descriptor.name):
97
+ logger.error("Failed to stop plugin")
98
+ return False
99
+ print("✓ Plugin stopped gracefully\n")
100
+
101
+ # Step 6: Unload plugin
102
+ print("[STEP 6] Unloading plugin...")
103
+ if not manager.unload_plugin(descriptor.name):
104
+ logger.error("Failed to unload plugin")
105
+ return False
106
+ print("✓ Plugin unloaded\n")
107
+
108
+ # Step 7: Verify shutdown
109
+ print("[STEP 7] Verification...")
110
+ if descriptor.name in manager._plugins:
111
+ logger.error("Plugin still in manager after unload!")
112
+ return False
113
+ print("✓ Plugin successfully cleaned up from manager\n")
114
+
115
+ # Summary
116
+ print("=" * 70)
117
+ print("GRACEFUL SHUTDOWN VERIFICATION PASSED ✓")
118
+ print("=" * 70)
119
+ print("\nExpected behavior verified:")
120
+ print(" 1. on_start() called when plugin started")
121
+ print(" 2. Plugin ran for ~2 seconds (main loop)")
122
+ print(" 3. on_stop() called when stop_plugin() invoked")
123
+ print(" 4. Process terminated cleanly")
124
+ print(" 5. Resources cleaned up after unload_plugin()")
125
+ print("\n")
126
+
127
+ return True
128
+
129
+
130
+ if __name__ == "__main__":
131
+ try:
132
+ success = main()
133
+ sys.exit(0 if success else 1)
134
+ except KeyboardInterrupt:
135
+ print("\nInterrupted by user")
136
+ sys.exit(1)
137
+ except Exception as e:
138
+ logger.error(f"Unexpected error: {e}", exc_info=True)
139
+ sys.exit(1)