ytdl-sub 2026.1.30__tar.gz → 2026.2.2.post1__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 (173) hide show
  1. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/PKG-INFO +1 -1
  2. ytdl_sub-2026.2.2.post1/src/ytdl_sub/__init__.py +1 -0
  3. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/cli/entrypoint.py +9 -0
  4. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/cli/parsers/main.py +11 -0
  5. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/preset.py +9 -2
  6. ytdl_sub-2026.2.2.post1/src/ytdl_sub/config/validators/variable_validation.py +196 -0
  7. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/script/variable_definitions.py +10 -0
  8. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/script.py +99 -27
  9. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/types/function.py +0 -7
  10. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/types/syntax_tree.py +4 -0
  11. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/types/variable_dependency.py +4 -1
  12. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/subscriptions/base_subscription.py +15 -4
  13. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/string_formatter_validators.py +33 -19
  14. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub.egg-info/PKG-INFO +1 -1
  15. ytdl_sub-2026.1.30/src/ytdl_sub/__init__.py +0 -1
  16. ytdl_sub-2026.1.30/src/ytdl_sub/config/validators/variable_validation.py +0 -94
  17. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/LICENSE +0 -0
  18. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/README.md +0 -0
  19. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/pyproject.toml +0 -0
  20. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/setup.cfg +0 -0
  21. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/cli/__init__.py +0 -0
  22. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/cli/output_summary.py +0 -0
  23. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/cli/output_transaction_log.py +0 -0
  24. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/cli/parsers/__init__.py +0 -0
  25. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/cli/parsers/cli_to_sub.py +0 -0
  26. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/cli/parsers/dl.py +0 -0
  27. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/__init__.py +0 -0
  28. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/config_file.py +0 -0
  29. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/config_validator.py +0 -0
  30. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/defaults.py +0 -0
  31. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/overrides.py +0 -0
  32. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/plugin/__init__.py +0 -0
  33. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/plugin/plugin.py +0 -0
  34. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/plugin/plugin_mapping.py +0 -0
  35. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/plugin/plugin_operation.py +0 -0
  36. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/plugin/preset_plugins.py +0 -0
  37. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/preset_options.py +0 -0
  38. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/validators/__init__.py +0 -0
  39. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/config/validators/options.py +0 -0
  40. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/downloaders/__init__.py +0 -0
  41. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/downloaders/info_json/__init__.py +0 -0
  42. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/downloaders/info_json/info_json_downloader.py +0 -0
  43. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/downloaders/source_plugin.py +0 -0
  44. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/downloaders/url/__init__.py +0 -0
  45. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/downloaders/url/downloader.py +0 -0
  46. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/downloaders/url/validators.py +0 -0
  47. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/downloaders/ytdl_options_builder.py +0 -0
  48. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/downloaders/ytdlp.py +0 -0
  49. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/__init__.py +0 -0
  50. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/base_entry.py +0 -0
  51. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/entry.py +0 -0
  52. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/entry_parent.py +0 -0
  53. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/script/__init__.py +0 -0
  54. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/script/custom_functions.py +0 -0
  55. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/script/function_scripts.py +0 -0
  56. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/script/variable_types.py +0 -0
  57. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/variables/__init__.py +0 -0
  58. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/entries/variables/override_variables.py +0 -0
  59. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/main.py +0 -0
  60. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/__init__.py +0 -0
  61. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/audio_extract.py +0 -0
  62. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/chapters.py +0 -0
  63. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/date_range.py +0 -0
  64. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/embed_thumbnail.py +0 -0
  65. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/file_convert.py +0 -0
  66. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/filter_exclude.py +0 -0
  67. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/filter_include.py +0 -0
  68. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/format.py +0 -0
  69. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/internal/__init__.py +0 -0
  70. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/internal/view.py +0 -0
  71. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/match_filters.py +0 -0
  72. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/music_tags.py +0 -0
  73. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/nfo_tags.py +0 -0
  74. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/output_directory_nfo_tags.py +0 -0
  75. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/split_by_chapters.py +0 -0
  76. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/square_thumbnail.py +0 -0
  77. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/static_nfo_tags.py +0 -0
  78. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/subtitles.py +0 -0
  79. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/throttle_protection.py +0 -0
  80. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/plugins/video_tags.py +0 -0
  81. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/__init__.py +0 -0
  82. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/__init__.py +0 -0
  83. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/download_deletion_options.yaml +0 -0
  84. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/filter_duration.yaml +0 -0
  85. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/filter_keywords.yaml +0 -0
  86. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/media_quality.yaml +0 -0
  87. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/players.yaml +0 -0
  88. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/throttle_protection.yaml +0 -0
  89. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/url.yaml +0 -0
  90. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/url_bilateral.yaml +0 -0
  91. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/helpers/url_categorized.yaml +0 -0
  92. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/internal/__init__.py +0 -0
  93. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/internal/view.yaml +0 -0
  94. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/music/__init__.py +0 -0
  95. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/music/albums_from_chapters.yaml +0 -0
  96. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/music/albums_from_playlists.yaml +0 -0
  97. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/music/singles.yaml +0 -0
  98. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/music/soundcloud.yaml +0 -0
  99. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/music_videos/__init__.py +0 -0
  100. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/music_videos/music_video_base.yaml +0 -0
  101. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/music_videos/music_video_extras.yaml +0 -0
  102. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/music_videos/music_videos.yaml +0 -0
  103. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/tv_show/__init__.py +0 -0
  104. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/tv_show/episode.yaml +0 -0
  105. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show.yaml +0 -0
  106. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_by_date.yaml +0 -0
  107. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml +0 -0
  108. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/__init__.py +0 -0
  109. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/__init__.py +0 -0
  110. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/array_functions.py +0 -0
  111. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/boolean_functions.py +0 -0
  112. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/conditional_functions.py +0 -0
  113. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/date_functions.py +0 -0
  114. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/error_functions.py +0 -0
  115. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/json_functions.py +0 -0
  116. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/map_functions.py +0 -0
  117. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/numeric_functions.py +0 -0
  118. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/print_functions.py +0 -0
  119. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/regex_functions.py +0 -0
  120. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/functions/string_functions.py +0 -0
  121. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/parser.py +0 -0
  122. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/script_output.py +0 -0
  123. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/types/__init__.py +0 -0
  124. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/types/array.py +0 -0
  125. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/types/map.py +0 -0
  126. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/types/resolvable.py +0 -0
  127. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/types/variable.py +0 -0
  128. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/utils/__init__.py +0 -0
  129. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/utils/exception_formatters.py +0 -0
  130. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/utils/exceptions.py +0 -0
  131. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/utils/name_validation.py +0 -0
  132. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/script/utils/type_checking.py +0 -0
  133. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/subscriptions/__init__.py +0 -0
  134. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/subscriptions/subscription.py +0 -0
  135. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/subscriptions/subscription_download.py +0 -0
  136. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/subscriptions/subscription_validators.py +0 -0
  137. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/subscriptions/subscription_ytdl_options.py +0 -0
  138. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/thread/__init__.py +0 -0
  139. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/thread/log_entries_downloaded_listener.py +0 -0
  140. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/__init__.py +0 -0
  141. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/chapters.py +0 -0
  142. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/datetime.py +0 -0
  143. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/exceptions.py +0 -0
  144. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/ffmpeg.py +0 -0
  145. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/file_handler.py +0 -0
  146. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/file_lock.py +0 -0
  147. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/file_path.py +0 -0
  148. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/logger.py +0 -0
  149. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/retry.py +0 -0
  150. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/script.py +0 -0
  151. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/scriptable.py +0 -0
  152. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/subtitles.py +0 -0
  153. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/system.py +0 -0
  154. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/thumbnail.py +0 -0
  155. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/xml.py +0 -0
  156. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/utils/yaml.py +0 -0
  157. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/__init__.py +0 -0
  158. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/audo_codec_validator.py +0 -0
  159. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/file_path_validators.py +0 -0
  160. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/nfo_validators.py +0 -0
  161. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/regex_validator.py +0 -0
  162. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/source_variable_validator.py +0 -0
  163. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/strict_dict_validator.py +0 -0
  164. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/string_datetime.py +0 -0
  165. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/string_select_validator.py +0 -0
  166. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/validators/validators.py +0 -0
  167. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/ytdl_additions/__init__.py +0 -0
  168. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py +0 -0
  169. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub.egg-info/SOURCES.txt +0 -0
  170. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub.egg-info/dependency_links.txt +0 -0
  171. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub.egg-info/entry_points.txt +0 -0
  172. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub.egg-info/requires.txt +0 -0
  173. {ytdl_sub-2026.1.30 → ytdl_sub-2026.2.2.post1}/src/ytdl_sub.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ytdl-sub
