fred-oss 0.5.0__tar.gz → 0.7.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.5.0/src/main/fred_oss.egg-info → fred_oss-0.7.0}/PKG-INFO +2 -1
  2. {fred_oss-0.5.0 → fred_oss-0.7.0}/requirements.txt +2 -0
  3. fred_oss-0.7.0/src/main/fred/version +1 -0
  4. fred_oss-0.7.0/src/main/fred/worker/interface.py +230 -0
  5. {fred_oss-0.5.0 → fred_oss-0.7.0/src/main/fred_oss.egg-info}/PKG-INFO +2 -1
  6. fred_oss-0.7.0/src/main/fred_oss.egg-info/requires.txt +2 -0
  7. fred_oss-0.5.0/src/main/fred/version +0 -1
  8. fred_oss-0.5.0/src/main/fred/worker/interface.py +0 -93
  9. fred_oss-0.5.0/src/main/fred_oss.egg-info/requires.txt +0 -1
  10. {fred_oss-0.5.0 → fred_oss-0.7.0}/MANIFEST.in +0 -0
  11. {fred_oss-0.5.0 → fred_oss-0.7.0}/NOTICE.txt +0 -0
  12. {fred_oss-0.5.0 → fred_oss-0.7.0}/README.md +0 -0
  13. {fred_oss-0.5.0 → fred_oss-0.7.0}/setup.cfg +0 -0
  14. {fred_oss-0.5.0 → fred_oss-0.7.0}/setup.py +0 -0
  15. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/cli/__init__.py +0 -0
  16. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/cli/__main__.py +0 -0
  17. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/cli/interface.py +0 -0
  18. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/cli/main.py +0 -0
  19. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/integrations/databricks/__init__.py +0 -0
  20. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/integrations/databricks/cli_ext.py +0 -0
  21. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/integrations/databricks/runtime.py +0 -0
  22. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/integrations/databricks/runtimes/__init__.py +0 -0
  23. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/integrations/databricks/runtimes/scanner.py +0 -0
  24. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/integrations/databricks/runtimes/sync.py +0 -0
  25. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/integrations/databricks/wrappers/__init__.py +0 -0
  26. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/integrations/databricks/wrappers/dbutils.py +0 -0
  27. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/integrations/runpod/__init__.py +0 -0
  28. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/integrations/runpod/cli_ext.py +0 -0
  29. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/integrations/runpod/helper.py +0 -0
  30. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/maturity.py +0 -0
  31. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/settings.py +0 -0
  32. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/utils/__init__.py +0 -0
  33. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/utils/dateops.py +0 -0
  34. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/utils/runtime.py +0 -0
  35. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/version.py +0 -0
  36. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred/worker/__init__.py +0 -0
  37. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred_oss.egg-info/SOURCES.txt +0 -0
  38. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred_oss.egg-info/dependency_links.txt +0 -0
  39. {fred_oss-0.5.0 → fred_oss-0.7.0}/src/main/fred_oss.egg-info/entry_points.txt +0 -0
  40. {fred_oss-0.5.0 → fred_oss-0.7.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.5.0
3
+ Version: 0.7.0
4
4
  Summary: FREDOSS
5
5
  Home-page: https://fred.fahera.mx
6
6
  Author: Fahera Research, Education, and Development
@@ -9,6 +9,7 @@ Requires-Python: >=3.12
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: NOTICE.txt
11
11
  Requires-Dist: fire==0.7.1
12
+ Requires-Dist: psutil==7.0.0
12
13
  Dynamic: author
13
14
  Dynamic: author-email
14
15
  Dynamic: description
@@ -1,3 +1,5 @@
1
1
  # CLI Tools
2
2
  fire==0.7.1
3
+ # Minimal requirements
4
+ psutil==7.0.0
3
5
  # Databricks Runtime: 16.4 LTS (Python 3.12.3)
@@ -0,0 +1 @@
1
+ 0.7.0
@@ -0,0 +1,230 @@
1
+ import time
2
+ from typing import Callable, Optional
3
+ from dataclasses import dataclass, field
4
+
5
+ from fred.utils.dateops import datetime_utcnow
6
+ from fred.settings import (
7
+ get_environ_variable,
8
+ logger_manager,
9
+ )
10
+
11
+ logger = logger_manager.get_logger(name=__name__)
12
+
13
+
14
+ @dataclass(frozen=True, slots=False)
15
+ class HandlerInterface:
16
+ """Base interface for handling events in a worker environment.
17
+
18
+ This class provides a structure for processing events with metadata tracking.
19
+ Subclasses should implement the `handler` method to define specific event processing logic.
20
+
21
+ Considerations: This interface is designed to be extended for various worker implementations, starting with Runpod.
22
+
23
+ Attributes:
24
+ context (dict): A dictionary to hold contextual information for the handler; this can be modified as needed.
25
+ metadata (dict): A dictionary to track metadata about the handler's operations.
26
+ """
27
+ context: dict = field(default_factory=dict)
28
+ metadata: dict = field(default_factory=dict)
29
+ custom_actions: dict = field(default_factory=dict)
30
+
31
+ def __post_init__(self):
32
+ self.metadata["handler_created_at"] = datetime_utcnow().isoformat()
33
+
34
+ @classmethod
35
+ def find_handler(
36
+ cls,
37
+ import_pattern: str,
38
+ handler_classname: str,
39
+ **init_kwargs,
40
+ ) -> 'HandlerInterface':
41
+ import importlib
42
+
43
+ # Dynamically import the handler class
44
+ handler_module = importlib.import_module(import_pattern)
45
+ handler_cls = getattr(handler_module, handler_classname)
46
+ # Ensure the handler class exists and is a subclass of HandlerInterface
47
+ if not handler_cls or not issubclass(handler_cls, cls):
48
+ logger.error(f"Handler class '{handler_classname}' not found or is not a subclass of HandlerInterface: {handler_cls}")
49
+ raise ValueError(f"Handler '{handler_classname}' not found in module '{import_pattern}' or is not a subclass of HandlerInterface.")
50
+ kwargs = {
51
+ "metadata": {
52
+ "handler_found_at": datetime_utcnow().isoformat()
53
+ },
54
+ **init_kwargs,
55
+ }
56
+ return handler_cls.with_custom_actions(**kwargs)
57
+
58
+ @classmethod
59
+ def with_custom_actions(cls, actions: Optional[dict] = None, **init_kwargs) -> 'HandlerInterface':
60
+ return cls(**init_kwargs).register_actions(actions=actions)
61
+
62
+ def register_actions(self, actions: Optional[dict] = None) -> 'HandlerInterface':
63
+ """Register multiple custom actions from a dictionary. You can chain this method after instantiation.
64
+ Consider extending this method on child classes to register additinal custom actions:
65
+
66
+ class MyHandler(HandlerInterface):
67
+
68
+ def my_custom_action_method(self, **kwargs) -> dict:
69
+ return {"status": "custom action executed", **kwargs}
70
+
71
+ def register_actions(self, actions: Optional[dict] = None) -> 'HandlerInterface':
72
+ # Call the parent method to register base actions
73
+ super().register_actions(actions=actions)
74
+ # Register additional custom actions specific to MyHandler
75
+ self.register_custom_action(
76
+ action_name="my_custom_action",
77
+ action_callable=self.my_custom_action_method
78
+ )
79
+ return self
80
+
81
+ Args:
82
+ actions (dict): A dictionary where keys are action names and values are callables.
83
+ Returns:
84
+ HandlerInterface: The instance itself to allow method chaining.
85
+ """
86
+ # Register provided custom actions as arguments
87
+ for action_name, action_callable in (actions or {}).items():
88
+ self.register_custom_action(action_name=action_name, action_callable=action_callable)
89
+ # Example custom action, just for demonstration purposes (e.g., a ping action)
90
+ self.register_custom_action(
91
+ action_name="ping",
92
+ action_callable=lambda **kwargs: {
93
+ "reply": "pong",
94
+ "reply_at": datetime_utcnow().isoformat(),
95
+ **kwargs,
96
+ }
97
+ )
98
+ return self
99
+
100
+ def register_custom_action(self, action_name: str, action_callable: Callable, ignore_if_exists: bool = False):
101
+ """Register a custom action that can be invoked via the `fred_worker_action` key in the event payload.
102
+ Args:
103
+ action_name (str): The name of the custom action to register.
104
+ action_callable (Callable): A callable (function or method) that implements the action.
105
+ ignore_if_exists (bool): If True, will not overwrite an existing action with the same name.
106
+ Raises:
107
+ ValueError: If the action_callable is not callable.
108
+ """
109
+ if not callable(action_callable):
110
+ raise ValueError(f"The action_callable must be a callable function or method: {type(action_callable)}")
111
+ if action_name in self.custom_actions:
112
+ if ignore_if_exists:
113
+ logger.info(f"Custom action '{action_name}' already exists; ignoring as per flag.")
114
+ return
115
+ logger.warning(f"Overwriting existing custom action: '{action_name}'")
116
+ self.custom_actions[action_name] = action_callable
117
+
118
+
119
+ def telemetry(self, include_modules: bool = False) -> dict:
120
+ from fred.utils.runtime import RuntimeInfo
121
+ runtime_info = RuntimeInfo.auto()
122
+ return runtime_info.to_dict(
123
+ exclude_modules=not include_modules
124
+ )
125
+
126
+ def handler(self, payload: dict) -> Optional[dict]:
127
+ logger.warning("Handler method not implemented.")
128
+ return payload
129
+
130
+ @property
131
+ def metadata_prepared(self) -> dict:
132
+ if not int(get_environ_variable("FRD_ENFORCE_METADATA_SERIALIZATION", default="0")):
133
+ return self.metadata
134
+ import json
135
+ # Ensure serializability
136
+ # TODO: Allow custom serialization methods
137
+ metadata_serialized = json.dumps(self.metadata, default=str)
138
+ return json.loads(metadata_serialized)
139
+
140
+ def run(self, event: dict) -> dict:
141
+ """Process an incoming event and return a structured response.
142
+ The event is expected to be a dictionary with at least an 'id' and 'input' keys.
143
+ The 'input' key should contain the payload to be processed.
144
+ Args:
145
+ event (dict): The incoming event containing 'id' and 'input'.
146
+ Returns:
147
+ dict: A structured response containing the result of processing the event.
148
+ """
149
+ # Extract payload and event ID
150
+ payload = event.get("input", {})
151
+ job_event_identifier = event.get("id")
152
+ # Update metadata for this run instance and timing information
153
+ self.metadata["run_seq"] = self.metadata.get("run_seq", 0) + 1
154
+ started_at = datetime_utcnow().isoformat()
155
+ start_time = time.perf_counter()
156
+ # Default response values
157
+ ok = True
158
+ response = None
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
+ ))
167
+ match (worker_action := payload.pop("fred_worker_action", "handler")):
168
+ case "telemetry":
169
+ # Collect and return telemetry data
170
+ logger.debug("Standard telemetry action requested.")
171
+ response = self.telemetry()
172
+ case "handler":
173
+ # Process the payload using the handler method
174
+ logger.debug("Standard handler action requested.")
175
+ try:
176
+ response = self.handler(payload=payload)
177
+ except Exception as e:
178
+ ok = False
179
+ logger.error(f"Error processing handler for event {job_event_identifier}: {e}")
180
+ if propagate_worker_error:
181
+ raise
182
+ response = {
183
+ "error": str(e)
184
+ }
185
+ case action if isinstance(action, str):
186
+ # Handle custom actions defined in the custom_actions dictionary
187
+ logger.info(f"Custom fred_worker_action '{action}' received.")
188
+ match self.custom_actions.get(action):
189
+ case None:
190
+ ok = False
191
+ response = {
192
+ "error": f"Custom action '{action}' not found."
193
+ }
194
+ case custom_action_function if callable(custom_action_function):
195
+ try:
196
+ response = custom_action_function(**payload)
197
+ except Exception as e:
198
+ ok = False
199
+ logger.error(f"Error processing custom action '{action}' for event {job_event_identifier}: {e}")
200
+ if propagate_worker_error:
201
+ raise
202
+ response = {
203
+ "error": str(e)
204
+ }
205
+ case _:
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.")
210
+ response = {
211
+ "error": f"Custom action '{action}' is not callable."
212
+ }
213
+ case _:
214
+ # Handle invalid action types
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.")
218
+ ok = False
219
+ response = {
220
+ "error": "Invalid fred_worker_action type."
221
+ }
222
+ return {
223
+ "ok": ok,
224
+ "id": job_event_identifier,
225
+ "started_at": started_at,
226
+ "duration": time.perf_counter() - start_time,
227
+ "worker_action": worker_action,
228
+ "response": response,
229
+ "metadata": self.metadata_prepared,
230
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fred-oss
3
- Version: 0.5.0
3
+ Version: 0.7.0
4
4
  Summary: FREDOSS
5
5
  Home-page: https://fred.fahera.mx
6
6
  Author: Fahera Research, Education, and Development
@@ -9,6 +9,7 @@ Requires-Python: >=3.12
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: NOTICE.txt
11
11
  Requires-Dist: fire==0.7.1
12
+ Requires-Dist: psutil==7.0.0
12
13
  Dynamic: author
13
14
  Dynamic: author-email
14
15
  Dynamic: description
@@ -0,0 +1,2 @@
1
+ fire==0.7.1
2
+ psutil==7.0.0
@@ -1 +0,0 @@
1
- 0.5.0
@@ -1,93 +0,0 @@
1
- import time
2
- from typing import Optional
3
- from dataclasses import dataclass, field
4
-
5
- from fred.utils.dateops import datetime_utcnow
6
- from fred.settings import (
7
- get_environ_variable,
8
- logger_manager,
9
- )
10
-
11
- logger = logger_manager.get_logger(name=__name__)
12
-
13
-
14
- @dataclass(frozen=True, slots=False)
15
- class HandlerInterface:
16
- """Base interface for handling events in a worker environment.
17
-
18
- This class provides a structure for processing events with metadata tracking.
19
- Subclasses should implement the `handler` method to define specific event processing logic.
20
-
21
- Considerations: This interface is designed to be extended for various worker implementations, starting with Runpod.
22
-
23
- Attributes:
24
- context (dict): A dictionary to hold contextual information for the handler; this can be modified as needed.
25
- metadata (dict): A dictionary to track metadata about the handler's operations.
26
- """
27
- context: dict = field(default_factory=dict)
28
- metadata: dict = field(default_factory=dict)
29
-
30
- def __post_init__(self):
31
- self.metadata["handler_created_at"] = datetime_utcnow().isoformat()
32
-
33
- @classmethod
34
- def find_handler(
35
- cls,
36
- import_pattern: str,
37
- handler_classname: str,
38
- **init_kwargs,
39
- ) -> 'HandlerInterface':
40
- import importlib
41
-
42
- # Dynamically import the handler class
43
- handler_module = importlib.import_module(import_pattern)
44
- handler_cls = getattr(handler_module, handler_classname)
45
- # Ensure the handler class exists and is a subclass of HandlerInterface
46
- if not handler_cls or not issubclass(handler_cls, cls):
47
- logger.error(f"Handler class '{handler_classname}' not found or is not a subclass of HandlerInterface: {handler_cls}")
48
- raise ValueError(f"Handler '{handler_classname}' not found in module '{import_pattern}' or is not a subclass of HandlerInterface.")
49
- kwargs = {
50
- "metadata": {
51
- "handler_found_at": datetime_utcnow().isoformat()
52
- },
53
- **init_kwargs,
54
- }
55
- return handler_cls(**kwargs)
56
-
57
- def handler(self, payload: dict) -> Optional[dict]:
58
- logger.warning("Handler method not implemented.")
59
- return payload
60
-
61
- @property
62
- def metadata_prepared(self) -> dict:
63
- if not int(get_environ_variable("FRD_ENFORCE_METADATA_SERIALIZATION", default="0")):
64
- return self.metadata
65
- import json
66
- # Ensure serializability
67
- # TODO: Allow custom serialization methods
68
- metadata_serialized = json.dumps(self.metadata, default=str)
69
- return json.loads(metadata_serialized)
70
-
71
- def run(self, event: dict) -> dict:
72
- job_event_identifier = event.get("id")
73
- self.metadata["run_seq"] = self.metadata.get("run_seq", 0) + 1
74
- payload = event.get("input", {})
75
- started_at = datetime_utcnow().isoformat()
76
- start_time = time.perf_counter()
77
- ok = True
78
- try:
79
- response = self.handler(payload=payload)
80
- except Exception as e:
81
- ok = False
82
- logger.error(f"Error processing event {job_event_identifier}: {e}")
83
- response = {
84
- "error": str(e)
85
- }
86
- return {
87
- "ok": ok,
88
- "id": job_event_identifier,
89
- "duration": time.perf_counter() - start_time,
90
- "started_at": started_at,
91
- "response": response,
92
- "metadata": self.metadata_prepared,
93
- }
@@ -1 +0,0 @@
1
- fire==0.7.1
File without changes
File without changes
File without changes
File without changes
File without changes