fred-oss 0.6.0__tar.gz → 0.8.0__tar.gz

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 (40) hide show
  1. {fred_oss-0.6.0/src/main/fred_oss.egg-info → fred_oss-0.8.0}/PKG-INFO +2 -1
  2. {fred_oss-0.6.0 → fred_oss-0.8.0}/requirements.txt +2 -0
  3. fred_oss-0.8.0/src/main/fred/version +1 -0
  4. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/worker/interface.py +16 -0
  5. fred_oss-0.8.0/src/main/fred/worker/runner/__init__.py +11 -0
  6. fred_oss-0.8.0/src/main/fred/worker/runner/handler.py +136 -0
  7. {fred_oss-0.6.0 → fred_oss-0.8.0/src/main/fred_oss.egg-info}/PKG-INFO +2 -1
  8. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred_oss.egg-info/SOURCES.txt +2 -0
  9. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred_oss.egg-info/requires.txt +1 -0
  10. fred_oss-0.6.0/src/main/fred/version +0 -1
  11. {fred_oss-0.6.0 → fred_oss-0.8.0}/MANIFEST.in +0 -0
  12. {fred_oss-0.6.0 → fred_oss-0.8.0}/NOTICE.txt +0 -0
  13. {fred_oss-0.6.0 → fred_oss-0.8.0}/README.md +0 -0
  14. {fred_oss-0.6.0 → fred_oss-0.8.0}/setup.cfg +0 -0
  15. {fred_oss-0.6.0 → fred_oss-0.8.0}/setup.py +0 -0
  16. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/cli/__init__.py +0 -0
  17. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/cli/__main__.py +0 -0
  18. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/cli/interface.py +0 -0
  19. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/cli/main.py +0 -0
  20. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/databricks/__init__.py +0 -0
  21. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/databricks/cli_ext.py +0 -0
  22. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/databricks/runtime.py +0 -0
  23. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/databricks/runtimes/__init__.py +0 -0
  24. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/databricks/runtimes/scanner.py +0 -0
  25. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/databricks/runtimes/sync.py +0 -0
  26. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/databricks/wrappers/__init__.py +0 -0
  27. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/databricks/wrappers/dbutils.py +0 -0
  28. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/runpod/__init__.py +0 -0
  29. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/runpod/cli_ext.py +0 -0
  30. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/runpod/helper.py +0 -0
  31. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/maturity.py +0 -0
  32. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/settings.py +0 -0
  33. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/utils/__init__.py +0 -0
  34. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/utils/dateops.py +0 -0
  35. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/utils/runtime.py +0 -0
  36. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/version.py +0 -0
  37. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/worker/__init__.py +0 -0
  38. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred_oss.egg-info/dependency_links.txt +0 -0
  39. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred_oss.egg-info/entry_points.txt +0 -0
  40. {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred_oss.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fred-oss
3
- Version: 0.6.0
3
+ Version: 0.8.0
4
4
  Summary: FREDOSS
5
5
  Home-page: https://fred.fahera.mx
6
6
  Author: Fahera Research, Education, and Development
@@ -10,6 +10,7 @@ Description-Content-Type: text/markdown
10
10
  License-File: NOTICE.txt
11
11
  Requires-Dist: fire==0.7.1
12
12
  Requires-Dist: psutil==7.0.0
13
+ Requires-Dist: redis==6.4.0
13
14
  Dynamic: author
14
15
  Dynamic: author-email
15
16
  Dynamic: description
@@ -2,4 +2,6 @@
2
2
  fire==0.7.1
3
3
  # Minimal requirements
4
4
  psutil==7.0.0
5
+ # Fred Workers Infra
6
+ redis==6.4.0
5
7
  # Databricks Runtime: 16.4 LTS (Python 3.12.3)
@@ -0,0 +1 @@
1
+ 0.8.0
@@ -157,6 +157,13 @@ class HandlerInterface:
157
157
  ok = True
158
158
  response = None
159
159
  # Determine action based on 'fred_worker_action' in payload
160
+ propagate_worker_error = int(payload.pop(
161
+ "propagate_worker_error",
162
+ get_environ_variable(
163
+ "FRD_PROPAGATE_WORKER_ERROR",
164
+ default="0"
165
+ )
166
+ ))
160
167
  match (worker_action := payload.pop("fred_worker_action", "handler")):
161
168
  case "telemetry":
162
169
  # Collect and return telemetry data
@@ -170,6 +177,8 @@ class HandlerInterface:
170
177
  except Exception as e:
171
178
  ok = False
172
179
  logger.error(f"Error processing handler for event {job_event_identifier}: {e}")
180
+ if propagate_worker_error:
181
+ raise
173
182
  response = {
174
183
  "error": str(e)
175
184
  }
@@ -188,17 +197,24 @@ class HandlerInterface:
188
197
  except Exception as e:
189
198
  ok = False
190
199
  logger.error(f"Error processing custom action '{action}' for event {job_event_identifier}: {e}")
200
+ if propagate_worker_error:
201
+ raise
191
202
  response = {
192
203
  "error": str(e)
193
204
  }
194
205
  case _:
195
206
  ok = False
207
+ logger.error(f"Custom action '{action}' is not callable.")
208
+ if propagate_worker_error:
209
+ raise ValueError(f"Custom action '{action}' is not callable.")
196
210
  response = {
197
211
  "error": f"Custom action '{action}' is not callable."
198
212
  }
199
213
  case _:
200
214
  # Handle invalid action types
201
215
  logger.error(f"Invalid fred_worker_action type received: {type(worker_action)}")
216
+ if propagate_worker_error:
217
+ raise ValueError("Invalid fred_worker_action type.")
202
218
  ok = False
203
219
  response = {
204
220
  "error": "Invalid fred_worker_action type."
@@ -0,0 +1,11 @@
1
+ from fred.maturity import Maturity, MaturityLevel
2
+
3
+
4
+ module_maturity = Maturity(
5
+ level=MaturityLevel.ALPHA,
6
+ reference=__name__,
7
+ message=(
8
+ "Fred-Worker Redis Runners implementation is in early development "
9
+ "and therefore currently with incomplete and unstable features."
10
+ )
11
+ )
@@ -0,0 +1,136 @@
1
+ import uuid
2
+ import json
3
+ from dataclasses import dataclass
4
+
5
+ from fred.utils.dateops import datetime_utcnow
6
+ from fred.worker.interface import HandlerInterface
7
+ from fred.settings import (
8
+ get_environ_variable,
9
+ logger_manager,
10
+ )
11
+
12
+ from redis import Redis
13
+
14
+ logger = logger_manager.get_logger(name=__name__)
15
+
16
+
17
+ @dataclass(frozen=True, slots=False)
18
+ class RunnerHandler(HandlerInterface):
19
+
20
+ def __post_init__(self):
21
+ super().__post_init__()
22
+ logger.info("Runpod Handler initialized using Fred-Worker interface.")
23
+
24
+ def handler(self, payload: dict) -> dict:
25
+ # TODO: Breakdown the handler logic into smaller methods for better readability and testing
26
+ # E.g., loop method, process item method, signal handling method, etc.
27
+ lifespan = payload.get("lifetime", 3600) # Default to 1 hour if not specified
28
+ timeout = payload.get("timeout", 30) # Default to 30 seconds if not specified
29
+ # Get Redis connection details from payload or environment variables
30
+ redis_configs = payload.pop(
31
+ "redis_configs",
32
+ {
33
+ "host": get_environ_variable(name="REDIS_HOST", default="localhost"),
34
+ "port": int(get_environ_variable(name="REDIS_PORT", default=6379)),
35
+ "db": int(get_environ_variable(name="REDIS_DB", default=0)),
36
+ }
37
+ )
38
+ # Connect to Redis
39
+ redis = Redis(**redis_configs)
40
+ req_queue = payload.pop("redis_request_queue", None) or get_environ_variable(name="FRD_RUNNER_REQUEST_QUEUE", default=None) or (
41
+ logger.warning("Redis request queue not specified; defaulting to: 'req:demo'") or "req:demo"
42
+ )
43
+ res_queue = payload.pop("redis_response_queue", None) or get_environ_variable(name="FRD_RUNNER_RESPONSE_QUEUE", default=None) or (
44
+ logger.warning("Redis response queue not specified; defaulting to: None (not using response queue)") or None
45
+ )
46
+ # Handoff to target handler (i.e., runner)
47
+ runner_configs = payload.pop("runner_configs")
48
+ runner_id = runner_configs.pop("id", str(uuid.uuid4()))
49
+ runner = HandlerInterface.find_handler(**runner_configs)
50
+ logger.info(f"Runpod Redis Handler started with runner '{runner_id}' listening to Redis queue: '{req_queue}'")
51
+ redis.set(f"runner_status:{runner_id}", "RUNNING")
52
+ redis.set(f"runner_created_at:{runner_id}", datetime_utcnow().isoformat())
53
+ # Main runner loop to process items from Redis queue
54
+ # TODO: Can we make this main-loop concurrent with threads or async?
55
+ # TODO: Consider collecting metrics (e.g., processing time per item, total items processed, errors, etc.) and stats
56
+ start_time = datetime_utcnow()
57
+ last_processed_time = datetime_utcnow()
58
+ while (elapsed_seconds := (datetime_utcnow() - start_time).total_seconds()):
59
+ if elapsed_seconds > lifespan:
60
+ logger.info("Lifespan exceeded; exiting runner loop.")
61
+ break
62
+ if (idle_seconds := (datetime_utcnow() - last_processed_time).total_seconds()) > timeout:
63
+ logger.info(f"Idle time ({idle_seconds}) exceeded timeout ({timeout}); exiting runner loop.")
64
+ break
65
+ # Fetch item from Redis queue
66
+ try:
67
+ item_raw = redis.rpop(req_queue)
68
+ except Exception as e:
69
+ logger.error(f"Error fetching item from Redis queue '{req_queue}': {e}")
70
+ continue
71
+ # If no item, iterate again
72
+ if not item_raw:
73
+ continue
74
+ try:
75
+ # Handle special signals
76
+ match (item_str := item_raw.decode("utf-8")):
77
+ case "STOP" | "SHUTDOWN" | "TERMINATE":
78
+ logger.info("Received STOP signal; exiting runner loop.")
79
+ break
80
+ case "PING":
81
+ logger.info("Received PING signal; continuing.")
82
+ last_processed_time = datetime_utcnow()
83
+ continue
84
+ case _:
85
+ pass
86
+ # Parse item payload and extract item_id
87
+ item_payload = json.loads(item_str)
88
+ item_id = item_payload.pop("item_id") or (
89
+ logger.warning("No item_id provided in payload; generating a new one using UUID5.")
90
+ or str(uuid.uuid5(uuid.NAMESPACE_OID, item_str))
91
+ )
92
+ except Exception as e:
93
+ logger.error(f"Error decoding or parsing item from Redis: {e}")
94
+ continue
95
+ logger.info(f"Processing item with ID: {item_id}")
96
+ redis.set(f"item_status:{item_id}", "IN_PROGRESS")
97
+ try:
98
+ out_payload = runner.run(
99
+ event={
100
+ "id": item_id,
101
+ "input": item_payload
102
+ }
103
+ )
104
+ out_str = json.dumps(out_payload) if isinstance(out_payload, dict) else str(out_payload)
105
+ redis.set(f"item_status:{item_id}", "COMPLETED")
106
+ # TODO: Consider adding a TTL to the result keys (e.g., 24 hours)
107
+ redis.set(f"item_output:{item_id}", out_str)
108
+ if res_queue:
109
+ redis.lpush(res_queue, out_str)
110
+ except Exception as e:
111
+ logger.error(f"Error processing item with ID {item_id}: {e}")
112
+ redis.set(f"item_status:{item_id}", "FAILED")
113
+ redis.set(f"item_output:{item_id}", str(e))
114
+ continue
115
+ logger.info(f"Processed item with ID: {item_id}")
116
+ last_processed_time = datetime_utcnow()
117
+ redis.set(f"runner_status:{runner_id}", "STOPPED")
118
+ pending_requests = redis.llen(req_queue)
119
+ if pending_requests:
120
+ logger.warning(f"Runner '{runner_id}' stopped with {pending_requests} pending items still in the queue '{req_queue}'.")
121
+ # TODO: Consider adding logic (optional/configurable) to spin up a new runner to handle pending items or notify runner-manager
122
+ else:
123
+ logger.info("Runner stopped with no pending items in the queue.")
124
+ return {
125
+ "status": "completed",
126
+ "runner_id": runner_id,
127
+ "processed_at": datetime_utcnow().isoformat(),
128
+ "total_elapsed_seconds": (datetime_utcnow() - start_time).total_seconds(),
129
+ "last_processed_at": last_processed_time.isoformat(),
130
+ "idle_seconds": (datetime_utcnow() - last_processed_time).total_seconds(),
131
+ "pending_requests": pending_requests,
132
+ "request_queue": req_queue,
133
+ "response_queue": res_queue,
134
+ "lifespan_seconds": lifespan,
135
+ "timeout_seconds": timeout,
136
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fred-oss
3
- Version: 0.6.0
3
+ Version: 0.8.0
4
4
  Summary: FREDOSS
5
5
  Home-page: https://fred.fahera.mx
6
6
  Author: Fahera Research, Education, and Development
@@ -10,6 +10,7 @@ Description-Content-Type: text/markdown
10
10
  License-File: NOTICE.txt
11
11
  Requires-Dist: fire==0.7.1
12
12
  Requires-Dist: psutil==7.0.0
13
+ Requires-Dist: redis==6.4.0
13
14
  Dynamic: author
14
15
  Dynamic: author-email
15
16
  Dynamic: description
@@ -27,6 +27,8 @@ src/main/fred/utils/dateops.py
27
27
  src/main/fred/utils/runtime.py
28
28
  src/main/fred/worker/__init__.py
29
29
  src/main/fred/worker/interface.py
30
+ src/main/fred/worker/runner/__init__.py
31
+ src/main/fred/worker/runner/handler.py
30
32
  src/main/fred_oss.egg-info/PKG-INFO
31
33
  src/main/fred_oss.egg-info/SOURCES.txt
32
34
  src/main/fred_oss.egg-info/dependency_links.txt
@@ -1,2 +1,3 @@
1
1
  fire==0.7.1
2
2
  psutil==7.0.0
3
+ redis==6.4.0
@@ -1 +0,0 @@
1
- 0.6.0
File without changes
File without changes
File without changes
File without changes
File without changes