yellowdog-cli 9.2.2__tar.gz → 9.2.4__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 (120) hide show
  1. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/PKG-INFO +3 -2
  2. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/README.md +8 -3
  3. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/pyproject.toml +3 -2
  4. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_submit_utils.py +56 -2
  5. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/__init__.py +2 -1
  6. yellowdog_cli-9.2.4/yellowdog_cli/_version.py +1 -0
  7. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/create.py +12 -43
  8. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/help.py +1 -1
  9. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/list.py +2 -1
  10. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/submit.py +2 -0
  11. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/args.py +1 -1
  12. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/config_types.py +2 -1
  13. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/csv_data.py +1 -0
  14. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/load_config.py +2 -1
  15. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/property_names.py +2 -0
  16. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/submit_utils.py +33 -12
  17. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/version.py +2 -1
  18. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli.egg-info/PKG-INFO +3 -2
  19. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli.egg-info/SOURCES.txt +1 -0
  20. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli.egg-info/requires.txt +2 -1
  21. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/LICENSE +0 -0
  22. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/PYPI_README.md +0 -0
  23. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/setup.cfg +0 -0
  24. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_add_to.py +0 -0
  25. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_arguments_assembly.py +0 -0
  26. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_build_dc_substitutions.py +0 -0
  27. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_compact_json.py +0 -0
  28. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_compare.py +0 -0
  29. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_create_remove.py +0 -0
  30. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_csv_data.py +0 -0
  31. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_dataclient_utils.py +0 -0
  32. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_demos.py +0 -0
  33. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_dryruns.py +0 -0
  34. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_entrypoints.py +0 -0
  35. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_environment_merge.py +0 -0
  36. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_instance_pricing_preference.py +0 -0
  37. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_interactive.py +0 -0
  38. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_list.py +0 -0
  39. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_load_config_helpers.py +0 -0
  40. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_ls_formatting.py +0 -0
  41. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_misc_utils.py +0 -0
  42. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_node_batching.py +0 -0
  43. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_nodeaction_args.py +0 -0
  44. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_nodeaction_parsing.py +0 -0
  45. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_printing.py +0 -0
  46. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_property_overrides.py +0 -0
  47. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_provision_utils.py +0 -0
  48. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_rclone_utils.py +0 -0
  49. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_resequence_resources.py +0 -0
  50. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_resolve_entity_type.py +0 -0
  51. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_select_dc_section.py +0 -0
  52. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_start_hold_common.py +0 -0
  53. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_submit_batching.py +0 -0
  54. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_submit_functions.py +0 -0
  55. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_system_cancel_hold_finish.py +0 -0
  56. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_system_csv_batch.py +0 -0
  57. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_system_dataclient.py +0 -0
  58. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_system_error_handling.py +0 -0
  59. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_system_lifecycle.py +0 -0
  60. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_system_resize.py +0 -0
  61. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_system_resources.py +0 -0
  62. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_type_check.py +0 -0
  63. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_validate_properties.py +0 -0
  64. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_variable_processing.py +0 -0
  65. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_variable_subs.py +0 -0
  66. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/tests/test_ydid_utils.py +0 -0
  67. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/abort.py +0 -0
  68. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/application.py +0 -0
  69. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/boost.py +0 -0
  70. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/cancel.py +0 -0
  71. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/cloudwizard.py +0 -0
  72. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/compare.py +0 -0
  73. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/delete.py +0 -0
  74. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/download.py +0 -0
  75. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/finish.py +0 -0
  76. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/follow.py +0 -0
  77. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/format_json.py +0 -0
  78. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/hold.py +0 -0
  79. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/instantiate.py +0 -0
  80. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/jsonnet2json.py +0 -0
  81. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/ls.py +0 -0
  82. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/nodeaction.py +0 -0
  83. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/provision.py +0 -0
  84. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/remove.py +0 -0
  85. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/resize.py +0 -0
  86. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/show.py +0 -0
  87. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/shutdown.py +0 -0
  88. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/start.py +0 -0
  89. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/terminate.py +0 -0
  90. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/upload.py +0 -0
  91. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/__init__.py +0 -0
  92. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/check_imports.py +0 -0
  93. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/cloudwizard_aws.py +0 -0
  94. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/cloudwizard_aws_types.py +0 -0
  95. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/cloudwizard_azure.py +0 -0
  96. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/cloudwizard_common.py +0 -0
  97. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/cloudwizard_gcp.py +0 -0
  98. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/compact_json.py +0 -0
  99. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/dataclient_utils.py +0 -0
  100. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/dataclient_wrapper.py +0 -0
  101. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/entity_utils.py +0 -0
  102. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/follow_utils.py +0 -0
  103. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/interactive.py +0 -0
  104. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/items.py +0 -0
  105. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/load_resources.py +0 -0
  106. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/misc_utils.py +0 -0
  107. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/printing.py +0 -0
  108. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/provision_utils.py +0 -0
  109. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/rclone_utils.py +0 -0
  110. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/rich_console_input_fixed.py +0 -0
  111. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/settings.py +0 -0
  112. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/start_hold_common.py +0 -0
  113. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/type_check.py +0 -0
  114. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/validate_properties.py +0 -0
  115. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/variables.py +0 -0
  116. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/wrapper.py +0 -0
  117. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli/utils/ydid_utils.py +0 -0
  118. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli.egg-info/dependency_links.txt +0 -0
  119. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli.egg-info/entry_points.txt +0 -0
  120. {yellowdog_cli-9.2.2 → yellowdog_cli-9.2.4}/yellowdog_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yellowdog-cli
