dask-setup 1.0.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.
- dask_setup/__init__.py +101 -0
- dask_setup/cli.py +344 -0
- dask_setup/client.py +280 -0
- dask_setup/cluster.py +174 -0
- dask_setup/config.py +404 -0
- dask_setup/config_manager.py +343 -0
- dask_setup/dashboard.py +71 -0
- dask_setup/error_handling.py +588 -0
- dask_setup/exceptions.py +46 -0
- dask_setup/io_patterns.py +738 -0
- dask_setup/legacy.py +191 -0
- dask_setup/logging.py +296 -0
- dask_setup/resources.py +302 -0
- dask_setup/tempdir.py +111 -0
- dask_setup/topology.py +118 -0
- dask_setup/types.py +51 -0
- dask_setup/xarray.py +636 -0
- dask_setup-1.0.0.dist-info/METADATA +817 -0
- dask_setup-1.0.0.dist-info/RECORD +23 -0
- dask_setup-1.0.0.dist-info/WHEEL +5 -0
- dask_setup-1.0.0.dist-info/entry_points.txt +2 -0
- dask_setup-1.0.0.dist-info/licenses/LICENSE +201 -0
- dask_setup-1.0.0.dist-info/top_level.txt +1 -0
dask_setup/__init__.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""HPC-tuned Dask helpers for single-node runs on NCI Gadi.
|
|
2
|
+
|
|
3
|
+
A drop-in convenience wrapper around dask.distributed.LocalCluster + Client that:
|
|
4
|
+
- Auto-detects CPU cores and memory from PBS/SLURM environment variables
|
|
5
|
+
- Routes all temp/spill files to $PBS_JOBFS for performance
|
|
6
|
+
- Configures aggressive memory spilling to prevent OOM crashes
|
|
7
|
+
- Chooses optimal process/thread topology based on workload type
|
|
8
|
+
- Provides SSH tunnel commands for dashboard access on HPC systems
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .client import setup_dask_client
|
|
12
|
+
|
|
13
|
+
# Xarray integration (optional)
|
|
14
|
+
try:
|
|
15
|
+
from .xarray import recommend_chunks
|
|
16
|
+
|
|
17
|
+
_xarray_available = True
|
|
18
|
+
except ImportError:
|
|
19
|
+
_xarray_available = False
|
|
20
|
+
recommend_chunks = None
|
|
21
|
+
|
|
22
|
+
# I/O optimization (optional)
|
|
23
|
+
try:
|
|
24
|
+
from .io_patterns import (
|
|
25
|
+
IORecommendation,
|
|
26
|
+
NetCDFOptimizer,
|
|
27
|
+
ZarrOptimizer,
|
|
28
|
+
detect_storage_format,
|
|
29
|
+
recommend_io_chunks,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
_io_patterns_available = True
|
|
33
|
+
except ImportError:
|
|
34
|
+
_io_patterns_available = False
|
|
35
|
+
IORecommendation = None
|
|
36
|
+
NetCDFOptimizer = None
|
|
37
|
+
ZarrOptimizer = None
|
|
38
|
+
detect_storage_format = None
|
|
39
|
+
recommend_io_chunks = None
|
|
40
|
+
|
|
41
|
+
# Enhanced error handling (optional)
|
|
42
|
+
try:
|
|
43
|
+
from .error_handling import (
|
|
44
|
+
ClusterSetupError,
|
|
45
|
+
ConfigurationValidationError,
|
|
46
|
+
DependencyError,
|
|
47
|
+
EnhancedDaskSetupError,
|
|
48
|
+
ErrorContext,
|
|
49
|
+
ResourceConstraintError,
|
|
50
|
+
StorageConfigurationError,
|
|
51
|
+
create_user_friendly_error,
|
|
52
|
+
format_exception_chain,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
_error_handling_available = True
|
|
56
|
+
except ImportError:
|
|
57
|
+
_error_handling_available = False
|
|
58
|
+
ClusterSetupError = None
|
|
59
|
+
ConfigurationValidationError = None
|
|
60
|
+
DependencyError = None
|
|
61
|
+
EnhancedDaskSetupError = None
|
|
62
|
+
ErrorContext = None
|
|
63
|
+
ResourceConstraintError = None
|
|
64
|
+
StorageConfigurationError = None
|
|
65
|
+
create_user_friendly_error = None
|
|
66
|
+
format_exception_chain = None
|
|
67
|
+
|
|
68
|
+
__version__ = "1.0.0"
|
|
69
|
+
__all__ = ["setup_dask_client"]
|
|
70
|
+
|
|
71
|
+
# Add xarray functions to exports if available
|
|
72
|
+
if _xarray_available:
|
|
73
|
+
__all__.append("recommend_chunks")
|
|
74
|
+
|
|
75
|
+
# Add I/O optimization functions to exports if available
|
|
76
|
+
if _io_patterns_available:
|
|
77
|
+
__all__.extend(
|
|
78
|
+
[
|
|
79
|
+
"recommend_io_chunks",
|
|
80
|
+
"detect_storage_format",
|
|
81
|
+
"IORecommendation",
|
|
82
|
+
"ZarrOptimizer",
|
|
83
|
+
"NetCDFOptimizer",
|
|
84
|
+
]
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Add enhanced error handling to exports if available
|
|
88
|
+
if _error_handling_available:
|
|
89
|
+
__all__.extend(
|
|
90
|
+
[
|
|
91
|
+
"ErrorContext",
|
|
92
|
+
"EnhancedDaskSetupError",
|
|
93
|
+
"ConfigurationValidationError",
|
|
94
|
+
"ResourceConstraintError",
|
|
95
|
+
"DependencyError",
|
|
96
|
+
"StorageConfigurationError",
|
|
97
|
+
"ClusterSetupError",
|
|
98
|
+
"create_user_friendly_error",
|
|
99
|
+
"format_exception_chain",
|
|
100
|
+
]
|
|
101
|
+
)
|
dask_setup/cli.py
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""Command-line interface for dask_setup profile management."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
from .config_manager import ConfigManager
|
|
12
|
+
from .exceptions import InvalidConfigurationError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def format_profile_list(profiles: dict[str, Any]) -> str:
|
|
16
|
+
"""Format profile list for display."""
|
|
17
|
+
if not profiles:
|
|
18
|
+
return "No profiles found."
|
|
19
|
+
|
|
20
|
+
lines = []
|
|
21
|
+
builtin_profiles = {name: p for name, p in profiles.items() if p.builtin}
|
|
22
|
+
user_profiles = {name: p for name, p in profiles.items() if not p.builtin}
|
|
23
|
+
|
|
24
|
+
if builtin_profiles:
|
|
25
|
+
lines.append(" Built-in Profiles:")
|
|
26
|
+
for name, profile in sorted(builtin_profiles.items()):
|
|
27
|
+
tags_str = f" [{', '.join(profile.tags)}]" if profile.tags else ""
|
|
28
|
+
lines.append(f" {name:20} - {profile.description}{tags_str}")
|
|
29
|
+
|
|
30
|
+
if user_profiles:
|
|
31
|
+
if builtin_profiles:
|
|
32
|
+
lines.append("")
|
|
33
|
+
lines.append(" User Profiles:")
|
|
34
|
+
for name, profile in sorted(user_profiles.items()):
|
|
35
|
+
tags_str = f" [{', '.join(profile.tags)}]" if profile.tags else ""
|
|
36
|
+
lines.append(f" {name:20} - {profile.description}{tags_str}")
|
|
37
|
+
|
|
38
|
+
return "\n".join(lines)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def format_profile_details(profile: Any) -> str:
|
|
42
|
+
"""Format profile details for display."""
|
|
43
|
+
config = profile.config
|
|
44
|
+
|
|
45
|
+
lines = [
|
|
46
|
+
f"Profile: {profile.name}",
|
|
47
|
+
f"Type: {'Built-in' if profile.builtin else 'User'}",
|
|
48
|
+
f"Description: {profile.description}",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
if profile.tags:
|
|
52
|
+
lines.append(f"Tags: {', '.join(profile.tags)}")
|
|
53
|
+
|
|
54
|
+
lines.extend(
|
|
55
|
+
[
|
|
56
|
+
"",
|
|
57
|
+
"Configuration:",
|
|
58
|
+
f" Workload Type: {config.workload_type}",
|
|
59
|
+
f" Max Workers: {config.max_workers or 'auto'}",
|
|
60
|
+
f" Reserve Memory: {config.reserve_mem_gb} GB",
|
|
61
|
+
f" Dashboard: {config.dashboard}",
|
|
62
|
+
f" Adaptive: {config.adaptive}",
|
|
63
|
+
]
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if config.adaptive and config.min_workers:
|
|
67
|
+
lines.append(f" Min Workers: {config.min_workers}")
|
|
68
|
+
|
|
69
|
+
lines.extend(
|
|
70
|
+
[
|
|
71
|
+
"",
|
|
72
|
+
"Memory Thresholds:",
|
|
73
|
+
f" Target: {config.memory_target:.0%}",
|
|
74
|
+
f" Spill: {config.memory_spill:.0%}",
|
|
75
|
+
f" Pause: {config.memory_pause:.0%}",
|
|
76
|
+
f" Terminate: {config.memory_terminate:.0%}",
|
|
77
|
+
]
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if profile.created_at:
|
|
81
|
+
lines.append(f"\nCreated: {profile.created_at}")
|
|
82
|
+
if profile.modified_at:
|
|
83
|
+
lines.append(f"Modified: {profile.modified_at}")
|
|
84
|
+
|
|
85
|
+
return "\n".join(lines)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def cmd_list_profiles(args: argparse.Namespace) -> int:
|
|
89
|
+
"""List all available profiles."""
|
|
90
|
+
manager = ConfigManager()
|
|
91
|
+
profiles = manager.list_profiles()
|
|
92
|
+
|
|
93
|
+
if args.tags:
|
|
94
|
+
# Filter by tags
|
|
95
|
+
tag_set = {tag.strip() for tag in args.tags.split(",")}
|
|
96
|
+
profiles = {
|
|
97
|
+
name: profile
|
|
98
|
+
for name, profile in profiles.items()
|
|
99
|
+
if tag_set.intersection(set(profile.tags))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
print(format_profile_list(profiles))
|
|
103
|
+
return 0
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def cmd_show_profile(args: argparse.Namespace) -> int:
|
|
107
|
+
"""Show detailed information about a profile."""
|
|
108
|
+
manager = ConfigManager()
|
|
109
|
+
profile = manager.get_profile(args.name)
|
|
110
|
+
|
|
111
|
+
if profile is None:
|
|
112
|
+
print(f" Profile '{args.name}' not found.", file=sys.stderr)
|
|
113
|
+
print("Available profiles:")
|
|
114
|
+
profiles = manager.list_profiles()
|
|
115
|
+
print(format_profile_list(profiles))
|
|
116
|
+
return 1
|
|
117
|
+
|
|
118
|
+
print(format_profile_details(profile))
|
|
119
|
+
|
|
120
|
+
# Show validation results
|
|
121
|
+
is_valid, errors, warnings = manager.validate_profile(args.name)
|
|
122
|
+
|
|
123
|
+
if not is_valid:
|
|
124
|
+
print("\n Validation Errors:")
|
|
125
|
+
for error in errors:
|
|
126
|
+
print(f" - {error}")
|
|
127
|
+
elif warnings:
|
|
128
|
+
print("\n⚠️ Warnings:")
|
|
129
|
+
for warning in warnings:
|
|
130
|
+
print(f" - {warning}")
|
|
131
|
+
else:
|
|
132
|
+
print("\n Profile is valid")
|
|
133
|
+
|
|
134
|
+
return 0 if is_valid else 1
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def cmd_create_profile(args: argparse.Namespace) -> int:
|
|
138
|
+
"""Create a new profile."""
|
|
139
|
+
manager = ConfigManager()
|
|
140
|
+
|
|
141
|
+
# Check if profile already exists
|
|
142
|
+
existing = manager.get_profile(args.name)
|
|
143
|
+
if existing is not None and not args.force:
|
|
144
|
+
print(
|
|
145
|
+
f" Profile '{args.name}' already exists. Use --force to overwrite.",
|
|
146
|
+
file=sys.stderr,
|
|
147
|
+
)
|
|
148
|
+
return 1
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
if args.from_profile:
|
|
152
|
+
# Create from existing profile
|
|
153
|
+
base_profile = manager.get_profile(args.from_profile)
|
|
154
|
+
if base_profile is None:
|
|
155
|
+
print(f" Base profile '{args.from_profile}' not found.", file=sys.stderr)
|
|
156
|
+
return 1
|
|
157
|
+
|
|
158
|
+
# Copy configuration and update name
|
|
159
|
+
new_config = base_profile.config
|
|
160
|
+
new_config.name = args.name
|
|
161
|
+
new_config.description = f"Based on {args.from_profile}"
|
|
162
|
+
|
|
163
|
+
from .config import ConfigProfile
|
|
164
|
+
|
|
165
|
+
profile = ConfigProfile(name=args.name, config=new_config)
|
|
166
|
+
else:
|
|
167
|
+
# Interactive creation
|
|
168
|
+
profile = manager.create_profile_interactively(args.name)
|
|
169
|
+
|
|
170
|
+
# Save the profile
|
|
171
|
+
manager.save_profile(profile)
|
|
172
|
+
print(f"\n Profile '{args.name}' created successfully!")
|
|
173
|
+
|
|
174
|
+
# Show the created profile
|
|
175
|
+
print("\n" + format_profile_details(profile))
|
|
176
|
+
|
|
177
|
+
return 0
|
|
178
|
+
|
|
179
|
+
except InvalidConfigurationError as e:
|
|
180
|
+
print(f" Configuration error: {e}", file=sys.stderr)
|
|
181
|
+
return 1
|
|
182
|
+
except Exception as e:
|
|
183
|
+
print(f" Failed to create profile: {e}", file=sys.stderr)
|
|
184
|
+
return 1
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def cmd_validate_profile(args: argparse.Namespace) -> int:
|
|
188
|
+
"""Validate a profile."""
|
|
189
|
+
manager = ConfigManager()
|
|
190
|
+
|
|
191
|
+
if args.all:
|
|
192
|
+
# Validate all profiles
|
|
193
|
+
profiles = manager.list_profiles()
|
|
194
|
+
all_valid = True
|
|
195
|
+
|
|
196
|
+
for name in sorted(profiles.keys()):
|
|
197
|
+
is_valid, errors, warnings = manager.validate_profile(name)
|
|
198
|
+
|
|
199
|
+
status = "Yes" if is_valid else "No"
|
|
200
|
+
print(f"{status} {name}")
|
|
201
|
+
|
|
202
|
+
if errors:
|
|
203
|
+
for error in errors:
|
|
204
|
+
print(f" Error: {error}")
|
|
205
|
+
all_valid = False
|
|
206
|
+
|
|
207
|
+
if warnings:
|
|
208
|
+
for warning in warnings:
|
|
209
|
+
print(f" Warning: {warning}")
|
|
210
|
+
|
|
211
|
+
return 0 if all_valid else 1
|
|
212
|
+
|
|
213
|
+
else:
|
|
214
|
+
# Validate specific profile
|
|
215
|
+
is_valid, errors, warnings = manager.validate_profile(args.name)
|
|
216
|
+
|
|
217
|
+
if not manager.get_profile(args.name):
|
|
218
|
+
print(f" Profile '{args.name}' not found.", file=sys.stderr)
|
|
219
|
+
return 1
|
|
220
|
+
|
|
221
|
+
if is_valid:
|
|
222
|
+
print(f" Profile '{args.name}' is valid")
|
|
223
|
+
else:
|
|
224
|
+
print(f" Profile '{args.name}' has validation errors:")
|
|
225
|
+
for error in errors:
|
|
226
|
+
print(f" - {error}")
|
|
227
|
+
|
|
228
|
+
if warnings:
|
|
229
|
+
print(" Warnings:")
|
|
230
|
+
for warning in warnings:
|
|
231
|
+
print(f" - {warning}")
|
|
232
|
+
|
|
233
|
+
return 0 if is_valid else 1
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def cmd_delete_profile(args: argparse.Namespace) -> int:
|
|
237
|
+
"""Delete a user profile."""
|
|
238
|
+
manager = ConfigManager()
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
if manager.delete_profile(args.name):
|
|
242
|
+
print(f" Profile '{args.name}' deleted successfully!")
|
|
243
|
+
return 0
|
|
244
|
+
else:
|
|
245
|
+
print(f" Profile '{args.name}' not found.", file=sys.stderr)
|
|
246
|
+
return 1
|
|
247
|
+
except InvalidConfigurationError as e:
|
|
248
|
+
print(f" {e}", file=sys.stderr)
|
|
249
|
+
return 1
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def cmd_export_profile(args: argparse.Namespace) -> int:
|
|
253
|
+
"""Export a profile to YAML."""
|
|
254
|
+
manager = ConfigManager()
|
|
255
|
+
profile = manager.get_profile(args.name)
|
|
256
|
+
|
|
257
|
+
if profile is None:
|
|
258
|
+
print(f" Profile '{args.name}' not found.", file=sys.stderr)
|
|
259
|
+
return 1
|
|
260
|
+
|
|
261
|
+
try:
|
|
262
|
+
yaml_content = yaml.safe_dump(profile.to_dict(), default_flow_style=False, indent=2)
|
|
263
|
+
|
|
264
|
+
if args.output:
|
|
265
|
+
with open(args.output, "w") as f:
|
|
266
|
+
f.write(yaml_content)
|
|
267
|
+
print(f" Profile exported to {args.output}")
|
|
268
|
+
else:
|
|
269
|
+
print(yaml_content)
|
|
270
|
+
|
|
271
|
+
return 0
|
|
272
|
+
except Exception as e:
|
|
273
|
+
print(f" Failed to export profile: {e}", file=sys.stderr)
|
|
274
|
+
return 1
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def create_parser() -> argparse.ArgumentParser:
|
|
278
|
+
"""Create the CLI argument parser."""
|
|
279
|
+
parser = argparse.ArgumentParser(
|
|
280
|
+
prog="dask-setup",
|
|
281
|
+
description="Manage dask_setup configuration profiles",
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
285
|
+
|
|
286
|
+
# List profiles
|
|
287
|
+
list_parser = subparsers.add_parser("list", help="List available profiles")
|
|
288
|
+
list_parser.add_argument("--tags", help="Filter by tags (comma-separated)")
|
|
289
|
+
list_parser.set_defaults(func=cmd_list_profiles)
|
|
290
|
+
|
|
291
|
+
# Show profile details
|
|
292
|
+
show_parser = subparsers.add_parser("show", help="Show profile details")
|
|
293
|
+
show_parser.add_argument("name", help="Profile name")
|
|
294
|
+
show_parser.set_defaults(func=cmd_show_profile)
|
|
295
|
+
|
|
296
|
+
# Create profile
|
|
297
|
+
create_parser = subparsers.add_parser("create", help="Create new profile")
|
|
298
|
+
create_parser.add_argument("name", help="Profile name")
|
|
299
|
+
create_parser.add_argument("--from-profile", help="Create from existing profile")
|
|
300
|
+
create_parser.add_argument("--force", action="store_true", help="Overwrite existing profile")
|
|
301
|
+
create_parser.set_defaults(func=cmd_create_profile)
|
|
302
|
+
|
|
303
|
+
# Validate profiles
|
|
304
|
+
validate_parser = subparsers.add_parser("validate", help="Validate profiles")
|
|
305
|
+
validate_group = validate_parser.add_mutually_exclusive_group(required=True)
|
|
306
|
+
validate_group.add_argument("name", nargs="?", help="Profile name to validate")
|
|
307
|
+
validate_group.add_argument("--all", action="store_true", help="Validate all profiles")
|
|
308
|
+
validate_parser.set_defaults(func=cmd_validate_profile)
|
|
309
|
+
|
|
310
|
+
# Delete profile
|
|
311
|
+
delete_parser = subparsers.add_parser("delete", help="Delete user profile")
|
|
312
|
+
delete_parser.add_argument("name", help="Profile name")
|
|
313
|
+
delete_parser.set_defaults(func=cmd_delete_profile)
|
|
314
|
+
|
|
315
|
+
# Export profile
|
|
316
|
+
export_parser = subparsers.add_parser("export", help="Export profile to YAML")
|
|
317
|
+
export_parser.add_argument("name", help="Profile name")
|
|
318
|
+
export_parser.add_argument("--output", "-o", help="Output file (default: stdout)")
|
|
319
|
+
export_parser.set_defaults(func=cmd_export_profile)
|
|
320
|
+
|
|
321
|
+
return parser
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def main() -> int:
|
|
325
|
+
"""Main CLI entry point."""
|
|
326
|
+
parser = create_parser()
|
|
327
|
+
args = parser.parse_args()
|
|
328
|
+
|
|
329
|
+
if args.command is None:
|
|
330
|
+
parser.print_help()
|
|
331
|
+
return 1
|
|
332
|
+
|
|
333
|
+
try:
|
|
334
|
+
return args.func(args)
|
|
335
|
+
except KeyboardInterrupt:
|
|
336
|
+
print("\n Cancelled by user.", file=sys.stderr)
|
|
337
|
+
return 1
|
|
338
|
+
except Exception as e:
|
|
339
|
+
print(f" Unexpected error: {e}", file=sys.stderr)
|
|
340
|
+
return 1
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
if __name__ == "__main__":
|
|
344
|
+
sys.exit(main())
|