ytdl-sub 2025.12.8__tar.gz → 2025.12.31.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-2025.12.8 → ytdl_sub-2025.12.31.post1}/PKG-INFO +1 -1
  2. ytdl_sub-2025.12.31.post1/src/ytdl_sub/__init__.py +1 -0
  3. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/config/overrides.py +39 -13
  4. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/config/plugin/preset_plugins.py +33 -0
  5. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/config/preset.py +36 -8
  6. ytdl_sub-2025.12.31.post1/src/ytdl_sub/config/validators/variable_validation.py +91 -0
  7. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/downloaders/url/validators.py +2 -2
  8. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/entries/variables/override_variables.py +1 -4
  9. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/helpers/download_deletion_options.yaml +1 -2
  10. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/parser.py +5 -0
  11. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/script.py +109 -43
  12. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/types/variable_dependency.py +12 -1
  13. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/utils/name_validation.py +21 -0
  14. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/subscriptions/base_subscription.py +21 -3
  15. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/utils/script.py +19 -2
  16. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/validators/string_formatter_validators.py +63 -31
  17. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/validators/validators.py +3 -13
  18. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub.egg-info/PKG-INFO +1 -1
  19. ytdl_sub-2025.12.8/src/ytdl_sub/__init__.py +0 -1
  20. ytdl_sub-2025.12.8/src/ytdl_sub/config/validators/variable_validation.py +0 -215
  21. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/LICENSE +0 -0
  22. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/README.md +0 -0
  23. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/pyproject.toml +0 -0
  24. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/setup.cfg +0 -0
  25. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/cli/__init__.py +0 -0
  26. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/cli/entrypoint.py +0 -0
  27. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/cli/output_summary.py +0 -0
  28. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/cli/output_transaction_log.py +0 -0
  29. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/cli/parsers/__init__.py +0 -0
  30. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/cli/parsers/cli_to_sub.py +0 -0
  31. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/cli/parsers/dl.py +0 -0
  32. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/cli/parsers/main.py +0 -0
  33. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/config/__init__.py +0 -0
  34. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/config/config_file.py +0 -0
  35. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/config/config_validator.py +0 -0
  36. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/config/defaults.py +0 -0
  37. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/config/plugin/__init__.py +0 -0
  38. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/config/plugin/plugin.py +0 -0
  39. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/config/plugin/plugin_mapping.py +0 -0
  40. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/config/plugin/plugin_operation.py +0 -0
  41. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/config/preset_options.py +0 -0
  42. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/config/validators/__init__.py +0 -0
  43. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/config/validators/options.py +0 -0
  44. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/downloaders/__init__.py +0 -0
  45. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/downloaders/info_json/__init__.py +0 -0
  46. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/downloaders/info_json/info_json_downloader.py +0 -0
  47. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/downloaders/source_plugin.py +0 -0
  48. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/downloaders/url/__init__.py +0 -0
  49. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/downloaders/url/downloader.py +0 -0
  50. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/downloaders/ytdl_options_builder.py +0 -0
  51. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/downloaders/ytdlp.py +0 -0
  52. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/entries/__init__.py +0 -0
  53. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/entries/base_entry.py +0 -0
  54. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/entries/entry.py +0 -0
  55. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/entries/entry_parent.py +0 -0
  56. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/entries/script/__init__.py +0 -0
  57. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/entries/script/custom_functions.py +0 -0
  58. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/entries/script/function_scripts.py +0 -0
  59. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/entries/script/variable_definitions.py +0 -0
  60. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/entries/script/variable_types.py +0 -0
  61. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/entries/variables/__init__.py +0 -0
  62. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/main.py +0 -0
  63. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/__init__.py +0 -0
  64. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/audio_extract.py +0 -0
  65. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/chapters.py +0 -0
  66. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/date_range.py +0 -0
  67. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/embed_thumbnail.py +0 -0
  68. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/file_convert.py +0 -0
  69. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/filter_exclude.py +0 -0
  70. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/filter_include.py +0 -0
  71. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/format.py +0 -0
  72. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/internal/__init__.py +0 -0
  73. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/internal/view.py +0 -0
  74. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/match_filters.py +0 -0
  75. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/music_tags.py +0 -0
  76. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/nfo_tags.py +0 -0
  77. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/output_directory_nfo_tags.py +0 -0
  78. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/split_by_chapters.py +0 -0
  79. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/square_thumbnail.py +0 -0
  80. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/static_nfo_tags.py +0 -0
  81. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/subtitles.py +0 -0
  82. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/throttle_protection.py +0 -0
  83. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/plugins/video_tags.py +0 -0
  84. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/__init__.py +0 -0
  85. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/helpers/__init__.py +0 -0
  86. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/helpers/filter_duration.yaml +0 -0
  87. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/helpers/filter_keywords.yaml +0 -0
  88. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/helpers/media_quality.yaml +0 -0
  89. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/helpers/players.yaml +0 -0
  90. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/helpers/throttle_protection.yaml +0 -0
  91. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/helpers/url.yaml +0 -0
  92. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/helpers/url_bilateral.yaml +0 -0
  93. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/helpers/url_categorized.yaml +0 -0
  94. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/internal/__init__.py +0 -0
  95. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/internal/view.yaml +0 -0
  96. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/music/__init__.py +0 -0
  97. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/music/albums_from_chapters.yaml +0 -0
  98. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/music/albums_from_playlists.yaml +0 -0
  99. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/music/singles.yaml +0 -0
  100. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/music/soundcloud.yaml +0 -0
  101. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/music_videos/__init__.py +0 -0
  102. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/music_videos/music_video_base.yaml +0 -0
  103. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/music_videos/music_video_extras.yaml +0 -0
  104. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/music_videos/music_videos.yaml +0 -0
  105. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/tv_show/__init__.py +0 -0
  106. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/tv_show/episode.yaml +0 -0
  107. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show.yaml +0 -0
  108. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_by_date.yaml +0 -0
  109. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml +0 -0
  110. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/__init__.py +0 -0
  111. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/functions/__init__.py +0 -0
  112. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/functions/array_functions.py +0 -0
  113. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/functions/boolean_functions.py +0 -0
  114. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/functions/conditional_functions.py +0 -0
  115. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/functions/date_functions.py +0 -0
  116. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/functions/error_functions.py +0 -0
  117. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/functions/json_functions.py +0 -0
  118. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/functions/map_functions.py +0 -0
  119. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/functions/numeric_functions.py +0 -0
  120. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/functions/print_functions.py +0 -0
  121. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/functions/regex_functions.py +0 -0
  122. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/functions/string_functions.py +0 -0
  123. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/script_output.py +0 -0
  124. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/types/__init__.py +0 -0
  125. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/types/array.py +0 -0
  126. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/types/function.py +0 -0
  127. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/types/map.py +0 -0
  128. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/types/resolvable.py +0 -0
  129. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/types/syntax_tree.py +0 -0
  130. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/types/variable.py +0 -0
  131. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/utils/__init__.py +0 -0
  132. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/utils/exception_formatters.py +0 -0
  133. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/utils/exceptions.py +0 -0
  134. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/script/utils/type_checking.py +0 -0
  135. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/subscriptions/__init__.py +0 -0
  136. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/subscriptions/subscription.py +0 -0
  137. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/subscriptions/subscription_download.py +0 -0
  138. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/subscriptions/subscription_validators.py +0 -0
  139. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/subscriptions/subscription_ytdl_options.py +0 -0
  140. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/thread/__init__.py +0 -0
  141. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/thread/log_entries_downloaded_listener.py +0 -0
  142. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/utils/__init__.py +0 -0
  143. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/utils/chapters.py +0 -0
  144. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/utils/datetime.py +0 -0
  145. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/utils/exceptions.py +0 -0
  146. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/utils/ffmpeg.py +0 -0
  147. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/utils/file_handler.py +0 -0
  148. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/utils/file_lock.py +0 -0
  149. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/utils/file_path.py +0 -0
  150. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/utils/logger.py +0 -0
  151. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/utils/retry.py +0 -0
  152. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/utils/scriptable.py +0 -0
  153. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/utils/subtitles.py +0 -0
  154. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/utils/system.py +0 -0
  155. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/utils/thumbnail.py +0 -0
  156. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/utils/xml.py +0 -0
  157. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/utils/yaml.py +0 -0
  158. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/validators/__init__.py +0 -0
  159. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/validators/audo_codec_validator.py +0 -0
  160. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/validators/file_path_validators.py +0 -0
  161. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/validators/nfo_validators.py +0 -0
  162. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/validators/regex_validator.py +0 -0
  163. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/validators/source_variable_validator.py +0 -0
  164. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/validators/strict_dict_validator.py +0 -0
  165. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/validators/string_datetime.py +0 -0
  166. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/validators/string_select_validator.py +0 -0
  167. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/ytdl_additions/__init__.py +0 -0
  168. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py +0 -0
  169. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub.egg-info/SOURCES.txt +0 -0
  170. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub.egg-info/dependency_links.txt +0 -0
  171. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub.egg-info/entry_points.txt +0 -0
  172. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.post1}/src/ytdl_sub.egg-info/requires.txt +0 -0
  173. {ytdl_sub-2025.12.8 → ytdl_sub-2025.12.31.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: 2025.12.8
3
+ Version: 2025.12.31.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__ = "2025.12.31.post1";__local_version__ = "2025.12.31+d373935"
@@ -1,17 +1,19 @@
1
1
  from typing import Any
2
2
  from typing import Dict
3
+ from typing import Iterable
3
4
  from typing import Optional
4
5
  from typing import Set
5
6
 
6
- import mergedeep
7
-
8
7
  from ytdl_sub.entries.entry import Entry
9
8
  from ytdl_sub.entries.script.variable_definitions import VARIABLES
10
9
  from ytdl_sub.entries.variables.override_variables import REQUIRED_OVERRIDE_VARIABLE_NAMES
11
10
  from ytdl_sub.entries.variables.override_variables import OverrideHelpers
12
11
  from ytdl_sub.script.parser import parse
13
12
  from ytdl_sub.script.script import Script
13
+ from ytdl_sub.script.types.function import BuiltInFunction
14
14
  from ytdl_sub.script.types.resolvable import Resolvable
15
+ from ytdl_sub.script.types.resolvable import String
16
+ from ytdl_sub.script.types.syntax_tree import SyntaxTree
15
17
  from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved
16
18
  from ytdl_sub.utils.exceptions import InvalidVariableNameException
17
19
  from ytdl_sub.utils.exceptions import StringFormattingException
@@ -88,6 +90,24 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable):
88
90
 