3
- Version: 2026.1.30
3
+ Version: 2026.2.2.post1
4
4
  Summary: Automate downloading metadata generation with YoutubeDL
5
5
  Author: Jesse Bannon
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -0,0 +1 @@
1
+ __pypi_version__ = "2026.02.02.post1";__local_version__ = "2026.02.02+ced547b"
@@ -1,5 +1,6 @@
1
1
  import gc
2
2
  import os
3
+ import random
3
4
  import sys
4
5
  from datetime import datetime
5
6
  from pathlib import Path
@@ -75,6 +76,7 @@ def _download_subscriptions_from_yaml_files(
75
76
  subscription_override_dict: Dict,
76
77
  update_with_info_json: bool,
77
78
  dry_run: bool,
79
+ shuffle: bool,
78
80
  ) -> List[Subscription]:
79
81
  """
80
82
  Downloads all subscriptions from one or many subscription yaml files.
@@ -91,6 +93,8 @@ def _download_subscriptions_from_yaml_files(
91
93
  Whether to actually download or update using existing info json
92
94
  dry_run
93
95
  Whether to dry run or not
96
+ shuffle
97
+ Whether to shuffle the subscription download order
94
98
 
95
99
  Returns
96
100
  -------
@@ -112,6 +116,10 @@ def _download_subscriptions_from_yaml_files(
112
116
  subscription_override_dict=subscription_override_dict,
113
117
  )
114
118
 
119
+ if shuffle:
120
+ logger.info("Shuffling subscriptions")
121
+ random.shuffle(subscriptions)
122
+
115
123
  for subscription in subscriptions:
116
124
  with subscription.exception_handling():
117
125
  logger.info(
@@ -253,6 +261,7 @@ def main() -> List[Subscription]:
253
261
  subscription_override_dict=subscription_override_dict,
254
262
  update_with_info_json=args.update_with_info_json,
255
263
  dry_run=args.dry_run,
264
+ shuffle=args.shuffle,
256
265
  )
257
266
 
258
267
  # One-off download
@@ -172,6 +172,10 @@ class SubArguments:
172
172
  short="-o",
173
173
  long="--dl-override",
174
174
  )
175
+ SHUFFLE = CLIArgument(
176
+ short="-sh",
177
+ long="--shuffle",
178
+ )
175
179
 
176
180
 
177
181
  subscription_parser = subparsers.add_parser("sub")
@@ -197,6 +201,13 @@ subscription_parser.add_argument(
197
201
  help="override all subscription config values using `dl` syntax, "
198
202
  "i.e. --dl-override='--ytdl_options.max_downloads 3'",
199
203
  )
204
+ subscription_parser.add_argument(
205
+ SubArguments.SHUFFLE.short,
206
+ SubArguments.SHUFFLE.long,
207
+ action="store_true",
208
+ help="shuffle subscription order when downloading",
209
+ default=False,
210
+ )
200
211
 
201
212
  ###################################################################################################
202
213
  # DOWNLOAD PARSER
@@ -255,11 +255,18 @@ class Preset(_PresetShell):
255
255
  """
