yellowdog-python-examples 5.0.0__tar.gz → 5.0.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 (47) hide show
  1. {yellowdog-python-examples-5.0.0/yellowdog_python_examples.egg-info → yellowdog-python-examples-5.0.2}/PKG-INFO +1 -1
  2. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/README.md +108 -7
  3. yellowdog-python-examples-5.0.2/yd_commands/__init__.py +1 -0
  4. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/csv_data.py +1 -0
  5. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/instantiate.py +9 -5
  6. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/submit.py +8 -2
  7. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/submit_utils.py +4 -1
  8. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/variables.py +6 -1
  9. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2/yellowdog_python_examples.egg-info}/PKG-INFO +1 -1
  10. yellowdog-python-examples-5.0.0/yd_commands/__init__.py +0 -1
  11. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/LICENSE +0 -0
  12. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/PYPI_README.md +0 -0
  13. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/pyproject.toml +0 -0
  14. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/requirements.txt +0 -0
  15. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/setup.cfg +0 -0
  16. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/setup.py +0 -0
  17. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/abort.py +0 -0
  18. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/admin.py +0 -0
  19. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/args.py +0 -0
  20. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/cancel.py +0 -0
  21. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/check_imports.py +0 -0
  22. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/compact_json.py +0 -0
  23. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/config.py +0 -0
  24. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/config_keys.py +0 -0
  25. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/delete.py +0 -0
  26. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/download.py +0 -0
  27. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/interactive.py +0 -0
  28. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/jsonnet2json.py +0 -0
  29. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/list.py +0 -0
  30. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/object_utilities.py +0 -0
  31. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/printing.py +0 -0
  32. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/provision.py +0 -0
  33. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/provision_utils.py +0 -0
  34. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/reformat_json.py +0 -0
  35. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/shutdown.py +0 -0
  36. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/terminate.py +0 -0
  37. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/type_check.py +0 -0
  38. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/upload.py +0 -0
  39. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/upload_utils.py +0 -0
  40. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/validate_properties.py +0 -0
  41. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/version.py +0 -0
  42. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yd_commands/wrapper.py +0 -0
  43. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yellowdog_python_examples.egg-info/SOURCES.txt +0 -0
  44. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yellowdog_python_examples.egg-info/dependency_links.txt +0 -0
  45. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yellowdog_python_examples.egg-info/entry_points.txt +0 -0
  46. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yellowdog_python_examples.egg-info/requires.txt +0 -0
  47. {yellowdog-python-examples-5.0.0 → yellowdog-python-examples-5.0.2}/yellowdog_python_examples.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: yellowdog-python-examples
3
- Version: 5.0.0
3
+ Version: 5.0.2
4
4
  Summary: Example Python commands using the YellowDog Python SDK
5
5
  Home-page: https://github.com/yellowdog/python-examples
6
6
  Author: YellowDog Limited