89
91
  return True
90
92
 
93
+ def ensure_variable_names_not_a_plugin(self, plugin_names: Iterable[str]) -> None:
94
+ """
95
+ Throws an error if an override variable or function has the same name as a
96
+ preset key. This is to avoid confusion when accidentally defining things in
97
+ overrides that are meant to be in the preset.
98
+ """
99
+ for name in self.keys:
100
+ if name.startswith("%"):
101
+ name = name[1:]
102
+
103
+ if name in plugin_names:
104
+ raise self._validation_exception(
105
+ f"Override variable with name {name} cannot be used since it is"
106
+ " the name of a plugin. Perhaps you meant to define it as a plugin? If so,"
107
+ " indent it left to make it at the same level as overrides.",
108
+ exception_class=InvalidVariableNameException,
109
+ )
110
+
91
111
  def ensure_variable_name_valid(self, name: str) -> None:
92
112
  """
93
113
  Ensures the variable name does not collide with any entry variables or built-in functions.
@@ -115,29 +135,35 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable):
115
135
  )
116
136
 
117
137
  def initial_variables(
118
- self, unresolved_variables: Optional[Dict[str, str]] = None
119
- ) -> Dict[str, str]:
138
+ self, unresolved_variables: Optional[Dict[str, SyntaxTree]] = None
139
+ ) -> Dict[str, SyntaxTree]:
120
140
  """