256
256
  return cls(config=config, name=preset_name, value=preset_dict)
257
257
 
258
- @property
259
- def yaml(self) -> str:
258
+ def yaml(self, subscription_only: bool) -> str:
260
259
  """
260
+ Parameters
261
+ ----------
262
+ subscription_only:
263
+ Only include the subscription contents, not the surrounding boiler-plate.
264
+
261
265
  Returns
262
266
  -------
263
267
  Preset in YAML format
264
268
  """
269
+ if subscription_only:
270
+ return dump_yaml(self._value)
271
+
265
272
  return dump_yaml({"presets": {self._name: self._value}})
@@ -0,0 +1,196 @@
1
+ from typing import Dict
2
+ from typing import List
3
+ from typing import Set
4
+
5
+ from ytdl_sub.config.overrides import Overrides
6
+ from ytdl_sub.config.plugin.plugin_mapping import PluginMapping
7
+ from ytdl_sub.config.plugin.plugin_operation import PluginOperation
8
+ from ytdl_sub.config.plugin.preset_plugins import PresetPlugins
9
+ from ytdl_sub.config.preset_options import OutputOptions
10
+ from ytdl_sub.config.validators.options import OptionsValidator
11
+ from ytdl_sub.downloaders.url.validators import MultiUrlValidator
12
+ from ytdl_sub.entries.script.variable_definitions import UNRESOLVED_VARIABLES
13
+ from ytdl_sub.entries.script.variable_definitions import VARIABLES
14
+ from ytdl_sub.script.script import Script
15
+ from ytdl_sub.script.utils.name_validation import is_function
16
+ from ytdl_sub.utils.script import ScriptUtils
17
+ from ytdl_sub.validators.string_formatter_validators import validate_formatters
18
+
19
+
20
+ class ResolutionLevel:
21
+ ORIGINAL = 0
22
+ FILL = 1
23
+ RESOLVE = 2
24
+ INTERNAL = 3
25
+
26
+ @classmethod
27
+ def name_of(cls, resolution_level: int) -> str:
28
+ """
29
+ Name of the resolution level.
30
+ """
31
+ if resolution_level == cls.ORIGINAL:
32
+ return "original"
33
+ if resolution_level == cls.FILL:
34
+ return "fill"
35
+ if resolution_level == cls.RESOLVE:
36
+ return "resolve"
37
+ if resolution_level == cls.INTERNAL:
38
+ return "internal"
39
+ raise ValueError("Invalid resolution level")
40
+
41
+ @classmethod
42
+ def all(cls) -> List[int]:
43
+ """
44
+ All possible resolution levels.
45
+ """
46
+ return [cls.ORIGINAL, cls.FILL, cls.RESOLVE, cls.INTERNAL]
47
+
48
+
49
+ class VariableValidation:
50
+
51
+ def _get_resolve_partial_filter(self) -> Set[str]:
52
+ # Exclude sanitized variables from partial validation. This lessens the work
53
+ # and prevents double-evaluation, which can lead to bad behavior like double-prints.
54
+ return {
55
+ name
56
+ for name in self.script.variable_names
57
+ if name not in self.unresolved_variables and not name.endswith("_sanitized")
58
+ }
59
+
60
+ def _apply_resolution_level(self) -> None:
61
+ if self._resolution_level == ResolutionLevel.FILL:
62
+ self.unresolved_variables |= VARIABLES.variable_names(include_sanitized=True)
63
+ # Only partial resolve definitions that are already resolved
64
+ self.unresolved_variables |= {
65
+ name
66
+ for name in self.overrides.keys
67
+ if not is_function(name) and not self.script.definition_of(name).maybe_resolvable
68
+ }
69
+ elif self._resolution_level == ResolutionLevel.RESOLVE:
70
+ # Partial resolve everything, but not including internal variables
71
+ self.unresolved_variables |= VARIABLES.variable_names(include_sanitized=True)
72
+ elif self._resolution_level == ResolutionLevel.INTERNAL:
73
+ # Partial resolve everything including internal variables
74
+ pass
75
+ else:
76
+ raise ValueError("Invalid resolution level for validation")
77
+
78
+ self.script = self.script.resolve_partial(
79
+ unresolvable=self.unresolved_variables,
80
+ output_filter=self._get_resolve_partial_filter(),
81
+ )
82
+
83
+ def __init__(
84
+ self,
85
+ overrides: Overrides,
86
+ downloader_options: MultiUrlValidator,
87
+ output_options: OutputOptions,
88
+ plugins: PresetPlugins,
89
+ resolution_level: int = ResolutionLevel.RESOLVE,
90
+ ):
91
+ self.overrides = overrides
92
+ self.downloader_options = downloader_options
93
+ self.output_options = output_options
94
+ self.plugins = plugins
95
+
96
+ self.script: Script = self.overrides.script
97
+ self.unresolved_variables = (
98
+ self.plugins.get_all_variables(
99
+ additional_options=[self.output_options, self.downloader_options]
100
+ )
101
+ | UNRESOLVED_VARIABLES
102
+ )
103
+ self.unresolved_runtime_variables = self.plugins.get_all_variables(
104
+ additional_options=[self.output_options, self.downloader_options]
105
+ )
106
+ self._resolution_level = resolution_level
107
+
108
+ self._apply_resolution_level()
109
+
110
+ def _add_runtime_variables(self, plugin_op: PluginOperation, options: OptionsValidator) -> None:
111
+ """
112
+ Add dummy variables for script validation
113
+ """
114
+ added_variables = options.added_variables(
115
+ unresolved_variables=self.unresolved_runtime_variables,
116
+ ).get(plugin_op, set())
117
+ modified_variables = options.modified_variables().get(plugin_op, set())
118
+
119
+ self.unresolved_runtime_variables -= added_variables | modified_variables
120
+
121
+ def ensure_proper_usage(self, partial_resolve_formatters: bool = False) -> Dict:
122
+ """
123
+ Validate variables resolve as plugins are executed, and return
124
+ a mock script which contains actualized added variables from the plugins
125
+ """
126
+ resolved_subscription: Dict = {}
127
+
128
+ self._add_runtime_variables(PluginOperation.DOWNLOADER, options=self.downloader_options)
129
+
130
+ # Always add output options first
131
+ self._add_runtime_variables(
132
+ PluginOperation.MODIFY_ENTRY_METADATA, options=self.output_options
133
+ )
134
+
135
+ # Metadata variables to be added
136
+ for plugin_options in PluginMapping.order_options_by(
137
+ self.plugins.zipped(), PluginOperation.MODIFY_ENTRY_METADATA
138
+ ):
139
+ self._add_runtime_variables(
140
+ PluginOperation.MODIFY_ENTRY_METADATA, options=plugin_options
141
+ )
142
+
143
+ for plugin_options in PluginMapping.order_options_by(
144
+ self.plugins.zipped(), PluginOperation.MODIFY_ENTRY
145
+ ):
146
+ self._add_runtime_variables(PluginOperation.MODIFY_ENTRY, options=plugin_options)
147
+
148
+ # Validate that any formatter in the plugin options can resolve
149
+ resolved_subscription |= validate_formatters(
150
+ script=self.script,
151
+ unresolved_variables=self.unresolved_variables,
152
+ unresolved_runtime_variables=self.unresolved_runtime_variables,
153
+ validator=plugin_options,
154
+ partial_resolve_formatters=partial_resolve_formatters,
155
+ )
156
+
157
+ resolved_subscription |= validate_formatters(
158
+ script=self.script,
159
+ unresolved_variables=self.unresolved_variables,
160
+ unresolved_runtime_variables=self.unresolved_runtime_variables,
161
+ validator=self.output_options,
162
+ partial_resolve_formatters=partial_resolve_formatters,
163
+ )
164
+
165
+ # TODO: make this a function
166
+ raw_download_output = validate_formatters(
167
+ script=self.script,
168
+ unresolved_variables=self.unresolved_variables,
169
+ unresolved_runtime_variables=self.unresolved_runtime_variables,
170
+ validator=self.downloader_options.urls,
171
+ partial_resolve_formatters=partial_resolve_formatters,
172
+ )
173
+ resolved_subscription["download"] = []
174
+ for url_output in raw_download_output["download"]:
175
+ if isinstance(url_output["url"], list):
176
+ url_output["url"] = [url for url in url_output["url"] if bool(url)]
177
+
178
+ if url_output["url"]:
179
+ resolved_subscription["download"].append(url_output)
180
+
181
+ # TODO: make function
182
+ resolved_subscription["overrides"] = {}
183
+ for name in self.overrides.keys:
184
+ value = self.script.definition_of(name)
185
+ if name in self.script.function_names:
186
+ # Keep custom functions as-is
187
+ resolved_subscription["overrides"][name] = self.overrides.dict_with_format_strings[
188
+ name
189
+ ]
190
+ elif resolved := value.maybe_resolvable:
191
+ resolved_subscription["overrides"][name] = resolved.native
192
+ else:
193
+ resolved_subscription["overrides"][name] = ScriptUtils.to_native_script(value)
194
+
195
+ assert not self.unresolved_runtime_variables
196
+ return resolved_subscription
@@ -1135,6 +1135,16 @@ class VariableDefinitions(
1135
1135
  ]
