yellowdog-python-examples 7.15.0__tar.gz → 7.15.2__tar.gz

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 (79) hide show
  1. {yellowdog_python_examples-7.15.0/yellowdog_python_examples.egg-info → yellowdog_python_examples-7.15.2}/PKG-INFO +1 -1
  2. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/README.md +11 -8
  3. yellowdog_python_examples-7.15.2/yellowdog_cli/__init__.py +1 -0
  4. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/analyse.py +3 -3
  5. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/list.py +0 -1
  6. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/show.py +6 -22
  7. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/submit.py +25 -4
  8. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/args.py +16 -2
  9. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/config_types.py +1 -0
  10. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/entity_utils.py +0 -13
  11. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/follow_utils.py +35 -18
  12. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/load_config.py +7 -0
  13. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/property_names.py +2 -0
  14. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/settings.py +2 -0
  15. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2/yellowdog_python_examples.egg-info}/PKG-INFO +1 -1
  16. yellowdog_python_examples-7.15.0/yellowdog_cli/__init__.py +0 -1
  17. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/LICENSE +0 -0
  18. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/PYPI_README.md +0 -0
  19. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/pyproject.toml +0 -0
  20. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/requirements.txt +0 -0
  21. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/setup.cfg +0 -0
  22. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/tests/test_create_remove.py +0 -0
  23. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/tests/test_demos.py +0 -0
  24. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/tests/test_dryruns.py +0 -0
  25. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/tests/test_entrypoints.py +0 -0
  26. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/tests/test_gui.py +0 -0
  27. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/tests/test_list.py +0 -0
  28. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/tests/test_objects.py +0 -0
  29. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/tests/test_variable_processing.py +0 -0
  30. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/abort.py +0 -0
  31. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/admin.py +0 -0
  32. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/boost.py +0 -0
  33. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/cancel.py +0 -0
  34. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/cloudwizard.py +0 -0
  35. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/create.py +0 -0
  36. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/delete.py +0 -0
  37. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/download.py +0 -0
  38. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/follow.py +0 -0
  39. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/format_json.py +0 -0
  40. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/hold.py +0 -0
  41. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/instantiate.py +0 -0
  42. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/jsonnet2json.py +0 -0
  43. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/provision.py +0 -0
  44. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/remove.py +0 -0
  45. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/resize.py +0 -0
  46. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/shutdown.py +0 -0
  47. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/start.py +0 -0
  48. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/terminate.py +0 -0
  49. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/upload.py +0 -0
  50. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/__init__.py +0 -0
  51. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/check_imports.py +0 -0
  52. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/cloudwizard_aws.py +0 -0
  53. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/cloudwizard_aws_types.py +0 -0
  54. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/cloudwizard_azure.py +0 -0
  55. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/cloudwizard_common.py +0 -0
  56. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/cloudwizard_gcp.py +0 -0
  57. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/compact_json.py +0 -0
  58. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/csv_data.py +0 -0
  59. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/interactive.py +0 -0
  60. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/items.py +0 -0
  61. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/load_resources.py +0 -0
  62. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/misc_utils.py +0 -0
  63. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/printing.py +0 -0
  64. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/provision_utils.py +0 -0
  65. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/rich_console_input_fixed.py +0 -0
  66. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/start_hold_common.py +0 -0
  67. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/submit_utils.py +0 -0
  68. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/type_check.py +0 -0
  69. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/upload_utils.py +0 -0
  70. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/validate_properties.py +0 -0
  71. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/variables.py +0 -0
  72. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/wrapper.py +0 -0
  73. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/utils/ydid_utils.py +0 -0
  74. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_cli/version.py +0 -0
  75. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_python_examples.egg-info/SOURCES.txt +0 -0
  76. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_python_examples.egg-info/dependency_links.txt +0 -0
  77. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_python_examples.egg-info/entry_points.txt +0 -0
  78. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_python_examples.egg-info/requires.txt +0 -0
  79. {yellowdog_python_examples-7.15.0 → yellowdog_python_examples-7.15.2}/yellowdog_python_examples.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: yellowdog-python-examples
