hpcflow-new2 0.2.0a179__py3-none-any.whl → 0.2.0a181__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 (70) hide show
  1. hpcflow/_version.py +1 -1
  2. hpcflow/data/demo_data_manifest/__init__.py +3 -0
  3. hpcflow/sdk/__init__.py +4 -1
  4. hpcflow/sdk/app.py +160 -15
  5. hpcflow/sdk/cli.py +14 -0
  6. hpcflow/sdk/cli_common.py +83 -0
  7. hpcflow/sdk/config/__init__.py +4 -0
  8. hpcflow/sdk/config/callbacks.py +25 -2
  9. hpcflow/sdk/config/cli.py +4 -1
  10. hpcflow/sdk/config/config.py +188 -14
  11. hpcflow/sdk/config/config_file.py +91 -3
  12. hpcflow/sdk/config/errors.py +33 -0
  13. hpcflow/sdk/core/__init__.py +2 -0
  14. hpcflow/sdk/core/actions.py +492 -35
  15. hpcflow/sdk/core/cache.py +22 -0
  16. hpcflow/sdk/core/command_files.py +221 -5
  17. hpcflow/sdk/core/commands.py +57 -0
  18. hpcflow/sdk/core/element.py +407 -8
  19. hpcflow/sdk/core/environment.py +92 -0
  20. hpcflow/sdk/core/errors.py +245 -61
  21. hpcflow/sdk/core/json_like.py +72 -14
  22. hpcflow/sdk/core/loop.py +122 -21
  23. hpcflow/sdk/core/loop_cache.py +34 -9
  24. hpcflow/sdk/core/object_list.py +172 -26
  25. hpcflow/sdk/core/parallel.py +14 -0
  26. hpcflow/sdk/core/parameters.py +478 -25
  27. hpcflow/sdk/core/rule.py +31 -1
  28. hpcflow/sdk/core/run_dir_files.py +12 -2
  29. hpcflow/sdk/core/task.py +407 -80
  30. hpcflow/sdk/core/task_schema.py +70 -9
  31. hpcflow/sdk/core/test_utils.py +35 -0
  32. hpcflow/sdk/core/utils.py +101 -4
  33. hpcflow/sdk/core/validation.py +13 -1
  34. hpcflow/sdk/core/workflow.py +316 -96
  35. hpcflow/sdk/core/zarr_io.py +23 -0
  36. hpcflow/sdk/data/__init__.py +13 -0
  37. hpcflow/sdk/demo/__init__.py +3 -0
  38. hpcflow/sdk/helper/__init__.py +3 -0
  39. hpcflow/sdk/helper/cli.py +9 -0
  40. hpcflow/sdk/helper/helper.py +28 -0
  41. hpcflow/sdk/helper/watcher.py +33 -0
  42. hpcflow/sdk/log.py +40 -0
  43. hpcflow/sdk/persistence/__init__.py +14 -4
  44. hpcflow/sdk/persistence/base.py +289 -23
  45. hpcflow/sdk/persistence/json.py +29 -0
  46. hpcflow/sdk/persistence/pending.py +217 -107
  47. hpcflow/sdk/persistence/store_resource.py +58 -2
  48. hpcflow/sdk/persistence/utils.py +8 -0
  49. hpcflow/sdk/persistence/zarr.py +68 -1
  50. hpcflow/sdk/runtime.py +52 -10
  51. hpcflow/sdk/submission/__init__.py +3 -0
  52. hpcflow/sdk/submission/jobscript.py +198 -9
  53. hpcflow/sdk/submission/jobscript_info.py +13 -0
  54. hpcflow/sdk/submission/schedulers/__init__.py +60 -0
  55. hpcflow/sdk/submission/schedulers/direct.py +53 -0
  56. hpcflow/sdk/submission/schedulers/sge.py +45 -7
  57. hpcflow/sdk/submission/schedulers/slurm.py +45 -8
  58. hpcflow/sdk/submission/schedulers/utils.py +4 -0
  59. hpcflow/sdk/submission/shells/__init__.py +11 -1
  60. hpcflow/sdk/submission/shells/base.py +32 -1
  61. hpcflow/sdk/submission/shells/bash.py +36 -1
  62. hpcflow/sdk/submission/shells/os_version.py +18 -6
  63. hpcflow/sdk/submission/shells/powershell.py +22 -0
  64. hpcflow/sdk/submission/submission.py +88 -3
  65. hpcflow/sdk/typing.py +10 -1
  66. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a181.dist-info}/METADATA +3 -3
  67. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a181.dist-info}/RECORD +70 -70
  68. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a181.dist-info}/LICENSE +0 -0
  69. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a181.dist-info}/WHEEL +0 -0
  70. {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a181.dist-info}/entry_points.txt +0 -0