121
141
  Returns
122
142
  -------
123
143
  Variables and format strings for all Override variables + additional variables (Optional)
124
144
  """
125
- initial_variables: Dict[str, str] = {}
126
- mergedeep.merge(
127
- initial_variables,
128
- self.dict_with_format_strings,
129
- unresolved_variables if unresolved_variables else {},
130
- )
131
- return ScriptUtils.add_sanitized_variables(initial_variables)
145
+ initial_variables: Dict[str, SyntaxTree] = self.dict_with_parsed_format_strings
146
+ if unresolved_variables:
147
+ initial_variables |= unresolved_variables
148
+ return ScriptUtils.add_sanitized_parsed_variables(initial_variables)
132
149
 
133
150
  def initialize_script(self, unresolved_variables: Set[str]) -> "Overrides":
134
151
  """
135
152
  Initialize the override script with any unresolved variables
136
153
  """
137
- self.script.add(
154
+ self.script.add_parsed(
138
155
  self.initial_variables(
139
156
  unresolved_variables={
140
- var_name: f"{{%throw('Plugin variable {var_name} has not been created yet')}}"
157
+ var_name: SyntaxTree(
158
+ ast=[
159
+ BuiltInFunction(
160
+ name="throw",
161
+ args=[
162
+ String(f"Plugin variable {var_name} has not been created yet")
163
+ ],
164
+ )
165
+ ]
166
+ )
141
167
  for var_name in unresolved_variables
142
168
  }
143
169
  )
@@ -1,5 +1,7 @@
1
+ from typing import Iterable
1
2
  from typing import List
2
3
  from typing import Optional
4
+ from typing import Set
3
5
  from typing import Tuple
4
6
  from typing import Type
5
7
 
@@ -44,3 +46,34 @@ class PresetPlugins:
44
46
  if plugin_type in plugin_option_types:
45
47
  return self.plugin_options[plugin_option_types.index(plugin_type)]
46
48
  return None
49
+
50
+ def get_added_and_modified_variables(
51
+ self, additional_options: List[OptionsValidator]
52
+ ) -> Iterable[Tuple[OptionsValidator, Set[str], Set[str]]]:
53
+ """
54
+ Iterates and returns the plugin options, added variables, modified variables
55
+ """
56
+ for plugin_options in self.plugin_options + additional_options:
57
+ added_variables: Set[str] = set()
58
+ modified_variables: Set[str] = set()
59
+
60
+ for plugin_added_variables in plugin_options.added_variables(
61
+ unresolved_variables=set(),
62
+ ).values():
63
+ added_variables |= set(plugin_added_variables)
64
+
65
+ for plugin_modified_variables in plugin_options.modified_variables().values():
66
+ modified_variables = plugin_modified_variables
67
+
68
+ yield plugin_options, added_variables, modified_variables
69
+
70
+ def get_all_variables(self, additional_options: List[OptionsValidator]) -> Set[str]:
71
+ """
72
+ Returns set of all added and modified variables' names.
73
+ """
74
+ all_variables: Set[str] = set()
75
+ for _, added, modified in self.get_added_and_modified_variables(additional_options):
76
+ all_variables.update(added)
77
+ all_variables.update(modified)
78
+
79
+ return all_variables
@@ -2,6 +2,7 @@ import copy
2
2
  from typing import Any
3
3
  from typing import Dict
4
4
  from typing import List
5
+ from typing import Set
5
6
 
6
7
  from mergedeep import mergedeep
7
8
 
@@ -11,7 +12,6 @@ from ytdl_sub.config.plugin.plugin_mapping import PluginMapping
11
12
  from ytdl_sub.config.plugin.preset_plugins import PresetPlugins
12
13
  from ytdl_sub.config.preset_options import OutputOptions
13
14
  from ytdl_sub.config.preset_options import YTDLOptions
14
- from ytdl_sub.config.validators.variable_validation import VariableValidation
15
15
  from ytdl_sub.downloaders.url.validators import MultiUrlValidator
16
16
  from ytdl_sub.prebuilt_presets import PREBUILT_PRESET_NAMES
17
17
  from ytdl_sub.prebuilt_presets import PUBLISHED_PRESET_NAMES
@@ -172,6 +172,37 @@ class Preset(_PresetShell):
172
172
  mergedeep.merge({}, *reversed(presets_to_merge), strategy=mergedeep.Strategy.ADDITIVE)
173
173
  )
174
174
 
175
+ def _initialize_overrides_script(self, overrides: Overrides) -> Overrides:
176
+ """
177
+ Do some gymnastics to initialize the Overrides script.
178
+ """
179
+ unresolved_variables: Set[str] = set()
180
+
181
+ for (
182
+ plugin_options,
183
+ added_variables,
184
+ modified_variables,
185
+ ) in self.plugins.get_added_and_modified_variables(
186
+ additional_options=[self.downloader_options, self.output_options]
187
+ ):
188
+ for added_variable in added_variables:
189
+ if not overrides.ensure_added_plugin_variable_valid(added_variable=added_variable):
190
+ # pylint: disable=protected-access
191
+ raise plugin_options._validation_exception(
192
+ f"Cannot use the variable name {added_variable} because it exists as a"
193
+ " built-in ytdl-sub variable name."
194
+ )
195
+ # pylint: enable=protected-access
196
+
197
+ # Set unresolved as variables that are added but do not exist as
198
+ # entry/override variables since they are created at run-time
199
+ unresolved_variables |= added_variables | modified_variables
200
+
201
+ # Initialize overrides with unresolved variables + modified variables to throw an error.
202
+ # For modified variables, this is to prevent a resolve(update=True) to setting any
203
+ # dependencies until it has been explicitly added
204
+ return overrides.initialize_script(unresolved_variables=unresolved_variables)
205
+
175
206
  def __init__(self, config: ConfigValidator, name: str, value: Any):
176
207
  super().__init__(name=name, value=value)
177
208
 
@@ -192,13 +223,10 @@ class Preset(_PresetShell):
192
223
  )
193
224
 
194
225
  self.plugins: PresetPlugins = self._validate_and_get_plugins()
195
- self.overrides = self._validate_key(key="overrides", validator=Overrides, default={})
196
-
197
- VariableValidation(
198
- downloader_options=self.downloader_options,
199
- output_options=self.output_options,
200
- plugins=self.plugins,
201
- ).initialize_preset_overrides(overrides=self.overrides).ensure_proper_usage()
226
+ self.overrides = self._initialize_overrides_script(
227
+ overrides=self._validate_key(key="overrides", validator=Overrides, default={})
228
+ )
229
+ self.overrides.ensure_variable_names_not_a_plugin(plugin_names=PRESET_KEYS)
202
230
 
203
231
  @property
204
232
  def name(self) -> str:
@@ -0,0 +1,91 @@
1
+ from typing import Dict
2
+
3
+ from ytdl_sub.config.overrides import Overrides
4
+ from ytdl_sub.config.plugin.plugin_mapping import PluginMapping
5
+ from ytdl_sub.config.plugin.plugin_operation import PluginOperation
6
+ from ytdl_sub.config.plugin.preset_plugins import PresetPlugins
7
+ from ytdl_sub.config.preset_options import OutputOptions
8
+ from ytdl_sub.config.validators.options import OptionsValidator
9
+ from ytdl_sub.downloaders.url.validators import MultiUrlValidator
10
+ from ytdl_sub.validators.string_formatter_validators import validate_formatters
11
+
12
+
13
+ class VariableValidation:
14
+ def __init__(
15
+ self,
16
+ overrides: Overrides,
17
+ downloader_options: MultiUrlValidator,
18
+ output_options: OutputOptions,
19
+ plugins: PresetPlugins,
20
+ ):
21
+ self.overrides = overrides
22
+ self.downloader_options = downloader_options
23
+ self.output_options = output_options
24
+ self.plugins = plugins
25
+
26
+ self.script = self.overrides.script
27
+ self.unresolved_variables = self.plugins.get_all_variables(
28
+ additional_options=[self.output_options, self.downloader_options]
29
+ )
30
+
31
+ def _add_variables(self, plugin_op: PluginOperation, options: OptionsValidator) -> None:
32
+ """
33
+ Add dummy variables for script validation
34
+ """
35
+ added_variables = options.added_variables(
36
+ unresolved_variables=self.unresolved_variables,
37
+ ).get(plugin_op, set())
38
+ modified_variables = options.modified_variables().get(plugin_op, set())
39
+
40
+ self.unresolved_variables -= added_variables | modified_variables
41
+
42
+ def ensure_proper_usage(self) -> Dict:
43
+ """
44
+ Validate variables resolve as plugins are executed, and return
45
+ a mock script which contains actualized added variables from the plugins
46
+ """
47
+
48
+ resolved_subscription: Dict = {}
49
+
50
+ self._add_variables(PluginOperation.DOWNLOADER, options=self.downloader_options)
51
+
52
+ # Always add output options first
53
+ self._add_variables(PluginOperation.MODIFY_ENTRY_METADATA, options=self.output_options)
54
+
55
+ # Metadata variables to be added
56
+ for plugin_options in PluginMapping.order_options_by(
57
+ self.plugins.zipped(), PluginOperation.MODIFY_ENTRY_METADATA
58
+ ):
59
+ self._add_variables(PluginOperation.MODIFY_ENTRY_METADATA, options=plugin_options)
60
+
61
+ for plugin_options in PluginMapping.order_options_by(
62
+ self.plugins.zipped(), PluginOperation.MODIFY_ENTRY
63
+ ):
64
+ self._add_variables(PluginOperation.MODIFY_ENTRY, options=plugin_options)
65
+
66
+ # Validate that any formatter in the plugin options can resolve
67
+ resolved_subscription |= validate_formatters(
68
+ script=self.script,
69
+ unresolved_variables=self.unresolved_variables,
70
+ validator=plugin_options,
71
+ )
72
+
73
+ resolved_subscription |= validate_formatters(
74
+ script=self.script,
75
+ unresolved_variables=self.unresolved_variables,
76
+ validator=self.output_options,
77
+ )
78
+
79
+ # TODO: make this a function
80
+ raw_download_output = validate_formatters(
81
+ script=self.script,
82
+ unresolved_variables=self.unresolved_variables,
83
+ validator=self.downloader_options.urls,
84
+ )
85
+ resolved_subscription["download"] = []
86
+ for url_output in raw_download_output["download"]:
87
+ if url_output["url"]:
88
+ resolved_subscription["download"].append(url_output)
89
+
90
+ assert not self.unresolved_variables
91
+ return resolved_subscription
@@ -21,7 +21,7 @@ class UrlThumbnailValidator(StrictDictValidator):
21
21
  def __init__(self, name, value):
22
22
  super().__init__(name, value)
23
23
 
24
- self._name = self._validate_key(key="name", validator=StringFormatterValidator)
24
+ self._thumb_name = self._validate_key(key="name", validator=StringFormatterValidator)
25
25
  self._uid = self._validate_key(key="uid", validator=OverridesStringFormatterValidator)
26
26
 
27
27
  @property
@@ -29,7 +29,7 @@ class UrlThumbnailValidator(StrictDictValidator):
29
29
  """