1136
1136
  }
1137
1137
 
1138
+ @cache
1139
+ def variable_names(self, include_sanitized: bool):
1140
+ """
1141
+ Returns all variable names, and can include sanitized.
1142
+ """
1143
+ var_names: Set[str] = self.scripts().keys()
1144
+ if include_sanitized:
1145
+ var_names |= {f"{name}_sanitized" for name in var_names}
1146
+ return var_names
1147
+
1138
1148
  @cache
1139
1149
  def injected_variables(self) -> Set[MetadataVariable]:
1140
1150
  """
@@ -1,5 +1,4 @@
1
1
  # pylint: disable=missing-raises-doc
2
- import copy
3
2
  from collections import defaultdict
4
3
  from typing import Dict
5
4
  from typing import List
@@ -693,6 +692,18 @@ class Script:
693
692
 
694
693
  raise RuntimeException(f"Tried to get unresolved variable {variable_name}")
695
694
 
695
+ def definition_of(self, name: str) -> SyntaxTree:
696
+ """
697
+ Returns
698
+ -------
699
+ The definition of the variable or function.
700
+ """
701
+ if name.startswith("%") and name[1:] in self._functions:
702
+ return self._functions[name[1:]]
703
+ if name in self._variables:
704
+ return self._variables[name]
705
+ raise RuntimeException(f"Tried to get non-existent definition with name {name}")
706
+
696
707
  @property
697
708
  def variable_names(self) -> Set[str]:
698
709
  """