@@ -15,6 +15,7 @@
15
15
  * [Variable Substitutions](#variable-substitutions)
16
16
  * [Default Variables](#default-variables)
17
17
  * [User-Defined Variables](#user-defined-variables)
18
+ * [Nested Variables](#nested-variables)
18
19
  * [Work Requirement Properties](#work-requirement-properties)
19
20
  * [Work Requirement JSON File Structure](#work-requirement-json-file-structure)
20
21
  * [Property Inheritance](#property-inheritance)
@@ -45,6 +46,11 @@
45
46
  * [Files Downloaded to a Node for use in Task Execution](#files-downloaded-to-a-node-for-use-in-task-execution)
46
47
  * [Files Uploaded from a Node to the Object Store after Task Execution](#files-uploaded-from-a-node-to-the-object-store-after-task-execution)
47
48
  * [Files Downloaded from the Object Store to Local Storage](#files-downloaded-from-the-object-store-to-local-storage)
49
+ * [Task Execution Context](#task-execution-context)
50
+ * [Task Execution Steps](#task-execution-steps)
51
+ * [The User and Group used for Tasks](#the-user-and-group-used-for-tasks)
52
+ * [Home Directory for yd-agent](#home-directory-for-yd-agent)
53
+ * [Task Execution Directory](#task-execution-directory)
48
54
  * [Specifying Work Requirements using CSV Data](#specifying-work-requirements-using-csv-data)
49
55
  * [Work Requirement CSV Data Example](#work-requirement-csv-data-example)
50
56
  * [CSV Variable Substitutions](#csv-variable-substitutions)
@@ -78,7 +84,7 @@
78
84
  * [Test-Running a Dynamic Template](#test-running-a-dynamic-template)
79
85
  * [yd-terminate](#yd-terminate)
80
86
 
81
- <!-- Added by: pwt, at: Mon Mar 13 14:01:55 GMT 2023 -->
87
+ <!-- Added by: pwt, at: Fri Apr 14 15:14:49 BST 2023 -->
82
88
 
83
89
  <!--te-->
84
90
 
@@ -335,6 +341,26 @@ Directives set on the command line take precedence over directives set in enviro
335
341
 
336
342
  This method can be used to override the default directives, e.g., setting `-v username="other-user"` will override the default `{{username}}` directive.
337
343
 
344
+ ## Nested Variables
345
+
346
+ In the case of **TOML properties only**, variable substitutions can be nested.
347
+
348
+ For example, if one wanted to select a different `templateId` for a Worker Pool depending on the value of a `region` variable, one could use the following:
349
+
350
+ ```toml
351
+ [common.variables]
352
+ template_london = "ydid:crt:65EF4F:a4d757cf-b67a-4eb6-bd39-8a6ffd46c8f4"
353
+ template_phoenix = "ydid:crt:65EF4F:e4239dec-78c2-421c-a7f3-71e61b72946f"
354
+ template_frankfurt = "ydid:crt:65EF4F:329602cf-5945-4aad-a288-ea424d64d55e"
355
+
356
+ [common.workerPool]
357
+ templateId = "{{template_{{region}}}}"
358
+ ```
359
+
360
+ Then, if one used `yd-provision -v region=phoenix`, the `templateId` property would first resolve to `"{{template_pheonix}}"`, and then to `"ydid:crt:65EF4F:e4239dec-78c2-421c-a7f3-71e61b72946f"`.
361
+
362
+ Nesting can be up to three levels deep including the top level.
363
+
338
364
  # Work Requirement Properties
339
365
 
340
366
  The `workRequirement` section of the configuration file is optional. It's used only by the `yd-submit` command, and controls the Work Requirement that is submitted to the Platform.
@@ -969,7 +995,7 @@ When a Task is started by the Agent, its working directory has a pattern somethi
969
995
 
970
996
  Files that are downloaded by the Agent prior to Task execution are located as follows:
971
997
 
972
- 1. If the `flattenInputPaths` property is set to `false` for the Task (this is the default), the downloaded objects are placed in subdirectories that mirror those in the Object Store, including the Work Requirement name, situated beneath the working directory.
998
+ 1. If the `flattenInputPaths` property is set to the default of `false` for the Task, the downloaded objects are placed in subdirectories that mirror those in the Object Store, including the Work Requirement name, situated beneath the working directory.
973
999
 
974
1000
 
975
1001
  2. If the `flattenInputPaths` property is set to `true` for the Task, the downloaded objects are all placed directly in root of the Task's working directory.
@@ -977,7 +1003,7 @@ Files that are downloaded by the Agent prior to Task execution are located as fo
977
1003
  For example:
978
1004
 
979
1005
  ```shell
980
- If the required object is: development:testrun_221108-120404-7d2/dev/file_1.txt
1006
+ If the required object is: development::testrun_221108-120404-7d2/dev/file_1.txt
981
1007
 
982
1008
  then, if flattenInputPaths is false, the file will be found at:
983
1009
  -> <working_directory>/testrun_221108-120404-7d2/dev/file_1.txt
@@ -989,9 +1015,17 @@ where <working_directory> is:
989
1015
  /var/opt/yellowdog/yd-agent-4/data/workers/1/ydid_task_D0D0D0_68f5e5be-dc93-49eb-a824-1fcdb52f9195_1_1/
990
1016
  ```
991
1017
 
1018
+ Note that Work Requirement name (e.g., `testrun_221108-120404-7d2`) is available via the variable substitution `wr_name`, so this could be supplied to the Task to help it locate its downloaded files. For example, in the `workRequirement` section of the `config.toml` file, I could specify:
1019
+
1020
+ ```toml
1021
+ environment = {WR_DIRECTORY = "{{wr_name}}"}
1022
+ ```
1023
+
1024
+ The Work Requirement name would then be available to the Task in the environment variable `$WR_DIRECTORY`.
1025
+
992
1026
  ### Files Uploaded from a Node to the Object Store after Task Execution
993
1027
 
994
- After Task completion, the Agent will upload specified output files to the Object Store. The files to be uploaded are those listed in the `outputs` property for the Task.
1028
+ After Task completion, the Agent will upload specified output files to the Object Store. The files to be uploaded are those listed in the `outputs` and `outputsRequired` properties for the Task.
995
1029
 
996
1030
  In addition, the console output of the Task is captured in a file called `taskoutput.txt` in the root of the Task's working directory. Whether the `taskoutput.txt` file is uploaded to the Object Store is determined by the `captureTaskOutput` property for the Task, and this is set to 'true' by default.
997
1031
 
@@ -1036,6 +1070,69 @@ Note that everything within the `namespace::work-requirement` directory in the O
1036
1070
 
1037
1071
  If the `development` directory already exists, `yd-download` will try `development.01`, etc., to avoid overwriting previous downloads.
1038
1072
 
1073
+ ## Task Execution Context
1074
+
1075
+ This section discusses the context within which a Task operates when it's executed by a Worker on a node. It applies specifically to the YellowDog Agent running on a Linux node, and configured using the default username, directories, etc. Configurations can vary.
1076
+
1077
+ ### Task Execution Steps
1078
+
1079
+ When a Task is allocated to a Worker on a node by the YellowDog Scheduler, the following steps are followed:
1080
+
1081
+ 1. The Agent running on the node downloads the Task's properties: its `taskType`, `arguments`, `environment`, `taskdata`, and (from the Object Store) any files in the `inputs` list and any available files in the `inputsOptional` list.
1082
+ 2. These files are placed in an ephemeral directory created for this Task's execution, and into which any output files are also placed by default.
1083
+ 2. The Agent runs the command specified for the `taskType` in the Agent's `application.yaml` configuration file. This done as a simple `exec` of a subprocess.
1084
+ 3. When the Task concludes, the Agent uses the exit code of the subprocess to report success (zero) or failure (non-zero).
1085
+ 4. The Agent then gathers any files in the `outputs` and `outputsRequired` lists and uploads them to the Object Store. If a file in the `outputsRequired` list is not found, the Task will be reported as failed. The Agent will also optionally upload the console output (including both `stdout` and `stderr`) of the Task, contained in the `taskoutput.txt` file.
1086
+ 5. The ephemeral Task directory is then deleted.
1087
+
1088
+ Note that if a Task is aborted during execution, the Task's subprocess is sent a `SIGINT`, allowing the Task an opportunity to terminate any child processes or other resources (e.g., containers) that may have been started as part of Task execution.
1089
+
1090
+ Once the steps above have been completed, the Worker is ready to accept its next Task from the YellowDog scheduler.
1091
+
1092
+ Note that if the Agent on a node has multiple Workers, then Tasks are executed in parallel on the node and can start and stop independently.
1093
+
1094
+ ### The User and Group used for Tasks
1095
+
1096
+ By default, the Agent runs as user and group `yd-agent`, and hence Tasks also execute under this user.
1097
+
1098
+ `yd-agent` does not have `sudo` privileges as standard, but this can be added if required at instance boot time via the `userData` property of a provisioning request. E.g. (for Ubuntu):
1099
+
1100
+ ```shell
1101
+ usermod -aG wheel yd-agent
1102
+ echo -e "yd-agent\tALL=(ALL)\tNOPASSWD: ALL" > /etc/sudoers.d/020-yd-agent
1103
+ ```
1104
+
1105
+ ### Home Directory for `yd-agent`
1106
+
1107
+ By default, the home directory of the `yd-agent` user is `/opt/yellowdog/agent`. This directory typically contains the `application.yaml` file used to configure the Agent, as well as any scripts that are used to execute the Task Types that the node supports.
1108
+
1109
+ If one wants to SSH to an instance as user `yd-agent`, perhaps for debugging purposes, SSH keys can be inserted via instance `userData`, e.g.:
1110
+
1111
+ ```shell
1112
+ YDA_HOME=/opt/yellowdog/agent
1113
+ mkdir -p $YDA_HOME/.ssh
1114
+ chmod og-rwx $YDA_HOME/.ssh
1115
+ cat >> $YDA_HOME/.ssh/authorized_keys << EOF
1116
+ <<Insert_Public_key_Here>>
1117
+ EOF
1118
+ chmod og-rw $YDA_HOME/.ssh/authorized_keys
1119
+ chown -R yd-agent:yd-agent $YDA_HOME/.ssh
1120
+ ```
1121
+
1122
+ ### Task Execution Directory
1123
+
1124
+ Ephemeral Task directories are created under `/var/opt/yellowdog/agent/data/workers`. This directory contains one or more numbered subdirectories where each numbered directory corresponds to a Worker on the node.
1125
+
1126
+ When a Task is allocated to a node, an ephemeral directory is created under the applicable Worker directory, with a name corresponding the YellowDog ID for the Task. For example, this is an ephemeral directory being used by Worker number `1`:
1127
+
1128
+ `/var/opt/yellowdog/agent/data/workers/1/ydid_task_559EBE_74949336-ac2b-4811-a7d5-f3ecd9739908_1_1`
1129
+
1130
+ This is the directory into which downloaded objects are placed, and in which output files are created by default. The console output `taskoutput.txt` file will also be created in this directory.
1131
+
1132
+ See the [Files Downloaded to a Node](#files-downloaded-to-a-node-for-use-in-task-execution) section above for more details on how files in this directory are handled.
1133
+
1134
+ At the conclusion of the Task, after any files requested for upload have been uploaded to the Object Store (see the [Files Uploaded from a Node](#files-uploaded-from-a-node-to-the-object-store-after-task-execution) section for more information), the `ydid_task_559EBE_74949336-ac2b-4811-a7d5-f3ecd9739908_1_1` will be removed.
1135
+
1039
1136
  ## Specifying Work Requirements using CSV Data
1040
1137
 
1041
1138
  CSV data files can be used to drive the generation of lists of Tasks, as follows:
@@ -1477,15 +1574,19 @@ Variable substitution is performed before Jsonnet expansion into JSON, and again
1477
1574
 
1478
1575
  ## Checking Jsonnet Processing
1479
1576
 
1480
- To inspect the basic conversion of Jsonnet into JSON, without any additional processing by the Python Examples scripts, the `yd-jsonnet2json` command can be used. This takes a single command line argument which is the name of the jsonnet file to be processed:
1577
+ There are three possibilities for verifying that a Jsonnet specification is doing what is intended:
1578
+
1579
+ 1. To inspect the basic conversion of Jsonnet into JSON, without any additional processing by the Python Examples scripts, the `yd-jsonnet2json` command can be used. This takes a single command line argument which is the name of the jsonnet file to be processed:
1481
1580
 
1482
1581
  ```shell
1483
1582
  yd-jsonnet2json my_file.jsonnet
1484
1583
  ```
1485
1584
 
1486
- The `jsonnet-dry-run` (`-J`) option of the `yd-submit`, `yd-provision` and `yd-instantiate` commands will generate JSON output representing the Jsonnet to JSON processing only, including applicable variable substitutions, but before full property expansion into the JSON that will be submitted to the Platform.
1487
1585
 
1488
- The `dry-run` (`-D`) option will generate JSON output representing the full processing of the Jsonnet file into what will be submitted to the API. This allows inspection to check that the output matches expectations, prior to submitting to the Platform.
1586
+ 2. The `jsonnet-dry-run` (`-J`) option of the `yd-submit`, `yd-provision` and `yd-instantiate` commands will generate JSON output representing the Jsonnet to JSON processing only, including applicable variable substitutions, but before full property expansion into the JSON that will be submitted to the Platform.
1587
+
1588
+
1589
+ 3. The `dry-run` (`-D`) option will generate JSON output representing the full processing of the Jsonnet file into what will be submitted to the API. This allows inspection to check that the output matches expectations, prior to submitting to the Platform.
1489
1590
 
1490
1591
  ## Jsonnet Example
1491
1592
 
@@ -0,0 +1 @@
1
+ __version__ = "5.0.2"
@@ -350,6 +350,7 @@ def csv_expand_toml_tasks(config_wr: ConfigWorkRequirement, csv_file: str) -> Di
350
350
  (config_wr.output_files_required, OUTPUT_FILES_REQUIRED),
351
351
  (config_wr.task_data, TASK_DATA),
352
352
  (config_wr.task_data_file, TASK_DATA_FILE),
353
+ (config_wr.task_group_name, TASK_GROUP_NAME), # Note: oddity
353
354
  (config_wr.task_name, TASK_NAME),
354
355
  (config_wr.task_type, TASK_TYPE),
355
356
  (config_wr.upload_files, UPLOAD_FILES),
@@ -114,12 +114,16 @@ def main():
114
114
 
115
115
  if ARGS_PARSER.report:
116
116
  print_log("Generating provisioning report only")
117
- test_result: ComputeRequirementTemplateTestResult = (
118
- CLIENT.compute_client.test_compute_requirement_template(
119
- compute_requirement_template_usage
117
+ try:
118
+ test_result: ComputeRequirementTemplateTestResult = (
119
+ CLIENT.compute_client.test_compute_requirement_template(
120
+ compute_requirement_template_usage
121
+ )
120
122
  )
121
- )
122
- print_compute_template_test_result(test_result)
123
+ print_compute_template_test_result(test_result)
124
+ except requests.HTTPError as http_error:
125
+ if "No sources" in http_error.response.text:
126
+ print_log("No Compute Sources match the Template's constraints")
123
127
  return
124
128
 
125
129
  if not ARGS_PARSER.dry_run:
@@ -209,7 +209,7 @@ def submit_work_requirement(
209
209
  # Overwrite the WR name?
210
210
  global ID, CONFIG_WR
211
211
  ID = format_yd_name(
212
- wr_data.get(L_WR_NAME, ID if CONFIG_WR.wr_name is None else CONFIG_WR.wr_name)
212
+ wr_data.get(NAME, ID if CONFIG_WR.wr_name is None else CONFIG_WR.wr_name)
213
213
  )
214
214
  # Lazy substitution of the Work Requirement name, now it's defined
215
215
  add_substitutions(subs={L_WR_NAME: ID})
@@ -304,6 +304,12 @@ def create_task_group(
304
304
  # Name the Task Group
305
305
  num_task_groups = len(wr_data[TASK_GROUPS])
306
306
  num_tasks = len(task_group_data[TASKS])
307
+ # The following handles possible CSV substitution at the config.toml level
308
+ try:
309
+ if task_group_data.get(NAME, None) is None:
310
+ task_group_data[NAME] = task_group_data[TASKS][0][TASK_GROUP_NAME]
311
+ except KeyError:
312
+ pass
307
313
  task_group_name = format_yd_name(
308
314
  get_task_group_name(
309
315
  task_group_data.get(NAME, CONFIG_WR.task_group_name),
@@ -482,7 +488,7 @@ def add_tasks_to_task_group(
482
488
  task = tasks[task_number] if task_count is None else tasks[0]
483
489
  task_name = format_yd_name(
484
490
  get_task_name(
485
- task.get(NAME, CONFIG_WR.task_name),
491
+ task.get(NAME, task.get(TASK_NAME, CONFIG_WR.task_name)),
486
492
  task_number,
487
493
  num_tasks,
488
494
  tg_number,
@@ -181,4 +181,7 @@ def format_yd_name(yd_name: str) -> str:
181
181
  # Make obvious substitutions
182
182
  yd_name = yd_name.replace("/", "-").replace(" ", "_").lower()
183
183
  # Enforce acceptable regex and name length
184
- return re.sub("[^a-z0-9_-]", "", yd_name)[:60]
184
+ yd_name = re.sub("[^a-z0-9_-]", "", yd_name)
185
+ if not yd_name[0].isalpha():
186
+ yd_name = f"z_{yd_name}"
187
+ return yd_name[:60]
@@ -41,6 +41,9 @@ if "submit" in sys.argv[0]:
41
41
  NUMBER_SUB = "num:"
42
42
  BOOL_SUB = "bool:"
43
43
 
44
+ # Nested variables depth supported in TOML files
45
+ NESTED_DEPTH = 3
46
+
44
47
  # Add user-defined variable substitutions
45
48
  # Can supersede the existing substitutions above
46
49
  ENV_VAR_PREFIX = "YD_VAR_"
@@ -251,7 +254,9 @@ def load_toml_file_with_variable_substitutions(filename: str, prefix: str = "")
251
254
  except KeyError:
252
255
  pass
253
256
 
254
- process_variable_substitutions(config, prefix=prefix)
257
+ for _ in range(NESTED_DEPTH):
258
+ process_variable_substitutions(config, prefix=prefix)
259
+
255
260
  return config
256
261
 
257
262
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: yellowdog-python-examples
3
- Version: 5.0.0
3
+ Version: 5.0.2
4
4
  Summary: Example Python commands using the YellowDog Python SDK
5
5
  Home-page: https://github.com/yellowdog/python-examples
6
6
  Author: YellowDog Limited
@@ -1 +0,0 @@
1
- __version__ = "5.0.0"