30
30
  File name for the thumbnail
31
31
  """
32
- return self._name
32
+ return self._thumb_name
33
33
 
34
34
  @property
35
35
  def uid(self) -> OverridesStringFormatterValidator:
@@ -11,9 +11,6 @@ from ytdl_sub.entries.script.variable_types import Variable
11
11
  from ytdl_sub.script.functions import Functions
12
12
  from ytdl_sub.script.utils.name_validation import is_valid_name
13
13
 
14
- # TODO: use this
15
- SUBSCRIPTION_ARRAY = "subscription_array"
16
-
17
14
 
18
15
  class SubscriptionVariables:
19
16
  @staticmethod
@@ -163,7 +160,7 @@ class OverrideHelpers:
163
160
  True if the override name itself is valid. False otherwise.
164
161
  """
165
162
  if name.startswith("%"):
166
- return is_valid_name(name=name[1:])
163
+ name = name[1:]
167
164
 
168
165
  return is_valid_name(name=name)
169
166
 
@@ -11,8 +11,7 @@ presets:
11
11
 
12
12
  # Set the default date_range to 2 months
13
13
  overrides:
14
- date_range: "2months" # keep for legacy-reasons
15
- only_recent_date_range: "{date_range}"
14
+ only_recent_date_range: "2months"
16
15
 