3
- Version: 9.2.2
3
+ Version: 9.2.4
4
4
  Summary: Python-based CLI the YellowDog Platform
5
5
  Author-email: YellowDog Limited <support@yellowdog.co>
6
6
  License-Expression: Apache-2.0
@@ -18,12 +18,13 @@ License-File: LICENSE
18
18
  Requires-Dist: PyPAC>=0.16.4
19
19
  Requires-Dist: dateparser
20
20
  Requires-Dist: python-dotenv
21
+ Requires-Dist: jsons
21
22
  Requires-Dist: requests
22
23
  Requires-Dist: rclone-api
23
24
  Requires-Dist: rich>=13.9.4
24
25
  Requires-Dist: tabulate>=0.9.0
25
26
  Requires-Dist: tomli>=2.4.1
26
- Requires-Dist: yellowdog-sdk>=15.1.0
27
+ Requires-Dist: yellowdog-sdk>=15.2.0
27
28
  Provides-Extra: jsonnet
28
29
  Requires-Dist: jsonnet; extra == "jsonnet"
29
30
  Provides-Extra: cloudwizard
@@ -178,7 +178,7 @@
178
178
  * [yd-upload](#yd-upload-1)
179
179
 
180
180
  <!-- Created by https://github.com/ekalinin/github-markdown-toc -->
181
- <!-- Added by: pwt, at: Wed Apr 22 11:28:15 BST 2026 -->
181
+ <!-- Added by: pwt, at: Wed Apr 22 11:51:22 BST 2026 -->
182
182
 
183
183
  <!--te-->
184
184
 
@@ -900,12 +900,13 @@ The following table outlines all the properties available for defining Work Requ
900
900
  | `taskCount` | The number of times to execute the Task. | Yes | Yes | Yes | |
901
901
  | `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 |
902
902
  | `taskDataFile` | Populate the `taskData` property above with the contents of the specified file. E.g., `"my_task_data_file.txt"`. | Yes | Yes | Yes | Yes |
903
+ | `taskDataFiles` | Populate the `taskData` property above by concatenating the contents of a list of files. Mutually exclusive with `taskData` and `taskDataFile`. E.g., `["header.txt", "body.txt"]`. | Yes | Yes | Yes | Yes |
903
904
  | `taskDataInputs` | A list of data inputs to be downloaded by the task E.g., JSON: `{"source": "src", "destination": "dest"}`, TOML: `{source = "src", destination = "dest"}`. | Yes | Yes | Yes | Yes |
904
905
  | `taskDataOutputs` | A list of data outputs to be uploaded at the conclusion of a task E.g., JSON: `{"source": "src", "destination": "dest", "alwaysUpload": true}`, TOML: `{source = "src", destination = "dest", alwaysUpload = true}`. | Yes | Yes | Yes | Yes |
905
906
  | `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 | | | |
906
907
  | `taskGroupCount` | Create `taskGroupCount` duplicates of a single Task Group. | Yes | Yes | | |
907
908
  | `taskGroupName` | The name to use for the Task Group. Only usable in the TOML file. E.g., `"my_tg_number_{{task_group_number}}"`. | Yes | | | |
908
- | `taskTemplate` | Sets default `taskType`, `taskData` (or `taskDataFile`), and/or `environment` for all Tasks in a Task Group; applied by the platform, allowing Tasks to be more compact. E.g., `{"taskType": "docker", "environment": {"X": "1"}}`. | Yes | Yes | Yes | |
909
+ | `taskTemplate` | Sets default `taskType`, `taskData` (or `taskDataFile`/`taskDataFiles`), and/or `environment` for all Tasks in a Task Group; applied by the platform, allowing Tasks to be more compact. E.g., `{"taskType": "docker", "environment": {"X": "1"}}`. | Yes | Yes | Yes | |
909
910
  | `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 | |
910
911
  | `timeout` | As above, but set at the individual Task level, which overrides the group level `taskTimeout` property (if present). | Yes | | | Yes |
911
912
  | `taskType` | The Task Type of a Task. E.g., `"docker"`. | Yes | | | Yes |
@@ -1017,7 +1018,7 @@ The `taskTemplate` property on a Task Group optionally sets default values for `
1017
1018
 
1018
1019
  Any combination of the three fields can be specified; omitted fields are simply not defaulted. Values specified directly on an individual Task take precedence over the template.
1019
1020
 
1020
- `taskDataFile` can be used inside `taskTemplate` as an alternative to `taskData`, exactly as it can at the Task level — the file contents are read client-side and used as the `taskData` value.
1021
+ `taskDataFile` or `taskDataFiles` can be used inside `taskTemplate` as an alternative to `taskData`, exactly as they can at the Task level — the file contents are read client-side and used as the `taskData` value. `taskDataFiles` concatenates multiple files in order.
1021
1022
 
1022
1023
  `taskTemplate` can be set in the TOML config (applying globally as a default), at the Work Requirement level, or at the Task Group level. More specific levels take precedence.
1023
1024
 
@@ -1136,6 +1137,7 @@ Here's an example of the `workRequirement` section of a TOML configuration file,
1136
1137
  taskCount = 100
1137
1138
  taskData = "my_data_string"
1138
1139
  taskDataFile = "my_data_file.txt"
1140
+ taskDataFiles = ["header.txt", "body.txt"]
1139
1141
  taskDataInputs = [
1140
1142
  {source = "in_src_path_1", destination = "dest_path_1"},
1141
1143
  {localPath = "local_file", uploadPath = "in_src_path_2", source = "in_src_path_2", destination = "dest_path_2"},
@@ -1194,6 +1196,7 @@ Showing all possible properties at the Work Requirement level:
1194
1196
  "taskCount": 100,
1195
1197
  "taskData": "my_task_data_string",
1196
1198
  "taskDataFile": "my_data_file.txt",
1199
+ "taskDataFiles": ["header.txt", "body.txt"],
1197
1200
  "taskDataInputs": [
1198
1201
  {"destination": "dest_path_1", "source": "in_src_path_1"},
1199
1202
  {"localPath": "local_file", "uploadPath": "in_src_path_2", "destination": "dest_path_2", "source": "in_src_path_2"}
@@ -1260,6 +1263,7 @@ Showing all possible properties at the Task Group level:
1260
1263
  "taskCount": 5,
1261
1264
  "taskData": "my_task_data_string",
1262
1265
  "taskDataFile": "my_data_file.txt",
1266
+ "taskDataFiles": ["header.txt", "body.txt"],
1263
1267
  "taskDataInputs": [
1264
1268
  {"destination": "dest_path_1", "source": "in_src_path_1"},
1265
1269
  {"localPath": "local_file", "uploadPath": "in_src_path_2", "destination": "dest_path_2", "source": "in_src_path_2"}
@@ -1307,6 +1311,7 @@ Showing all possible properties at the Task level:
1307
1311
  "tag": "my_tag",
1308
1312
  "taskData": "my_task_data_string",
1309
1313
  "taskDataFile": "my_data_file.txt",
1314
+ "taskDataFiles": ["header.txt", "body.txt"],
1310
1315
  "taskDataInputs": [
1311
1316
  {"destination": "dest_path_1", "source": "in_src_path_1"},
1312
1317
  {"localPath": "local_file", "uploadPath": "in_src_path_2", "destination": "dest_path_2", "source": "in_src_path_2"}
@@ -21,12 +21,13 @@
21
21
  "PyPAC >= 0.16.4",
22
22
  "dateparser",
23
23
  "python-dotenv",
24
+ "jsons",
24
25
  "requests",
25
26
  "rclone-api",
26
27
  "rich >= 13.9.4",
27
28
  "tabulate >= 0.9.0",
28
29
  "tomli >= 2.4.1",
29
- "yellowdog-sdk >= 15.1.0",
30
+ "yellowdog-sdk >= 15.2.0",
30
31
  ]
31
32
 
32
33
  [[project.authors]]
@@ -102,7 +103,7 @@
102
103
  packages = ["yellowdog_cli", "yellowdog_cli.utils"]
103
104
 
104
105
  [tool.setuptools.dynamic.version]
105
- attr = "yellowdog_cli.__init__.__version__"
106
+ attr = "yellowdog_cli._version.__version__"
106
107
 
107
108
  [tool.ruff]
108
109
  target-version = "py310"
@@ -15,6 +15,7 @@ from yellowdog_cli.utils.config_types import ConfigWorkRequirement
15
15
  from yellowdog_cli.utils.property_names import (
16
16
  TASK_DATA,
17
17
  TASK_DATA_FILE,
18
+ TASK_DATA_FILES,
18
19
  TASK_GROUPS,
19
20
  TASK_TAG,
20
21
  TASKS,
@@ -166,9 +167,40 @@ class TestResolveTaskData:
166
167
  assert su.resolve_task_data({TASK_DATA_FILE: str(f)}) == "from-file"
167
168
 
168
169
  def test_raises_when_both_set(self):
169
- with pytest.raises(ValueError, match="both set"):
170
+ with pytest.raises(ValueError, match="Only one of"):
170
171
  su.resolve_task_data({TASK_DATA: "x", TASK_DATA_FILE: "f.txt"})
171
172
 
173
+ def test_raises_when_task_data_and_files_both_set(self):
174
+ with pytest.raises(ValueError, match="Only one of"):
175
+ su.resolve_task_data({TASK_DATA: "x", TASK_DATA_FILES: ["f.txt"]})
176
+
177
+ def test_raises_when_task_data_file_and_files_both_set(self, tmp_path):
178
+ f = tmp_path / "a.txt"
179
+ f.write_text("a")
180
+ with pytest.raises(ValueError, match="Only one of"):
181
+ su.resolve_task_data({TASK_DATA_FILE: str(f), TASK_DATA_FILES: [str(f)]})
182
+
183
+ def test_concatenates_task_data_files(self, tmp_path):
184
+ f1 = tmp_path / "a.txt"
185
+ f1.write_text("hello")
186
+ f2 = tmp_path / "b.txt"
187
+ f2.write_text("world")
188
+ result = su.resolve_task_data({TASK_DATA_FILES: [str(f1), str(f2)]})
189
+ assert result == "hello\nworld\n"
190
+
191
+ def test_task_data_files_single_file(self, tmp_path):
192
+ f = tmp_path / "only.txt"
193
+ f.write_text("content")
194
+ result = su.resolve_task_data({TASK_DATA_FILES: [str(f)]})
195
+ assert result == "content\n"
196
+
197
+ def test_task_data_files_variable_substitution(self, tmp_path, monkeypatch):
198
+ monkeypatch.setenv("_YD_TEST_TDF_VAR", "sub")
199
+ f = tmp_path / "t.txt"
200
+ f.write_text("{{env:_YD_TEST_TDF_VAR}}")
201
+ result = su.resolve_task_data({TASK_DATA_FILES: [str(f)]})
202
+ assert result == "sub\n"
203
+
172
204
  def test_variable_substitution_applied_to_file_contents(
173
205
  self, tmp_path, monkeypatch
174
206
  ):
@@ -218,7 +250,7 @@ class TestGetTaskDataProperty:
218
250
  self, empty_config_wr
219
251
  ):
220
252
  task = {TASK_DATA: "inline", TASK_DATA_FILE: "file.txt"}
221
- with pytest.raises(ValueError, match="both set"):
253
+ with pytest.raises(ValueError, match="Only one of"):
222
254
  su.get_task_data_property(empty_config_wr, {}, {}, task, "t1")
223
255
 
224
256
  def test_task_data_file_read_from_disk(self, empty_config_wr, tmp_path):
@@ -235,6 +267,28 @@ class TestGetTaskDataProperty:
235
267
  result = su.get_task_data_property(config, {}, {}, {}, "t1")
236
268
  assert result == "cfg-file-contents"
237
269
 
270
+ def test_task_level_task_data_files_wins_over_wr(self, empty_config_wr, tmp_path):
271
+ f = tmp_path / "t.txt"
272
+ f.write_text("task-files")
273
+ task = {TASK_DATA_FILES: [str(f)]}
274
+ wr_data = {TASK_DATA: "wr-level"}
275
+ result = su.get_task_data_property(empty_config_wr, wr_data, {}, task, "t1")
276
+ assert result == "task-files\n"
277
+
278
+ def test_config_wr_task_data_files_used_as_final_fallback(self, tmp_path):
279
+ f1 = tmp_path / "a.txt"
280
+ f1.write_text("part1")
281
+ f2 = tmp_path / "b.txt"
282
+ f2.write_text("part2")
283
+ config = ConfigWorkRequirement(task_data_files=[str(f1), str(f2)])
284
+ result = su.get_task_data_property(config, {}, {}, {}, "t1")
285
+ assert result == "part1\npart2\n"
286
+
287
+ def test_raises_when_task_data_and_files_set_at_task_level(self, empty_config_wr):
288
+ task = {TASK_DATA: "inline", TASK_DATA_FILES: ["f.txt"]}
289
+ with pytest.raises(ValueError, match="Only one of"):
290
+ su.get_task_data_property(empty_config_wr, {}, {}, task, "t1")
291
+
238
292
 
239
293
  # ---------------------------------------------------------------------------
240
294
  # create_task
@@ -1,3 +1,4 @@
1
- __version__ = "9.2.2"
1
+ from yellowdog_cli._version import __version__ as __version__
2
+
2
3
  __author__ = "YellowDog Limited"
3
4
  __email__ = "support@yellowdog.ai"
@@ -0,0 +1 @@
1
+ __version__ = "9.2.4"
@@ -14,32 +14,24 @@ import yellowdog_client.model as model
14
14
  from dateparser import parse as date_parse
15
15
  from requests import post, put
16
16
  from requests.exceptions import HTTPError
17
+ from yellowdog_client.common.json import Json
17
18
  from yellowdog_client.model import (
18
- AccountAllowance,
19
19
  AddApplicationResponse,
20
20
  AddConfiguredWorkerPoolResponse,
21
21
  AddGroupRequest,
22
- AllowanceLimitEnforcement,
23
- AllowanceResetType,
24
22
  ApiKey,
25
23
  Application,
26
- AwsFleetComputeSource,
27
- AwsFleetPurchaseOption,
28
24
  CloudProvider,
29
25
  CreateNamespaceRequest,
30
26
  Group,
31
27
  GroupRole,
32
28
  ImageOsType,
33
- InstanceStatus,
34
29
  InternalUser,
35
30
  MachineImage,
36
31
  MachineImageFamily,
37
32
  MachineImageGroup,
38
33
  NamespacePolicy,
39
- RequirementsAllowance,
40
34
  RoleScope,
41
- SourceAllowance,
42
- SourcesAllowance,
43
35
  UpdateGroupRequest,
44
36
  User,
45
37
  )
@@ -1328,40 +1320,17 @@ def _get_model_object(class_name: str, resource: dict, **kwargs):
1328
1320
  if missing:
1329
1321
  raise KeyError(f"Missing expected property '{missing[0]}'")
1330
1322
 
1331
- model_object = cls(**resource, **kwargs)
1332
- _patch_aws_fleet_enums(model_object)
1333
- _patch_allowance_enums(model_object)
1334
- return model_object
1335
-
1336
-
1337
- def _patch_aws_fleet_enums(model_object) -> None:
1338
- if isinstance(model_object, AwsFleetComputeSource):
1339
- try:
1340
- model_object.purchaseOption = AwsFleetPurchaseOption[
1341
- str(model_object.purchaseOption)
1342
- ]
1343
- except KeyError:
1344
- raise ValueError(
1345
- "Invalid AWS Fleet Compute Source Purchase Option property: "
1346
- f"'{model_object.purchaseOption!s}'"
1347
- )
1348
-
1349
-
1350
- def _patch_allowance_enums(model_object) -> None:
1351
- if isinstance(
1352
- model_object,
1353
- (SourceAllowance, SourcesAllowance, RequirementsAllowance, AccountAllowance),
1354
- ):
1355
- try:
1356
- model_object.limitEnforcement = AllowanceLimitEnforcement(
1357
- model_object.limitEnforcement
1358
- )
1359
- model_object.resetType = AllowanceResetType(model_object.resetType)
1360
- model_object.monitoredStatuses = [
1361
- InstanceStatus(status) for status in model_object.monitoredStatuses
1362
- ]
1363
- except KeyError as e:
1364
- raise KeyError(f"Invalid Allowance property: {e}")
1323
+ # Normalize all values to their JSON-compatible representations so that
1324
+ # Json.load can properly structure nested typed fields (e.g. enums,
1325
+ # timedeltas, and nested model objects) — necessary because the SDK's
1326
+ # proxy now calls Json.dump on every outbound request.
1327
+ merged = {}
1328
+ for k, v in {**resource, **kwargs}.items():
1329
+ if isinstance(v, (str, int, float, bool, type(None))):
1330
+ merged[k] = v
1331
+ else:
1332
+ merged[k] = Json.dump(v)
1333
+ return Json.load(merged, cls)
1365
1334
 
1366
1335
 
1367
1336
  def _get_model_class(class_name: str):
@@ -4,7 +4,7 @@
4
4
  List all available yd-* commands and their purposes.
5
5
  """
6
6
 
7
- from yellowdog_cli.__init__ import __version__
7
+ from yellowdog_cli._version import __version__
8
8
 
9
9
  # Ordered list of (command, one-line description) pairs.
10
10
  _COMMANDS: list[tuple[str, str]] = [
@@ -10,6 +10,7 @@ from typing import cast
10
10
 
11
11
  from requests import get
12
12
  from yellowdog_client.common import SearchClient
13
+ from yellowdog_client.common.json import Json
13
14
  from yellowdog_client.model import (
14
15
  Allowance,
15
16
  AllowanceSearch,
@@ -620,7 +621,7 @@ def get_keyring(name: str) -> Keyring:
620
621
  headers={"Authorization": f"yd-key {CONFIG_COMMON.key}:{CONFIG_COMMON.secret}"},
621
622
  )
622
623
  if response.status_code == 200:
623
- return Keyring(**response.json())
624
+ return Json.load(response.json(), Keyring)
624
625
  else:
625
626
  raise RuntimeError(f"Failed to get Keyring '{name}' ({response.text})")
626
627
 
@@ -79,6 +79,7 @@ from yellowdog_cli.utils.property_names import (
79
79
  TASK_COUNT,
80
80
  TASK_DATA,
81
81
  TASK_DATA_FILE,
82
+ TASK_DATA_FILES,
82
83
  TASK_DATA_INPUTS,
83
84
  TASK_DATA_OUTPUTS,
84
85
  TASK_GROUP_COUNT,
@@ -651,6 +652,7 @@ def create_task_group(
651
652
  except ValueError as e:
652
653
  raise ValueError(f"taskTemplate: {e}") from e
653
654
  tt.pop(TASK_DATA_FILE, None)
655
+ tt.pop(TASK_DATA_FILES, None)
654
656
  if task_data is not None:
655
657
  tt[TASK_DATA] = task_data
656
658
  task_template = TaskTemplate(**tt)
@@ -5,7 +5,7 @@ Class to parse command line arguments for all commands.
5
5
  import argparse
6
6
  import sys
7
7
 
8
- from yellowdog_cli.__init__ import __version__
8
+ from yellowdog_cli._version import __version__
9
9
  from yellowdog_cli.utils.settings import (
10
10
  DEFAULT_PARALLEL_TASK_BATCH_UPLOAD_THREADS,
11
11
  DEFAULT_URL,
@@ -1,5 +1,5 @@
1
1
  """
2
- Configuration classes and constants.
2
+ Configuration classes.
3
3
  """
4
4
 
5
5
  from dataclasses import dataclass, field
@@ -54,6 +54,7 @@ class ConfigWorkRequirement:
54
54
  task_count: int = 1
55
55
  task_data: str | None = None
56
56
  task_data_file: str | None = None
57
+ task_data_files: list[str] | None = None
57
58
  task_data_inputs: list[dict] | None = None
58
59
  task_data_outputs: list[dict] | None = None
59
60
  task_group_count: int = 1
@@ -380,6 +380,7 @@ def csv_expand_toml_tasks(
380
380
  (config_wr.set_task_names, SET_TASK_NAMES),
381
381
  (config_wr.task_data, TASK_DATA),
382
382
  (config_wr.task_data_file, TASK_DATA_FILE),
383
+ (config_wr.task_data_files, TASK_DATA_FILES),
383
384
  (config_wr.task_data_inputs, TASK_DATA_INPUTS),
384
385
  (config_wr.task_data_outputs, TASK_DATA_OUTPUTS),
385
386
  (config_wr.task_group_name, TASK_GROUP_NAME), # Note: oddity
@@ -622,8 +622,9 @@ def load_config_work_requirement() -> ConfigWorkRequirement:
622
622
  task_batch_size=task_batch_size,
623
623
  task_count=task_count,
624
624
  task_data=wr_section.get(TASK_DATA),
625
- task_data_inputs=wr_section.get(TASK_DATA_INPUTS),
626
625
  task_data_file=wr_section.get(TASK_DATA_FILE),
626
+ task_data_files=wr_section.get(TASK_DATA_FILES),
627
+ task_data_inputs=wr_section.get(TASK_DATA_INPUTS),
627
628
  task_data_outputs=wr_section.get(TASK_DATA_OUTPUTS),
628
629
  task_group_count=task_group_count,
629
630
  task_group_name=wr_section.get(TASK_GROUP_NAME),
@@ -84,6 +84,7 @@ TASK_COUNT = "taskCount" # Integer
84
84
  TASK_DATA = "taskData" # String
85
85
  TASK_DATA_DESTINATION = "destination" # String
86
86
  TASK_DATA_FILE = "taskDataFile" # String
87
+ TASK_DATA_FILES = "taskDataFiles" # List of Strings
87
88
  TASK_DATA_INPUTS = "taskDataInputs" # List of dictionaries
88
89
  TASK_DATA_OUTPUTS = "taskDataOutputs" # List of dictionaries
89
90
  TASK_DATA_SOURCE = "source" # String
@@ -200,6 +201,7 @@ ALL_KEYS = [
200
201
  TASK_DATA,
201
202
  TASK_DATA_DESTINATION,
202
203
  TASK_DATA_FILE,
204
+ TASK_DATA_FILES,
203
205
  TASK_DATA_INPUTS,
204
206
  TASK_DATA_OUTPUTS,
205
207
  TASK_DATA_SOURCE,
@@ -34,6 +34,7 @@ from yellowdog_cli.utils.property_names import (
34
34
  STATUSES_AT_FAILURE,
35
35
  TASK_DATA,
36
36
  TASK_DATA_FILE,
37
+ TASK_DATA_FILES,
37
38
  TASK_DATA_SOURCE,
38
39
  TASK_GROUPS,
39
40
  TASK_TAG,
@@ -582,25 +583,36 @@ def resolve_task_data(
582
583
  files_directory: str = "",
583
584
  task_data_default: str | None = None,
584
585
  task_data_file_default: str | None = None,
586
+ task_data_files_default: list[str] | None = None,
585
587
  ) -> str | None:
586
588
  """
587
589
  Resolve 'taskData' from a single dict level.
588
590
 
589
- Returns the task data string (reading from file if 'taskDataFile' is used),
590
- or None if neither property is present at this level.
591
- Raises ValueError if both 'taskData' and 'taskDataFile' are set.
591
+ Returns the task data string inline, read from a single file, or
592
+ concatenated from a list of files. Returns None if none of the three
593
+ properties are present at this level. Raises ValueError if more than
594
+ one of 'taskData', 'taskDataFile', 'taskDataFiles' is set.
592
595
  """
593
596
  task_data = data.get(TASK_DATA, task_data_default)
594
597
  task_data_file = data.get(TASK_DATA_FILE, task_data_file_default)
595
- if task_data and task_data_file:
598
+ task_data_files = data.get(TASK_DATA_FILES, task_data_files_default)
599
+ if sum(bool(x) for x in [task_data, task_data_file, task_data_files]) > 1:
596
600
  raise ValueError(
597
- f"Properties '{TASK_DATA}' and '{TASK_DATA_FILE}' are both set"
601
+ f"Only one of '{TASK_DATA}', '{TASK_DATA_FILE}' or "
602
+ f"'{TASK_DATA_FILES}' should be set"
598
603
  )
599
604
  if task_data:
600
605
  return task_data
601
606
  if task_data_file:
602
607
  with open(resolve_filename(files_directory, task_data_file)) as f:
603
608
  return process_variable_substitutions_in_file_contents(f.read())
609
+ if task_data_files:
610
+ result = ""
611
+ for filename in task_data_files:
612
+ with open(resolve_filename(files_directory, filename)) as f:
613
+ result += process_variable_substitutions_in_file_contents(f.read())
614
+ result += "\n"
615
+ return result
604
616
  return None
605
617
 
606
618
 
@@ -617,19 +629,28 @@ def get_task_data_property(
617
629
  level in order. 'taskDataFile' is resolved to its file contents at whichever
618
630
  level it is found.
619
631
  """
620
- for data, task_data_default, task_data_file_default in [
621
- (task, None, None),
622
- (task_group_data, None, None),
623
- (wr_data, config_wr.task_data, config_wr.task_data_file),
632
+ for data, task_data_default, task_data_file_default, task_data_files_default in [
633
+ (task, None, None, None),
634
+ (task_group_data, None, None, None),
635
+ (
636
+ wr_data,
637
+ config_wr.task_data,
638
+ config_wr.task_data_file,
639
+ config_wr.task_data_files,
640
+ ),
624
641
  ]:
625
642
  try:
626
643
  result = resolve_task_data(
627
- data, files_directory, task_data_default, task_data_file_default
644
+ data,
645
+ files_directory,
646
+ task_data_default,
647
+ task_data_file_default,
648
+ task_data_files_default,
628
649
  )
629
650
  except ValueError:
630
651
  raise ValueError(
631
- f"Task '{task_name}': Properties '{TASK_DATA}' and "
632
- f"'{TASK_DATA_FILE}' are both set"
652
+ f"Task '{task_name}': Only one of '{TASK_DATA}', "
653
+ f"'{TASK_DATA_FILE}' or '{TASK_DATA_FILES}' should be set"
633
654
  )
634
655
  if result is not None:
635
656
  return result
@@ -10,7 +10,8 @@ from sys import version as py_version
10
10
 
11
11
  from yellowdog_client._version import __version__ as yd_sdk_version
12
12
 
13
- from yellowdog_cli.__init__ import __author__, __email__, __version__
13
+ from yellowdog_cli import __author__, __email__
14
+ from yellowdog_cli._version import __version__
14
15
 
15
16
  DOCS_URL = f"https://github.com/yellowdog/yellowdog-cli/blob/v{__version__}/README.md"
16
17
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yellowdog-cli
3
- Version: 9.2.2
3
+ Version: 9.2.4
4
4
  Summary: Python-based CLI the YellowDog Platform
5
5
  Author-email: YellowDog Limited <support@yellowdog.co>
6
6
  License-Expression: Apache-2.0
@@ -18,12 +18,13 @@ License-File: LICENSE
18
18
  Requires-Dist: PyPAC>=0.16.4
19
19
  Requires-Dist: dateparser
20
20
  Requires-Dist: python-dotenv
21
+ Requires-Dist: jsons
21
22
  Requires-Dist: requests
22
23
  Requires-Dist: rclone-api
23
24
  Requires-Dist: rich>=13.9.4
24
25
  Requires-Dist: tabulate>=0.9.0
25
26
  Requires-Dist: tomli>=2.4.1
26
- Requires-Dist: yellowdog-sdk>=15.1.0
27
+ Requires-Dist: yellowdog-sdk>=15.2.0
27
28
  Provides-Extra: jsonnet
28
29
  Requires-Dist: jsonnet; extra == "jsonnet"
29
30
  Provides-Extra: cloudwizard
@@ -47,6 +47,7 @@ tests/test_variable_processing.py
47
47
  tests/test_variable_subs.py
48
48
  tests/test_ydid_utils.py
49
49
  yellowdog_cli/__init__.py
50
+ yellowdog_cli/_version.py
50
51
  yellowdog_cli/abort.py
51
52
  yellowdog_cli/application.py
52
53
  yellowdog_cli/boost.py
@@ -1,12 +1,13 @@
1
1
  PyPAC>=0.16.4
2
2
  dateparser
3
3
  python-dotenv
4
+ jsons
4
5
  requests
5
6
  rclone-api
6
7
  rich>=13.9.4
7
8
  tabulate>=0.9.0
8
9
  tomli>=2.4.1
9
- yellowdog-sdk>=15.1.0
10
+ yellowdog-sdk>=15.2.0
10
11
 
11
12
  [cloudwizard]
12
13
  boto3
File without changes
File without changes