@@ -1,3 +1,7 @@
1
+ """
2
+ Model of information submitted to a scheduler.
3
+ """
4
+
1
5
  from __future__ import annotations
2
6
  import copy
3
7
 
@@ -28,8 +32,10 @@ def generate_EAR_resource_map(
28
32
  task: app.WorkflowTask,
29
33
  loop_idx: Dict,
30
34
  ) -> Tuple[List[app.ElementResources], List[int], NDArray, NDArray]:
31
- """Generate an integer array whose rows represent actions and columns represent task
32
- elements and whose values index unique resources."""
35
+ """
36
+ Generate an integer array whose rows represent actions and columns represent task
37
+ elements and whose values index unique resources.
38
+ """
33
39
  # TODO: assume single iteration for now; later we will loop over Loop tasks for each
34
40
  # included task and call this func with specific loop indices
35
41
  none_val = -1
@@ -83,6 +89,9 @@ def group_resource_map_into_jobscripts(
83
89
  resource_map: Union[List, NDArray],
84
90
  none_val: Any = -1,
85
91
  ):
92
+ """
93
+ Convert a resource map into a plan for what elements to group together into jobscripts.
94
+ """
86
95
  resource_map = np.asanyarray(resource_map)
87
96
  resource_idx = np.unique(resource_map)
88
97
  jobscripts = []
