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,120 @@
1
+ import logging
2
+
3
+ from simstack.core.artifacts import ArtifactArguments, create_artifacts
4
+ from simstack.core.definitions import TaskStatus
5
+ from simstack.core.engine import current_engine_context
6
+ from simstack.core.node import node_from_database
7
+ from simstack.models import NodeRegistry, ArtifactModel
8
+ from simstack.models.charts_artifact import ChartArtifactModel
9
+ from simstack.models.table_artifact import TableArtifactModel
10
+ from simstack.util.importer import import_class
11
+ from simstack.util.importer import import_function
12
+
13
+ logger = logging.getLogger("recompute_artifacts")
14
+
15
+
16
+ async def recompute_artifacts(node_registry: NodeRegistry):
17
+ """
18
+ Recomputes artifacts for a node and all its children recursively.
19
+
20
+ First processes all children nodes recursively, then removes and recomputes
21
+ the current node's artifacts.
22
+
23
+ :param node_registry: The registry entry for the node to process
24
+ :type node_registry: NodeRegistry
25
+ """
26
+ # Create Node from the registry
27
+
28
+ engine = current_engine_context.get()
29
+ node = await node_from_database(node_registry)
30
+ if node is None:
31
+ logger.error(f"Failed to create node from registry task_id: {node_registry.id}")
32
+ return
33
+
34
+ if node.status != TaskStatus.COMPLETED:
35
+ logger.error(f"Cannot recompute artifacts for task_id: {node_registry.id} name: {node.name} status: {node.status}")
36
+ return
37
+
38
+ # Find all children of this node
39
+ children = await engine.find(NodeRegistry, NodeRegistry.parent_ids.in_([node_registry.id]))
40
+
41
+ # Recursively recompute artifacts for all children first
42
+ for child_registry in children:
43
+ await recompute_artifacts(child_registry)
44
+
45
+ # Remove current node's artifacts
46
+ if node_registry.artifact_ids:
47
+ logger.info(f"Removing {len(node_registry.artifact_ids)} artifacts for node {node_registry.id}")
48
+
49
+ table_artifacts = await engine.find(
50
+ TableArtifactModel, TableArtifactModel.parent_id == node_registry.id
51
+ )
52
+ for table_artifact in table_artifacts:
53
+ await engine.delete(table_artifact)
54
+
55
+ chart_artifacts = await engine.find(
56
+ ChartArtifactModel, ChartArtifactModel.parent_id == node_registry.id
57
+ )
58
+ for chart_artifact in chart_artifacts:
59
+ await engine.delete(chart_artifact)
60
+ # Delete artifacts from the database
61
+ for artifact_id in node_registry.artifact_ids:
62
+ instance = await engine.find_one(
63
+ ArtifactModel, ArtifactModel.id == artifact_id
64
+ )
65
+ if instance:
66
+ await engine.delete(instance)
67
+ else:
68
+ logger.warning(
69
+ f"task_id: {node_registry.id} Failed to delete artifact {artifact_id} from database"
70
+ )
71
+ node_registry.artifact_ids = []
72
+
73
+ # Recompute artifacts for this node
74
+ if node_registry.status == TaskStatus.COMPLETED:
75
+ logger.info(f"Recomputing artifacts for node {node_registry.id}")
76
+
77
+ # Load the result to create new artifacts
78
+ result = await node.load_results()
79
+ if result is not None:
80
+ artifact_arguments = ArtifactArguments(result, node_registry.id)
81
+ # Reconstruct the function arguments for artifact creation
82
+ args = []
83
+ for table, table_id in zip(
84
+ node_registry.input_tables, node_registry.input_ids
85
+ ):
86
+ model = await import_class(table)
87
+ arg = await engine.find_one(model, model.id == table_id)
88
+ if arg:
89
+ args.append(arg)
90
+
91
+ # Get the function for artifact creation
92
+ wrapped_func = await import_function(node_registry.func_mapping)
93
+ func = (
94
+ wrapped_func
95
+ if not hasattr(wrapped_func, "_inner")
96
+ else wrapped_func._inner
97
+ )
98
+
99
+ # Create node kwargs similar to run_local
100
+ node_kwargs = {
101
+ "parent_id": node_registry.id,
102
+ "task_id": node_registry.id,
103
+ "call_path": node_registry.call_path,
104
+ "parent_parameters": node_registry.parameters,
105
+ }
106
+
107
+ artifact_arguments.add_attributes(func, *args, **node_kwargs)
108
+ node_registry.artifact_ids = await create_artifacts(
109
+ artifact_arguments, node_registry
110
+ )
111
+
112
+ # Save the updated registry
113
+ await engine.save(node_registry)
114
+ logger.info(
115
+ f"Recomputed {len(node_registry.artifact_ids)} artifacts for node {node_registry.id}"
116
+ )
117
+ else:
118
+ logger.warning(
119
+ f"Node {node_registry.id} is not completed, cannot recompute artifacts"
120
+ )
@@ -0,0 +1,12 @@
1
+ from simstack.core.context import context
2
+
3
+
4
+ async def main():
5
+ context.initialize()
6
+ await context.db.reset_database()
7
+
8
+
9
+ if __name__ == "__main__":
10
+ import asyncio
11
+
12
+ asyncio.run(main())
@@ -0,0 +1,101 @@
1
+ import logging
2
+ import os
3
+ import sys
4
+ import tomllib
5
+ from typing import List
6
+
7
+ from simstack.core.config_file import get_config_file
8
+ from simstack.util.project_root_finder import find_project_root
9
+
10
+ logger = logging.getLogger("resources")
11
+
12
+
13
+ class AllowedResources:
14
+ """
15
+ Singleton class that holds a list of allowed resource strings.
16
+ # This class is independent of context because its initialized to an empty list.
17
+ Creating any resource will fail if the config has not been read
18
+ """
19
+
20
+ _instance = None
21
+ _resources: List[str] = []
22
+
23
+ def __new__(cls):
24
+ if cls._instance is None:
25
+ cls._instance = super(AllowedResources, cls).__new__(cls)
26
+ return cls._instance
27
+
28
+ def __init__(self):
29
+ # Only initialize once
30
+ if not hasattr(self, "_initialized"):
31
+ config_file = get_config_file().config_file_path
32
+ config_path = find_project_root()
33
+
34
+ try:
35
+ toml_file = os.path.join(config_path, config_file)
36
+ if os.path.exists(toml_file):
37
+ with open(toml_file, "rb") as f:
38
+ self.config = tomllib.load(f)
39
+ else:
40
+ print("simstack.toml file not found in the project root.")
41
+ sys.exit(-1)
42
+ except tomllib.TOMLDecodeError:
43
+ print("There was an error decoding the TOML file.")
44
+ sys.exit(-1)
45
+
46
+ self._resources = (
47
+ self.config.get("parameters", {}).get("common", {}).get("resources", [])
48
+ )
49
+ logger.info(f"Initialized resources to: {self._resources}")
50
+ self._initialized = True
51
+
52
+ def set_resources(self, resources: List[str]) -> None:
53
+ """Set the list of allowed resources."""
54
+ self._resources = resources.copy() if resources else []
55
+
56
+ def get_resources(self) -> List[str]:
57
+ """Get the list of allowed resources."""
58
+ return self._resources.copy()
59
+
60
+ def add_resource(self, resource: str) -> None:
61
+ """Add a single resource to the list."""
62
+ if resource not in self._resources:
63
+ self._resources.append(resource)
64
+
65
+ def remove_resource(self, resource: str) -> None:
66
+ if resource == "self":
67
+ raise ValueError("Cannot remove 'self' resource")
68
+ """Remove a resource from the list."""
69
+ if resource in self._resources:
70
+ self._resources.remove(resource)
71
+
72
+ def clear_resources(self) -> None:
73
+ """Clear all resources from the list."""
74
+ self._resources.clear()
75
+
76
+ def has_resource(self, resource: str) -> bool:
77
+ """Check if a resource exists in the list."""
78
+ return resource in self._resources
79
+
80
+ def __len__(self) -> int:
81
+ """Return the number of resources."""
82
+ return len(self._resources)
83
+
84
+ def __iter__(self):
85
+ """Make the class iterable."""
86
+ return iter(self._resources)
87
+
88
+ def __contains__(self, resource: str) -> bool:
89
+ """Support 'in' operator."""
90
+ return resource in self._resources
91
+
92
+ def __str__(self) -> str:
93
+ """String representation."""
94
+ return f"AllowedResources({self._resources})"
95
+
96
+ def __repr__(self) -> str:
97
+ """String representation."""
98
+ return f"AllowedResources({self._resources!r})"
99
+
100
+
101
+ allowed_resources = AllowedResources()
@@ -0,0 +1,69 @@
1
+ import argparse
2
+ import asyncio
3
+ import logging
4
+
5
+ from simstack.core.context import context
6
+ from simstack.core.definitions import TaskStatus
7
+ from simstack.core.node import node_from_database
8
+
9
+ logger = logging.getLogger("SlurmRunner")
10
+
11
+
12
+ async def run_node(node_id: str, **kwargs):
13
+ """Run a single node by its ID from the database"""
14
+ registry_entry = None
15
+ try:
16
+ registry_entry = await context.db.load_task_by_id(node_id)
17
+
18
+ if not registry_entry:
19
+ logger.error(f"Node with ID {node_id} not found in the database")
20
+ return False
21
+ # Create node from the registry entry
22
+ node = await node_from_database(registry_entry)
23
+ if not node:
24
+ logger.error(
25
+ f"Failed to create node from registry entry task_id: {registry_entry.id}"
26
+ )
27
+ registry_entry.status = TaskStatus.FAILED
28
+ await context.db.save(registry_entry)
29
+ return False
30
+ registry_entry = node.registry_entry # it may have changed
31
+ # if the node was recovered we do not have to run it again
32
+ if node.status == TaskStatus.SUBMITTED or node.status == TaskStatus.SLURM_QUEUED or node.status == TaskStatus.SLURM_QUEUED:
33
+ await node.execute_node_locally()
34
+ else:
35
+ logger.info(f"task_id: {registry_entry.id} skipping task: {registry_entry.name} with status {registry_entry.status}")
36
+ return node.status == TaskStatus.COMPLETED
37
+ except Exception as e:
38
+ logger.exception(f"Error running node task_id: {node_id}: {str(e)}")
39
+ if registry_entry:
40
+ registry_entry.status = TaskStatus.FAILED
41
+ await context.db.save(registry_entry)
42
+ return False
43
+
44
+ def run_node_main():
45
+ parser = argparse.ArgumentParser(description="Run nodes for a specific resource")
46
+ parser.add_argument(
47
+ "--node-id",
48
+ type=str,
49
+ help="Specific node ID to run (overrides resource-based polling)",
50
+ )
51
+
52
+ parser.add_argument(
53
+ "--resource",
54
+ default="local",
55
+ nargs="?",
56
+ type=str,
57
+ help="resource to load",
58
+ )
59
+
60
+ args = parser.parse_args()
61
+ context.initialize(resource=args.resource)
62
+
63
+ if args.node_id:
64
+ # Run a specific node once
65
+ asyncio.run(run_node(args.node_id))
66
+
67
+
68
+ if __name__ == "__main__":
69
+ run_node_main()