manta-node 0.5b0.dev245__py3-none-any.whl → 0.5b0.dev259__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 (35) hide show
  1. manta_node/__main__.py +1 -3
  2. manta_node/cli/commands/cluster.py +3 -4
  3. manta_node/cli/commands/config.py +2 -3
  4. manta_node/cli/commands/logs.py +3 -4
  5. manta_node/cli/commands/start.py +3 -6
  6. manta_node/cli/commands/status.py +5 -7
  7. manta_node/cli/commands/stop.py +1 -2
  8. manta_node/cli/config_manager.py +7 -7
  9. manta_node/cli/main.py +2 -3
  10. manta_node/cli/version.py +3 -3
  11. manta_node/domain/task_lifecycle.py +3 -1
  12. manta_node/infrastructure/config/__init__.py +9 -9
  13. manta_node/infrastructure/config/node_config_manager.py +49 -41
  14. manta_node/infrastructure/container/docker_adapter.py +34 -7
  15. manta_node/infrastructure/container/image_executor.py +3 -6
  16. manta_node/infrastructure/container/manager.py +33 -34
  17. manta_node/infrastructure/filesystem/dataset_manager.py +7 -6
  18. manta_node/infrastructure/grpc/__init__.py +1 -1
  19. manta_node/infrastructure/grpc/client.py +7 -12
  20. manta_node/infrastructure/grpc/local_servicer.py +4 -2
  21. manta_node/infrastructure/grpc/world_servicer.py +3 -2
  22. manta_node/infrastructure/metrics/collector.py +2 -1
  23. manta_node/infrastructure/metrics/metrics_collector.py +3 -9
  24. manta_node/infrastructure/mqtt/command_handler.py +9 -8
  25. manta_node/node_orchestrator.py +19 -19
  26. manta_node/task_manager.py +20 -20
  27. manta_node/tasks.py +9 -8
  28. manta_node/utils.py +1 -2
  29. {manta_node-0.5b0.dev245.dist-info → manta_node-0.5b0.dev259.dist-info}/METADATA +1 -1
  30. manta_node-0.5b0.dev259.dist-info/RECORD +43 -0
  31. manta_node-0.5b0.dev245.dist-info/RECORD +0 -43
  32. {manta_node-0.5b0.dev245.dist-info → manta_node-0.5b0.dev259.dist-info}/WHEEL +0 -0
  33. {manta_node-0.5b0.dev245.dist-info → manta_node-0.5b0.dev259.dist-info}/entry_points.txt +0 -0
  34. {manta_node-0.5b0.dev245.dist-info → manta_node-0.5b0.dev259.dist-info}/licenses/LICENSE +0 -0
  35. {manta_node-0.5b0.dev245.dist-info → manta_node-0.5b0.dev259.dist-info}/top_level.txt +0 -0
manta_node/__main__.py CHANGED
@@ -40,9 +40,7 @@ def main():
40
40
  ]
41
41
 
42
42
  if first_arg not in cli_commands and (
43
- first_arg.endswith(".toml")
44
- or first_arg.endswith(".yaml")
45
- or first_arg.endswith(".json")
43
+ first_arg.endswith((".toml", ".yaml", ".json"))
46
44
  or Path(first_arg).exists()
47
45
  or (Path.home() / ".manta" / "nodes" / f"{first_arg}.toml").exists()
48
46
  ):
@@ -5,7 +5,6 @@ import subprocess
5
5
  import sys
6
6
  import time
7
7
  from pathlib import Path
8
- from typing import List
9
8
 
10
9
  from rich.console import Console
11
10
  from rich.prompt import Confirm, Prompt
@@ -119,7 +118,7 @@ def start_cluster_multi_config(count: int) -> int:
119
118
  "--background",
120
119
  ]
121
120
 
122
- result = subprocess.run(cmd, capture_output=True, text=True)
121
+ result = subprocess.run(cmd, capture_output=True, text=True, check=False)
123
122
 
124
123
  if result.returncode == 0:
125
124
  started_nodes.append(node["alias"])
@@ -199,7 +198,7 @@ def start_cluster_simple(count: int, config_name: str) -> int:
199
198
  "--background",
200
199
  ]
201
200
 
202
- result = subprocess.run(cmd, capture_output=True, text=True)
201
+ result = subprocess.run(cmd, capture_output=True, text=True, check=False)
203
202
 
204
203
  if result.returncode == 0:
205
204
  started_nodes.append(node_alias)
@@ -289,7 +288,7 @@ def stop_cluster() -> int:
289
288
  return 1
290
289
 
291
290
 
292
- def cluster_command(args: List[str]) -> int:
291
+ def cluster_command(args: list[str]) -> int:
293
292
  """Handle node cluster command.
294
293
 
295
294
  Args:
@@ -5,7 +5,6 @@ import os
5
5
  import subprocess
6
6
  import sys
7
7
  from pathlib import Path
8
- from typing import List
9
8
 
10
9
  from rich.console import Console
11
10
  from rich.panel import Panel
@@ -383,7 +382,7 @@ def edit_config(config_name: str) -> int:
383
382
  console.print(f"Opening {config_path} in {editor}...")
384
383
 
385
384
  try:
386
- result = subprocess.run([editor, str(config_path)])
385
+ result = subprocess.run([editor, str(config_path)], check=False)
387
386
  if result.returncode == 0:
388
387
  print_success(f"Configuration '{config_name}' edited successfully")
389
388
 
@@ -410,7 +409,7 @@ def edit_config(config_name: str) -> int:
410
409
  return 1
411
410
 
412
411
 
413
- def config_command(args: List[str]) -> int:
412
+ def config_command(args: list[str]) -> int:
414
413
  """Handle node config command.
415
414
 
416
415
  Args:
@@ -3,7 +3,6 @@
3
3
  import argparse
4
4
  import time
5
5
  from pathlib import Path
6
- from typing import List
7
6
 
8
7
  from rich.console import Console
9
8
 
@@ -73,7 +72,7 @@ def tail_log_file(log_file: Path, lines: int = 50, follow: bool = False) -> None
73
72
  print_error(f"Error reading log file: {e}")
74
73
 
75
74
 
76
- def logs_command(args: List[str]) -> int:
75
+ def logs_command(args: list[str]) -> int:
77
76
  """Handle node logs command.
78
77
 
79
78
  Args:
@@ -136,7 +135,7 @@ def logs_command(args: List[str]) -> int:
136
135
  else:
137
136
  # Multiple log files, ask user to specify
138
137
  print_error("Multiple log files found. Please specify an instance:")
139
- for instance_id in log_files.keys():
138
+ for instance_id in log_files:
140
139
  console.print(f" - {instance_id}")
141
140
  return 1
142
141
  else:
@@ -153,7 +152,7 @@ def logs_command(args: List[str]) -> int:
153
152
  print_error(f"No log file found for instance '{instance_name}'")
154
153
  if log_files:
155
154
  console.print("Available log files:")
156
- for instance_id in log_files.keys():
155
+ for instance_id in log_files:
157
156
  console.print(f" - {instance_id}")
158
157
  return 1
159
158
 
@@ -11,15 +11,12 @@ import time
11
11
  import uuid
12
12
  from datetime import datetime
13
13
  from pathlib import Path
14
- from typing import List
15
-
16
- from rich.console import Console
17
14
 
18
15
  from manta_common.logging_config import configure_logging
19
-
20
- from ...node_orchestrator import Node
16
+ from rich.console import Console
21
17
 
22
18
  from ...infrastructure.config import NodeConfigManager
19
+ from ...node_orchestrator import Node
23
20
 
24
21
  console = Console()
25
22
 
@@ -302,7 +299,7 @@ def start_node_background(
302
299
  return 1
303
300
 
304
301
 
305
- def start_command(args: List[str]) -> int:
302
+ def start_command(args: list[str]) -> int:
306
303
  """Handle node start command.
307
304
 
308
305
  Args:
@@ -1,11 +1,11 @@
1
1
  """Node status command implementation."""
2
2
 
3
3
  import argparse
4
+ import contextlib
4
5
  import json
5
6
  import os
6
7
  from datetime import datetime
7
8
  from pathlib import Path
8
- from typing import List
9
9
 
10
10
  import psutil
11
11
  from rich.console import Console
@@ -59,10 +59,8 @@ def find_running_instances():
59
59
  config_name = instance_data.get("config_name", "unknown")
60
60
  start_time_str = instance_data.get("start_time")
61
61
  if start_time_str and not create_time:
62
- try:
62
+ with contextlib.suppress(ValueError, TypeError):
63
63
  create_time = datetime.fromisoformat(start_time_str)
64
- except Exception:
65
- pass
66
64
 