17
16
  #############################################################################
18
17
  # Only Recent
@@ -31,6 +31,8 @@ from ytdl_sub.script.utils.exceptions import InvalidSyntaxException
31
31
  from ytdl_sub.script.utils.exceptions import InvalidVariableName
32
32
  from ytdl_sub.script.utils.exceptions import UserException
33
33
  from ytdl_sub.script.utils.exceptions import VariableDoesNotExist
34
+ from ytdl_sub.script.utils.name_validation import is_function
35
+ from ytdl_sub.script.utils.name_validation import to_function_name
34
36
  from ytdl_sub.script.utils.name_validation import validate_variable_name
35
37
 
36
38
  # pylint: disable=invalid-name
@@ -144,6 +146,9 @@ class _Parser:
144
146
  ):
145
147
  self._text = text
146
148
  self._name = name
149
+ if name and is_function(name):
150
+ self._name = to_function_name(name)
151
+
147
152
  self._custom_function_names = custom_function_names
148
153
  self._variable_names = variable_names
149
154
  self._pos = 0
@@ -20,28 +20,13 @@ from ytdl_sub.script.utils.exceptions import IncompatibleFunctionArguments
20
20
  from ytdl_sub.script.utils.exceptions import InvalidCustomFunctionArguments
21
21
  from ytdl_sub.script.utils.exceptions import RuntimeException
