simstack 0.2.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 (91) hide show
  1. simstack/__init__.py +0 -0
  2. simstack/_version.py +34 -0
  3. simstack/core/__init__.py +0 -0
  4. simstack/core/artifacts.py +447 -0
  5. simstack/core/asnyc_helper.py +14 -0
  6. simstack/core/config_file.py +60 -0
  7. simstack/core/context.py +167 -0
  8. simstack/core/data.py +139 -0
  9. simstack/core/definitions.py +21 -0
  10. simstack/core/engine.py +116 -0
  11. simstack/core/find_simstack_modules.py +43 -0
  12. simstack/core/hash.py +177 -0
  13. simstack/core/model_table.py +199 -0
  14. simstack/core/node.py +911 -0
  15. simstack/core/node_runner.py +254 -0
  16. simstack/core/node_table.py +380 -0
  17. simstack/core/recompute_artifacts.py +120 -0
  18. simstack/core/reset_db.py +12 -0
  19. simstack/core/resources.py +101 -0
  20. simstack/core/run_node.py +69 -0
  21. simstack/core/runner.py +435 -0
  22. simstack/core/simstack_result.py +41 -0
  23. simstack/core/submit_node.py +143 -0
  24. simstack/core/task_id.py +22 -0
  25. simstack/data/__init__.py +0 -0
  26. simstack/methods/__init__.py +0 -0
  27. simstack/methods/get_file.py +10 -0
  28. simstack/methods/multiple_executor.py +307 -0
  29. simstack/models/__init__.py +37 -0
  30. simstack/models/array_list.py +48 -0
  31. simstack/models/array_storage.py +54 -0
  32. simstack/models/artifact_models.py +127 -0
  33. simstack/models/base_types.py +208 -0
  34. simstack/models/charts_artifact.py +411 -0
  35. simstack/models/dataset.py +432 -0
  36. simstack/models/dataset_metadata.py +303 -0
  37. simstack/models/file_instance.py +77 -0
  38. simstack/models/file_list.py +321 -0
  39. simstack/models/files.py +229 -0
  40. simstack/models/log_entry_model.py +37 -0
  41. simstack/models/models.py +40 -0
  42. simstack/models/node_registry.py +105 -0
  43. simstack/models/pandas_model.py +210 -0
  44. simstack/models/parameters.py +388 -0
  45. simstack/models/pickle_models.py +311 -0
  46. simstack/models/runner_model.py +39 -0
  47. simstack/models/simple_table.py +32 -0
  48. simstack/models/simstack_model.py +97 -0
  49. simstack/models/slurm_info.py +17 -0
  50. simstack/models/table_artifact.py +87 -0
  51. simstack/util/__init__.py +0 -0
  52. simstack/util/async_zip_utils.py +18 -0
  53. simstack/util/cleaned_json_schema.py +20 -0
  54. simstack/util/config_reader.py +273 -0
  55. simstack/util/custom_model_dump.py +75 -0
  56. simstack/util/db.py +457 -0
  57. simstack/util/db_logger.py +117 -0
  58. simstack/util/default_from_dict.py +57 -0
  59. simstack/util/directory_iterator.py +216 -0
  60. simstack/util/file_hashing.py +68 -0
  61. simstack/util/files.py +396 -0
  62. simstack/util/find_python_files.py +33 -0
  63. simstack/util/generate_ui_schema.py +80 -0
  64. simstack/util/geometry_utils.py +118 -0
  65. simstack/util/get_module_path.py +26 -0
  66. simstack/util/git_pull.py +65 -0
  67. simstack/util/git_repository_status.py +93 -0
  68. simstack/util/import_module.py +65 -0
  69. simstack/util/importer.py +163 -0
  70. simstack/util/make_graph.py +107 -0
  71. simstack/util/make_table.py +827 -0
  72. simstack/util/minimal_route_finder.py +72 -0
  73. simstack/util/mock_db.py +144 -0
  74. simstack/util/module_path_checker.py +38 -0
  75. simstack/util/path_manager.py +234 -0
  76. simstack/util/project_root_finder.py +36 -0
  77. simstack/util/queue_watcher.py +262 -0
  78. simstack/util/route_finder.py +74 -0
  79. simstack/util/runner_util.py +162 -0
  80. simstack/util/safe_code_executor.py +194 -0
  81. simstack/util/sample_config.py +98 -0
  82. simstack/util/sensitive_content_filter.py +48 -0
  83. simstack/util/setup_logging.py +49 -0
  84. simstack/util/submit_to_watchdog.py +106 -0
  85. simstack/util/ui_tools.py +213 -0
  86. simstack-0.2.0.dist-info/METADATA +361 -0
  87. simstack-0.2.0.dist-info/RECORD +91 -0
  88. simstack-0.2.0.dist-info/WHEEL +5 -0
  89. simstack-0.2.0.dist-info/entry_points.txt +10 -0
  90. simstack-0.2.0.dist-info/licenses/LICENSE +21 -0
  91. simstack-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,432 @@
