tooluniverse 1.0.9__py3-none-any.whl → 1.0.10__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.
Potentially problematic release.
This version of tooluniverse might be problematic. Click here for more details.
- tooluniverse/admetai_tool.py +1 -1
- tooluniverse/agentic_tool.py +65 -17
- tooluniverse/base_tool.py +19 -8
- tooluniverse/boltz_tool.py +1 -1
- tooluniverse/cache/result_cache_manager.py +167 -12
- tooluniverse/compose_scripts/drug_safety_analyzer.py +1 -1
- tooluniverse/compose_scripts/multi_agent_literature_search.py +1 -1
- tooluniverse/compose_scripts/output_summarizer.py +4 -4
- tooluniverse/compose_scripts/tool_graph_composer.py +1 -1
- tooluniverse/compose_scripts/tool_metadata_generator.py +1 -1
- tooluniverse/compose_tool.py +9 -9
- tooluniverse/core_tool.py +2 -2
- tooluniverse/ctg_tool.py +4 -4
- tooluniverse/custom_tool.py +1 -1
- tooluniverse/dataset_tool.py +2 -2
- tooluniverse/default_config.py +1 -1
- tooluniverse/enrichr_tool.py +14 -14
- tooluniverse/execute_function.py +520 -15
- tooluniverse/extended_hooks.py +4 -4
- tooluniverse/gene_ontology_tool.py +1 -1
- tooluniverse/generate_tools.py +3 -3
- tooluniverse/humanbase_tool.py +10 -10
- tooluniverse/logging_config.py +2 -2
- tooluniverse/mcp_client_tool.py +57 -129
- tooluniverse/mcp_integration.py +52 -49
- tooluniverse/mcp_tool_registry.py +147 -528
- tooluniverse/openalex_tool.py +8 -8
- tooluniverse/openfda_tool.py +2 -2
- tooluniverse/output_hook.py +15 -15
- tooluniverse/package_tool.py +1 -1
- tooluniverse/pmc_tool.py +2 -2
- tooluniverse/remote/boltz/boltz_mcp_server.py +1 -1
- tooluniverse/remote/depmap_24q2/depmap_24q2_mcp_tool.py +2 -2
- tooluniverse/remote/immune_compass/compass_tool.py +3 -3
- tooluniverse/remote/pinnacle/pinnacle_tool.py +2 -2
- tooluniverse/remote/transcriptformer/transcriptformer_tool.py +3 -3
- tooluniverse/remote/uspto_downloader/uspto_downloader_mcp_server.py +3 -3
- tooluniverse/remote_tool.py +4 -4
- tooluniverse/scripts/filter_tool_files.py +2 -2
- tooluniverse/smcp.py +93 -12
- tooluniverse/smcp_server.py +100 -20
- tooluniverse/space/__init__.py +46 -0
- tooluniverse/space/loader.py +133 -0
- tooluniverse/space/validator.py +353 -0
- tooluniverse/tool_finder_embedding.py +2 -2
- tooluniverse/tool_finder_keyword.py +9 -9
- tooluniverse/tool_finder_llm.py +6 -6
- tooluniverse/tools/_shared_client.py +3 -3
- tooluniverse/url_tool.py +1 -1
- tooluniverse/uspto_tool.py +1 -1
- tooluniverse/utils.py +10 -10
- {tooluniverse-1.0.9.dist-info → tooluniverse-1.0.10.dist-info}/METADATA +7 -3
- {tooluniverse-1.0.9.dist-info → tooluniverse-1.0.10.dist-info}/RECORD +57 -54
- {tooluniverse-1.0.9.dist-info → tooluniverse-1.0.10.dist-info}/WHEEL +0 -0
- {tooluniverse-1.0.9.dist-info → tooluniverse-1.0.10.dist-info}/entry_points.txt +0 -0
- {tooluniverse-1.0.9.dist-info → tooluniverse-1.0.10.dist-info}/licenses/LICENSE +0 -0
- {tooluniverse-1.0.9.dist-info → tooluniverse-1.0.10.dist-info}/top_level.txt +0 -0
tooluniverse/smcp_server.py
CHANGED
|
@@ -37,6 +37,31 @@ Examples:
|
|
|
37
37
|
|
|
38
38
|
# Start with custom hook configuration
|
|
39
39
|
tooluniverse-smcp-server --hook-config-file /path/to/hook_config.json
|
|
40
|
+
|
|
41
|
+
# Load Space configuration
|
|
42
|
+
tooluniverse-smcp-server --load "community/proteomics-toolkit"
|
|
43
|
+
tooluniverse-smcp-server --load "./my-config.yaml"
|
|
44
|
+
""",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Space configuration options
|
|
48
|
+
space_group = parser.add_argument_group("Space Configuration")
|
|
49
|
+
space_group.add_argument(
|
|
50
|
+
"--load",
|
|
51
|
+
"-l",
|
|
52
|
+
type=str,
|
|
53
|
+
metavar="CONFIG",
|
|
54
|
+
help="""Load space configuration (preset/workspace).
|
|
55
|
+
|
|
56
|
+
Supports multiple formats:
|
|
57
|
+
• HuggingFace: username/repo, hf:username/repo@v1.0.0
|
|
58
|
+
• Local files: ./config.yaml, /absolute/path.yaml
|
|
59
|
+
• HTTP URLs: https://example.com/config.yaml
|
|
60
|
+
|
|
61
|
+
Examples:
|
|
62
|
+
--load "community/proteomics-toolkit"
|
|
63
|
+
--load "./my-config.yaml"
|
|
64
|
+
--load "https://example.com/config.yaml"
|
|
40
65
|
""",
|
|
41
66
|
)
|
|
42
67
|
|
|
@@ -106,9 +131,10 @@ Examples:
|
|
|
106
131
|
|
|
107
132
|
print()
|
|
108
133
|
|
|
109
|
-
# Create SMCP server with
|
|
134
|
+
# Create SMCP server with Space support
|
|
110
135
|
server = SMCP(
|
|
111
136
|
name=args.name,
|
|
137
|
+
space=args.load, # Pass Space URI directly to SMCP
|
|
112
138
|
auto_expose_tools=True,
|
|
113
139
|
search_enabled=True,
|
|
114
140
|
max_workers=5,
|
|
@@ -157,47 +183,73 @@ def run_stdio_server():
|
|
|
157
183
|
epilog="""
|
|
158
184
|
Examples:
|
|
159
185
|
# Start server with all tools using stdio transport (hooks enabled by default)
|
|
160
|
-
tooluniverse-stdio
|
|
186
|
+
tooluniverse-smcp-stdio
|
|
161
187
|
|
|
162
188
|
# Start with specific categories
|
|
163
|
-
tooluniverse-stdio --categories uniprot ChEMBL opentarget
|
|
189
|
+
tooluniverse-smcp-stdio --categories uniprot ChEMBL opentarget
|
|
164
190
|
|
|
165
191
|
# Enable hooks
|
|
166
|
-
tooluniverse-stdio --hooks
|
|
192
|
+
tooluniverse-smcp-stdio --hooks
|
|
167
193
|
|
|
168
194
|
# Use FileSaveHook instead of SummarizationHook
|
|
169
|
-
tooluniverse-stdio --hook-type FileSaveHook
|
|
195
|
+
tooluniverse-smcp-stdio --hook-type FileSaveHook
|
|
170
196
|
|
|
171
197
|
# Use custom hook configuration
|
|
172
|
-
tooluniverse-stdio --hook-config-file /path/to/hook_config.json
|
|
198
|
+
tooluniverse-smcp-stdio --hook-config-file /path/to/hook_config.json
|
|
173
199
|
|
|
174
200
|
# Start with categories but exclude specific tools
|
|
175
|
-
tooluniverse-stdio --categories uniprot ChEMBL --exclude-tools "ChEMBL_get_molecule_by_chembl_id"
|
|
201
|
+
tooluniverse-smcp-stdio --categories uniprot ChEMBL --exclude-tools "ChEMBL_get_molecule_by_chembl_id"
|
|
176
202
|
|
|
177
203
|
# Start with all tools but exclude entire categories
|
|
178
|
-
tooluniverse-stdio --exclude-categories mcp_auto_loader_boltz mcp_auto_loader_expert_feedback
|
|
204
|
+
tooluniverse-smcp-stdio --exclude-categories mcp_auto_loader_boltz mcp_auto_loader_expert_feedback
|
|
179
205
|
|
|
180
206
|
# Load only specific tools by name
|
|
181
|
-
tooluniverse-stdio --include-tools "UniProt_get_entry_by_accession" "ChEMBL_get_molecule_by_chembl_id"
|
|
207
|
+
tooluniverse-smcp-stdio --include-tools "UniProt_get_entry_by_accession" "ChEMBL_get_molecule_by_chembl_id"
|
|
182
208
|
|
|
183
209
|
# Load tools from a file
|
|
184
|
-
tooluniverse-stdio --tools-file "/path/to/tool_names.txt"
|
|
210
|
+
tooluniverse-smcp-stdio --tools-file "/path/to/tool_names.txt"
|
|
185
211
|
|
|
186
212
|
# Load additional config files
|
|
187
|
-
tooluniverse-stdio --tool-config-files "custom:/path/to/custom_tools.json"
|
|
213
|
+
tooluniverse-smcp-stdio --tool-config-files "custom:/path/to/custom_tools.json"
|
|
188
214
|
|
|
189
215
|
# Include/exclude specific tool types
|
|
190
|
-
tooluniverse-stdio --include-tool-types "OpenTarget" "ToolFinderEmbedding"
|
|
191
|
-
tooluniverse-stdio --exclude-tool-types "ToolFinderLLM" "Unknown"
|
|
216
|
+
tooluniverse-smcp-stdio --include-tool-types "OpenTarget" "ToolFinderEmbedding"
|
|
217
|
+
tooluniverse-smcp-stdio --exclude-tool-types "ToolFinderLLM" "Unknown"
|
|
192
218
|
|
|
193
219
|
# List available categories
|
|
194
|
-
tooluniverse-stdio --list-categories
|
|
220
|
+
tooluniverse-smcp-stdio --list-categories
|
|
195
221
|
|
|
196
222
|
# List all available tools
|
|
197
|
-
tooluniverse-stdio --list-tools
|
|
223
|
+
tooluniverse-smcp-stdio --list-tools
|
|
198
224
|
|
|
199
225
|
# Start minimal server with just search tools
|
|
200
|
-
tooluniverse-stdio --categories special_tools tool_finder
|
|
226
|
+
tooluniverse-smcp-stdio --categories special_tools tool_finder
|
|
227
|
+
|
|
228
|
+
# Load Space configuration
|
|
229
|
+
tooluniverse-smcp-stdio --load "hf:community/proteomics-toolkit"
|
|
230
|
+
tooluniverse-smcp-stdio --load "./my-config.yaml"
|
|
231
|
+
tooluniverse-smcp-stdio --load "https://example.com/config.yaml"
|
|
232
|
+
""",
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Space configuration options
|
|
236
|
+
space_group = parser.add_argument_group("Space Configuration")
|
|
237
|
+
space_group.add_argument(
|
|
238
|
+
"--load",
|
|
239
|
+
"-l",
|
|
240
|
+
type=str,
|
|
241
|
+
metavar="CONFIG",
|
|
242
|
+
help="""Load space configuration (preset/workspace).
|
|
243
|
+
|
|
244
|
+
Supports multiple formats:
|
|
245
|
+
• HuggingFace: username/repo, hf:username/repo@v1.0.0
|
|
246
|
+
• Local files: ./config.yaml, /absolute/path.yaml
|
|
247
|
+
• HTTP URLs: https://example.com/config.yaml
|
|
248
|
+
|
|
249
|
+
Examples:
|
|
250
|
+
--load "community/proteomics-toolkit"
|
|
251
|
+
--load "./my-config.yaml"
|
|
252
|
+
--load "https://example.com/config.yaml"
|
|
201
253
|
""",
|
|
202
254
|
)
|
|
203
255
|
|
|
@@ -378,11 +430,12 @@ Examples:
|
|
|
378
430
|
total_tools = 0
|
|
379
431
|
for category in sorted(tools_by_category.keys()):
|
|
380
432
|
tools = sorted(tools_by_category[category])
|
|
381
|
-
print(f"\n📁 {category} ({len(tools, file=sys.stderr)
|
|
433
|
+
print(f"\n📁 {category} ({len(tools)} tools):", file=sys.stderr)
|
|
382
434
|
for tool in tools[:10]: # Show first 10 tools per category
|
|
383
435
|
print(f" {tool}", file=sys.stderr)
|
|
384
436
|
if len(tools) > 10:
|
|
385
|
-
|
|
437
|
+
# Print remaining count to stderr
|
|
438
|
+
print(f" ... and {len(tools) - 10} more tools", file=sys.stderr)
|
|
386
439
|
total_tools += len(tools)
|
|
387
440
|
|
|
388
441
|
print(f"\nTotal: {total_tools} tools available", file=sys.stderr)
|
|
@@ -508,9 +561,10 @@ Examples:
|
|
|
508
561
|
print(f"⚡ Max workers: {args.max_workers}", file=sys.stderr)
|
|
509
562
|
print(file=sys.stderr)
|
|
510
563
|
|
|
511
|
-
# Create SMCP server with
|
|
564
|
+
# Create SMCP server with Space and tool configuration support
|
|
512
565
|
server = SMCP(
|
|
513
566
|
name=args.name,
|
|
567
|
+
space=args.load, # Pass Space URI directly to SMCP
|
|
514
568
|
tool_categories=tool_categories,
|
|
515
569
|
exclude_tools=exclude_tools,
|
|
516
570
|
exclude_categories=exclude_categories,
|
|
@@ -588,6 +642,31 @@ Examples:
|
|
|
588
642
|
|
|
589
643
|
# Start server for Claude Desktop (stdio transport)
|
|
590
644
|
tooluniverse-smcp --transport stdio
|
|
645
|
+
|
|
646
|
+
# Load Space configuration
|
|
647
|
+
tooluniverse-smcp --load "community/proteomics-toolkit" --port 8000
|
|
648
|
+
tooluniverse-smcp --load "./my-config.yaml" --transport stdio
|
|
649
|
+
""",
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
# Space configuration options
|
|
653
|
+
space_group = parser.add_argument_group("Space Configuration")
|
|
654
|
+
space_group.add_argument(
|
|
655
|
+
"--load",
|
|
656
|
+
"-l",
|
|
657
|
+
type=str,
|
|
658
|
+
metavar="CONFIG",
|
|
659
|
+
help="""Load space configuration (preset/workspace).
|
|
660
|
+
|
|
661
|
+
Supports multiple formats:
|
|
662
|
+
• HuggingFace: username/repo, hf:username/repo@v1.0.0
|
|
663
|
+
• Local files: ./config.yaml, /absolute/path.yaml
|
|
664
|
+
• HTTP URLs: https://example.com/config.yaml
|
|
665
|
+
|
|
666
|
+
Examples:
|
|
667
|
+
--load "community/proteomics-toolkit"
|
|
668
|
+
--load "./my-config.yaml"
|
|
669
|
+
--load "https://example.com/config.yaml"
|
|
591
670
|
""",
|
|
592
671
|
)
|
|
593
672
|
|
|
@@ -873,9 +952,10 @@ Examples:
|
|
|
873
952
|
print(f"⚡ Max workers: {args.max_workers}")
|
|
874
953
|
print()
|
|
875
954
|
|
|
876
|
-
# Create SMCP server with hook support
|
|
955
|
+
# Create SMCP server with Space and hook support
|
|
877
956
|
server = SMCP(
|
|
878
957
|
name=args.name,
|
|
958
|
+
space=args.load, # Pass Space URI directly to SMCP
|
|
879
959
|
tool_categories=tool_categories,
|
|
880
960
|
exclude_tools=exclude_tools,
|
|
881
961
|
exclude_categories=exclude_categories,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ToolUniverse Space Configuration Management
|
|
3
|
+
|
|
4
|
+
This module provides tools for loading, validating, and managing ToolUniverse Space configurations.
|
|
5
|
+
Space allows users to define collections of tools with specific configurations,
|
|
6
|
+
LLM settings, and hooks for advanced scientific workflows.
|
|
7
|
+
|
|
8
|
+
Main Components:
|
|
9
|
+
- SpaceLoader: Loads Space configurations from various sources (HuggingFace, local files, URLs)
|
|
10
|
+
- SpaceValidator: Validates Space configurations using JSON Schema
|
|
11
|
+
- ValidationError: Exception raised when configuration validation fails
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
from tooluniverse.space import SpaceLoader, validate_space_config
|
|
15
|
+
|
|
16
|
+
# Load a Space configuration
|
|
17
|
+
loader = SpaceLoader()
|
|
18
|
+
config = loader.load("hf:user/repo")
|
|
19
|
+
|
|
20
|
+
# Validate a configuration
|
|
21
|
+
is_valid, errors = validate_space_config(config)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from .loader import SpaceLoader
|
|
25
|
+
from .validator import (
|
|
26
|
+
validate_space_config,
|
|
27
|
+
validate_with_schema,
|
|
28
|
+
validate_yaml_file_with_schema,
|
|
29
|
+
validate_yaml_format_by_template,
|
|
30
|
+
validate_yaml_file,
|
|
31
|
+
fill_defaults,
|
|
32
|
+
ValidationError,
|
|
33
|
+
SPACE_SCHEMA,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"SpaceLoader",
|
|
38
|
+
"validate_space_config",
|
|
39
|
+
"validate_with_schema",
|
|
40
|
+
"validate_yaml_file_with_schema",
|
|
41
|
+
"validate_yaml_format_by_template",
|
|
42
|
+
"validate_yaml_file",
|
|
43
|
+
"fill_defaults",
|
|
44
|
+
"ValidationError",
|
|
45
|
+
"SPACE_SCHEMA",
|
|
46
|
+
]
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ToolUniverse Space Configuration Loader
|
|
3
|
+
|
|
4
|
+
Simplified loader supporting HuggingFace, local files, and HTTP/HTTPS.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Dict, Optional, Union
|
|
10
|
+
import yaml
|
|
11
|
+
import requests
|
|
12
|
+
from huggingface_hub import hf_hub_download
|
|
13
|
+
|
|
14
|
+
from ..utils import get_user_cache_dir
|
|
15
|
+
from .validator import validate_space_config
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SpaceLoader:
|
|
19
|
+
"""Simplified loader for ToolUniverse Space configurations."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, cache_dir: Optional[Union[str, Path]] = None):
|
|
22
|
+
"""
|
|
23
|
+
Initialize the Space loader.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
cache_dir: Directory for caching downloaded configurations
|
|
27
|
+
"""
|
|
28
|
+
if cache_dir:
|
|
29
|
+
self.cache_dir = Path(cache_dir)
|
|
30
|
+
else:
|
|
31
|
+
self.cache_dir = Path(get_user_cache_dir()) / "spaces"
|
|
32
|
+
|
|
33
|
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
|
|
35
|
+
def load(self, uri: str) -> Dict[str, Any]:
|
|
36
|
+
"""
|
|
37
|
+
Load Space configuration from URI.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
uri: Space URI (e.g., "hf:user/repo", "./config.yaml", "https://example.com/config.yaml")
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
Loaded configuration dictionary
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
ValueError: If URI is unsupported or configuration is invalid
|
|
47
|
+
"""
|
|
48
|
+
# Use URI directly (no alias resolution)
|
|
49
|
+
resolved_uri = uri
|
|
50
|
+
|
|
51
|
+
# Detect URI type
|
|
52
|
+
if resolved_uri.startswith("hf:"):
|
|
53
|
+
config = self._load_from_hf(resolved_uri)
|
|
54
|
+
elif resolved_uri.startswith(("http://", "https://")):
|
|
55
|
+
config = self._load_from_url(resolved_uri)
|
|
56
|
+
else:
|
|
57
|
+
config = self._load_from_file(resolved_uri)
|
|
58
|
+
|
|
59
|
+
# Validate configuration
|
|
60
|
+
is_valid, errors = validate_space_config(config)
|
|
61
|
+
if not is_valid:
|
|
62
|
+
error_msg = "Configuration validation failed:\n" + "\n".join(
|
|
63
|
+
f" - {e}" for e in errors
|
|
64
|
+
)
|
|
65
|
+
raise ValueError(error_msg)
|
|
66
|
+
|
|
67
|
+
return config
|
|
68
|
+
|
|
69
|
+
def _load_from_hf(self, uri: str) -> Dict[str, Any]:
|
|
70
|
+
"""Load configuration from HuggingFace Hub."""
|
|
71
|
+
repo_id = uri[3:] # Remove 'hf:' prefix
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
# Try to download the config file
|
|
75
|
+
config_path = hf_hub_download(
|
|
76
|
+
repo_id=repo_id,
|
|
77
|
+
filename="space.yaml",
|
|
78
|
+
cache_dir=self.cache_dir,
|
|
79
|
+
local_files_only=False,
|
|
80
|
+
)
|
|
81
|
+
return self._load_from_file(config_path)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
# Fallback: try space.json
|
|
84
|
+
try:
|
|
85
|
+
config_path = hf_hub_download(
|
|
86
|
+
repo_id=repo_id,
|
|
87
|
+
filename="space.json",
|
|
88
|
+
cache_dir=self.cache_dir,
|
|
89
|
+
local_files_only=False,
|
|
90
|
+
)
|
|
91
|
+
return self._load_from_file(config_path)
|
|
92
|
+
except Exception:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
f"Failed to load Space from HuggingFace {repo_id}: {e}"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def _load_from_url(self, uri: str) -> Dict[str, Any]:
|
|
98
|
+
"""Load configuration from HTTP/HTTPS URL."""
|
|
99
|
+
try:
|
|
100
|
+
response = requests.get(uri, timeout=30)
|
|
101
|
+
response.raise_for_status()
|
|
102
|
+
|
|
103
|
+
# Try YAML first, then JSON
|
|
104
|
+
try:
|
|
105
|
+
return yaml.safe_load(response.text)
|
|
106
|
+
except yaml.YAMLError:
|
|
107
|
+
return response.json()
|
|
108
|
+
except Exception as e:
|
|
109
|
+
raise ValueError(f"Failed to load Space from URL {uri}: {e}")
|
|
110
|
+
|
|
111
|
+
def _load_from_file(self, file_path: str) -> Dict[str, Any]:
|
|
112
|
+
"""Load configuration from local file."""
|
|
113
|
+
path = Path(file_path)
|
|
114
|
+
|
|
115
|
+
if not path.exists():
|
|
116
|
+
raise ValueError(f"Space file not found: {file_path}")
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
120
|
+
if path.suffix.lower() in [".yaml", ".yml"]:
|
|
121
|
+
return yaml.safe_load(f)
|
|
122
|
+
elif path.suffix.lower() == ".json":
|
|
123
|
+
return json.load(f)
|
|
124
|
+
else:
|
|
125
|
+
# Try YAML first, then JSON
|
|
126
|
+
try:
|
|
127
|
+
f.seek(0)
|
|
128
|
+
return yaml.safe_load(f)
|
|
129
|
+
except yaml.YAMLError:
|
|
130
|
+
f.seek(0)
|
|
131
|
+
return json.load(f)
|
|
132
|
+
except Exception as e:
|
|
133
|
+
raise ValueError(f"Failed to load Space from file {file_path}: {e}")
|