3
- Version: 7.15.0
3
+ Version: 7.15.2
4
4
  Summary: Python CLI commands using the YellowDog Python SDK
5
5
  Author-email: YellowDog Limited <support@yellowdog.co>
6
6
  Project-URL: Homepage, https://github.com/yellowdog/python-examples
@@ -34,7 +34,7 @@
34
34
  * [Bash, Python, PowerShell and cmd/bat Tasks](#bash-python-powershell-and-cmdbat-tasks)
35
35
  * [Docker Tasks](#docker-tasks)
36
36
  * [Bash, Python, PowerShell, cmd.exe/batch, and Docker without Automatic Processing](#bash-python-powershell-cmdexebatch-and-docker-without-automatic-processing)
37
- * [Task Counts](#task-counts)
37
+ * [Task and Task Group Counts](#task-and-task-group-counts)
38
38
  * [Examples](#examples)
39
39
  * [TOML Properties in the workRequirement Section](#toml-properties-in-the-workrequirement-section)
40
40
  * [JSON Properties at the Work Requirement Level](#json-properties-at-the-work-requirement-level)
@@ -126,7 +126,7 @@
126
126
  * [yd-analyse](#yd-analyse)
127
127
 
128
128
  <!-- Created by https://github.com/ekalinin/github-markdown-toc -->
129
- <!-- Added by: pwt, at: Wed Feb 12 12:13:59 GMT 2025 -->
129
+ <!-- Added by: pwt, at: Sun Feb 23 10:18:32 GMT 2025 -->
130
130
 
131
131
  <!--te-->
132
132
 
@@ -620,6 +620,7 @@ All properties are optional except for **`taskType`** (or **`taskTypes`**).
620
620
  | `taskData` | The data to be passed to the Worker when the Task is started. E.g., `"mydata"`. Becomes file `taskdata.txt` in the Task's working directory when The Task executes. | Yes | Yes | Yes | Yes |
621
621
  | `taskDataFile` | Populate the `taskData` property above with the contents of the specified file. E.g., `"my_task_data_file.txt"`. | Yes | Yes | Yes | Yes |
622
622
  | `taskName` | The name to use for the Task. Only usable in the TOML file. Mostly useful in conjunction with CSV Task data. E.g., `"my_task_number_{{task_number}}"`. | Yes | | | |
623
+ | `taskGroupCount` | Create `taskGroupCount` duplicates of a single Task Group. | Yes | Yes | | |
623
624
  | `taskGroupName` | The name to use for the Task Group. Only usable in the TOML file. E.g., `"my_tg_number_{{task_group_number}}"`. | Yes | | | |
624
625
  | `taskTimeout` | The timeout in minutes after which an executing Task will be terminated and reported as `FAILED`. E.g. `120.0`. The default is no timeout. | Yes | Yes | Yes | |
625
626
  | `timeout` | As above, but set at the individual Task level, which overrides the group level `taskTimeout` property (if present). | Yes | | | Yes |
@@ -763,11 +764,11 @@ arguments = ["--runtime=nvidia", "--gpus=all", "my_dockerhubrepo/my_container_im
763
764
 
764
765
  If the `executable` property is not supplied, none of the automatic processing described above for `bash`, `python`, `powershell`, `cmd` (or `bat`) and `docker` task types is applied.
765
766
 
766
- ### Task Counts
767
+ ### Task and Task Group Counts
767
768
 
768
- The `taskCount` property can be used to expand the number of Tasks within a Task Group, by creating duplicates of a single Task; this can be handy for testing and demos. In JSON specifications, there must be zero or one Task(s) listed within each Task Group or `taskCount` is ignored.
769
+ The `taskCount` property can be used to expand the number of Tasks within a Task Group, by creating duplicates of a single Task; this can be handy for testing and demos. In JSON specifications, there must be zero or one Task(s) listed within each Task Group or `taskCount` is ignored. This property can also be set on the command line using the `--task-count`/`-C` option of `yd-submit` followed by the required number of Tasks.
769
770
 
770
- This property can also be set on the command line using the `--task-count`/`-C` option of `yd-submit` followed by the required number of Tasks.
771
+ Also useful for testing, the `taskGroupCount` property or the command line option `--task-group-count`/`-G` can be set to expand the number of Task Groups in the Work Requirement, by creating duplicates of a single Task Group. If used, the `taskCount` property will apply to every Task Group, i.e., the total number of tasks is the multiple of `taskGroupCount` and `taskGroup`.
771
772
 
772
773
  ## Examples
773
774
 
@@ -819,6 +820,7 @@ Here's an example of the `workRequirement` section of a TOML configuration file,
819
820
  taskData = "my_data_string"
820
821
  taskDataFile = "my_data_file.txt"
821
822
  taskName = "my_task_number_{{task_number}}"
823
+ taskGroupCount = 5
822
824
  taskGroupName = "my_task_group_number_{{task_group_number}}"
823
825
  taskTimeout = 120.0
824
826
  taskType = "docker"
@@ -872,6 +874,7 @@ Showing all possible properties at the Work Requirement level:
872
874
  "taskCount": 100,
873
875
  "taskData": "my_task_data_string",
874
876
  "taskDataFile": "my_data_file.txt",
877
+ "taskGroupCount": 5,
875
878
  "taskTimeout": 120.0,
876
879
  "taskTypes": ["docker"],
877
880
  "tasksPerWorker": 1,
@@ -1247,7 +1250,7 @@ It's possible to make Tasks dependent on the presence of files in the Object Sto
1247
1250
 
1248
1251
  Note that a given file can only appear in *one* of the `inputs`, `verifyAtStart` or `verifyWait` lists.
1249
1252
 
1250
- Tasks with `verifyAtStart` dependencies will fail immediately if the required files are not present when the Task is submitted. Tasks with `verifyWait` dependencies will not become `READY` to be scheduled to Workers until their dependencies are satisfied.
1253
+ Tasks with `verifyAtStart` dependencies will fail immediately if the required files are not present when the Task is submitted. Tasks with `verifyWait` dependencies will not become `READY` to be allocated to Workers until their dependencies are satisfied.
1251
1254
 
1252
1255
  When specifying files in the `verifyAtStart` and `verifyWait` lists, as with the `uploadPath` property discussed above, the file locations can be (1) relative to the Work Requirement name in the current namespace (the default), (2) relative to the root of the current namespace, or (3) relative to the root of a different namespace in the user's Account.
1253
1256
 
@@ -1258,12 +1261,12 @@ When specifying files in the `verifyAtStart` and `verifyWait` lists, as with the
1258
1261
 
1259
1262
  2. For files relative to the root of the current namespace, prefix the file path with `::`, e.g.
1260
1263
  ```shell
1261
- "verifyWait": ["::file_1.txt"] -> development:file_1.txt
1264
+ "verifyWait": ["::file_1.txt"] -> development::file_1.txt
1262
1265
  ```
1263
1266
 
1264
1267
  3. For files relative to the root of a different namespace, prefix the file path with the namespace name and `::`, e.g.
1265
1268
  ```shell
1266
- "verifyWait": ["other_namespace::file_1.txt"] -> other_namespace:file_1.txt
1269
+ "verifyWait": ["other_namespace::file_1.txt"] -> other_namespace::file_1.txt
1267
1270
  ```
1268
1271
 
1269
1272
  The use of the three different forms can be mixed within a single list, e.g.:
@@ -0,0 +1 @@
1
+ __version__ = "7.15.2"
@@ -82,11 +82,11 @@ class MatchReport:
82
82
  self._property_match_list = [
83
83
  self._worker_tags,
84
84
  self._namespaces,
85
- self._task_types,
86
85
  self._instance_types,
87
86
  self._providers,
88
- self._regions,
89
87
  self._ram,
88
+ self._regions,
89
+ self._task_types,
90
90
  self._vcpus,
91
91
  ]
92
92
 
@@ -124,7 +124,7 @@ class MatchReport:
124
124
  if self.summary() == MatchType.YES:
125
125
  match_str = "MATCHING"
126
126
  elif self.summary() == MatchType.MAYBE:
127
- match_str = "POSSIBLY MATCHING"
127
+ match_str = "MAYBE MATCHING"
128
128
  elif self.summary() == MatchType.PARTIAL:
129
129
  match_str = "PARTIALLY MATCHING"
130
130
  else:
@@ -36,7 +36,6 @@ from yellowdog_client.model import (
36
36
  Task,
37
37
  TaskGroup,
38
38
  Worker,
39
- WorkerPool,
40
39
  WorkerPoolStatus,
41
40
  WorkerPoolSummary,
42
41
  WorkerStatus,
@@ -4,15 +4,10 @@
4
4
  Command to show the JSON details of YellowDog entities via their IDs.
5
5
  """
6
6
 
7
- from typing import Optional
8
-
9
7
  from yellowdog_client.model import ConfiguredWorkerPool, Task
10
8
 
11
9
  from yellowdog_cli.list import get_keyring
12
- from yellowdog_cli.utils.entity_utils import (
13
- get_task_by_id,
14
- substitute_ids_for_names_in_crt,
15
- )
10
+ from yellowdog_cli.utils.entity_utils import substitute_ids_for_names_in_crt
16
11
  from yellowdog_cli.utils.printing import print_error, print_log, print_yd_object
17
12
  from yellowdog_cli.utils.settings import (
18
13
  RESOURCE_PROPERTY_NAME,
@@ -151,22 +146,11 @@ def show_details(ydid: str, initial_indent: int = 0, with_final_comma: bool = Fa
151
146
 
152
147
  elif ydid_type == YDIDType.TASK:
153
148
  print_log(f"Showing details of Task ID '{ydid}'")
154
- work_requirement = CLIENT.work_client.get_work_requirement_by_id(
155
- ydid.rsplit(":", 2)[0].replace("task", "workreq")
156
- )
157
- for task_group in work_requirement.taskGroups:
158
- if task_group.id == ydid.rsplit(":", 1)[0].replace("task", "taskgrp"):
159
- break
160
- else:
161
- print_error(f"Task Group ID '{ydid}' not found")
162
- return
163
- task: Optional[Task] = get_task_by_id(
164
- CLIENT, work_requirement.id, task_group.id, ydid
165
- )
166
- if task is not None:
167
- print_yd_object(task)
168
- else:
169
- print_error(f"Task ID '{ydid}' not found")
149
+ try:
150
+ print_yd_object(CLIENT.work_client.get_task_by_id(ydid))
151
+ except Exception as e:
152
+ if "404" in str(e):
153
+ print_error(f"Task ID '{ydid}' not found")
170
154
 
171
155
  elif ydid_type == YDIDType.IMAGE_FAMILY:
172
156
  print_log(f"Showing details of Image Family ID '{ydid}'")
@@ -290,7 +290,28 @@ def submit_work_requirement(
290
290
  wr_data.get(FLATTEN_UPLOAD_PATHS, CONFIG_WR.flatten_upload_paths)
291
291
  )
292
292
 
293
- # Create the list of Task Groups
293
+ # Expand number of task groups if there's a single task group
294
+ # and taskGroupCount is set
295
+ task_group_count = check_float_or_int(
296
+ wr_data.get(TASK_GROUP_COUNT, CONFIG_WR.task_group_count)
297
+ )
298
+ if task_group_count > 1:
299
+ if len(wr_data[TASK_GROUPS]) == 1:
300
+ print_log(
301
+ f"Expanding number of Task Groups to '{TASK_GROUP_COUNT}="
302
+ f"{task_group_count}'"
303
+ )
304
+ wr_data[TASK_GROUPS] = [
305
+ deepcopy(wr_data[TASK_GROUPS][0]) for _ in range(task_group_count)
306
+ ]
307
+ elif len(wr_data[TASK_GROUPS]) > 1:
308
+ print_warning(
309
+ f"Note: Work Requirement already contains"
310
+ f" {len(wr_data[TASK_GROUPS])} Task Groups: ignoring expansion "
311
+ f"using '{TASK_GROUP_COUNT} = {int(task_group_count)}'"
312
+ )
313
+
314
+ # Create the list of TaskGroup objects
294
315
  task_groups: List[TaskGroup] = []
295
316
  for tg_number, task_group_data in enumerate(wr_data[TASK_GROUPS]):
296
317
  task_groups.append(create_task_group(tg_number, wr_data, task_group_data))
@@ -571,16 +592,16 @@ def add_tasks_to_task_group(
571
592
  # Expand the number of Tasks to match the specified Task count
572
593
  print_log(
573
594
  f"Expanding number of Tasks in Task Group '{task_group.name}' to"
574
- f" 'taskCount={task_group_task_count}' Tasks"
595
+ f" '{TASK_COUNT}={task_group_task_count}' Tasks"
575
596
  )
576
597
  for _ in range(1, task_group_task_count):
577
598
  wr_data[TASK_GROUPS][tg_number][TASKS].append(
578
599
  deepcopy(wr_data[TASK_GROUPS][tg_number][TASKS][0])
579
600
  )
580
601
  elif task_group_task_count > 1:
581
- print_log(
602
+ print_warning(
582
603
  f"Note: Task Group '{task_group.name}' already contains"
583
- f" {num_tasks} Tasks: ignoring expansion using 'taskCount ="
604
+ f" {num_tasks} Tasks: ignoring expansion using '{TASK_COUNT} ="
584
605
  f" {int(task_group_task_count)}'"
585
606
  )
586
607
 
@@ -245,9 +245,17 @@ class CLIParser:
245
245
  "-C",
246
246
  type=int,
247
247
  required=False,
248
- help="the number of times to submit the Task",
248
+ help="the number of tasks to submit (copies of a single task)",
249
249
  metavar="<task_count>",
250
250
  )
251
+ parser.add_argument(
252
+ "--task-group-count",
253
+ "-G",
254
+ type=int,
255
+ required=False,
256
+ help="the number of task groups to submit (copies of a single task group)",
257
+ metavar="<task_group_count>",
258
+ )
251
259
  parser.add_argument(
252
260
  "--task-batch-size",
253
261
  "-b",
@@ -1330,6 +1338,11 @@ class CLIParser:
1330
1338
  def task_count(self) -> Optional[int]:
1331
1339
  return self.args.task_count
1332
1340
 
1341
+ @property
1342
+ @allow_missing_attribute
1343
+ def task_group_count(self) -> Optional[int]:
1344
+ return self.args.task_group_count
1345
+
1333
1346
  @property
1334
1347
  @allow_missing_attribute
1335
1348
  def csv_files(self) -> Optional[List[str]]:
@@ -1670,7 +1683,8 @@ def lookup_module_description(module_name: str) -> Optional[str]:
1670
1683
  suffix = "showing the JSON details of entities referenced by their YDIDs"
1671
1684
  elif "analyse" in module_name:
1672
1685
  suffix = (
1673
- "analysing if a task group is matched by workers in selected worker pools"
1686
+ "analysing if a work requirement or single task group is matched by "
1687
+ "workers in the specified worker pools"
1674
1688
  )
1675
1689
 
1676
1690
  return None if suffix is None else prefix + suffix
@@ -55,6 +55,7 @@ class ConfigWorkRequirement:
55
55
  task_count: int = 1
56
56
  task_data: Optional[str] = None
57
57
  task_data_file: Optional[str] = None
58
+ task_group_count: int = 1
58
59
  task_group_name: Optional[str] = None
59
60
  task_level_timeout: Optional[float] = None
60
61
  task_name: Optional[str] = None
@@ -440,23 +440,10 @@ def list_matching_object_paths(
440
440
  ]
441
441
 
442
442
 
443
- def get_task_by_id(
444
- client: PlatformClient, wr_id: str, task_group_id: str, task_id: str
445
- ) -> Optional[Task]:
446
- """
447
- Find a task by its ID.
448
- """
449
- tasks: List[Task] = get_tasks(client, wr_id, task_group_id)
450
- for task in tasks:
451
- if task.id == task_id:
452
- return task
453
-
454
-
455
443
  @lru_cache
456
444
  def get_tasks(client: PlatformClient, wr_id: str, task_group_id: str) -> List[Task]:
457
445
  """
458
446
  Return all the tasks in a task group, with caching.
459
- There is no native way to search for a task by its id.
460
447
  """
461
448
  task_search = TaskSearch(
462
449
  workRequirementId=wr_id,
@@ -3,6 +3,7 @@ Utility function to follow event streams.
3
3
  """
4
4
 
5
5
  from threading import Thread
6
+ from time import sleep
6
7
  from typing import List, Optional
7
8
 
8
9
  import requests
@@ -19,6 +20,8 @@ from yellowdog_cli.utils.printing import (
19
20
  from yellowdog_cli.utils.wrapper import CLIENT, CONFIG_COMMON
20
21
  from yellowdog_cli.utils.ydid_utils import YDIDType, get_ydid_type
21
22
 
23
+ EVENT_STREAM_RETRY_INTERVAL = 5.0 # Seconds
24
+
22
25
 
23
26
  def follow_ids(ydids: List[str], auto_cr: bool = False):
24
27
  """
@@ -82,25 +85,39 @@ def follow_events(ydid: str, ydid_type: YDIDType):
82
85
  """
83
86
  Follow events.
84
87
  """
85
- response = requests.get(
86
- headers={"Authorization": f"yd-key {CONFIG_COMMON.key}:{CONFIG_COMMON.secret}"},
87
- url=get_event_url(ydid, ydid_type),
88
- stream=True,
89
- )
90
-
91
- if response.status_code != 200:
92
- print_error(f"'{ydid}': {response.json()['message']}")
93
- return
88
+ while True:
89
+ response = requests.get(
90
+ headers={
91
+ "Authorization": f"yd-key {CONFIG_COMMON.key}:{CONFIG_COMMON.secret}"
92
+ },
93
+ url=get_event_url(ydid, ydid_type),
94
+ stream=True,
95
+ )
96
+
97
+ if response.status_code != 200:
98
+ print_error(f"'{ydid}': {response.json()['message']}")
99
+ return
100
+
101
+ if response.encoding is None:
102
+ response.encoding = "utf-8"
94
103
 
95
- if response.encoding is None:
96
- response.encoding = "utf-8"
97
-
98
- try:
99
- for event in response.iter_lines(decode_unicode=True):
100
- if event:
101
- print_event(event, ydid_type)
102
- except Exception as e:
103
- print_warning(f"Event stream error: {e}")
104
+ try:
105
+ for event in response.iter_lines(decode_unicode=True):
106
+ if event:
107
+ print_event(event, ydid_type)
108
+ break
109
+
110
+ except Exception as e:
111
+ if "Connection broken" in str(e):
112
+ print_warning(
113
+ f"Event stream interruption for '{ydid}'"
114
+ f"(retrying in {EVENT_STREAM_RETRY_INTERVAL}s): {e}"
115
+ )
116
+ sleep(EVENT_STREAM_RETRY_INTERVAL)
117
+ continue
118
+ else:
119
+ print_error(f"Event stream error: {e}")
120
+ break
104
121
 
105
122
  print_log(f"Event stream concluded for '{ydid}'")
106
123
 
@@ -266,6 +266,12 @@ def load_config_work_requirement() -> ConfigWorkRequirement:
266
266
  else wr_section.get(TASK_COUNT, 1)
267
267
  )
268
268
 
269
+ task_group_count = (
270
+ ARGS_PARSER.task_group_count
271
+ if ARGS_PARSER.task_group_count is not None
272
+ else wr_section.get(TASK_GROUP_COUNT, 1)
273
+ )
274
+
269
275
  return ConfigWorkRequirement(
270
276
  add_yd_env_vars=wr_section.get(ADD_YD_ENV_VARS, False),
271
277
  always_upload=wr_section.get(ALWAYS_UPLOAD, True),
@@ -305,6 +311,7 @@ def load_config_work_requirement() -> ConfigWorkRequirement:
305
311
  task_count=task_count,
306
312
  task_data=wr_section.get(TASK_DATA, None),
307
313
  task_data_file=wr_section.get(TASK_DATA_FILE, None),
314
+ task_group_count=task_group_count,
308
315
  task_group_name=wr_section.get(TASK_GROUP_NAME, None),
309
316
  task_name=wr_section.get(TASK_NAME, None),
310
317
  task_timeout=wr_section.get(TASK_TIMEOUT, None),
@@ -68,6 +68,7 @@ TASK_COUNT = "taskCount" # Integer
68
68
  TASK_DATA = "taskData" # String
69
69
  TASK_DATA_FILE = "taskDataFile" # String
70
70
  TASK_GROUPS = "taskGroups" # List of Task Groups
71
+ TASK_GROUP_COUNT = "taskGroupCount" # Integer
71
72
  TASK_GROUP_NAME = "taskGroupName" # String
72
73
  TASK_GROUP_TAG = "tag" # String
73
74
  TASK_LEVEL_TIMEOUT = "timeout" # Float
@@ -167,6 +168,7 @@ ALL_KEYS = [
167
168
  TASK_DATA,
168
169
  TASK_DATA_FILE,
169
170
  TASK_GROUPS,
171
+ TASK_GROUP_COUNT,
170
172
  TASK_GROUP_NAME,
171
173
  TASK_GROUP_TAG,
172
174
  TASK_LEVEL_TIMEOUT,
@@ -55,6 +55,7 @@ HIGHLIGHTED_STATES = [
55
55
  r"(?P<active>RUNNING)",
56
56
  r"(?P<active>TARGET)",
57
57
  r"(?P<active>ALIVE)",
58
+ r"(?P<active>MATCHING)",
58
59
  r"(?P<cancelled>ABORTED)",
59
60
  r"(?P<cancelled>CANCELLED)",
60
61
  r"(?P<cancelled>CANCELLING)",
@@ -66,6 +67,7 @@ HIGHLIGHTED_STATES = [
66
67
  r"(?P<failed>FAILED)",
67
68
  r"(?P<failed>FAILING)",
68
69
  r"(?P<failed>LOST)",
70
+ r"(?P<failed>NON-MATCHING)",
69
71
  r"(?P<idle>EMPTY)",
70
72
  r"(?P<idle>FOUND)",
71
73
  r"(?P<idle>IDLE)",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: yellowdog-python-examples
3
- Version: 7.15.0
3
+ Version: 7.15.2
4
4
  Summary: Python CLI commands using the YellowDog Python SDK
5
5
  Author-email: YellowDog Limited <support@yellowdog.co>
6
6
  Project-URL: Homepage, https://github.com/yellowdog/python-examples
@@ -1 +0,0 @@
1
- __version__ = "7.15.0"