@@ -713,35 +724,33 @@ class Script:
713
724
  """
714
725
  return set(to_function_definition_name(name) for name in self._functions.keys())
715
726
 
716
- def resolve_partial(
727
+ def _resolve_partial_loop(
717
728
  self,
718
- unresolvable: Optional[Set[str]] = None,
719
- ) -> "Script":
720
- """
721
- Returns
722
- -------
723
- New (deep-copied) script that resolves inner variables as much
724
- as possible.
725
- """
726
- unresolvable: Set[str] = unresolvable or {}
727
- resolved: Dict[Variable, Resolvable] = {}
728
- unresolved: Dict[Variable, Argument] = {
729
- Variable(name): definition
730
- for name, definition in self._variables.items()
731
- if name not in unresolvable
732
- }
729
+ output_filter: Optional[Set[str]],
730
+ resolved: Dict[Variable, Resolvable],
731
+ unresolved: Dict[Variable, Argument],
732
+ unresolvable: Optional[Set[str]],
733
+ ):
734
+ to_partially_resolve: Set[Variable] = (
735
+ {Variable(name) for name in output_filter} if output_filter else set(unresolved.keys())
736
+ )
733
737
 
734
738
  partially_resolved = True
735
739
  while partially_resolved:
736
740
 
737
741
  partially_resolved = False
738
742
 
739
- for variable in list(unresolved.keys()):
743
+ for variable in list(to_partially_resolve):
740
744
  definition = unresolved[variable]
741
-
742
745
  maybe_resolved = definition
746
+
743
747
  if isinstance(definition, Variable) and definition.name not in unresolvable:
744
- maybe_resolved = resolved.get(definition, unresolved[definition])
748
+ if definition in resolved:
749
+ maybe_resolved = resolved[definition]
750
+ elif definition in unresolved:
751
+ maybe_resolved = unresolved[definition]
752
+ else:
753
+ raise UNREACHABLE
745
754
  elif isinstance(definition, VariableDependency):
746
755
  maybe_resolved = definition.partial_resolve(
747
756
  resolved_variables=resolved,
@@ -752,6 +761,7 @@ class Script:
752
761
  if isinstance(maybe_resolved, Resolvable):
753
762
  resolved[variable] = maybe_resolved
754
763
  del unresolved[variable]
764
+ to_partially_resolve.remove(variable)
755
765
  partially_resolved = True
756
766
  else:
757
767
  unresolved[variable] = maybe_resolved
@@ -760,11 +770,73 @@ class Script:
760
770
  # which means we can iterate again
761
771
  partially_resolved |= definition != maybe_resolved
762
772
 
763
- return copy.deepcopy(self).add_parsed(
764
- {var_name: self._variables[var_name] for var_name in unresolvable}
765
- | {
766
- var.name: ResolvedSyntaxTree(ast=[definition])
767
- for var, definition in resolved.items()
768
- }
769
- | {var.name: SyntaxTree(ast=[definition]) for var, definition in unresolved.items()}
773
+ def _resolve_partial(
774
+ self,
775
+ unresolvable: Optional[Set[str]] = None,
776
+ output_filter: Optional[Set[str]] = None,
777
+ ) -> Dict[str, SyntaxTree]:
778
+ """
779
+ Returns
780
+ -------
781
+ New (deep-copied) script that resolves inner variables as much
782
+ as possible.
783
+ """
784
+ unresolvable: Set[str] = unresolvable or {}
785
+ resolved: Dict[Variable, Resolvable] = {}
786
+ unresolved: Dict[Variable, Argument] = {
787
+ Variable(name): definition
788
+ for name, definition in self._variables.items()
789
+ if name not in unresolvable
790
+ }
791
+
792
+ self._resolve_partial_loop(
793
+ output_filter=output_filter,
794
+ resolved=resolved,
795
+ unresolved=unresolved,
796
+ unresolvable=unresolvable,
770
797
  )
798
+
799
+ if output_filter:
800
+ out: Dict[str, SyntaxTree] = {}
801
+ for name in output_filter:
802
+ variable_name = Variable(name)
803
+ if variable_name in resolved:
804
+ out[name] = ResolvedSyntaxTree(ast=[resolved[variable_name]])
805
+ else:
806
+ out[name] = SyntaxTree(ast=[unresolved[variable_name]])
807
+
808
+ return out
809
+
810
+ return {
811
+ var.name: ResolvedSyntaxTree(ast=[definition]) for var, definition in resolved.items()
812
+ } | {var.name: SyntaxTree(ast=[definition]) for var, definition in unresolved.items()}
813
+
814
+ def resolve_partial(
815
+ self,
816
+ unresolvable: Optional[Set[str]] = None,
817
+ output_filter: Optional[Set[str]] = None,
818
+ ) -> "Script":
819
+ """
820
+ Updates the internal script to resolve as much as possible.
821
+ """
822
+ out = self._resolve_partial(unresolvable=unresolvable, output_filter=output_filter)
823
+ for var_name, definition in out.items():
824
+ self._variables[var_name] = definition
825
+
826
+ return self
827
+
828
+ def resolve_partial_once(
829
+ self, variable_definitions: Dict[str, SyntaxTree], unresolvable: Optional[Set[str]] = None
830
+ ) -> Dict[str, SyntaxTree]:
831
+ """
832
+ Partially resolves the input variable definitions as much as possible.
833
+ """
834
+ try:
835
+ self.add_parsed(variable_definitions)
836
+ return self._resolve_partial(
837
+ unresolvable=unresolvable,
838
+ output_filter=set(list(variable_definitions.keys())),
839
+ )
840
+ finally:
841
+ for name in variable_definitions.keys():
842
+ self._variables.pop(name, None)
@@ -371,13 +371,6 @@ class BuiltInFunction(Function, BuiltInFunctionType):
371
371
  If the conditional partially resolvable enough to warrant evaluation,
372
372
  perform it here.
373
373
  """
