flowyml 1.1.0__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.
Files changed (159) hide show
  1. flowyml/__init__.py +207 -0
  2. flowyml/assets/__init__.py +22 -0
  3. flowyml/assets/artifact.py +40 -0
  4. flowyml/assets/base.py +209 -0
  5. flowyml/assets/dataset.py +100 -0
  6. flowyml/assets/featureset.py +301 -0
  7. flowyml/assets/metrics.py +104 -0
  8. flowyml/assets/model.py +82 -0
  9. flowyml/assets/registry.py +157 -0
  10. flowyml/assets/report.py +315 -0
  11. flowyml/cli/__init__.py +5 -0
  12. flowyml/cli/experiment.py +232 -0
  13. flowyml/cli/init.py +256 -0
  14. flowyml/cli/main.py +327 -0
  15. flowyml/cli/run.py +75 -0
  16. flowyml/cli/stack_cli.py +532 -0
  17. flowyml/cli/ui.py +33 -0
  18. flowyml/core/__init__.py +68 -0
  19. flowyml/core/advanced_cache.py +274 -0
  20. flowyml/core/approval.py +64 -0
  21. flowyml/core/cache.py +203 -0
  22. flowyml/core/checkpoint.py +148 -0
  23. flowyml/core/conditional.py +373 -0
  24. flowyml/core/context.py +155 -0
  25. flowyml/core/error_handling.py +419 -0
  26. flowyml/core/executor.py +354 -0
  27. flowyml/core/graph.py +185 -0
  28. flowyml/core/parallel.py +452 -0
  29. flowyml/core/pipeline.py +764 -0
  30. flowyml/core/project.py +253 -0
  31. flowyml/core/resources.py +424 -0
  32. flowyml/core/scheduler.py +630 -0
  33. flowyml/core/scheduler_config.py +32 -0
  34. flowyml/core/step.py +201 -0
  35. flowyml/core/step_grouping.py +292 -0
  36. flowyml/core/templates.py +226 -0
  37. flowyml/core/versioning.py +217 -0
  38. flowyml/integrations/__init__.py +1 -0
  39. flowyml/integrations/keras.py +134 -0
  40. flowyml/monitoring/__init__.py +1 -0
  41. flowyml/monitoring/alerts.py +57 -0
  42. flowyml/monitoring/data.py +102 -0
  43. flowyml/monitoring/llm.py +160 -0
  44. flowyml/monitoring/monitor.py +57 -0
  45. flowyml/monitoring/notifications.py +246 -0
  46. flowyml/registry/__init__.py +5 -0
  47. flowyml/registry/model_registry.py +491 -0
  48. flowyml/registry/pipeline_registry.py +55 -0
  49. flowyml/stacks/__init__.py +27 -0
  50. flowyml/stacks/base.py +77 -0
  51. flowyml/stacks/bridge.py +288 -0
  52. flowyml/stacks/components.py +155 -0
  53. flowyml/stacks/gcp.py +499 -0
  54. flowyml/stacks/local.py +112 -0
  55. flowyml/stacks/migration.py +97 -0
  56. flowyml/stacks/plugin_config.py +78 -0
  57. flowyml/stacks/plugins.py +401 -0
  58. flowyml/stacks/registry.py +226 -0
  59. flowyml/storage/__init__.py +26 -0
  60. flowyml/storage/artifacts.py +246 -0
  61. flowyml/storage/materializers/__init__.py +20 -0
  62. flowyml/storage/materializers/base.py +133 -0
  63. flowyml/storage/materializers/keras.py +185 -0
  64. flowyml/storage/materializers/numpy.py +94 -0
  65. flowyml/storage/materializers/pandas.py +142 -0
  66. flowyml/storage/materializers/pytorch.py +135 -0
  67. flowyml/storage/materializers/sklearn.py +110 -0
  68. flowyml/storage/materializers/tensorflow.py +152 -0
  69. flowyml/storage/metadata.py +931 -0
  70. flowyml/tracking/__init__.py +1 -0
  71. flowyml/tracking/experiment.py +211 -0
  72. flowyml/tracking/leaderboard.py +191 -0
  73. flowyml/tracking/runs.py +145 -0
  74. flowyml/ui/__init__.py +15 -0
  75. flowyml/ui/backend/Dockerfile +31 -0
  76. flowyml/ui/backend/__init__.py +0 -0
  77. flowyml/ui/backend/auth.py +163 -0
  78. flowyml/ui/backend/main.py +187 -0
  79. flowyml/ui/backend/routers/__init__.py +0 -0
  80. flowyml/ui/backend/routers/assets.py +45 -0
  81. flowyml/ui/backend/routers/execution.py +179 -0
  82. flowyml/ui/backend/routers/experiments.py +49 -0
  83. flowyml/ui/backend/routers/leaderboard.py +118 -0
  84. flowyml/ui/backend/routers/notifications.py +72 -0
  85. flowyml/ui/backend/routers/pipelines.py +110 -0
  86. flowyml/ui/backend/routers/plugins.py +192 -0
  87. flowyml/ui/backend/routers/projects.py +85 -0
  88. flowyml/ui/backend/routers/runs.py +66 -0
  89. flowyml/ui/backend/routers/schedules.py +222 -0
  90. flowyml/ui/backend/routers/traces.py +84 -0
  91. flowyml/ui/frontend/Dockerfile +20 -0
  92. flowyml/ui/frontend/README.md +315 -0
  93. flowyml/ui/frontend/dist/assets/index-DFNQnrUj.js +448 -0
  94. flowyml/ui/frontend/dist/assets/index-pWI271rZ.css +1 -0
  95. flowyml/ui/frontend/dist/index.html +16 -0
  96. flowyml/ui/frontend/index.html +15 -0
  97. flowyml/ui/frontend/nginx.conf +26 -0
  98. flowyml/ui/frontend/package-lock.json +3545 -0
  99. flowyml/ui/frontend/package.json +33 -0
  100. flowyml/ui/frontend/postcss.config.js +6 -0
  101. flowyml/ui/frontend/src/App.jsx +21 -0
  102. flowyml/ui/frontend/src/app/assets/page.jsx +397 -0
  103. flowyml/ui/frontend/src/app/dashboard/page.jsx +295 -0
  104. flowyml/ui/frontend/src/app/experiments/[experimentId]/page.jsx +255 -0
  105. flowyml/ui/frontend/src/app/experiments/page.jsx +360 -0
  106. flowyml/ui/frontend/src/app/leaderboard/page.jsx +133 -0
  107. flowyml/ui/frontend/src/app/pipelines/page.jsx +454 -0
  108. flowyml/ui/frontend/src/app/plugins/page.jsx +48 -0
  109. flowyml/ui/frontend/src/app/projects/page.jsx +292 -0
  110. flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +682 -0
  111. flowyml/ui/frontend/src/app/runs/page.jsx +470 -0
  112. flowyml/ui/frontend/src/app/schedules/page.jsx +585 -0
  113. flowyml/ui/frontend/src/app/settings/page.jsx +314 -0
  114. flowyml/ui/frontend/src/app/tokens/page.jsx +456 -0
  115. flowyml/ui/frontend/src/app/traces/page.jsx +246 -0
  116. flowyml/ui/frontend/src/components/Layout.jsx +108 -0
  117. flowyml/ui/frontend/src/components/PipelineGraph.jsx +295 -0
  118. flowyml/ui/frontend/src/components/header/Header.jsx +72 -0
  119. flowyml/ui/frontend/src/components/plugins/AddPluginDialog.jsx +121 -0
  120. flowyml/ui/frontend/src/components/plugins/InstalledPlugins.jsx +124 -0
  121. flowyml/ui/frontend/src/components/plugins/PluginBrowser.jsx +167 -0
  122. flowyml/ui/frontend/src/components/plugins/PluginManager.jsx +60 -0
  123. flowyml/ui/frontend/src/components/sidebar/Sidebar.jsx +145 -0
  124. flowyml/ui/frontend/src/components/ui/Badge.jsx +26 -0
  125. flowyml/ui/frontend/src/components/ui/Button.jsx +34 -0
  126. flowyml/ui/frontend/src/components/ui/Card.jsx +44 -0
  127. flowyml/ui/frontend/src/components/ui/CodeSnippet.jsx +38 -0
  128. flowyml/ui/frontend/src/components/ui/CollapsibleCard.jsx +53 -0
  129. flowyml/ui/frontend/src/components/ui/DataView.jsx +175 -0
  130. flowyml/ui/frontend/src/components/ui/EmptyState.jsx +49 -0
  131. flowyml/ui/frontend/src/components/ui/ExecutionStatus.jsx +122 -0
  132. flowyml/ui/frontend/src/components/ui/KeyValue.jsx +25 -0
  133. flowyml/ui/frontend/src/components/ui/ProjectSelector.jsx +134 -0
  134. flowyml/ui/frontend/src/contexts/ProjectContext.jsx +79 -0
  135. flowyml/ui/frontend/src/contexts/ThemeContext.jsx +54 -0
  136. flowyml/ui/frontend/src/index.css +11 -0
  137. flowyml/ui/frontend/src/layouts/MainLayout.jsx +23 -0
  138. flowyml/ui/frontend/src/main.jsx +10 -0
  139. flowyml/ui/frontend/src/router/index.jsx +39 -0
  140. flowyml/ui/frontend/src/services/pluginService.js +90 -0
  141. flowyml/ui/frontend/src/utils/api.js +47 -0
  142. flowyml/ui/frontend/src/utils/cn.js +6 -0
  143. flowyml/ui/frontend/tailwind.config.js +31 -0
  144. flowyml/ui/frontend/vite.config.js +21 -0
  145. flowyml/ui/utils.py +77 -0
  146. flowyml/utils/__init__.py +67 -0
  147. flowyml/utils/config.py +308 -0
  148. flowyml/utils/debug.py +240 -0
  149. flowyml/utils/environment.py +346 -0
  150. flowyml/utils/git.py +319 -0
  151. flowyml/utils/logging.py +61 -0
  152. flowyml/utils/performance.py +314 -0
  153. flowyml/utils/stack_config.py +296 -0
  154. flowyml/utils/validation.py +270 -0
  155. flowyml-1.1.0.dist-info/METADATA +372 -0
  156. flowyml-1.1.0.dist-info/RECORD +159 -0
  157. flowyml-1.1.0.dist-info/WHEEL +4 -0
  158. flowyml-1.1.0.dist-info/entry_points.txt +3 -0
  159. flowyml-1.1.0.dist-info/licenses/LICENSE +17 -0