22
22
  from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved
23
+ from ytdl_sub.script.utils.name_validation import is_function
24
+ from ytdl_sub.script.utils.name_validation import to_function_definition_name
25
+ from ytdl_sub.script.utils.name_validation import to_function_name
23
26
  from ytdl_sub.script.utils.name_validation import validate_variable_name
24
27
  from ytdl_sub.script.utils.type_checking import FunctionSpec
25
28
 
26
29
 
27
- def _is_function(override_name: str):
28
- return override_name.startswith("%")
29
-
30
-
31
- def _function_name(function_key: str) -> str:
32
- """
33
- Drop the % in %custom_function
34
- """
35
- return function_key[1:]
36
-
37
-
38
- def _to_function_definition_name(function_key: str) -> str:
39
- """
40
- Add % in %custom_function
41
- """
42
- return f"%{function_key}"
43
-
44
-
45
30
  class Script:
46
31
  """
47
32
  Takes a dictionary of both
@@ -241,23 +226,23 @@ class Script:
241
226
 
242
227
  def __init__(self, script: Dict[str, str]):
243
228
  function_names: Set[str] = {
244
- _function_name(name) for name in script.keys() if _is_function(name)
229
+ to_function_name(name) for name in script.keys() if is_function(name)
245
230
  }
246
231
  variable_names: Set[str] = {
247
- validate_variable_name(name) for name in script.keys() if not _is_function(name)
232
+ validate_variable_name(name) for name in script.keys() if not is_function(name)
248
233
  }
249
234
 
250
235
  self._functions: Dict[str, SyntaxTree] = {
251
236
  # custom_function_name must be passed to properly type custom function
252
237
  # arguments uniquely if they're nested (i.e. $0 to $custom_func___0)
253
- _function_name(function_key): parse(
238
+ to_function_name(function_key): parse(
254
239
  text=function_value,
255
- name=_function_name(function_key),
240
+ name=to_function_name(function_key),
256
241
  custom_function_names=function_names,
257
242
  variable_names=variable_names,
258
243
  )
259
244
  for function_key, function_value in script.items()
260
- if _is_function(function_key)
245
+ if is_function(function_key)
261
246
  }
262
247
 
263
248
  self._variables: Dict[str, SyntaxTree] = {
@@ -268,7 +253,7 @@ class Script:
268
253
  variable_names=variable_names,
269
254
  )
270
255
  for variable_key, variable_value in script.items()
271
- if not _is_function(variable_key)
256
+ if not is_function(variable_key)
272
257
  }
273
258
  self._validate()
274
259
 
@@ -307,10 +292,9 @@ class Script:
307
292
 
308
293
  def _get_unresolved_output_filter(
309
294
  self,
310
- unresolved: Dict[Variable, SyntaxTree],
311
295
  output_filter: Set[str],
312
296
  unresolvable: Set[Variable],
313
- ) -> Dict[Variable, SyntaxTree]:
297
+ ) -> Set[str]:
314
298
  """