374
- if self.is_subset_of(
375
- variables=resolved_variables, custom_function_definitions=custom_functions
376
- ):
377
- return self.resolve(
378
- resolved_variables=resolved_variables,
379
- custom_functions=custom_functions,
380
- )
381
374
 
382
375
  if self.name == "if":
383
376
  maybe_resolvable_arg, is_resolvable = VariableDependency.try_partial_resolve(
@@ -55,6 +55,10 @@ class SyntaxTree(VariableDependency):
55
55
  custom_functions=custom_functions,
56
56
  )
57
57
 
58
+ # If no arguments, must be empty string
59
+ if len(maybe_resolvable_values) == 0:
60
+ return String(value="")
61
+
58
62
  # Mimic the above resolve behavior
59
63
  if len(maybe_resolvable_values) > 1:
60
64
  return BuiltInFunction(name="concat", args=maybe_resolvable_values)
@@ -279,8 +279,11 @@ class VariableDependency(ABC):
279
279
  if not isinstance(maybe_resolvable_args[-1], Resolvable):
280
280
  is_resolvable = False
281
281
  elif isinstance(arg, Variable):
282
- if arg not in resolved_variables:
282
+ if arg in resolved_variables:
283
+ maybe_resolvable_args[-1] = resolved_variables[arg]
284
+ else:
283
285
  is_resolvable = False
