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.
- {fred_oss-0.6.0/src/main/fred_oss.egg-info → fred_oss-0.8.0}/PKG-INFO +2 -1
- {fred_oss-0.6.0 → fred_oss-0.8.0}/requirements.txt +2 -0
- fred_oss-0.8.0/src/main/fred/version +1 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/worker/interface.py +16 -0
- fred_oss-0.8.0/src/main/fred/worker/runner/__init__.py +11 -0
- fred_oss-0.8.0/src/main/fred/worker/runner/handler.py +136 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0/src/main/fred_oss.egg-info}/PKG-INFO +2 -1
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred_oss.egg-info/SOURCES.txt +2 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred_oss.egg-info/requires.txt +1 -0
- fred_oss-0.6.0/src/main/fred/version +0 -1
- {fred_oss-0.6.0 → fred_oss-0.8.0}/MANIFEST.in +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/NOTICE.txt +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/README.md +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/setup.cfg +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/setup.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/cli/__init__.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/cli/__main__.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/cli/interface.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/cli/main.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/databricks/__init__.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/databricks/cli_ext.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/databricks/runtime.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/databricks/runtimes/__init__.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/databricks/runtimes/scanner.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/databricks/runtimes/sync.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/databricks/wrappers/__init__.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/databricks/wrappers/dbutils.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/runpod/__init__.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/runpod/cli_ext.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/runpod/helper.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/maturity.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/settings.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/utils/__init__.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/utils/dateops.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/utils/runtime.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/version.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/worker/__init__.py +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred_oss.egg-info/dependency_links.txt +0 -0
- {fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred_oss.egg-info/entry_points.txt +0 -0
- {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.
|
|
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
|
|
@@ -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.
|
|
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 +0,0 @@
|
|
|
1
|
-
0.6.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/databricks/runtimes/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fred_oss-0.6.0 → fred_oss-0.8.0}/src/main/fred/integrations/databricks/wrappers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|