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 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())