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.
- simstack/__init__.py +0 -0
- simstack/_version.py +34 -0
- simstack/core/__init__.py +0 -0
- simstack/core/artifacts.py +447 -0
- simstack/core/asnyc_helper.py +14 -0
- simstack/core/config_file.py +60 -0
- simstack/core/context.py +167 -0
- simstack/core/data.py +139 -0
- simstack/core/definitions.py +21 -0
- simstack/core/engine.py +116 -0
- simstack/core/find_simstack_modules.py +43 -0
- simstack/core/hash.py +177 -0
- simstack/core/model_table.py +199 -0
- simstack/core/node.py +911 -0
- simstack/core/node_runner.py +254 -0
- simstack/core/node_table.py +380 -0
- simstack/core/recompute_artifacts.py +120 -0
- simstack/core/reset_db.py +12 -0
- simstack/core/resources.py +101 -0
- simstack/core/run_node.py +69 -0
- simstack/core/runner.py +435 -0
- simstack/core/simstack_result.py +41 -0
- simstack/core/submit_node.py +143 -0
- simstack/core/task_id.py +22 -0
- simstack/data/__init__.py +0 -0
- simstack/methods/__init__.py +0 -0
- simstack/methods/get_file.py +10 -0
- simstack/methods/multiple_executor.py +307 -0
- simstack/models/__init__.py +37 -0
- simstack/models/array_list.py +48 -0
- simstack/models/array_storage.py +54 -0
- simstack/models/artifact_models.py +127 -0
- simstack/models/base_types.py +208 -0
- simstack/models/charts_artifact.py +411 -0
- simstack/models/dataset.py +432 -0
- simstack/models/dataset_metadata.py +303 -0
- simstack/models/file_instance.py +77 -0
- simstack/models/file_list.py +321 -0
- simstack/models/files.py +229 -0
- simstack/models/log_entry_model.py +37 -0
- simstack/models/models.py +40 -0
- simstack/models/node_registry.py +105 -0
- simstack/models/pandas_model.py +210 -0
- simstack/models/parameters.py +388 -0
- simstack/models/pickle_models.py +311 -0
- simstack/models/runner_model.py +39 -0
- simstack/models/simple_table.py +32 -0
- simstack/models/simstack_model.py +97 -0
- simstack/models/slurm_info.py +17 -0
- simstack/models/table_artifact.py +87 -0
- simstack/util/__init__.py +0 -0
- simstack/util/async_zip_utils.py +18 -0
- simstack/util/cleaned_json_schema.py +20 -0
- simstack/util/config_reader.py +273 -0
- simstack/util/custom_model_dump.py +75 -0
- simstack/util/db.py +457 -0
- simstack/util/db_logger.py +117 -0
- simstack/util/default_from_dict.py +57 -0
- simstack/util/directory_iterator.py +216 -0
- simstack/util/file_hashing.py +68 -0
- simstack/util/files.py +396 -0
- simstack/util/find_python_files.py +33 -0
- simstack/util/generate_ui_schema.py +80 -0
- simstack/util/geometry_utils.py +118 -0
- simstack/util/get_module_path.py +26 -0
- simstack/util/git_pull.py +65 -0
- simstack/util/git_repository_status.py +93 -0
- simstack/util/import_module.py +65 -0
- simstack/util/importer.py +163 -0
- simstack/util/make_graph.py +107 -0
- simstack/util/make_table.py +827 -0
- simstack/util/minimal_route_finder.py +72 -0
- simstack/util/mock_db.py +144 -0
- simstack/util/module_path_checker.py +38 -0
- simstack/util/path_manager.py +234 -0
- simstack/util/project_root_finder.py +36 -0
- simstack/util/queue_watcher.py +262 -0
- simstack/util/route_finder.py +74 -0
- simstack/util/runner_util.py +162 -0
- simstack/util/safe_code_executor.py +194 -0
- simstack/util/sample_config.py +98 -0
- simstack/util/sensitive_content_filter.py +48 -0
- simstack/util/setup_logging.py +49 -0
- simstack/util/submit_to_watchdog.py +106 -0
- simstack/util/ui_tools.py +213 -0
- simstack-0.2.0.dist-info/METADATA +361 -0
- simstack-0.2.0.dist-info/RECORD +91 -0
- simstack-0.2.0.dist-info/WHEEL +5 -0
- simstack-0.2.0.dist-info/entry_points.txt +10 -0
- simstack-0.2.0.dist-info/licenses/LICENSE +21 -0
- simstack-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from collections import deque
|
|
2
|
+
from typing import List, Dict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def find_minimal_route(
|
|
6
|
+
routes: List[Dict[str, str]], source: str, target: str
|
|
7
|
+
) -> List[Dict[str, str]]:
|
|
8
|
+
"""
|
|
9
|
+
Find the minimal (shortest) route from source to target.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
routes: List of route dictionaries with 'source', 'target', and 'host'
|
|
13
|
+
source: The starting node
|
|
14
|
+
target: The destination node
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
A list of route dictionaries representing the shortest path from source to target,
|
|
18
|
+
or an empty list if no path exists
|
|
19
|
+
"""
|
|
20
|
+
if not routes:
|
|
21
|
+
return []
|
|
22
|
+
|
|
23
|
+
# Build the graph and route map
|
|
24
|
+
adj: Dict[str, List[str]] = {}
|
|
25
|
+
route_map: Dict[tuple, Dict[str, str]] = {}
|
|
26
|
+
nodes = set()
|
|
27
|
+
|
|
28
|
+
for route in routes:
|
|
29
|
+
src = route["source"]
|
|
30
|
+
dst = route["target"]
|
|
31
|
+
nodes.add(src)
|
|
32
|
+
nodes.add(dst)
|
|
33
|
+
|
|
34
|
+
if src not in adj:
|
|
35
|
+
adj[src] = []
|
|
36
|
+
|
|
37
|
+
# Avoid adding duplicates to adjacency list, but allow route_map update (last one wins)
|
|
38
|
+
if dst not in adj[src]:
|
|
39
|
+
adj[src].append(dst)
|
|
40
|
+
|
|
41
|
+
route_map[(src, dst)] = route
|
|
42
|
+
|
|
43
|
+
if source not in nodes or target not in nodes:
|
|
44
|
+
return []
|
|
45
|
+
|
|
46
|
+
# BFS to find shortest path
|
|
47
|
+
queue = deque([(source, [source])])
|
|
48
|
+
visited = {source}
|
|
49
|
+
|
|
50
|
+
while queue:
|
|
51
|
+
current_node, path = queue.popleft()
|
|
52
|
+
|
|
53
|
+
if current_node == target:
|
|
54
|
+
# Convert the path of nodes to a list of route dictionaries
|
|
55
|
+
path_routes = []
|
|
56
|
+
for i in range(len(path) - 1):
|
|
57
|
+
u = path[i]
|
|
58
|
+
v = path[i + 1]
|
|
59
|
+
path_routes.append(route_map[(u, v)])
|
|
60
|
+
return path_routes
|
|
61
|
+
|
|
62
|
+
if current_node in adj:
|
|
63
|
+
for neighbor in adj[current_node]:
|
|
64
|
+
if neighbor not in visited:
|
|
65
|
+
visited.add(neighbor)
|
|
66
|
+
queue.append((neighbor, path + [neighbor]))
|
|
67
|
+
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Add an alias for find_minimal_route as find_shortest_route for compatibility
|
|
72
|
+
find_shortest_route = find_minimal_route
|
simstack/util/mock_db.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Type, TypeVar, Dict, List, Optional
|
|
3
|
+
|
|
4
|
+
from odmantic import Model, ObjectId
|
|
5
|
+
|
|
6
|
+
T = TypeVar("T", bound=Model)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InMemoryCollection:
|
|
10
|
+
def __init__(self, engine: "InMemoryAIOEngine"):
|
|
11
|
+
self._engine = engine
|
|
12
|
+
self._store: List[Dict] = []
|
|
13
|
+
|
|
14
|
+
def __getitem__(self, index: int) -> Dict:
|
|
15
|
+
return self._store[index]
|
|
16
|
+
|
|
17
|
+
def __setitem__(self, index: int, value: Dict):
|
|
18
|
+
self._store[index] = value
|
|
19
|
+
|
|
20
|
+
def __iter__(self):
|
|
21
|
+
return iter(self._store)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class InMemoryAIOEngine:
|
|
25
|
+
def __init__(self):
|
|
26
|
+
# Stores data as {collection_name: [documents]}
|
|
27
|
+
self._store: Dict[str, InMemoryCollection] = {}
|
|
28
|
+
|
|
29
|
+
async def save(self, instance: T) -> T:
|
|
30
|
+
collection_name = (
|
|
31
|
+
instance.__collection__
|
|
32
|
+
if hasattr(instance, "__collection__")
|
|
33
|
+
else instance.__class__.__name__.lower()
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
obj_dict = instance.model_dump()
|
|
37
|
+
|
|
38
|
+
# Ensure the instance has an ObjectId assigned
|
|
39
|
+
if not instance.id:
|
|
40
|
+
instance.id = ObjectId()
|
|
41
|
+
obj_dict["id"] = instance.id
|
|
42
|
+
|
|
43
|
+
# Initialize collection if it doesn't exist
|
|
44
|
+
if collection_name not in self._store:
|
|
45
|
+
self._store[collection_name] = InMemoryCollection(self)
|
|
46
|
+
|
|
47
|
+
# Update if already exists
|
|
48
|
+
existing_index = next(
|
|
49
|
+
(
|
|
50
|
+
i
|
|
51
|
+
for i, obj in enumerate(self._store[collection_name])
|
|
52
|
+
if obj["id"] == instance.id
|
|
53
|
+
),
|
|
54
|
+
None,
|
|
55
|
+
)
|
|
56
|
+
if existing_index is not None:
|
|
57
|
+
self._store[collection_name][existing_index] = obj_dict
|
|
58
|
+
else:
|
|
59
|
+
self._store[collection_name].append(obj_dict)
|
|
60
|
+
|
|
61
|
+
return instance
|
|
62
|
+
|
|
63
|
+
async def find(self, model: Type[T], **query) -> List[T]:
|
|
64
|
+
collection_name = (
|
|
65
|
+
model.__collection__
|
|
66
|
+
if hasattr(model, "__collection__")
|
|
67
|
+
else model.__name__.lower()
|
|
68
|
+
)
|
|
69
|
+
all_objects = self._store.get(collection_name, [])
|
|
70
|
+
results = []
|
|
71
|
+
|
|
72
|
+
for obj_dict in all_objects:
|
|
73
|
+
if all(obj_dict.get(k) == v for k, v in query.items()):
|
|
74
|
+
results.append(model.model_validate(obj_dict))
|
|
75
|
+
return results
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
async def find_one(self, model: Type[T], condition=None, **query) -> Optional[T]:
|
|
79
|
+
# Handle both keyword arguments and condition objects
|
|
80
|
+
if condition is not None:
|
|
81
|
+
# For conditions like ModelMapping.name == class_name
|
|
82
|
+
# This is a more robust approach to extract field name and value
|
|
83
|
+
condition_str = str(condition)
|
|
84
|
+
|
|
85
|
+
# Look for the pattern: field_name == value or field_name: {$eq: value}
|
|
86
|
+
if "==" in condition_str:
|
|
87
|
+
# Handle direct comparison like ModelMapping.name == 'TestClass'
|
|
88
|
+
parts = condition_str.split("==")
|
|
89
|
+
if len(parts) == 2:
|
|
90
|
+
field_name = parts[0].strip().split(".")[-1]
|
|
91
|
+
value = parts[1].strip().strip("'\"")
|
|
92
|
+
query[field_name] = value
|
|
93
|
+
elif "$eq" in condition_str:
|
|
94
|
+
# Handle MongoDB-style query like {'mapping': {'$eq': 'tests.core.test_import_class.TestClass'}}
|
|
95
|
+
import re
|
|
96
|
+
|
|
97
|
+
# Extract field name and value using regex
|
|
98
|
+
field_match = re.search(
|
|
99
|
+
r"'(\w+)':\s*\{'?\$eq'?:\s*'([^']+)'", condition_str
|
|
100
|
+
)
|
|
101
|
+
if field_match:
|
|
102
|
+
field_name = field_match.group(1)
|
|
103
|
+
value = field_match.group(2)
|
|
104
|
+
query[field_name] = value
|
|
105
|
+
else:
|
|
106
|
+
# Fallback - try to extract from the string representation
|
|
107
|
+
# Look for pattern like "ModelMapping.field_name"
|
|
108
|
+
field_match = re.search(r"(\w+)\.(\w+)", condition_str)
|
|
109
|
+
if field_match:
|
|
110
|
+
field_name = field_match.group(2)
|
|
111
|
+
# Try to extract the value (this is still a fallback)
|
|
112
|
+
value_match = re.search(r"'([^']+)'(?:[^']*$)", condition_str)
|
|
113
|
+
if value_match:
|
|
114
|
+
value = value_match.group(1)
|
|
115
|
+
query[field_name] = value
|
|
116
|
+
|
|
117
|
+
found = await self.find(model, **query)
|
|
118
|
+
return found[0] if found else None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# Example Usage:
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
from odmantic import Model
|
|
124
|
+
|
|
125
|
+
class BinaryOperationInput(Model):
|
|
126
|
+
arg1: int
|
|
127
|
+
arg2: int
|
|
128
|
+
|
|
129
|
+
async def test_engine():
|
|
130
|
+
engine = InMemoryAIOEngine()
|
|
131
|
+
obj = BinaryOperationInput(arg1=1, arg2=2)
|
|
132
|
+
|
|
133
|
+
# Saving object
|
|
134
|
+
await engine.save(obj)
|
|
135
|
+
|
|
136
|
+
# Finding objects
|
|
137
|
+
found_objs = await engine.find(BinaryOperationInput, arg1=1)
|
|
138
|
+
print("Found objects:", found_objs)
|
|
139
|
+
|
|
140
|
+
# Find one object
|
|
141
|
+
found_one = await engine.find_one(BinaryOperationInput, arg2=2)
|
|
142
|
+
print("Found single object:", found_one)
|
|
143
|
+
|
|
144
|
+
asyncio.run(test_engine())
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from simstack.util.project_root_finder import find_project_root
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def is_module_subpath_of_path(module_path: str, path_info_path: str) -> bool:
|
|
8
|
+
"""
|
|
9
|
+
Check whether a dot-separated module path is a subpath of an absolute path.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
module_path: A dot-separated path relative to the project root (e.g., "src.simstack.core.artifacts")
|
|
13
|
+
path_info_path: An absolute path below the project root (e.g., "/path/to/project/src/simstack/core")
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
True if the module path corresponds to a location under the given absolute path, False otherwise
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# Convert the module path to file system path
|
|
20
|
+
# Replace dots with path separators
|
|
21
|
+
module_file_path = module_path.replace(".", os.sep)
|
|
22
|
+
|
|
23
|
+
# Convert path_info_path to Path object for easier manipulation
|
|
24
|
+
path_info_path_obj = Path(path_info_path)
|
|
25
|
+
|
|
26
|
+
# Get the project root using the existing utility function
|
|
27
|
+
project_root = Path(find_project_root())
|
|
28
|
+
|
|
29
|
+
# Convert module path to absolute path relative to project root
|
|
30
|
+
module_absolute_path = project_root / module_file_path
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
# Check if the module path is under the given path_info_path
|
|
34
|
+
module_absolute_path.relative_to(path_info_path_obj)
|
|
35
|
+
return True
|
|
36
|
+
except ValueError:
|
|
37
|
+
# If relative_to raises ValueError, the module path is not under path_info_path
|
|
38
|
+
return False
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import List, Dict, Optional, Iterator, Any
|
|
4
|
+
|
|
5
|
+
from simstack.util.directory_iterator import DirectoryPath
|
|
6
|
+
from simstack.util.project_root_finder import find_project_root
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PathManager:
|
|
10
|
+
"""
|
|
11
|
+
Manages paths for the SimStack application, providing mechanisms to find Python files
|
|
12
|
+
and other resources. Uses DirectoryPath for efficient directory traversal.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, use_pickle: bool = False):
|
|
16
|
+
"""
|
|
17
|
+
Initialize the PathManager.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
use_pickle: Boolean flag indicating whether to use pickle for serialization
|
|
21
|
+
"""
|
|
22
|
+
self.use_pickle = use_pickle
|
|
23
|
+
self.root_dir = find_project_root()
|
|
24
|
+
self.paths: Dict[str, Dict[str, str]] = {}
|
|
25
|
+
self.packages: Dict[str] = {}
|
|
26
|
+
self.precompiled: Dict[str] = {}
|
|
27
|
+
self._default_excluded_patterns = [
|
|
28
|
+
"__pycache__",
|
|
29
|
+
"*.pyc",
|
|
30
|
+
".git",
|
|
31
|
+
".venv",
|
|
32
|
+
"venv",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
def add_path(
|
|
36
|
+
self, name: str, path: str, drops: str = "", use_pickle: bool = False
|
|
37
|
+
) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Add a path to the manager.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
name: Name identifier for the path
|
|
43
|
+
path: The directory path
|
|
44
|
+
drops: Prefix to drop from module names (for import paths)
|
|
45
|
+
use_pickle: Whether to use pickle for this path
|
|
46
|
+
"""
|
|
47
|
+
if not os.path.isdir(path):
|
|
48
|
+
raise ValueError(f"'{path}' is not a valid directory")
|
|
49
|
+
|
|
50
|
+
self.paths[name] = {"path": path, "drops": drops, "use_pickle": use_pickle}
|
|
51
|
+
|
|
52
|
+
def get_path(self, name: str) -> Dict[str, str]:
|
|
53
|
+
"""
|
|
54
|
+
Get a path by name.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
name: Name of the path to retrieve
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Dictionary containing path information
|
|
61
|
+
"""
|
|
62
|
+
if name not in self.paths:
|
|
63
|
+
raise KeyError(f"Path '{name}' not found in PathManager")
|
|
64
|
+
|
|
65
|
+
return self.paths[name]
|
|
66
|
+
|
|
67
|
+
def find_python_files(
|
|
68
|
+
self, path_name: str, excluded_patterns: Optional[List[str]] = None
|
|
69
|
+
) -> List[str]:
|
|
70
|
+
"""
|
|
71
|
+
Find Python files in the specified path, excluding __init__.py files.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
path_name: Name of the path to search in
|
|
75
|
+
excluded_patterns: Additional patterns to exclude
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
List of absolute file paths to Python files
|
|
79
|
+
"""
|
|
80
|
+
path_info = self.get_path(path_name)
|
|
81
|
+
path = path_info["path"]
|
|
82
|
+
|
|
83
|
+
# Combine default and additional exclusion patterns
|
|
84
|
+
all_excluded_patterns = self._default_excluded_patterns.copy()
|
|
85
|
+
if excluded_patterns:
|
|
86
|
+
all_excluded_patterns.extend(excluded_patterns)
|
|
87
|
+
|
|
88
|
+
# Add __init__.py to excluded patterns
|
|
89
|
+
all_excluded_patterns.append("__init__.py")
|
|
90
|
+
|
|
91
|
+
# Use DirectoryPath to find Python files
|
|
92
|
+
dir_path = DirectoryPath(path, all_excluded_patterns, [".py"])
|
|
93
|
+
|
|
94
|
+
# Convert Path objects to strings for compatibility with existing code
|
|
95
|
+
return [str(file_path) for file_path in dir_path.get_files_list()]
|
|
96
|
+
|
|
97
|
+
def iterate_python_files(
|
|
98
|
+
self, path_name: str, excluded_patterns: Optional[List[str]] = None
|
|
99
|
+
) -> Iterator[Path]:
|
|
100
|
+
"""
|
|
101
|
+
Iterate over Python files in the specified path, excluding __init__.py files.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
path_name: Name of the path to search in
|
|
105
|
+
excluded_patterns: Additional patterns to exclude
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Iterator of Path objects for Python files
|
|
109
|
+
"""
|
|
110
|
+
path_info = self.get_path(path_name)
|
|
111
|
+
path = path_info["path"]
|
|
112
|
+
|
|
113
|
+
# Combine default and additional exclusion patterns
|
|
114
|
+
all_excluded_patterns = self._default_excluded_patterns.copy()
|
|
115
|
+
if excluded_patterns:
|
|
116
|
+
all_excluded_patterns.extend(excluded_patterns)
|
|
117
|
+
|
|
118
|
+
# Add __init__.py to excluded patterns
|
|
119
|
+
all_excluded_patterns.append("__init__.py")
|
|
120
|
+
|
|
121
|
+
# Use DirectoryPath to iterate over Python files
|
|
122
|
+
dir_path = DirectoryPath(path, all_excluded_patterns, [".py"])
|
|
123
|
+
return dir_path.iterate_files()
|
|
124
|
+
|
|
125
|
+
def get_drops(self, path_name: str) -> str:
|
|
126
|
+
"""
|
|
127
|
+
Get the drops value for a path.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
path_name: Name of the path
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
The drops value for the path
|
|
134
|
+
"""
|
|
135
|
+
path_info = self.get_path(path_name)
|
|
136
|
+
return path_info["drops"]
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def from_config(cls, config: Any) -> "PathManager":
|
|
140
|
+
"""
|
|
141
|
+
Create a PathManager from configuration.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
config: Configuration object containing path information
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Initialized PathManager instance
|
|
148
|
+
"""
|
|
149
|
+
# Get use_pickle from config
|
|
150
|
+
use_pickle = False
|
|
151
|
+
if hasattr(config, "config"):
|
|
152
|
+
# If config is a ConfigReader instance
|
|
153
|
+
use_pickle = (
|
|
154
|
+
config.config.get("parameters", {})
|
|
155
|
+
.get("common", {})
|
|
156
|
+
.get("use_pickle", False)
|
|
157
|
+
)
|
|
158
|
+
else:
|
|
159
|
+
# If config is a dictionary
|
|
160
|
+
use_pickle = config.get("use_pickle", False)
|
|
161
|
+
|
|
162
|
+
path_manager = cls(use_pickle=use_pickle)
|
|
163
|
+
|
|
164
|
+
# Get paths from config
|
|
165
|
+
if hasattr(config, "paths"):
|
|
166
|
+
# If config is a ConfigReader instance
|
|
167
|
+
paths = config.paths
|
|
168
|
+
else:
|
|
169
|
+
# If config is a dictionary
|
|
170
|
+
paths = config.get("paths", {})
|
|
171
|
+
|
|
172
|
+
# Add paths from configuration if available
|
|
173
|
+
root_dir = find_project_root()
|
|
174
|
+
for name, path_info in paths.items():
|
|
175
|
+
if isinstance(path_info, dict) and "path" in path_info:
|
|
176
|
+
# Get path, drops, and use_pickle from path_info
|
|
177
|
+
path = path_info["path"]
|
|
178
|
+
# Convert Unix-style path to Windows path if on Windows
|
|
179
|
+
if os.name == "nt": # Windows OS
|
|
180
|
+
path = path.replace("/", "\\")
|
|
181
|
+
|
|
182
|
+
drops = path_info.get("drops", "")
|
|
183
|
+
path_use_pickle = path_info.get("use_pickle", False)
|
|
184
|
+
|
|
185
|
+
# Convert relative paths to absolute paths
|
|
186
|
+
if not os.path.isabs(path):
|
|
187
|
+
path = os.path.join(root_dir, path)
|
|
188
|
+
|
|
189
|
+
# Add path to PathManager
|
|
190
|
+
path_manager.add_path(name, path, drops, path_use_pickle)
|
|
191
|
+
|
|
192
|
+
return path_manager
|
|
193
|
+
|
|
194
|
+
def find_parent_path(self, path: str) -> Optional[str]:
|
|
195
|
+
"""
|
|
196
|
+
Find the parent path from self.paths that contains the given path.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
path: The path to find the parent of
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
The name of the parent path from self.paths that contains the given path,
|
|
203
|
+
or None if no parent is found
|
|
204
|
+
"""
|
|
205
|
+
path_obj = Path(path)
|
|
206
|
+
|
|
207
|
+
# Convert to the absolute path if needed
|
|
208
|
+
if not path_obj.is_absolute():
|
|
209
|
+
path_obj = self.root_dir / path_obj
|
|
210
|
+
|
|
211
|
+
# Search through all paths in self.paths to find the one that contains path_obj
|
|
212
|
+
best_match = None
|
|
213
|
+
best_match_depth = -1
|
|
214
|
+
|
|
215
|
+
for path_name, path_info in self.paths.items():
|
|
216
|
+
registered_path = Path(path_info["path"])
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
# Check if path_obj is under this registered path
|
|
220
|
+
path_obj.relative_to(registered_path)
|
|
221
|
+
|
|
222
|
+
# Calculate depth (number of parent directories)
|
|
223
|
+
depth = len(registered_path.parts)
|
|
224
|
+
|
|
225
|
+
# Keep the deepest match (the most specific parent)
|
|
226
|
+
if depth > best_match_depth:
|
|
227
|
+
best_match = path_name
|
|
228
|
+
best_match_depth = depth
|
|
229
|
+
|
|
230
|
+
except ValueError:
|
|
231
|
+
# path_obj is not under this registered path, continue
|
|
232
|
+
continue
|
|
233
|
+
|
|
234
|
+
return best_match
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def find_project_root(
|
|
5
|
+
current_file=None, marker_files=(".git", "simstack.toml", "setup.py")
|
|
6
|
+
):
|
|
7
|
+
"""
|
|
8
|
+
Find the project root directory by searching for common marker files
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
current_file: Path to the current file, defaults to __file__ if None
|
|
12
|
+
marker_files: Tuple of files/directories that indicate the project root
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
Absolute path to the project root directory
|
|
16
|
+
"""
|
|
17
|
+
if current_file is None:
|
|
18
|
+
current_file = __file__
|
|
19
|
+
|
|
20
|
+
# Get the directory of the current file
|
|
21
|
+
current_dir = os.path.abspath(os.path.dirname(current_file))
|
|
22
|
+
|
|
23
|
+
# Walk up the directory tree until we find a marker file
|
|
24
|
+
prev_dir = None
|
|
25
|
+
while current_dir != prev_dir:
|
|
26
|
+
# Check if any marker files/directories exist in the current directory
|
|
27
|
+
for marker in marker_files:
|
|
28
|
+
if os.path.exists(os.path.join(current_dir, marker)):
|
|
29
|
+
return current_dir
|
|
30
|
+
|
|
31
|
+
# Move up one directory
|
|
32
|
+
prev_dir = current_dir
|
|
33
|
+
current_dir = os.path.abspath(os.path.join(current_dir, os.pardir))
|
|
34
|
+
|
|
35
|
+
# If we can't find any markers, return the directory of the current file
|
|
36
|
+
return os.path.abspath(os.path.dirname(current_file))
|