fred-oss 0.5.0__tar.gz → 0.6.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.5.0/src/main/fred_oss.egg-info → fred_oss-0.6.0}/PKG-INFO +2 -1
- {fred_oss-0.5.0 → fred_oss-0.6.0}/requirements.txt +2 -0
- fred_oss-0.6.0/src/main/fred/version +1 -0
- fred_oss-0.6.0/src/main/fred/worker/interface.py +214 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0/src/main/fred_oss.egg-info}/PKG-INFO +2 -1
- fred_oss-0.6.0/src/main/fred_oss.egg-info/requires.txt +2 -0
- fred_oss-0.5.0/src/main/fred/version +0 -1
- fred_oss-0.5.0/src/main/fred/worker/interface.py +0 -93
- fred_oss-0.5.0/src/main/fred_oss.egg-info/requires.txt +0 -1
- {fred_oss-0.5.0 → fred_oss-0.6.0}/MANIFEST.in +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/NOTICE.txt +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/README.md +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/setup.cfg +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/setup.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/cli/__init__.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/cli/__main__.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/cli/interface.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/cli/main.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/integrations/databricks/__init__.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/integrations/databricks/cli_ext.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/integrations/databricks/runtime.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/integrations/databricks/runtimes/__init__.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/integrations/databricks/runtimes/scanner.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/integrations/databricks/runtimes/sync.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/integrations/databricks/wrappers/__init__.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/integrations/databricks/wrappers/dbutils.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/integrations/runpod/__init__.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/integrations/runpod/cli_ext.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/integrations/runpod/helper.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/maturity.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/settings.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/utils/__init__.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/utils/dateops.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/utils/runtime.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/version.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/worker/__init__.py +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred_oss.egg-info/SOURCES.txt +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred_oss.egg-info/dependency_links.txt +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred_oss.egg-info/entry_points.txt +0 -0
- {fred_oss-0.5.0 → fred_oss-0.6.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.6.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 @@
|
|
|
1
|
+
0.6.0
|
|
@@ -0,0 +1,214 @@
|
|
|
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
|
+
match (worker_action := payload.pop("fred_worker_action", "handler")):
|
|
161
|
+
case "telemetry":
|
|
162
|
+
# Collect and return telemetry data
|
|
163
|
+
logger.debug("Standard telemetry action requested.")
|
|
164
|
+
response = self.telemetry()
|
|
165
|
+
case "handler":
|
|
166
|
+
# Process the payload using the handler method
|
|
167
|
+
logger.debug("Standard handler action requested.")
|
|
168
|
+
try:
|
|
169
|
+
response = self.handler(payload=payload)
|
|
170
|
+
except Exception as e:
|
|
171
|
+
ok = False
|
|
172
|
+
logger.error(f"Error processing handler for event {job_event_identifier}: {e}")
|
|
173
|
+
response = {
|
|
174
|
+
"error": str(e)
|
|
175
|
+
}
|
|
176
|
+
case action if isinstance(action, str):
|
|
177
|
+
# Handle custom actions defined in the custom_actions dictionary
|
|
178
|
+
logger.info(f"Custom fred_worker_action '{action}' received.")
|
|
179
|
+
match self.custom_actions.get(action):
|
|
180
|
+
case None:
|
|
181
|
+
ok = False
|
|
182
|
+
response = {
|
|
183
|
+
"error": f"Custom action '{action}' not found."
|
|
184
|
+
}
|
|
185
|
+
case custom_action_function if callable(custom_action_function):
|
|
186
|
+
try:
|
|
187
|
+
response = custom_action_function(**payload)
|
|
188
|
+
except Exception as e:
|
|
189
|
+
ok = False
|
|
190
|
+
logger.error(f"Error processing custom action '{action}' for event {job_event_identifier}: {e}")
|
|
191
|
+
response = {
|
|
192
|
+
"error": str(e)
|
|
193
|
+
}
|
|
194
|
+
case _:
|
|
195
|
+
ok = False
|
|
196
|
+
response = {
|
|
197
|
+
"error": f"Custom action '{action}' is not callable."
|
|
198
|
+
}
|
|
199
|
+
case _:
|
|
200
|
+
# Handle invalid action types
|
|
201
|
+
logger.error(f"Invalid fred_worker_action type received: {type(worker_action)}")
|
|
202
|
+
ok = False
|
|
203
|
+
response = {
|
|
204
|
+
"error": "Invalid fred_worker_action type."
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
"ok": ok,
|
|
208
|
+
"id": job_event_identifier,
|
|
209
|
+
"started_at": started_at,
|
|
210
|
+
"duration": time.perf_counter() - start_time,
|
|
211
|
+
"worker_action": worker_action,
|
|
212
|
+
"response": response,
|
|
213
|
+
"metadata": self.metadata_prepared,
|
|
214
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fred-oss
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.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 +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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fred_oss-0.5.0 → fred_oss-0.6.0}/src/main/fred/integrations/databricks/runtimes/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fred_oss-0.5.0 → fred_oss-0.6.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
|
|
File without changes
|