67
65
  instances.append(
68
66
  {
@@ -89,7 +87,7 @@ def find_running_instances():
89
87
  return instances
90
88
 
91
89
 
92
- def show_status_table(instances: List[dict]) -> None:
90
+ def show_status_table(instances: list[dict]) -> None:
93
91
  """Display instances in a formatted table."""
94
92
  if not instances:
95
93
  console.print("[yellow]No running node instances found.[/yellow]")
@@ -135,7 +133,7 @@ def show_status_table(instances: List[dict]) -> None:
135
133
  console.print(table)
136
134
 
137
135
 
138
- def show_status_plain(instances: List[dict]) -> None:
136
+ def show_status_plain(instances: list[dict]) -> None:
139
137
  """Display instances in plain text format."""
140
138
  if not instances:
141
139
  console.print("No running node instances found.")
@@ -164,7 +162,7 @@ def show_status_plain(instances: List[dict]) -> None:
164
162
  console.print("")
165
163
 
166
164
 
167
- def status_command(args: List[str]) -> int:
165
+ def status_command(args: list[str]) -> int:
168
166
  """Handle node status command.
169
167
 
170
168
  Args:
@@ -6,7 +6,6 @@ import os
6
6
  import signal
7
7
  import time
8
8
  from pathlib import Path
9
- from typing import List
10
9
 
11
10
  from rich.console import Console
12
11
 
@@ -202,7 +201,7 @@ def stop_specific_instance(
202
201
  return 1
203
202
 
204
203
 
205
- def stop_command(args: List[str]) -> int:
204
+ def stop_command(args: list[str]) -> int:
206
205
  """Handle node stop command.
207
206
 
208
207
  Args:
@@ -6,7 +6,7 @@ but delegate to the new NodeConfiguration system.
6
6
  """
7
7
 
8
8
  from dataclasses import dataclass
9
- from typing import Any, Dict, Optional
9
+ from typing import Any
10
10
 
11
11
  from manta_node.infrastructure.config.node_config_manager import IdentityConfig
12
12
 
@@ -20,14 +20,14 @@ class ConnectionConfig:
20
20
  host: str = "localhost"
21
21
  port: int = 50051
22
22
  use_tls: bool = False
23
- cert_folder: Optional[str] = None
23
+ cert_folder: str | None = None
24
24
 
25
25
 
26
26
  @dataclass
27
27
  class HardwareConfig:
28
28
  """Compatibility class for old HardwareConfig."""
29
29
 
30
- gpu_enabled: Optional[bool] = None
30
+ gpu_enabled: bool | None = None
31
31
  max_concurrent_tasks: int = 2
32
32
  cpu_limit: str = "2.0"
33
33
  memory_limit: str = "4G"
@@ -53,12 +53,12 @@ class NodeConfig:
53
53
  name: str
54
54
  alias: str
55
55
  connection: ConnectionConfig
56
- hardware: Optional[HardwareConfig] = None
57
- datasets: Optional[Dict[str, str]] = None
56
+ hardware: HardwareConfig | None = None
57
+ datasets: dict[str, str] | None = None
58
58
  auto_register: bool = True
59
59
  random_id: bool = False
60
- created_at: Optional[Any] = None
61
- updated_at: Optional[Any] = None
60
+ created_at: Any | None = None
61
+ updated_at: Any | None = None
62
62
 
63
63
  def __post_init__(self):
64
64
  """Ensure datasets is a proper dict if provided."""
manta_node/cli/main.py CHANGED
@@ -1,7 +1,6 @@
1
1
  """Manta Node CLI main entry point."""
2
2
 
3
3
  import sys
4
- from typing import List, Optional
5
4
 
6
5
  from rich.console import Console
7
6
 
@@ -11,7 +10,7 @@ from .commands.logs import logs_command
11
10
  from .commands.start import start_command
12
11
  from .commands.status import status_command
13
12
  from .commands.stop import stop_command
14
- from .version import get_version_string, get_full_version_info
13
+ from .version import get_full_version_info, get_version_string
15
14
 
16
15
  console = Console()
17
16
 
@@ -67,7 +66,7 @@ For more help on a specific command:
67
66
  console.print(help_text)
68
67
 
69
68
 
70
- def main(args: Optional[List[str]] = None) -> int:
69
+ def main(args: list[str] | None = None) -> int:
71
70
  """Main CLI entry point for manta-node.
72
71
 
73
72
  Args:
manta_node/cli/version.py CHANGED
@@ -8,10 +8,9 @@ Version information is loaded from:
8
8
  import subprocess
9
9
  from datetime import datetime
10
10
  from pathlib import Path
11
- from typing import Optional
12
11
 
13
12
 
14
- def _load_build_version() -> Optional[dict]:
13
+ def _load_build_version() -> dict | None:
15
14
  """Try to load version from build-time generated file.
16
15
 
17
16
  Returns:
@@ -32,7 +31,7 @@ def _load_build_version() -> Optional[dict]:
32
31
  return None
33
32
 
34
33
 
35
- def _get_git_commit_id() -> Optional[str]:
34
+ def _get_git_commit_id() -> str | None:
36
35
  """Get the current git commit ID (short form) dynamically.
37
36
 
38
37
  This is used as fallback when build-time version is not available.
@@ -50,6 +49,7 @@ def _get_git_commit_id() -> Optional[str]:
50
49
  capture_output=True,
51
50
  text=True,
52
51
  timeout=2,
52
+ check=False,
53
53
  )
54
54
 
55
55
  if result.returncode == 0:
@@ -1,6 +1,8 @@
1
1
  # domain/task_lifecycle.py
2
2
  """Pure business logic for task lifecycle management."""
3
3
 
4
+ from typing import ClassVar
5
+
4
6
  # Import domain entities (no infrastructure!)
5
7
  from manta_common.build.common.tasks import TaskStatus
6
8
 
@@ -8,7 +10,7 @@ from manta_common.build.common.tasks import TaskStatus
8
10
  class TaskTransitionRule:
9
11
  """Business rules for task state transitions."""
10
12
 
11
- ALLOWED_TRANSITIONS = {
13
+ ALLOWED_TRANSITIONS: ClassVar[dict[TaskStatus, list[TaskStatus]]] = {
12
14
  TaskStatus.PENDING: [TaskStatus.RUNNING, TaskStatus.FAILED],
13
15
  TaskStatus.RUNNING: [
14
16
  TaskStatus.COMPLETED,
@@ -8,24 +8,24 @@ from .node_config_manager import (
8
8
  MetadataConfig,
9
9
  MQTTConfig,
10
10
  NetworkConfig,
11
- NodeConfiguration,
12
11
  NodeConfigManager,
12
+ NodeConfiguration,
13
13
  ResourceConfig,
14
14
  SecurityConfig,
15
15
  TaskConfig,
16
16
  )
17
17
 
18
18
  __all__ = [
19
- "NodeConfigManager",
20
- "NodeConfiguration",
21
- "IdentityConfig",
22
- "NetworkConfig",
23
- "MQTTConfig",
24
19
  "ContainerConfig",
25
20
  "DatasetConfig",
26
- "TaskConfig",
27
- "ResourceConfig",
21
+ "IdentityConfig",
28
22
  "LoggingConfig",
29
- "SecurityConfig",
23
+ "MQTTConfig",
30
24
  "MetadataConfig",
25
+ "NetworkConfig",
26
+ "NodeConfigManager",
27
+ "NodeConfiguration",
28
+ "ResourceConfig",
29
+ "SecurityConfig",
30
+ "TaskConfig",
31
31
  ]
@@ -4,6 +4,7 @@ This module provides a comprehensive configuration management system for manta-n
4
4
  using TOML files for persistent storage and dataclasses for type-safe configuration.
5
5
  """
6
6
 
7
+ import logging
7
8
  import os
8
9
  import threading
9
10
 
@@ -12,16 +13,19 @@ try:
12
13
  except ImportError:
13
14
  import tomli as tomllib # Python < 3.11 fallback
14
15
 
16
+ from collections.abc import Callable
15
17
  from dataclasses import asdict, dataclass, field
16
18
  from datetime import datetime
17
19
  from pathlib import Path
18
- from typing import Any, Callable, Dict, List, Optional, Union
20
+ from typing import Any, ClassVar
19
21
 
20
22
  import toml # For writing TOML files
21
23
  from manta_common.errors import MantaConfigurationError
22
24
 
23
25
  __all__ = ["NodeConfigManager", "NodeConfiguration"]
24
26
 
27
+ logger = logging.getLogger(__name__)
28
+
25
29
 
26
30
  # Configuration Section Dataclasses
27
31
 
@@ -31,7 +35,7 @@ class IdentityConfig:
31
35
  """Node identity configuration."""
32
36
 
33
37
  secured_token: str # Required field - must be provided
34
- alias: Optional[str] = None
38
+ alias: str | None = None
35
39
  random_id: bool = False
36
40
 
37
41
 
@@ -42,7 +46,7 @@ class NetworkConfig:
42
46
  manager_host: str = "localhost"
43
47
  manager_port: int = 50051
44
48
  light_service_host: str = "0.0.0.0"
45
- light_service_port: Optional[int] = None # Auto-assigned if not specified
49
+ light_service_port: int | None = None # Auto-assigned if not specified
46
50
 
47
51
 
48
52
  @dataclass
@@ -53,22 +57,22 @@ class MQTTConfig:
53
57
  reconnect_attempts: int = 5
54
58
  reconnect_delay: float = 5.0
55
59
  # Runtime populated from manager registration
56
- broker_host: Optional[str] = None
57
- broker_port: Optional[int] = None
58
- topics: List[str] = field(default_factory=list)
60
+ broker_host: str | None = None
61
+ broker_port: int | None = None
62
+ topics: list[str] = field(default_factory=list)
59
63
 
60
64
 
61
65
  @dataclass
62
66
  class ContainerConfig:
63
67
  """Container/Docker configuration."""
64
68
 
65
- gpu_enabled: Optional[bool] = None # Auto-detect if None
69
+ gpu_enabled: bool | None = None # Auto-detect if None
66
70
  default_memory_limit: str = "4G"
67
71
  default_cpu_limit: str = "2.0"
68
72
  docker_network: str = "host"
69
73
  runtime: str = "docker" # docker or podman
70
74
  pull_timeout: int = 300
71
- log_config: Dict[str, Any] = field(
75
+ log_config: dict[str, Any] = field(
72
76
  default_factory=lambda: {
73
77
  "type": "json-file",
74
78
  "config": {"max-size": "10m", "max-file": "3"},
@@ -82,7 +86,7 @@ class DatasetConfig:
82
86
 
83
87
  base_path: str = "/data/datasets"
84
88
  mount_readonly: bool = True
85
- mappings: Dict[str, str] = field(default_factory=dict) # name -> path
89
+ mappings: dict[str, str] = field(default_factory=dict) # name -> path
86
90
  cache_metadata: bool = True
87
91
 
88
92
 
@@ -93,7 +97,7 @@ class TaskConfig:
93
97
  max_concurrent: int = 2
94
98
  task_timeout: int = 3600 # seconds
95
99
  retry_attempts: int = 3
96
- temp_folder: Optional[str] = None
100
+ temp_folder: str | None = None
97
101
  cleanup_on_failure: bool = True
98
102
  stream_logs: bool = True
99
103
 
@@ -178,12 +182,12 @@ class NodeConfiguration:
178
182
  Path.home() / ".manta" / "logs" / "nodes" / f"{safe_name}.log"
179
183
  )
180
184
 
181
- def to_dict(self) -> Dict[str, Any]:
185
+ def to_dict(self) -> dict[str, Any]:
182
186
  """Convert configuration to dictionary."""
183
187
  return asdict(self)
184
188
 
185
189
  @classmethod
186
- def from_dict(cls, data: Dict[str, Any]) -> "NodeConfiguration":
190
+ def from_dict(cls, data: dict[str, Any]) -> "NodeConfiguration":
187
191
  """Create configuration from dictionary."""
188
192
  # For identity, we need to ensure secured_token is present
189
193
  identity_data = data.get("identity", {})
@@ -231,14 +235,14 @@ class NodeConfigManager:
231
235
  """
232
236
 
233
237
  # Runtime update categories
234
- SAFE_RUNTIME_UPDATES = [
238
+ SAFE_RUNTIME_UPDATES: ClassVar[list[str]] = [
235
239
  "logging", # Can change log levels
236
240
  "datasets.mappings", # Can add/remove datasets
237
241
  "tasks.max_concurrent", # Can adjust concurrency
238
242
  "resources", # Can adjust resource limits
239
243
  ]
240
244
 
241
- REQUIRES_RESTART = [
245
+ REQUIRES_RESTART: ClassVar[list[str]] = [
242
246
  "identity", # Node ID is immutable
243
247
  "network", # Port changes need restart
244
248
  "mqtt.broker_host", # MQTT connection needs restart
@@ -247,16 +251,16 @@ class NodeConfigManager:
247
251
  "containers.runtime", # Runtime change needs restart
248
252
  ]
249
253
 
250
- def __init__(self, config_path: Optional[Union[str, Path]] = None):
254
+ def __init__(self, config_path: str | Path | None = None):
251
255
  """Initialize configuration manager.
252
256
 
253
257
  Args:
254
258
  config_path: Path to configuration file or None for default
255
259
  """
256
260
  self._lock = threading.RLock()
257
- self._observers: List[Callable] = []
261
+ self._observers: list[Callable] = []
258
262
  self._config = NodeConfiguration(IdentityConfig(secured_token="<REQUIRED>"))
259
- self._instances: List[Any] = [] # Track running instances
263
+ self._instances: list[Any] = [] # Track running instances
260
264
 
261
265
  # Determine config path
262
266
  if config_path:
@@ -286,7 +290,7 @@ class NodeConfigManager:
286
290
  return self.config
287
291
 
288
292
  @current_config.setter
289
- def current_config(self, config_dict: Union[Dict[str, Any], NodeConfiguration]):
293
+ def current_config(self, config_dict: dict[str, Any] | NodeConfiguration):
290
294
  """Set current configuration from dictionary or NodeConfiguration.
291
295
 
292
296
  Args:
@@ -343,9 +347,9 @@ class NodeConfigManager:
343
347
 
344
348
  # Print helpful message to inform user
345
349
  print(f"📝 Created default configuration template at: {self._config_path}")
346
- print("")
350
+ print()
347
351
  print("⚠️ IMPORTANT: Please set your secured_token before starting the node!")
348
- print("")
352
+ print()
349
353
  print("Next steps:")
350
354
  print(" 1. Get your JWT token from the manta manager")
351
355
  print(f" 2. Edit the config file: {self._config_path}")
@@ -353,7 +357,7 @@ class NodeConfigManager:
353
357
  print(
354
358
  " 4. Or run interactive setup: manta_node config init <path/to/config.yaml>"
355
359
  )
356
- print("")
360
+ print()
357
361
 
358
362
  def _expand_env_vars(self, value: Any) -> Any:
359
363
  """Expand environment variables in configuration values.
@@ -407,7 +411,7 @@ class NodeConfigManager:
407
411
  return [self._expand_paths(item) for item in value]
408
412
  return value
409
413
 
410
- def load(self, config_path: Optional[Union[str, Path]] = None) -> NodeConfiguration:
414
+ def load(self, config_path: str | Path | None = None) -> NodeConfiguration:
411
415
  """Load configuration from TOML file.
412
416
 
413
417
  Args:
@@ -448,7 +452,7 @@ class NodeConfigManager:
448
452
  except Exception as e:
449
453
  raise MantaConfigurationError(f"Failed to load configuration: {e}")
450
454
 
451
- def save(self, config_path: Optional[Union[str, Path]] = None) -> bool:
455
+ def save(self, config_path: str | Path | None = None) -> bool:
452
456
  """Save configuration to TOML file.
453
457
 
454
458
  Args:
@@ -486,7 +490,7 @@ class NodeConfigManager:
486
490
  raise MantaConfigurationError(f"Failed to save configuration: {e}")
487
491
 
488
492
  def update(
489
- self, updates: Dict[str, Any], allow_restart_required: bool = False
493
+ self, updates: dict[str, Any], allow_restart_required: bool = False
490
494
  ) -> bool:
491
495
  """Update configuration with validation.
492
496
 
@@ -545,7 +549,7 @@ class NodeConfigManager:
545
549
  # Set the value
546
550
  setattr(target, parts[-1], value)
547
551
 
548
- def validate(self) -> List[str]:
552
+ def validate(self) -> list[str]:
549
553
  """Validate configuration.
550
554
 
551
555
  Returns:
@@ -569,11 +573,12 @@ class NodeConfigManager:
569
573
  issues.append(
570
574
  f"network.manager_port ({self._config.network.manager_port}) must be between 443 and 65535"
571
575
  )
572
- if self._config.network.light_service_port is not None:
573
- if not (1024 <= self._config.network.light_service_port <= 65535):
574
- issues.append(
575
- f"network.light_service_port ({self._config.network.light_service_port}) must be between 1024 and 65535"
576
- )
576
+ if self._config.network.light_service_port is not None and not (
577
+ 1024 <= self._config.network.light_service_port <= 65535
578
+ ):
579
+ issues.append(
580
+ f"network.light_service_port ({self._config.network.light_service_port}) must be between 1024 and 65535"
581
+ )
577
582
 
578
583
  # Validate dataset paths
579
584
  for name, path in self._config.datasets.mappings.items():
@@ -587,7 +592,7 @@ class NodeConfigManager:
587
592
 
588
593
  return issues
589
594
 
590
- def register_observer(self, callback: Callable[[Dict[str, Any]], None]):
595
+ def register_observer(self, callback: Callable[[dict[str, Any]], None]):
591
596
  """Register observer for configuration changes.
592
597
 
593
598
  Args:
@@ -595,7 +600,7 @@ class NodeConfigManager:
595
600
  """
596
601
  self._observers.append(callback)
597
602
 
598
- def _notify_observers(self, change: Dict[str, Any]):
603
+ def _notify_observers(self, change: dict[str, Any]):
599
604
  """Notify all observers of configuration change.
600
605
 
601
606
  Args:
@@ -605,9 +610,9 @@ class NodeConfigManager:
605
610
  try:
606
611
  observer(change)
607
612
  except Exception:
608
- pass # Ignore observer errors
613
+ logger.debug("Observer callback raised an error", exc_info=True)
609
614
 
610
- def list_available_configs(self) -> List[str]:
615
+ def list_available_configs(self) -> list[str]:
611
616
  """List available configuration files.
612
617
 
613
618
  Returns:
@@ -623,7 +628,7 @@ class NodeConfigManager:
623
628
 
624
629
  return sorted(configs)
625
630
 
626
- def get_node_config(self, config_name: str) -> Optional[NodeConfiguration]:
631
+ def get_node_config(self, config_name: str) -> NodeConfiguration | None:
627
632
  """Load and return a specific node configuration by name.
628
633
 
629
634
  Args:
@@ -647,7 +652,7 @@ class NodeConfigManager:
647
652
  except Exception:
648
653
  return None
649
654
 
650
- def export_template(self, path: Optional[Union[str, Path]] = None) -> str:
655
+ def export_template(self, path: str | Path | None = None) -> str:
651
656
  """Export a configuration template.
652
657
 
653
658
  Args:
@@ -704,7 +709,7 @@ class NodeConfigManager:
704
709
  except Exception as e:
705
710
  raise MantaConfigurationError(f"Failed to set manager configuration: {e}")
706
711
 
707
- def get_manager_config(self) -> Dict[str, Any]:
712
+ def get_manager_config(self) -> dict[str, Any]:
708
713
  """Get manager configuration as dictionary.
709
714
 
710
715
  Returns:
@@ -788,7 +793,7 @@ class NodeConfigManager:
788
793
 
789
794
  @dataclass
790
795
  class HardwareConfig:
791
- gpu_enabled: Optional[bool]
796
+ gpu_enabled: bool | None
792
797
  max_concurrent_tasks: int
793
798
  cpu_limit: str
794
799
  memory_limit: str
@@ -799,7 +804,7 @@ class NodeConfigManager:
799
804
  alias: str
800
805
  connection: ConnectionConfig
801
806
  hardware: HardwareConfig
802
- datasets: Optional[Any]
807
+ datasets: Any | None
803
808
  auto_register: bool
804
809
  random_id: bool
805
810
 
@@ -849,7 +854,7 @@ class NodeConfigManager:
849
854
  random_id=False,
850
855
  )
851
856
 
852
- def list_running_instances(self) -> List[Any]:
857
+ def list_running_instances(self) -> list[Any]:
853
858
  """List all running node instances.
854
859
 
855
860
  Returns:
@@ -880,7 +885,7 @@ class NodeConfigManager:
880
885
  return True
881
886
  return False
882
887
 
883
- def list_node_configs(self) -> List[Any]:
888
+ def list_node_configs(self) -> list[Any]:
884
889
  """List all available node configurations.
885
890
 
886
891
  Returns:
@@ -913,6 +918,9 @@ class NodeConfigManager:
913
918
  configs.append(config_info)
914
919
  except Exception:
915
920
  # Skip invalid configs
921
+ logger.debug(
922
+ "Skipping invalid config file: %s", config_file, exc_info=True
923
+ )
916
924
  continue
917
925
 
918
926
  return configs