1
+ from typing import Dict, Iterator, Union, Tuple, KeysView, ValuesView, ItemsView, List
2
+
3
+ from odmantic import Model, ObjectId, EmbeddedModel, Field, Reference
4
+
5
+ from simstack.core.asnyc_helper import async_helper
6
+ from simstack.core.context import context
7
+ from simstack.core.engine import current_engine_context
8
+ from simstack.models import simstack_model
9
+ from simstack.models.dataset_metadata import DataSetMetadata
10
+ from simstack.util.importer import import_class_by_name
11
+ from simstack.util.make_table import make_column_defs_instance
12
+ from simstack.util.make_table import make_table_entries_helper
13
+
14
+
15
+ @simstack_model
16
+ class DataSetSection(EmbeddedModel):
17
+ """
18
+ Represents a section of a dataset containing tuples of models.
19
+
20
+ A DataSetSection is a list of tuples where all tuples contain the same types of models.
21
+ For example, if one tuple contains (ModelA, ModelB), then all tuples in this section
22
+ must contain (ModelA, ModelB) instances.
23
+
24
+ :ivar model_types: List of model class names that define the structure of each tuple.
25
+ :type model_types: List[str]
26
+ :ivar data: List of tuples, where each tuple contains model IDs corresponding to model_types.
27
+ :type data: List[List[ObjectId]]
28
+ """
29
+
30
+ model_types: List[str] = Field(
31
+ default_factory=list
32
+ ) # Class names of models in each tuple
33
+ data: List[List[ObjectId]] = Field(
34
+ default_factory=list
35
+ ) # List of tuples (as lists of ObjectIds)
36
+
37
+ model_config = {"extra": "forbid"}
38
+
39
+ def add_model_group(self, models: Union[Model, Tuple[Model, ...]]) -> None:
40
+ """
41
+ Add a tuple of models to this section.
42
+
43
+ :param models: Tuple of model instances to add
44
+ :raises ValueError: If the model types don't match the section's expected types
45
+ """
46
+ if isinstance(models, Model):
47
+ models = (models,)
48
+ model_names = [model.__class__.__name__ for model in models]
49
+ model_ids = [model.id for model in models]
50
+
51
+ # If this is the first tuple, set the model types
52
+ if not self.model_types:
53
+ self.model_types = model_names
54
+ else:
55
+ # Verify that the model types match
56
+ if model_names != self.model_types:
57
+ raise ValueError(
58
+ f"Model types {model_names} don't match section's expected types {self.model_types}"
59
+ )
60
+
61
+ self.data.append(model_ids)
62
+
63
+ async def make_column_defs(self):
64
+ """
65
+ Generate ag-grid column definitions for all model types in this section.
66
+
67
+ :return: List of column definitions for ag-grid
68
+ """
69
+ column_defs = []
70
+ if len(self.data) == 0:
71
+ return column_defs
72
+ engine = current_engine_context.get()
73
+ for model_group_id, model_type in zip(self.data[0], self.model_types):
74
+ model_class = await import_class_by_name(model_type)
75
+ model_instance = await engine.find_one(
76
+ model_class, model_class.id == model_group_id
77
+ )
78
+ model_columns = make_column_defs_instance(model_instance)
79
+ column_defs.extend(model_columns)
80
+ return column_defs
81
+
82
+ async def make_table_entries(self):
83
+ all_data = []
84
+ engine = current_engine_context.get()
85
+ for model_group_ids in self.data:
86
+ data = []
87
+ for model_group_id, model_type in zip(model_group_ids, self.model_types):
88
+ model_class = await import_class_by_name(model_type)
89
+ model_instance = await engine.find_one(
90
+ model_class, model_class.id == model_group_id
91
+ )
92
+ model_data = make_table_entries_helper(model_instance)
93
+ data.append(model_data)
94
+ all_data.append(data)
95
+ return all_data
96
+
97
+ @async_helper
98
+ async def get_model_group(self, index: int) -> Tuple[Model, ...]:
99
+ """
100
+ Retrieve a tuple of models at the specified index.
101
+
102
+ :param index: Index of the tuple to retrieve
103
+ :return: Tuple of model instances
104
+ """
105
+ if index >= len(self.data):
106
+ raise IndexError(
107
+ f"Index {index} out of range for section with {len(self.data)} model groups"
108
+ )
109
+
110
+ model_ids = self.data[index]
111
+ models = []
112
+
113
+ for model_type, model_id in zip(self.model_types, model_ids):
114
+ model_class = await import_class_by_name(model_type)
115
+ model_instance = await context.db.find_one(
116
+ model_class, model_class.id == model_id
117
+ )
118
+ if model_instance is None:
119
+ raise ValueError(
120
+ f"Model of type {model_type} with id {model_id} not found"
121
+ )
122
+ models.append(model_instance)
123
+
124
+ if len(models) == 1:
125
+ return models[0]
126
+ return tuple(models)
127
+
128
+ def get_all_model_groups(self) -> List[Tuple[Model, ...]]:
129
+ """
130
+ Retrieve all tuples in this section.
131
+
132
+ :return: List of tuples of model instances
133
+ """
134
+ all_tuples = []
135
+ for i in range(len(self.data)):
136
+ tuple_models = self.get_model_group(i)
137
+ all_tuples.append(tuple_models)
138
+ return all_tuples
139
+
140
+ # List-like behavior methods
141
+ def __len__(self) -> int:
142
+ """Return the number of model groups in this section."""
143
+ return len(self.data)
144
+
145
+ def __getitem__(
146
+ self, index: Union[int, slice]
147
+ ) -> Union[Tuple[Model, ...], List[Tuple[Model, ...]]]:
148
+ """
149
+ Get model group(s) at the specified index or slice.
150
+
151
+ :param index: Index or slice to retrieve
152
+ :return: Single tuple or list of tuples of model instances
153
+ """
154
+ if isinstance(index, slice):
155
+ indices = list(range(*index.indices(len(self.data))))
156
+ return [self.get_model_group(i) for i in indices]
157
+ else:
158
+ return self.get_model_group(index)
159
+
160
+ def __setitem__(self, index: int, value: Tuple[Model, ...]) -> None:
161
+ """
162
+ Set model group at the specified index.
163
+
164
+ :param index: Index to set
165
+ :param value: Tuple of model instances to set
166
+ """
167
+ if index >= len(self.data):
168
+ raise IndexError(
169
+ f"Index {index} out of range for section with {len(self.data)} model groups"
170
+ )
171
+
172
+ model_names = [model.__class__.__name__ for model in value]
173
+ model_ids = [model.id for model in value]
174
+
175
+ # Verify that the model types match
176
+ if self.model_types and model_names != self.model_types:
177
+ raise ValueError(
178
+ f"Model types {model_names} don't match section's expected types {self.model_types}"
179
+ )
180
+
181
+ self.data[index] = model_ids
182
+
183
+ def __delitem__(self, index: int) -> None:
184
+ """
185
+ Delete model group at the specified index.
186
+
187
+ :param index: Index to delete
188
+ """
189
+ if index >= len(self.data):
190
+ raise IndexError(
191
+ f"Index {index} out of range for section with {len(self.data)} model groups"
192
+ )
193
+ del self.data[index]
194
+
195
+ def append(self, models: Tuple[Model, ...]) -> None:
196
+ """
197
+ Append a tuple of models to the section.
198
+
199
+ :param models: Tuple of model instances to append
200
+ """
201
+ self.add_model_group(models)
202
+
203
+ def insert(self, index: int, models: Tuple[Model, ...]) -> None:
204
+ """
205
+ Insert a tuple of models at the specified index.
206
+
207
+ :param index: Index to insert at
208
+ :param models: Tuple of model instances to insert
209
+ """
210
+ model_names = [model.__class__.__name__ for model in models]
211
+ model_ids = [model.id for model in models]
212
+
213
+ # If this is the first tuple, set the model types
214
+ if not self.model_types:
215
+ self.model_types = model_names
216
+ else:
217
+ # Verify that the model types match
218
+ if model_names != self.model_types:
219
+ raise ValueError(
220
+ f"Model types {model_names} don't match section's expected types {self.model_types}"
221
+ )
222
+
223
+ self.data.insert(index, model_ids)
224
+
225
+ def extend(self, models_list: List[Tuple[Model, ...]]) -> None:
226
+ """
227
+ Extend the section with multiple tuples of models.
228
+
229
+ :param models_list: List of tuples of model instances to extend with
230
+ """
231
+ for models in models_list:
232
+ self.add_model_group(models)
233
+
234
+ def pop(self, index: int = -1) -> Tuple[Model, ...]:
235
+ """
236
+ Remove and return a model group at the specified index (default last).
237
+
238
+ :param index: Index to pop (default -1 for last)
239
+ :return: Tuple of model instances that was removed
240
+ """
241
+ if len(self.data) == 0:
242
+ raise IndexError("pop from empty DataSetSection")
243
+
244
+ # Get the models first before removing
245
+ models = self.get_model_group(index)
246
+ del self.data[index]
247
+ return models
248
+
249
+ def remove(self, models: Tuple[Model, ...]) -> None:
250
+ """
251
+ Remove the first occurrence of the specified tuple of models.
252
+
253
+ :param models: Tuple of model instances to remove
254
+ :raises ValueError: If the tuple is not found
255
+ """
256
+ model_ids = [model.id for model in models]
257
+ try:
258
+ self.data.remove(model_ids)
259
+ except ValueError:
260
+ raise ValueError(f"Tuple {models} not found in DataSetSection")
261
+
262
+ def clear(self) -> None:
263
+ """Remove all model groups from the section."""
264
+ self.data.clear()
265
+ self.model_types.clear()
266
+
267
+ async def index(
268
+ self, models: Tuple[Model, ...], start: int = 0, stop: int = None
269
+ ) -> int:
270
+ """
271
+ Return the index of the first occurrence of the specified tuple of models.
272
+
273
+ :param models: Tuple of model instances to find
274
+ :param start: Start index for search
275
+ :param stop: Stop index for search
276
+ :return: Index of the tuple
277
+ :raises ValueError: If the tuple is not found
278
+ """
279
+ model_ids = [model.id for model in models]
280
+ if stop is None:
281
+ stop = len(self.data)
282
+
283
+ for i in range(start, min(stop, len(self.data))):
284
+ if self.data[i] == model_ids:
285
+ return i
286
+
287
+ raise ValueError(f"Tuple {models} not found in DataSetSection")
288
+
289
+ def count(self, models: Tuple[Model, ...]) -> int:
290
+ """
291
+ Return the number of occurrences of the specified tuple of models.
292
+
293
+ :param models: Tuple of model instances to count
294
+ :return: Number of occurrences
295
+ """
296
+ model_ids = [model.id for model in models]
297
+ return self.data.count(model_ids)
298
+
299
+ def reverse(self) -> None:
300
+ """Reverse the order of model groups in the section."""
301
+ self.data.reverse()
302
+
303
+ def __iter__(self):
304
+ """
305
+ Iterate over model groups in the section.
306
+
307
+ :return: Async iterator over tuples of model instances
308
+ """
309
+ for i in range(len(self.data)):
310
+ yield self.get_model_group(i)
311
+
312
+ def __contains__(self, models: Tuple[Model, ...]) -> bool:
313
+ """
314
+ Check if the specified tuple of models exists in the section.
315
+
316
+ :param models: Tuple of model instances to check for
317
+ :return: True if found, False otherwise
318
+ """
319
+ model_ids = [model.id for model in models]
320
+ return model_ids in self.data
321
+
322
+ def __bool__(self) -> bool:
323
+ """Return True if the section is not empty."""
324
+ return len(self.data) > 0
325
+
326
+ def __repr__(self) -> str:
327
+ """Return string representation of the section."""
328
+ return (
329
+ f"DataSetSection(model_types={self.model_types}, length={len(self.data)})"
330
+ )
331
+
332
+
333
+ @simstack_model
334
+ class DataSet(Model):
335
+ name: str = Field(default="dataset")
336
+ metadata: DataSetMetadata = Reference()
337
+ sections: Dict[str, DataSetSection] = Field(default_factory=dict)
338
+
339
+ model_config = {"extra": "forbid"}
340
+
341
+ @property
342
+ def dataset_type(self) -> str:
343
+ return self.metadata.dataset_type
344
+
345
+ async def save(self, engine):
346
+ # engine = current_engine_context.get()
347
+ structure = self.collect_structure()
348
+ ok = await self.metadata.validate_dict(structure)
349
+ if not ok:
350
+ raise ValueError("Metadata validation failed")
351
+ await engine.save_unchecked(self)
352
+
353
+ async def custom_model_dump(self, **kwargs) -> Dict[str, str]:
354
+ """
355
+ :return: dict with id
356
+ """
357
+ return {"id": str(self.id)}
358
+
359
+ def collect_structure(self) -> Dict[str, List[str]]:
360
+ """
361
+ Returns a dictionary where keys are section names and values are lists of model types.
362
+
363
+ :return: Dictionary mapping section names to their model types
364
+ :rtype: Dict[str, List[str]]
365
+ """
366
+ return {
367
+ section_name: section.model_types if len(section) > 0 else None
368
+ for section_name, section in self.sections.items()
369
+ }
370
+
371
+ # Dict-like behavior methods
372
+ def __getitem__(self, key: str) -> DataSetSection:
373
+ return self.sections[key]
374
+
375
+ def __setitem__(self, key: str, value: DataSetSection) -> None:
376
+ self.sections[key] = value
377
+
378
+ def __delitem__(self, key: str) -> None:
379
+ del self.sections[key]
380
+
381
+ def __len__(self) -> int:
382
+ return len(self.sections)
383
+
384
+ def __iter__(self) -> Iterator[str]:
385
+ return iter(self.sections)
386
+
387
+ def __contains__(self, key: str) -> bool:
388
+ return key in self.sections
389
+
390
+ def keys(self) -> KeysView[str]:
391
+ return self.sections.keys()
392
+
393
+ def values(self) -> ValuesView[DataSetSection]:
394
+ return self.sections.values()
395
+
396
+ def items(self) -> ItemsView[str, DataSetSection]:
397
+ return self.sections.items()
398
+
399
+ def get(self, key: str, default: DataSetSection = None) -> DataSetSection:
400
+ return self.sections.get(key, default)
401
+
402
+ def pop(self, key: str, default=None) -> DataSetSection:
403
+ if default is None:
404
+ return self.sections.pop(key)
405
+ return self.sections.pop(key, default)
406
+
407
+ def popitem(self) -> Tuple[str, DataSetSection]:
408
+ return self.sections.popitem()
409
+
410
+ def clear(self) -> None:
411
+ self.sections.clear()
412
+
413
+ def update(
414
+ self, other: Union[Dict[str, DataSetSection], "DataSet"] = None, **kwargs
415
+ ) -> None:
416
+ if other is not None:
417
+ if hasattr(other, "sections"):
418
+ self.sections.update(other.sections)
419
+ else:
420
+ self.sections.update(other)
421
+ self.sections.update(kwargs)
422
+
423
+ def setdefault(self, key: str, default: DataSetSection = None) -> DataSetSection:
424
+ return self.sections.setdefault(key, default)
425
+
426
+ @classmethod
427
+ def ui_schema(cls) -> dict:
428
+ return {
429
+ "ui:field": "DataSetField",
430
+ "metadata": {"ui:widget": "hidden"},
431
+ "sections": {"ui:widget": "hidden"},
432
+ }