315
299
  When an output filter is applied, only a subset of variables that the filter
316
300
  depends on need to be resolved.
@@ -331,7 +315,7 @@ class Script:
331
315
  unresolvable=unresolvable,
332
316
  )
333
317
 
334
- return {var: syntax for var, syntax in unresolved.items() if var.name in subset_to_resolve}
318
+ return subset_to_resolve
335
319
 
336
320
  def _resolve(
337
321
  self,
@@ -367,18 +351,21 @@ class Script:
367
351
 
368
352
  unresolvable: Set[Variable] = {Variable(name) for name in (unresolvable or {})}
369
353
  unresolved_filter = set(resolved.keys()).union(unresolvable)
370
- unresolved: Dict[Variable, SyntaxTree] = {
371
- Variable(name): ast
372
- for name, ast in self._variables.items()
373
- if Variable(name) not in unresolved_filter
374
- }
375
354
 
376
355
  if output_filter:
377
- unresolved = self._get_unresolved_output_filter(
378
- unresolved=unresolved,
379
- output_filter=output_filter,
380
- unresolvable=unresolvable,
381
- )
356
+ unresolved = {
357
+ Variable(name): self._variables[name]
358
+ for name in self._get_unresolved_output_filter(
359
+ output_filter=output_filter,
360
+ unresolvable=unresolvable,
361
+ )
362
+ }
363
+ else:
364
+ unresolved = {
365
+ Variable(name): ast
366
+ for name, ast in self._variables.items()
367
+ if Variable(name) not in unresolved_filter
368
+ }
382
369
 
383
370
  while unresolved:
384
371
  unresolved_count: int = len(unresolved)
@@ -483,12 +470,12 @@ class Script:
483
470
  added_variables_to_validate: Set[str] = set()
484
471
 
485
472
  functions_to_add = {
486
- _function_name(name): definition
473
+ to_function_name(name): definition
487
474
  for name, definition in variables.items()
488
- if _is_function(name)
475
+ if is_function(name)
489
476
  }
490
477
  variables_to_add = {
491
- name: definition for name, definition in variables.items() if not _is_function(name)
478
+ name: definition for name, definition in variables.items() if not is_function(name)
492
479
  }
493
480
 
494
481
  custom_function_names = set(self._functions.keys()) | functions_to_add.keys()
@@ -518,6 +505,46 @@ class Script:
518
505
 
519
506
  return self
520
507
 
508
+ def add_parsed(self, variables: Dict[str, SyntaxTree]) -> "Script":
509
+ """
510
+ Adds already parsed, new variables to the script.
511
+
512
+ Parameters
513
+ ----------
514
+ variables
515
+ Mapping containing variable name to definition.
516
+
517
+ Returns
518
+ -------
519
+ Script
520
+ self
521
+ """
522
+ added_variables_to_validate: Set[str] = set()
523
+
524
+ functions_to_add = {
525
+ to_function_name(name): definition
526
+ for name, definition in variables.items()
527
+ if is_function(name)
528
+ }
529
+ variables_to_add = {
530
+ name: definition for name, definition in variables.items() if not is_function(name)
531
+ }
532
+
533
+ for definitions in [functions_to_add, variables_to_add]:
534
+ for name, parsed in definitions.items():
535
+ if parsed.maybe_resolvable is None:
536
+ added_variables_to_validate.add(name)
537
+
538
+ if name in functions_to_add:
539
+ self._functions[name] = parsed
540
+ else:
541
+ self._variables[name] = parsed
542
+
543
+ if added_variables_to_validate:
544
+ self._validate(added_variables=added_variables_to_validate)
545
+
546
+ return self
547
+
521
548
  def resolve_once(
522
549
  self,
523
550
  variable_definitions: Dict[str, str],
@@ -556,8 +583,47 @@ class Script:
556
583
  ).output
557
584
  finally:
558
585
  for name in variable_definitions.keys():
559
- if name in self._variables:
560
- del self._variables[name]
586
+ self._variables.pop(name, None)
587
+
588
+ def resolve_once_parsed(
589
+ self,
590
+ variable_definitions: Dict[str, SyntaxTree],
591
+ resolved: Optional[Dict[str, Resolvable]] = None,
592
+ unresolvable: Optional[Set[str]] = None,
593
+ update: bool = False,
594
+ ) -> Dict[str, Resolvable]:
595
+ """
596
+ Given a new set of variable definitions, resolve them using the Script, but do not
597
+ add them to the Script itself.
598
+
599
+ Parameters
600
+ ----------
601
+ variable_definitions
602
+ Variables to resolve, but not store in the Script
603
+ resolved
604
+ Optional. Pre-resolved variables that should be used instead of what is in the script.
605
+ unresolvable
606
+ Optional. Unresolvable variables that will be ignored in resolution, including all
607
+ variables with a dependency to them.
608
+ update
609
+ Whether to update the script's state with resolved variables. Defaults to False.
610
+
611
+ Returns
612
+ -------
613
+ Dict[str, Resolvable]
614
+ Dict containing the variable names to their resolved values.
615
+ """
616
+ try:
617
+ self.add_parsed(variable_definitions)
618
+ return self._resolve(
619
+ pre_resolved=resolved,
620
+ unresolvable=unresolvable,
621
+ output_filter=set(list(variable_definitions.keys())),
622
+ update=update,
623
+ ).output
624
+ finally:
625
+ for name in variable_definitions.keys():
626
+ self._variables.pop(name, None)
561
627
 
562
628
  def get(self, variable_name: str) -> Resolvable:
563
629
  """
@@ -604,4 +670,4 @@ class Script:
604
670
  Set[str]
605
671
  Names of all functions within the Script.
606
672
  """
607
- return set(_to_function_definition_name(name) for name in self._functions.keys())
673
+ return set(to_function_definition_name(name) for name in self._functions.keys())
@@ -114,6 +114,7 @@ class VariableDependency(ABC):
114
114
  output.add(ParsedCustomFunction(name=arg.name, num_input_args=len(arg.args)))
115
115
  if isinstance(arg, VariableDependency):
116
116
  output.update(arg.custom_functions)
117
+ # if isinstance(arg, Lambda)
117
118
 
118
119
  return output
119
120
 
@@ -189,7 +190,17 @@ class VariableDependency(ABC):
189
190
  -------
190
191
  True if it contains any of the input variables. False otherwise.
191
192
  """
192
- for custom_function in self.custom_functions:
193
+ # If there are lambdas, see if they are custom functions. If so, check them
194
+ custom_functions_to_check = self.custom_functions
195
+ for lambda_func in self.lambdas:
196
+ if lambda_func.value in custom_function_definitions:
197
+ custom_functions_to_check.add(
198
+ ParsedCustomFunction(
199
+ name=lambda_func.value, num_input_args=lambda_func.num_input_args()
200
+ )
201
+ )
202
+
203
+ for custom_function in custom_functions_to_check:
193
204
  if custom_function_definitions[custom_function.name].contains(
194
205
  variables=variables, custom_function_definitions=custom_function_definitions
195
206
  ):
@@ -58,3 +58,24 @@ def validate_custom_function_name(custom_function_name: str) -> None:
58
58
  f"Custom function name '%{custom_function_name}' is invalid:"
59
59
  " The name is used by a built-in function and cannot be overwritten."
60
60
  )
61
+
62
+
63
+ def is_function(override_name: str):
64
+ """
65
+ Whether the definition is a function or not.
66
+ """
67
+ return override_name.startswith("%")
68
+
69
+
70
+ def to_function_name(function_key: str) -> str:
71
+ """
72
+ Drop the % in %custom_function
73
+ """
74
+ return function_key[1:]
75
+
76
+
77
+ def to_function_definition_name(function_key: str) -> str:
78
+ """
79
+ Add % in %custom_function
80
+ """
81
+ return f"%{function_key}"