@@ -152,6 +161,9 @@ def group_resource_map_into_jobscripts(
152
161
 
153
162
  @TimeIt.decorator
154
163
  def resolve_jobscript_dependencies(jobscripts, element_deps):
164
+ """
165
+ Discover concrete dependencies between jobscripts.
166
+ """
155
167
  # first pass is to find the mappings between jobscript elements:
156
168
  jobscript_deps = {}
157
169
  for js_idx, elem_deps in element_deps.items():
@@ -302,6 +314,52 @@ def jobscripts_to_list(jobscripts: Dict[int, Dict]) -> List[Dict]:
302
314
 
303
315
 
304
316
  class Jobscript(JSONLike):
317
+ """
318
+ A group of actions that are submitted together to be executed by the underlying job
319
+ management system as a single unit.
320
+
321
+ Parameters
322
+ ----------
323
+ task_insert_IDs: list[int]
324
+ The task insertion IDs.
325
+ task_actions: list[tuple]
326
+ The actions of the tasks.
327
+ ``task insert ID, action_idx, index into task_loop_idx`` for each ``JS_ACTION_IDX``
328
+ task_elements: dict[int, list[int]]
329
+ The elements of the tasks.
330
+ Maps ``JS_ELEMENT_IDX`` to list of ``TASK_ELEMENT_IDX`` for each ``TASK_INSERT_ID``
331
+ EAR_ID:
332
+ Element action run information.
333
+ resources: ~hpcflow.app.ElementResources
334
+ Resources to use
335
+ task_loop_idx: list[dict]
336
+ Description of what loops are in play.
337
+ dependencies: dict[int, dict]
338
+ Description of dependencies.
339
+ submit_time: datetime
340
+ When the jobscript was submitted, if known.
341
+ submit_hostname: str
342
+ Where the jobscript was submitted, if known.
343
+ submit_machine: str
344
+ Description of what the jobscript was submitted to, if known.
345
+ submit_cmdline: str
346
+ The command line used to do the commit, if known.
347
+ scheduler_job_ID: str
348
+ The job ID from the scheduler, if known.
349
+ process_ID: int
350
+ The process ID of the subprocess, if known.
351
+ version_info: tuple[str, ...]
352
+ Version info about the target system.
353
+ os_name: str
354
+ The name of the OS.
355
+ shell_name: str
356
+ The name of the shell.
357
+ scheduler_name: str
358
+ The scheduler used.
359
+ running: bool
360
+ Whether the jobscript is currently running.
361
+ """
362
+
305
363
  _app_attr = "app"
306
364
  _EAR_files_delimiter = ":"
307
365
  _workflow_app_alias = "wkflow_app"
@@ -399,9 +457,15 @@ class Jobscript(JSONLike):
399
457
 
400
458
  @property
401
459
  def workflow_app_alias(self):
460
+ """
461
+ Alias for the workflow app in job scripts.
462
+ """
402
463
  return self._workflow_app_alias
403
464
 
404
465
  def get_commands_file_name(self, js_action_idx, shell=None):
466
+ """
467
+ Get the name of a file containing commands for a particular jobscript action.
468
+ """
405
469
  return self.app.RunDirAppFiles.get_commands_file_name(
406
470
  js_idx=self.index,
407
471
  js_action_idx=js_action_idx,
@@ -410,47 +474,74 @@ class Jobscript(JSONLike):
410
474
 
411
475
  @property
412
476
  def task_insert_IDs(self):
477
+ """
478
+ The insertion IDs of tasks in this jobscript.
479
+ """
413
480
  return self._task_insert_IDs
414
481
 
415
482
  @property
416
483
  def task_actions(self):
484
+ """
485
+ The IDs of actions of each task in this jobscript.
486
+ """
417
487
  return self._task_actions
418
488
 
419
489
  @property
420
490
  def task_elements(self):
491
+ """
492
+ The IDs of elements of each task in this jobscript.
493
+ """
421
494
  return self._task_elements
422
495
 
423
496
  @property
424
497
  def EAR_ID(self):
498
+ """
499
+ The array of EAR IDs.
500
+ """
425
501
  return self._EAR_ID
426
502
 
427
503
  @property
428
504
  def all_EAR_IDs(self) -> List[int]:
505
+ """
506
+ The IDs of all EARs in this jobscript.
507
+ """
429
508
  return self.EAR_ID.flatten()
430
509
 
431
510
  @property
432
511
  @TimeIt.decorator
433
512
  def all_EARs(self) -> List:
513
+ """
514
+ Description of EAR information for this jobscript.
515
+ """
434
516
  if not self._all_EARs:
435
517
  self._all_EARs = self.workflow.get_EARs_from_IDs(self.all_EAR_IDs)
436
518
  return self._all_EARs
437
519
 
438
520
  @property
439
521
  def resources(self):
522
+ """
523
+ The common resources that this jobscript requires.
524
+ """
440
525
  return self._resources
441
526
 
442
527
  @property
443
528
  def task_loop_idx(self):
529
+ """
530
+ The description of where various task loops are.
531
+ """
444
532
  return self._task_loop_idx
445
533
 
446
534
  @property
447
535
  def dependencies(self):
536
+ """
537
+ The dependency descriptor.
538
+ """
448
539
  return self._dependencies
449
540
 
450
541
  @property
451
542
  @TimeIt.decorator
452
543
  def start_time(self):
453
- """Get the first start time from all EARs."""
544
+ """The first known start time of any EAR in this jobscript."""
454
545
  if not self.is_submitted:
455
546
  return
456
547
  all_times = [i.start_time for i in self.all_EARs if i.start_time]
@@ -462,7 +553,7 @@ class Jobscript(JSONLike):
462
553
  @property
463
554
  @TimeIt.decorator
464
555
  def end_time(self):
465
- """Get the last end time from all EARs."""
556
+ """The last known end time of any EAR in this jobscript."""
466
557
  if not self.is_submitted:
467
558
  return
468
559
  all_times = [i.end_time for i in self.all_EARs if i.end_time]
@@ -473,6 +564,9 @@ class Jobscript(JSONLike):
473
564
 
474
565
  @property
475
566
  def submit_time(self):
567
+ """
568
+ When the jobscript was submitted, if known.
569
+ """
476
570
  if self._submit_time_obj is None and self._submit_time:
477
571
  self._submit_time_obj = (
478
572
  datetime.strptime(self._submit_time, self.workflow.ts_fmt)
@@ -483,50 +577,86 @@ class Jobscript(JSONLike):
483
577
 
484
578
  @property
485
579
  def submit_hostname(self):
580
+ """
581
+ Where the jobscript was submitted, if known.
582
+ """
486
583
  return self._submit_hostname
487
584
 
488
585
  @property
489
586
  def submit_machine(self):
587
+ """
588
+ Description of what the jobscript was submitted to, if known.
589
+ """
490
590
  return self._submit_machine
491
591
 
492
592
  @property
493
593
  def submit_cmdline(self):
594
+ """
595
+ The command line used to do the commit, if known.
596
+ """
494
597
  return self._submit_cmdline
495
598
 
496
599
  @property
497
600
  def scheduler_job_ID(self):
601
+ """
602
+ The job ID from the scheduler, if known.
603
+ """
498
604
  return self._scheduler_job_ID
499
605
 
500
606
  @property
501
607
  def process_ID(self):
608
+ """
609
+ The process ID from direct execution, if known.
610
+ """
502
611
  return self._process_ID
503
612
 
504
613
  @property
505
614
  def version_info(self):
615
+ """
616
+ Version information about the execution environment (OS, etc).
617
+ """
506
618
  return self._version_info
507
619
 
508
620
  @property
509
621
  def index(self):
622
+ """
623
+ The index of this jobscript within its parent :py:class:`Submission`.
624
+ """
510
625
  return self._index
511
626
 
512
627
  @property
513
628
  def submission(self):
629
+ """
630
+ The parent submission.
631
+ """
514
632
  return self._submission
515
633
 
516
634
  @property
517
635
  def workflow(self):
636
+ """
637
+ The workflow this is all on behalf of.
638
+ """
518
639
  return self.submission.workflow
519
640
 
520
641
  @property
521
642
  def num_actions(self):
643
+ """
644
+ The number of actions in this jobscript.
645
+ """
522
646
  return self.EAR_ID.shape[0]
523
647
 
524
648
  @property
525
649
  def num_elements(self):
650
+ """
651
+ The number of elements in this jobscript.
652
+ """
526
653
  return self.EAR_ID.shape[1]
527
654
 
528
655
  @property
529
656
  def is_array(self):
657
+ """
658
+ Whether to generate an array job.
659
+ """
530
660
  if self.scheduler_name == "direct":
531
661
  return False
532
662
 
@@ -546,14 +676,23 @@ class Jobscript(JSONLike):
546
676
 
547
677
  @property
548
678
  def os_name(self) -> Union[str, None]:
679
+ """
680
+ The name of the OS to use.
681
+ """
549
682
  return self._os_name or self.resources.os_name
550
683
 
551
684
  @property
552
685
  def shell_name(self) -> Union[str, None]:
686
+ """
687
+ The name of the shell to use.
688
+ """
553
689
  return self._shell_name or self.resources.shell
554
690
 
555
691
  @property
556
692
  def scheduler_name(self) -> Union[str, None]:
693
+ """
694
+ The name of the scheduler to use.
695
+ """
557
696
  return self._scheduler_name or self.resources.scheduler
558
697
 
559
698
  def _get_submission_os_args(self):
@@ -578,7 +717,7 @@ class Jobscript(JSONLike):
578
717
 
579
718
  @property
580
719
  def shell(self):
581
- """Retrieve the shell object for submission."""
720
+ """The shell for composing submission scripts."""
582
721
  if self._shell_obj is None:
583
722
  self._shell_obj = self._get_shell(
584
723
  os_name=self.os_name,
@@ -590,7 +729,7 @@ class Jobscript(JSONLike):
590
729
 
591
730
  @property
592
731
  def scheduler(self):
593
- """Retrieve the scheduler object for submission."""
732
+ """The scheduler that submissions go to from this jobscript."""
594
733
  if self._scheduler_obj is None:
595
734
  self._scheduler_obj = self.app.get_scheduler(
596
735
  scheduler_name=self.scheduler_name,
@@ -601,52 +740,81 @@ class Jobscript(JSONLike):
601
740
 
602
741
  @property
603
742
  def EAR_ID_file_name(self):
743
+ """
744
+ The name of a file containing EAR IDs.
745
+ """
604
746
  return f"js_{self.index}_EAR_IDs.txt"
605
747
 
606
748
  @property
607
749
  def element_run_dir_file_name(self):
750
+ """
751
+ The name of a file containing run directory names.
752
+ """
608
753
  return f"js_{self.index}_run_dirs.txt"
609
754
 
610
755
  @property
611
756
  def direct_stdout_file_name(self):
612
- """For direct execution stdout."""
757
+ """File for direct execution stdout."""
613
758
  return f"js_{self.index}_stdout.log"
614
759
 
615
760
  @property
616
761
  def direct_stderr_file_name(self):
617
- """For direct execution stderr."""
762
+ """File for direct execution stderr."""
618
763
  return f"js_{self.index}_stderr.log"
619
764
 
620
765
  @property
621
766
  def direct_win_pid_file_name(self):
767
+ """File for holding the direct execution PID."""
622
768
  return f"js_{self.index}_pid.txt"
623
769
 
624
770
  @property
625
771
  def jobscript_name(self):
772
+ """The name of the jobscript file."""
626
773
  return f"js_{self.index}{self.shell.JS_EXT}"
627
774
 
628
775
  @property
629
776
  def EAR_ID_file_path(self):
777
+ """
778
+ The path to the file containing EAR IDs for this jobscript.
779
+ """
630
780
  return self.submission.path / self.EAR_ID_file_name
631
781
 
632
782
  @property
633
783
  def element_run_dir_file_path(self):
784
+ """
785
+ The path to the file containing run directory names for this jobscript.
786
+ """
634
787
  return self.submission.path / self.element_run_dir_file_name
635
788
 
636
789
  @property
637
790
  def jobscript_path(self):
791
+ """
792
+ The path to the file containing the jobscript file.
793
+ """
638
794
  return self.submission.path / self.jobscript_name
639
795
 
640
796
  @property
641
797
  def direct_stdout_path(self):
798
+ """
799
+ The path to the file containing the stdout from directly executed commands
800
+ for this jobscript.
801
+ """
642
802
  return self.submission.path / self.direct_stdout_file_name
643
803
 
644
804
  @property
645
805
  def direct_stderr_path(self):
806
+ """
807
+ The path to the file containing the stderr from directly executed commands
808
+ for this jobscript.
809
+ """
646
810
  return self.submission.path / self.direct_stderr_file_name
647
811
 
648
812
  @property
649
813
  def direct_win_pid_file_path(self):
814
+ """
815
+ The path to the file containing PIDs for directly executed commands for this
816
+ jobscript. Windows only.
817
+ """
650
818
  return self.submission.path / self.direct_win_pid_file_name
651
819
 
652
820
  def _set_submit_time(self, submit_time: datetime) -> None:
@@ -737,6 +905,9 @@ class Jobscript(JSONLike):
737
905
  )
738
906
 
739
907
  def get_task_loop_idx_array(self):
908
+ """
909
+ Get an array of task loop indices.
910
+ """
740
911
  loop_idx = np.empty_like(self.EAR_ID)
741
912
  loop_idx[:] = np.array([i[2] for i in self.task_actions]).reshape(
742
913
  (len(self.task_actions), 1)
@@ -916,6 +1087,9 @@ class Jobscript(JSONLike):
916
1087
  scheduler_name: Optional[str] = None,
917
1088
  scheduler_args: Optional[Dict] = None,
918
1089
  ):
1090
+ """
1091
+ Write the jobscript to its file.
1092
+ """
919
1093
  js_str = self.compose_jobscript(
920
1094
  deps=deps,
921
1095
  os_name=os_name,
@@ -935,6 +1109,9 @@ class Jobscript(JSONLike):
935
1109
 
936
1110
  @TimeIt.decorator
937
1111
  def make_artifact_dirs(self):
1112
+ """
1113
+ Create the directories that will hold artifacts associated with this jobscript.
1114
+ """
938
1115
  EARs_arr = self._get_EARs_arr()
939
1116
  task_loop_idx_arr = self.get_task_loop_idx_array()
940
1117
 
@@ -1036,6 +1213,9 @@ class Jobscript(JSONLike):
1036
1213
  scheduler_refs: Dict[int, (str, bool)],
1037
1214
  print_stdout: Optional[bool] = False,
1038
1215
  ) -> str:
1216
+ """
1217
+ Submit the jobscript to the scheduler.
1218
+ """
1039
1219
  # map each dependency jobscript index to the JS ref (job/process ID) and if the
1040
1220
  # dependency is an array dependency:
1041
1221
  deps = {}
@@ -1143,11 +1323,14 @@ class Jobscript(JSONLike):
1143
1323
 
1144
1324
  @property
1145
1325
  def is_submitted(self):
1146
- """Return True if this jobscript has been submitted."""
1326
+ """Whether this jobscript has been submitted."""
1147
1327
  return self.index in self.submission.submitted_jobscripts
1148
1328
 
1149
1329
  @property
1150
1330
  def scheduler_js_ref(self):
1331
+ """
1332
+ The reference to the submitted job for the jobscript.
1333
+ """
1151
1334
  if isinstance(self.scheduler, Scheduler):
1152
1335
  return self.scheduler_job_ID
1153
1336
  else:
@@ -1155,6 +1338,9 @@ class Jobscript(JSONLike):
1155
1338
 
1156
1339
  @property
1157
1340
  def scheduler_ref(self):
1341
+ """
1342
+ The generalised scheduler reference descriptor.
1343
+ """
1158
1344
  out = {"js_refs": [self.scheduler_js_ref]}
1159
1345
  if not isinstance(self.scheduler, Scheduler):
1160
1346
  out["num_js_elements"] = self.num_elements
@@ -1210,6 +1396,9 @@ class Jobscript(JSONLike):
1210
1396
  return out
1211
1397
 
1212
1398
  def cancel(self):
1399
+ """
1400
+ Cancel this jobscript.
1401
+ """
1213
1402
  self.app.submission_logger.info(
1214
1403
  f"Cancelling jobscript {self.index} of submission {self.submission.index}"
1215
1404
  )
@@ -1,3 +1,7 @@
1
+ """
2
+ Jobscript state enumeration.
3
+ """
4
+
1
5
  import enum
2
6
 
3
7
 
@@ -13,36 +17,42 @@ class JobscriptElementState(enum.Enum):
13
17
  member.__doc__ = doc
14
18
  return member
15
19
 
20
+ #: Waiting for resource allocation.
16
21
  pending = (
17
22
  0,
18
23
  "○",
19
24
  "yellow",
20
25
  "Waiting for resource allocation.",
21
26
  )
27
+ #: Waiting for one or more dependencies to finish.
22
28
  waiting = (
23
29
  1,
24
30
  "◊",
25
31
  "grey46",
26
32
  "Waiting for one or more dependencies to finish.",
27
33
  )
34
+ #: Executing now.
28
35
  running = (
29
36
  2,
30
37
  "●",
31
38
  "dodger_blue1",
32
39
  "Executing now.",
33
40
  )
41
+ #: Previously submitted but is no longer active.
34
42
  finished = (
35
43
  3,
36
44
  "■",
37
45
  "grey46",
38
46
  "Previously submitted but is no longer active.",
39
47
  )
48
+ #: Cancelled by the user.
40
49
  cancelled = (
41
50
  4,
42
51
  "C",
43
52
  "red3",
44
53
  "Cancelled by the user.",
45
54
  )
55
+ #: The scheduler reports an error state.
46
56
  errored = (
47
57
  5,
48
58
  "E",
@@ -52,4 +62,7 @@ class JobscriptElementState(enum.Enum):
52
62
 
53
63
  @property
54
64
  def rich_repr(self):
65
+ """
66
+ Rich representation of this enumeration element.
67
+ """
55
68
  return f"[{self.colour}]{self.symbol}[/{self.colour}]"
@@ -1,3 +1,7 @@
1
+ """
2
+ Job scheduler models.
3
+ """
4
+
1
5
  from pathlib import Path
2
6
  import sys
3
7
  import time
@@ -5,7 +9,22 @@ from typing import Any, List, Tuple
5
9
 
6
10
 
7
11
  class NullScheduler:
12
+ """
13
+ Abstract base class for schedulers.
14
+
15
+ Keyword Args
16
+ ------------
17
+ shell_args: str
18
+ Arguments to pass to the shell. Pre-quoted.
19
+ shebang_args: str
20
+ Arguments to set on the shebang line. Pre-quoted.
21
+ options: dict
22
+ Options to the scheduler.
23
+ """
24
+
25
+ #: Default value for arguments to the shell.
8
26
  DEFAULT_SHELL_ARGS = ""
27
+ #: Default value for arguments on the shebang line.
9
28
  DEFAULT_SHEBANG_ARGS = ""
10
29
 
11
30
  def __init__(
@@ -20,6 +39,9 @@ class NullScheduler:
20
39
 
21
40
  @property
22
41
  def unique_properties(self):
42
+ """
43
+ Unique properties, for hashing.
44
+ """
23
45
  return (self.__class__.__name__,)
24
46
 
25
47
  def __eq__(self, other) -> bool:
@@ -29,20 +51,52 @@ class NullScheduler:
29
51
  return self.__dict__ == other.__dict__
30
52
 
31
53
  def get_version_info(self):
54
+ """
55
+ Get the version of the scheduler.
56
+ """
32
57
  return {}
33
58
 
34
59
  def parse_submission_output(self, stdout: str) -> None:
60
+ """
61
+ Parse the output from a submission to determine the submission ID.
62
+ """
35
63
  return None
36
64
 
37
65
  @staticmethod
38
66
  def is_num_cores_supported(num_cores, core_range: List[int]):
67
+ """
68
+ Test whether particular number of cores is supported in given range of cores.
69
+ """
39
70
  step = core_range[1] if core_range[1] is not None else 1
40
71
  upper = core_range[2] + 1 if core_range[2] is not None else sys.maxsize
41
72
  return num_cores in range(core_range[0], upper, step)
42
73
 
43
74
 
44
75
  class Scheduler(NullScheduler):
76
+ """
77
+ Base class for schedulers that use a job submission system.
78
+
79
+ Parameters
80
+ ----------
81
+ submit_cmd: str
82
+ The submission command, if overridden from default.
83
+ show_cmd: str
84
+ The show command, if overridden from default.
85
+ del_cmd: str
86
+ The delete command, if overridden from default.
87
+ js_cmd: str
88
+ The job script command, if overridden from default.
89
+ login_nodes_cmd: str
90
+ The login nodes command, if overridden from default.
91
+ array_switch: str
92
+ The switch to enable array jobs, if overridden from default.
93
+ array_item_var: str
94
+ The variable for array items, if overridden from default.
95
+ """
96
+
97
+ #: Default command for logging into nodes.
45
98
  DEFAULT_LOGIN_NODES_CMD = None
99
+ #: Default pattern for matching the names of login nodes.
46
100
  DEFAULT_LOGIN_NODE_MATCH = "*login*"
47
101
 
48
102
  def __init__(
@@ -72,6 +126,9 @@ class Scheduler(NullScheduler):
72
126
  return (self.__class__.__name__, self.submit_cmd, self.show_cmd, self.del_cmd)
73
127
 
74
128
  def format_switch(self, switch):
129
+ """
130
+ Format a particular switch to use the JS command.
131
+ """
75
132
  return f"{self.js_cmd} {switch}"
76
133
 
77
134
  def is_jobscript_active(self, job_ID: str):
@@ -79,6 +136,9 @@ class Scheduler(NullScheduler):
79
136
  return bool(self.get_job_state_info([job_ID]))
80
137
 
81
138
  def wait_for_jobscripts(self, js_refs: List[Any]) -> None:
139
+ """
140
+ Wait for jobscripts to update their state.
141
+ """
82
142
  while js_refs:
83
143
  info = self.get_job_state_info(js_refs)
84
144
  print(info)