286
+ # Could be un unresolvable
284
287
  if arg in unresolved_variables:
285
288
  maybe_resolvable_args[-1] = unresolved_variables[arg]
286
289
 
@@ -8,6 +8,7 @@ from ytdl_sub.config.plugin.preset_plugins import PresetPlugins
8
8
  from ytdl_sub.config.preset import Preset
9
9
  from ytdl_sub.config.preset_options import OutputOptions
10
10
  from ytdl_sub.config.preset_options import YTDLOptions
11
+ from ytdl_sub.config.validators.variable_validation import ResolutionLevel
11
12
  from ytdl_sub.config.validators.variable_validation import VariableValidation
12
13
  from ytdl_sub.downloaders.url.validators import MultiUrlValidator
13
14
  from ytdl_sub.entries.variables.override_variables import SubscriptionVariables
@@ -79,7 +80,7 @@ class BaseSubscription(ABC):
79
80
  )
80
81
 
81
82
  # Validate after adding the subscription name
82
- self._validated_dict = VariableValidation(
83
+ _ = VariableValidation(
83
84
  overrides=self.overrides,
84
85
  downloader_options=self.downloader_options,
85
86
  output_options=self.output_options,
@@ -254,12 +255,22 @@ class BaseSubscription(ABC):
254
255
  -------
255
256
  Subscription in yaml format
256
257
  """
257
- return self._preset_options.yaml
258
+ return self._preset_options.yaml(subscription_only=False)
258
259
 
259
- def resolved_yaml(self) -> str:
260
+ def resolved_yaml(self, resolution_level: int = ResolutionLevel.RESOLVE) -> str:
260
261
  """
261
262
  Returns
262
263
  -------
263
264
  Human-readable, condensed YAML definition of the subscription.
264
265
  """
265
- return dump_yaml(self._validated_dict)
266
+ if resolution_level == ResolutionLevel.ORIGINAL:
267
+ return self._preset_options.yaml(subscription_only=True)
268
+
269
+ out = VariableValidation(
270
+ overrides=self.overrides,
271
+ downloader_options=self.downloader_options,
272
+ output_options=self.output_options,
273
+ plugins=self.plugins,
274
+ resolution_level=resolution_level,
275
+ ).ensure_proper_usage(partial_resolve_formatters=True)
276
+ return dump_yaml(out)