@@ -0,0 +1,373 @@
1
+ """Conditional execution for dynamic pipelines."""
2
+
3
+ from typing import Any
4
+ from collections.abc import Callable
5
+ from functools import wraps
6
+
7
+
8
+ class Condition:
9
+ """Condition for conditional step execution.
10
+
11
+ Example:
12
+ ```python
13
+ from flowyml import step, Condition
14
+
15
+
16
+ @step
17
+ def check_quality(data):
18
+ return {"score": 0.95, "needs_cleaning": False}
19
+
20
+
21
+ @step
22
+ @Condition(lambda result: result["needs_cleaning"])
23
+ def clean_data(data, quality_result):
24
+ return cleaned_data
25
+ ```
26
+ """
27
+
28
+ def __init__(self, condition_func: Callable[[Any], bool]):
29
+ """Initialize condition.
30
+
31
+ Args:
32
+ condition_func: Function that takes step inputs and returns bool
33
+ """
34
+ self.condition_func = condition_func
35
+
36
+ def should_execute(self, *args, **kwargs) -> bool:
37
+ """Check if condition is met.
38
+
39
+ Args:
40
+ *args: Positional arguments
41
+ **kwargs: Keyword arguments
42
+
43
+ Returns:
44
+ True if step should execute
45
+ """
46
+ try:
47
+ # Try to evaluate with first argument if available
48
+ if args:
49
+ return bool(self.condition_func(args[0]))
50
+ elif kwargs:
51
+ # Try with all kwargs
52
+ return bool(self.condition_func(kwargs))
53
+ return False
54
+ except Exception:
55
+ # If condition evaluation fails, don't execute
56
+ return False
57
+
58
+ def __call__(self, func: Callable) -> Callable:
59
+ """Decorator to add conditional execution.
60
+
61
+ Args:
62
+ func: Function to wrap
63
+
64
+ Returns:
65
+ Wrapped function
66
+ """
67
+
68
+ @wraps(func)
69
+ def wrapper(*args, **kwargs):
70
+ if self.should_execute(*args, **kwargs):
71
+ return func(*args, **kwargs)
72
+ else:
73
+ # Return None or a special marker for skipped execution
74
+ return SkippedExecution(func.__name__)
75
+
76
+ # Mark function as conditional
77
+ wrapper._is_conditional = True
78
+ wrapper._condition = self
79
+
80
+ return wrapper
81
+
82
+
83
+ class SkippedExecution:
84
+ """Marker for skipped conditional execution."""
85
+
86
+ def __init__(self, step_name: str):
87
+ self.step_name = step_name
88
+ self.skipped = True
89
+
90
+ def __repr__(self):
91
+ return f"<SkippedExecution: {self.step_name}>"
92
+
93
+ def __bool__(self):
94
+ return False
95
+
96
+
97
+ def condition(condition_func: Callable[[Any], bool]) -> Condition:
98
+ """Create a condition decorator.
99
+
100
+ Args:
101
+ condition_func: Function that evaluates to bool
102
+
103
+ Returns:
104
+ Condition decorator
105
+
106
+ Example:
107
+ ```python
108
+ @step
109
+ @condition(lambda x: x["quality"] > 0.9)
110
+ def train_complex_model(data):
111
+ return complex_model
112
+ ```
113
+ """
114
+ return Condition(condition_func)
115
+
116
+
117
+ class ConditionalBranch:
118
+ """Branch execution based on condition.
119
+
120
+ Example:
121
+ ```python
122
+ from flowyml import ConditionalBranch
123
+
124
+ branch = ConditionalBranch()
125
+
126
+
127
+ @branch.if_condition(lambda x: x > 10)
128
+ def process_large(data):
129
+ return process_with_large_model(data)
130
+
131
+
132
+ @branch.else_condition()
133
+ def process_small(data):
134
+ return process_with_small_model(data)
135
+ ```
136
+ """
137
+
138
+ def __init__(self):
139
+ self.if_func: Callable | None = None
140
+ self.if_condition_func: Callable | None = None
141
+ self.else_func: Callable | None = None
142
+ self.elif_branches: list[tuple[Callable, Callable]] = []
143
+
144
+ def if_condition(self, condition_func: Callable[[Any], bool]) -> Callable:
145
+ """Define if branch.
146
+
147
+ Args:
148
+ condition_func: Condition function
149
+
150
+ Returns:
151
+ Decorator
152
+ """
153
+
154
+ def decorator(func: Callable) -> Callable:
155
+ self.if_func = func
156
+ self.if_condition_func = condition_func
157
+ return func
158
+
159
+ return decorator
160
+
161
+ def elif_condition(self, condition_func: Callable[[Any], bool]) -> Callable:
162
+ """Define elif branch.
163
+
164
+ Args:
165
+ condition_func: Condition function
166
+
167
+ Returns:
168
+ Decorator
169
+ """
170
+
171
+ def decorator(func: Callable) -> Callable:
172
+ self.elif_branches.append((condition_func, func))
173
+ return func
174
+
175
+ return decorator
176
+
177
+ def else_condition(self) -> Callable:
178
+ """Define else branch.
179
+
180
+ Returns:
181
+ Decorator
182
+ """
183
+
184
+ def decorator(func: Callable) -> Callable:
185
+ self.else_func = func
186
+ return func
187
+
188
+ return decorator
189
+
190
+ def execute(self, *args, **kwargs) -> Any:
191
+ """Execute appropriate branch based on conditions.
192
+
193
+ Args:
194
+ *args: Positional arguments
195
+ **kwargs: Keyword arguments
196
+
197
+ Returns:
198
+ Result from executed branch
199
+ """
200
+ # Try if condition
201
+ if self.if_func and self.if_condition_func:
202
+ try:
203
+ if self.if_condition_func(*args, **kwargs):
204
+ return self.if_func(*args, **kwargs)
205
+ except Exception:
206
+ pass
207
+
208
+ # Try elif conditions
209
+ for condition_func, func in self.elif_branches:
210
+ try:
211
+ if condition_func(*args, **kwargs):
212
+ return func(*args, **kwargs)
213
+ except Exception:
214
+ pass
215
+
216
+ # Execute else
217
+ if self.else_func:
218
+ return self.else_func(*args, **kwargs)
219
+
220
+ # No branch executed
221
+ return None
222
+
223
+
224
+ class Switch:
225
+ """Switch-case style conditional execution.
226
+
227
+ Example:
228
+ ```python
229
+ from flowyml import Switch
230
+
231
+ switch = Switch(lambda x: x["type"])
232
+
233
+
234
+ @switch.case("image")
235
+ def process_image(data):
236
+ return process_image_data(data)
237
+
238
+
239
+ @switch.case("text")
240
+ def process_text(data):
241
+ return process_text_data(data)
242
+
243
+
244
+ @switch.default()
245
+ def process_other(data):
246
+ return process_generic(data)
247
+ ```
248
+ """
249
+
250
+ def __init__(self, selector_func: Callable[[Any], Any]):
251
+ """Initialize switch.
252
+
253
+ Args:
254
+ selector_func: Function to select case value
255
+ """
256
+ self.selector_func = selector_func
257
+ self.cases: dict[Any, Callable] = {}
258
+ self.default_func: Callable | None = None
259
+
260
+ def case(self, value: Any) -> Callable:
261
+ """Define a case branch.
262
+
263
+ Args:
264
+ value: Value to match
265
+
266
+ Returns:
267
+ Decorator
268
+ """
269
+
270
+ def decorator(func: Callable) -> Callable:
271
+ self.cases[value] = func
272
+ return func
273
+
274
+ return decorator
275
+
276
+ def default(self) -> Callable:
277
+ """Define default branch.
278
+
279
+ Returns:
280
+ Decorator
281
+ """
282
+
283
+ def decorator(func: Callable) -> Callable:
284
+ self.default_func = func
285
+ return func
286
+
287
+ return decorator
288
+
289
+ def execute(self, *args, **kwargs) -> Any:
290
+ """Execute appropriate case based on selector.
291
+
292
+ Args:
293
+ *args: Positional arguments
294
+ **kwargs: Keyword arguments
295
+
296
+ Returns:
297
+ Result from executed case
298
+ """
299
+ # Get selector value
300
+ try:
301
+ selector_value = self.selector_func(*args, **kwargs)
302
+ except Exception:
303
+ selector_value = None
304
+
305
+ # Find matching case
306
+ if selector_value in self.cases:
307
+ return self.cases[selector_value](*args, **kwargs)
308
+
309
+ # Execute default
310
+ if self.default_func:
311
+ return self.default_func(*args, **kwargs)
312
+
313
+ return None
314
+
315
+
316
+ def when(condition_func: Callable[[Any], bool]) -> Callable:
317
+ """Simple conditional execution helper.
318
+
319
+ Args:
320
+ condition_func: Condition to evaluate
321
+
322
+ Returns:
323
+ Decorator that executes function only if condition is true
324
+
325
+ Example:
326
+ ```python
327
+ @step
328
+ @when(lambda data: len(data) > 1000)
329
+ def process_large_dataset(data):
330
+ return expensive_processing(data)
331
+ ```
332
+ """
333
+
334
+ def decorator(func: Callable) -> Callable:
335
+ @wraps(func)
336
+ def wrapper(*args, **kwargs):
337
+ if condition_func(*args, **kwargs):
338
+ return func(*args, **kwargs)
339
+ return SkippedExecution(func.__name__)
340
+
341
+ return wrapper
342
+
343
+ return decorator
344
+
345
+
346
+ def unless(condition_func: Callable[[Any], bool]) -> Callable:
347
+ """Execute unless condition is true.
348
+
349
+ Args:
350
+ condition_func: Condition to evaluate
351
+
352
+ Returns:
353
+ Decorator that executes function only if condition is false
354
+
355
+ Example:
356
+ ```python
357
+ @step
358
+ @unless(lambda data: data["cached"])
359
+ def compute_features(data):
360
+ return expensive_computation(data)
361
+ ```
362
+ """
363
+
364
+ def decorator(func: Callable) -> Callable:
365
+ @wraps(func)
366
+ def wrapper(*args, **kwargs):
367
+ if not condition_func(*args, **kwargs):
368
+ return func(*args, **kwargs)
369
+ return SkippedExecution(func.__name__)
370
+
371
+ return wrapper
372
+
373
+ return decorator
@@ -0,0 +1,155 @@
1
+ """Context Management - Automatic parameter injection for ML pipelines."""
2
+
3
+ import inspect
4
+ from typing import Any
5
+
6
+
7
+ class Context:
8
+ """Pipeline context with automatic parameter injection.
9
+
10
+ Example:
11
+ >>> ctx = Context(learning_rate=0.001, epochs=10, batch_size=32, device="cuda")
12
+ """
13
+
14
+ def __init__(self, **kwargs):
15
+ self._params = kwargs
16
+ self._parent = None
17
+ self._metadata = {}
18
+
19
+ def __getattr__(self, name: str) -> Any:
20
+ """Allow dot notation access to parameters."""
21
+ if name.startswith("_"):
22
+ return super().__getattribute__(name)
23
+
24
+ if name in self._params:
25
+ return self._params[name]
26
+
27
+ if self._parent and name in self._parent._params:
28
+ return self._parent._params[name]
29
+
30
+ raise AttributeError(f"Context has no parameter '{name}'")
31
+
32
+ def __getitem__(self, key: str) -> Any:
33
+ """Allow dict-style access to parameters."""
34
+ if key in self._params:
35
+ return self._params[key]
36
+
37
+ if self._parent and key in self._parent._params:
38
+ return self._parent._params[key]
39
+
40
+ raise KeyError(f"Context has no parameter '{key}'")
41
+
42
+ def get(self, key: str, default: Any = None) -> Any:
43
+ """Get parameter with default value."""
44
+ try:
45
+ return self[key]
46
+ except KeyError:
47
+ return default
48
+
49
+ def keys(self) -> set[str]:
50
+ """Return all parameter keys."""
51
+ keys = set(self._params.keys())
52
+ if self._parent:
53
+ keys.update(self._parent.keys())
54
+ return keys
55
+
56
+ def items(self) -> list[tuple]:
57
+ """Return all parameter items."""
58
+ result = {}
59
+ if self._parent:
60
+ result.update(dict(self._parent.items()))
61
+ result.update(self._params)
62
+ return list(result.items())
63
+
64
+ def to_dict(self) -> dict[str, Any]:
65
+ """Convert context to dictionary."""
66
+ result = {}
67
+ if self._parent:
68
+ result.update(self._parent.to_dict())
69
+ result.update(self._params)
70
+ return result
71
+
72
+ def update(self, data: dict[str, Any]) -> None:
73
+ """Update context with new data.
74
+
75
+ Args:
76
+ data: Dictionary of key-value pairs to add to context
77
+ """
78
+ self._params.update(data)
79
+
80
+ def inherit(self, **overrides) -> "Context":
81
+ """Create child context with inheritance."""
82
+ child = Context(**overrides)
83
+ child._parent = self
84
+ return child
85
+
86
+ def inject_params(self, func: callable) -> dict[str, Any]:
87
+ """Automatically inject parameters based on function signature.
88
+
89
+ Args:
90
+ func: Function to analyze and inject parameters for
91
+
92
+ Returns:
93
+ Dictionary of parameters to inject
94
+ """
95
+ sig = inspect.signature(func)
96
+ injected = {}
97
+
98
+ for param_name, param in sig.parameters.items():
99
+ # Skip self, cls, args, kwargs
100
+ if param_name in ("self", "cls"):
101
+ continue
102
+ if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
103
+ continue
104
+
105
+ # Check if parameter exists in context
106
+ if param_name in self.keys():
107
+ injected[param_name] = self[param_name]
108
+
109
+ return injected
110
+
111
+ def validate_for_step(self, step_func: callable, exclude: list[str] = None) -> list[str]:
112
+ """Validate that all required parameters are available.
113
+
114
+ Args:
115
+ step_func: Step function to validate
116
+ exclude: List of parameter names to exclude from validation (e.g. inputs)
117
+
118
+ Returns:
119
+ List of missing required parameters
120
+ """
121
+ sig = inspect.signature(step_func)
122
+ missing = []
123
+ exclude = exclude or []
124
+
125
+ for param_name, param in sig.parameters.items():
126
+ # Skip optional parameters
127
+ if param_name in ("self", "cls"):
128
+ continue
129
+ if param_name in exclude:
130
+ continue
131
+ if param.default != inspect.Parameter.empty:
132
+ continue
133
+ if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
134
+ continue
135
+
136
+ # Check if required param is missing
137
+ if param_name not in self.keys():
138
+ missing.append(param_name)
139
+
140
+ return missing
141
+
142
+ def __repr__(self) -> str:
143
+ params_str = ", ".join(f"{k}={v}" for k, v in list(self._params.items())[:5])
144
+ if len(self._params) > 5:
145
+ params_str += "..."
146
+ return f"Context({params_str})"
147
+
148
+
149
+ def context(**kwargs) -> Context:
150
+ """Create a new context with parameters.
151
+
152
+ Example:
153
+ >>> ctx = context(learning_rate=0.001, epochs=10, batch_size=32)
154
+ """
155
+ return Context(**kwargs)