ytdl-sub 2025.12.30__py3-none-any.whl → 2025.12.31.post1__py3-none-any.whl
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.
- ytdl_sub/__init__.py +1 -1
- ytdl_sub/config/overrides.py +20 -13
- ytdl_sub/config/plugin/preset_plugins.py +33 -0
- ytdl_sub/config/preset.py +35 -9
- ytdl_sub/config/validators/variable_validation.py +23 -147
- ytdl_sub/downloaders/url/validators.py +2 -2
- ytdl_sub/script/parser.py +5 -0
- ytdl_sub/script/script.py +93 -28
- ytdl_sub/script/types/variable_dependency.py +12 -1
- ytdl_sub/script/utils/name_validation.py +21 -0
- ytdl_sub/subscriptions/base_subscription.py +21 -3
- ytdl_sub/utils/script.py +19 -2
- ytdl_sub/validators/string_formatter_validators.py +63 -31
- ytdl_sub/validators/validators.py +3 -13
- {ytdl_sub-2025.12.30.dist-info → ytdl_sub-2025.12.31.post1.dist-info}/METADATA +1 -1
- {ytdl_sub-2025.12.30.dist-info → ytdl_sub-2025.12.31.post1.dist-info}/RECORD +20 -20
- {ytdl_sub-2025.12.30.dist-info → ytdl_sub-2025.12.31.post1.dist-info}/WHEEL +0 -0
- {ytdl_sub-2025.12.30.dist-info → ytdl_sub-2025.12.31.post1.dist-info}/entry_points.txt +0 -0
- {ytdl_sub-2025.12.30.dist-info → ytdl_sub-2025.12.31.post1.dist-info}/licenses/LICENSE +0 -0
- {ytdl_sub-2025.12.30.dist-info → ytdl_sub-2025.12.31.post1.dist-info}/top_level.txt +0 -0
ytdl_sub/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__pypi_version__ = "2025.12.
|
|
1
|
+
__pypi_version__ = "2025.12.31.post1";__local_version__ = "2025.12.31+d373935"
|
ytdl_sub/config/overrides.py
CHANGED
|
@@ -4,15 +4,16 @@ from typing import Iterable
|
|
|
4
4
|
from typing import Optional
|
|
5
5
|
from typing import Set
|
|
6
6
|
|
|
7
|
-
import mergedeep
|
|
8
|
-
|
|
9
7
|
from ytdl_sub.entries.entry import Entry
|
|
10
8
|
from ytdl_sub.entries.script.variable_definitions import VARIABLES
|
|
11
9
|
from ytdl_sub.entries.variables.override_variables import REQUIRED_OVERRIDE_VARIABLE_NAMES
|
|
12
10
|
from ytdl_sub.entries.variables.override_variables import OverrideHelpers
|
|
13
11
|
from ytdl_sub.script.parser import parse
|
|
14
12
|
from ytdl_sub.script.script import Script
|
|
13
|
+
from ytdl_sub.script.types.function import BuiltInFunction
|
|
15
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
|
|
16
17
|
from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved
|
|
17
18
|
from ytdl_sub.utils.exceptions import InvalidVariableNameException
|
|
18
19
|
from ytdl_sub.utils.exceptions import StringFormattingException
|
|
@@ -134,29 +135,35 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable):
|
|
|
134
135
|
)
|
|
135
136
|
|
|
136
137
|
def initial_variables(
|
|
137
|
-
self, unresolved_variables: Optional[Dict[str,
|
|
138
|
-
) -> Dict[str,
|
|
138
|
+
self, unresolved_variables: Optional[Dict[str, SyntaxTree]] = None
|
|
139
|
+
) -> Dict[str, SyntaxTree]:
|
|
139
140
|
"""
|
|
140
141
|
Returns
|
|
141
142
|
-------
|
|
142
143
|
Variables and format strings for all Override variables + additional variables (Optional)
|
|
143
144
|
"""
|
|
144
|
-
initial_variables: Dict[str,
|
|
145
|
-
|
|
146
|
-
initial_variables
|
|
147
|
-
|
|
148
|
-
unresolved_variables if unresolved_variables else {},
|
|
149
|
-
)
|
|
150
|
-
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)
|
|
151
149
|
|
|
152
150
|
def initialize_script(self, unresolved_variables: Set[str]) -> "Overrides":
|
|
153
151
|
"""
|
|
154
152
|
Initialize the override script with any unresolved variables
|
|
155
153
|
"""
|
|
156
|
-
self.script.
|
|
154
|
+
self.script.add_parsed(
|
|
157
155
|
self.initial_variables(
|
|
158
156
|
unresolved_variables={
|
|
159
|
-
var_name:
|
|
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
|
+
)
|
|
160
167
|
for var_name in unresolved_variables
|
|
161
168
|
}
|
|
162
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
|
ytdl_sub/config/preset.py
CHANGED
|
@@ -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,16 +223,11 @@ class Preset(_PresetShell):
|
|
|
192
223
|
)
|
|
193
224
|
|
|
194
225
|
self.plugins: PresetPlugins = self._validate_and_get_plugins()
|
|
195
|
-
self.overrides = self.
|
|
196
|
-
|
|
226
|
+
self.overrides = self._initialize_overrides_script(
|
|
227
|
+
overrides=self._validate_key(key="overrides", validator=Overrides, default={})
|
|
228
|
+
)
|
|
197
229
|
self.overrides.ensure_variable_names_not_a_plugin(plugin_names=PRESET_KEYS)
|
|
198
230
|
|
|
199
|
-
VariableValidation(
|
|
200
|
-
downloader_options=self.downloader_options,
|
|
201
|
-
output_options=self.output_options,
|
|
202
|
-
plugins=self.plugins,
|
|
203
|
-
).initialize_preset_overrides(overrides=self.overrides).ensure_proper_usage()
|
|
204
|
-
|
|
205
231
|
@property
|
|
206
232
|
def name(self) -> str:
|
|
207
233
|
"""
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import copy
|
|
2
1
|
from typing import Dict
|
|
3
|
-
from typing import Iterable
|
|
4
|
-
from typing import List
|
|
5
|
-
from typing import Optional
|
|
6
|
-
from typing import Set
|
|
7
|
-
from typing import Tuple
|
|
8
2
|
|
|
9
3
|
from ytdl_sub.config.overrides import Overrides
|
|
10
4
|
from ytdl_sub.config.plugin.plugin_mapping import PluginMapping
|
|
@@ -13,155 +7,27 @@ from ytdl_sub.config.plugin.preset_plugins import PresetPlugins
|
|
|
13
7
|
from ytdl_sub.config.preset_options import OutputOptions
|
|
14
8
|
from ytdl_sub.config.validators.options import OptionsValidator
|
|
15
9
|
from ytdl_sub.downloaders.url.validators import MultiUrlValidator
|
|
16
|
-
from ytdl_sub.entries.variables.override_variables import REQUIRED_OVERRIDE_VARIABLE_NAMES
|
|
17
|
-
from ytdl_sub.script.script import Script
|
|
18
|
-
from ytdl_sub.script.script import _is_function
|
|
19
|
-
from ytdl_sub.utils.scriptable import BASE_SCRIPT
|
|
20
|
-
from ytdl_sub.validators.string_formatter_validators import to_variable_dependency_format_string
|
|
21
10
|
from ytdl_sub.validators.string_formatter_validators import validate_formatters
|
|
22
11
|
|
|
23
|
-
# Entry variables to mock during validation
|
|
24
|
-
_DUMMY_ENTRY_VARIABLES: Dict[str, str] = {
|
|
25
|
-
name: to_variable_dependency_format_string(
|
|
26
|
-
# pylint: disable=protected-access
|
|
27
|
-
script=BASE_SCRIPT,
|
|
28
|
-
parsed_format_string=BASE_SCRIPT._variables[name],
|
|
29
|
-
# pylint: enable=protected-access
|
|
30
|
-
)
|
|
31
|
-
for name in BASE_SCRIPT.variable_names
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _add_dummy_variables(variables: Iterable[str]) -> Dict[str, str]:
|
|
36
|
-
dummy_variables: Dict[str, str] = {}
|
|
37
|
-
for var in variables:
|
|
38
|
-
dummy_variables[var] = ""
|
|
39
|
-
dummy_variables[f"{var}_sanitized"] = ""
|
|
40
|
-
|
|
41
|
-
return dummy_variables
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def _add_dummy_overrides(overrides: Overrides) -> Dict[str, str]:
|
|
45
|
-
# Have the dummy override variable contain all variable deps that it uses in the string
|
|
46
|
-
dummy_overrides: Dict[str, str] = {}
|
|
47
|
-
for override_name in _override_variables(overrides):
|
|
48
|
-
if _is_function(override_name):
|
|
49
|
-
continue
|
|
50
|
-
|
|
51
|
-
# pylint: disable=protected-access
|
|
52
|
-
dummy_overrides[override_name] = to_variable_dependency_format_string(
|
|
53
|
-
script=overrides.script, parsed_format_string=overrides.script._variables[override_name]
|
|
54
|
-
)
|
|
55
|
-
# pylint: enable=protected-access
|
|
56
|
-
return dummy_overrides
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def _get_added_and_modified_variables(
|
|
60
|
-
plugins: PresetPlugins, downloader_options: MultiUrlValidator, output_options: OutputOptions
|
|
61
|
-
) -> Iterable[Tuple[OptionsValidator, Set[str], Set[str]]]:
|
|
62
|
-
"""
|
|
63
|
-
Iterates and returns the plugin options, added variables, modified variables
|
|
64
|
-
"""
|
|
65
|
-
options: List[OptionsValidator] = plugins.plugin_options
|
|
66
|
-
options.append(downloader_options)
|
|
67
|
-
options.append(output_options)
|
|
68
|
-
|
|
69
|
-
for plugin_options in options:
|
|
70
|
-
added_variables: Set[str] = set()
|
|
71
|
-
modified_variables: Set[str] = set()
|
|
72
|
-
|
|
73
|
-
for plugin_added_variables in plugin_options.added_variables(
|
|
74
|
-
unresolved_variables=set(),
|
|
75
|
-
).values():
|
|
76
|
-
added_variables |= set(plugin_added_variables)
|
|
77
|
-
|
|
78
|
-
for plugin_modified_variables in plugin_options.modified_variables().values():
|
|
79
|
-
modified_variables = plugin_modified_variables
|
|
80
|
-
|
|
81
|
-
yield plugin_options, added_variables, modified_variables
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def _override_variables(overrides: Overrides) -> Set[str]:
|
|
85
|
-
return set(list(overrides.initial_variables().keys()))
|
|
86
|
-
|
|
87
12
|
|
|
88
13
|
class VariableValidation:
|
|
89
14
|
def __init__(
|
|
90
15
|
self,
|
|
16
|
+
overrides: Overrides,
|
|
91
17
|
downloader_options: MultiUrlValidator,
|
|
92
18
|
output_options: OutputOptions,
|
|
93
19
|
plugins: PresetPlugins,
|
|
94
20
|
):
|
|
21
|
+
self.overrides = overrides
|
|
95
22
|
self.downloader_options = downloader_options
|
|
96
23
|
self.output_options = output_options
|
|
97
24
|
self.plugins = plugins
|
|
98
25
|
|
|
99
|
-
self.script
|
|
100
|
-
self.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def initialize_preset_overrides(self, overrides: Overrides) -> "VariableValidation":
|
|
104
|
-
"""
|
|
105
|
-
Do some gymnastics to initialize the Overrides script.
|
|
106
|
-
"""
|
|
107
|
-
override_variables = set(list(overrides.initial_variables().keys()))
|
|
108
|
-
|
|
109
|
-
# Set resolved variables as all entry + override variables
|
|
110
|
-
# at this point to generate every possible added/modified variable
|
|
111
|
-
self.resolved_variables = set(_DUMMY_ENTRY_VARIABLES.keys()) | override_variables
|
|
112
|
-
plugin_variables: Set[str] = set()
|
|
113
|
-
|
|
114
|
-
for (
|
|
115
|
-
plugin_options,
|
|
116
|
-
added_variables,
|
|
117
|
-
modified_variables,
|
|
118
|
-
) in _get_added_and_modified_variables(
|
|
119
|
-
plugins=self.plugins,
|
|
120
|
-
downloader_options=self.downloader_options,
|
|
121
|
-
output_options=self.output_options,
|
|
122
|
-
):
|
|
123
|
-
|
|
124
|
-
for added_variable in added_variables:
|
|
125
|
-
if not overrides.ensure_added_plugin_variable_valid(added_variable=added_variable):
|
|
126
|
-
# pylint: disable=protected-access
|
|
127
|
-
raise plugin_options._validation_exception(
|
|
128
|
-
f"Cannot use the variable name {added_variable} because it exists as a"
|
|
129
|
-
" built-in ytdl-sub variable name."
|
|
130
|
-
)
|
|
131
|
-
# pylint: enable=protected-access
|
|
132
|
-
|
|
133
|
-
# Set unresolved as variables that are added but do not exist as
|
|
134
|
-
# entry/override variables since they are created at run-time
|
|
135
|
-
self.unresolved_variables |= added_variables | modified_variables
|
|
136
|
-
plugin_variables |= added_variables | modified_variables
|
|
137
|
-
|
|
138
|
-
# Then update resolved variables to reflect that
|
|
139
|
-
self.resolved_variables -= self.unresolved_variables
|
|
140
|
-
|
|
141
|
-
# Initialize overrides with unresolved variables + modified variables to throw an error.
|
|
142
|
-
# For modified variables, this is to prevent a resolve(update=True) to setting any
|
|
143
|
-
# dependencies until it has been explicitly added
|
|
144
|
-
overrides = overrides.initialize_script(unresolved_variables=self.unresolved_variables)
|
|
145
|
-
|
|
146
|
-
# copy the script and mock entry variables
|
|
147
|
-
self.script = copy.deepcopy(overrides.script)
|
|
148
|
-
self.script.add(
|
|
149
|
-
variables=_add_dummy_overrides(overrides=overrides)
|
|
150
|
-
| _add_dummy_variables(variables=plugin_variables)
|
|
151
|
-
| _DUMMY_ENTRY_VARIABLES
|
|
26
|
+
self.script = self.overrides.script
|
|
27
|
+
self.unresolved_variables = self.plugins.get_all_variables(
|
|
28
|
+
additional_options=[self.output_options, self.downloader_options]
|
|
152
29
|
)
|
|
153
30
|
|
|
154
|
-
return self
|
|
155
|
-
|
|
156
|
-
def _update_script(self) -> None:
|
|
157
|
-
_ = self.script.resolve(unresolvable=self.unresolved_variables, update=True)
|
|
158
|
-
|
|
159
|
-
def _add_subscription_override_variables(self) -> None:
|
|
160
|
-
"""
|
|
161
|
-
Add dummy subscription variables for script validation
|
|
162
|
-
"""
|
|
163
|
-
self.resolved_variables |= REQUIRED_OVERRIDE_VARIABLE_NAMES
|
|
164
|
-
|
|
165
31
|
def _add_variables(self, plugin_op: PluginOperation, options: OptionsValidator) -> None:
|
|
166
32
|
"""
|
|
167
33
|
Add dummy variables for script validation
|
|
@@ -171,19 +37,17 @@ class VariableValidation:
|
|
|
171
37
|
).get(plugin_op, set())
|
|
172
38
|
modified_variables = options.modified_variables().get(plugin_op, set())
|
|
173
39
|
|
|
174
|
-
|
|
40
|
+
self.unresolved_variables -= added_variables | modified_variables
|
|
175
41
|
|
|
176
|
-
|
|
177
|
-
self.unresolved_variables -= resolved_variables
|
|
178
|
-
|
|
179
|
-
def ensure_proper_usage(self) -> None:
|
|
42
|
+
def ensure_proper_usage(self) -> Dict:
|
|
180
43
|
"""
|
|
181
44
|
Validate variables resolve as plugins are executed, and return
|
|
182
45
|
a mock script which contains actualized added variables from the plugins
|
|
183
46
|
"""
|
|
184
47
|
|
|
48
|
+
resolved_subscription: Dict = {}
|
|
49
|
+
|
|
185
50
|
self._add_variables(PluginOperation.DOWNLOADER, options=self.downloader_options)
|
|
186
|
-
self._add_subscription_override_variables()
|
|
187
51
|
|
|
188
52
|
# Always add output options first
|
|
189
53
|
self._add_variables(PluginOperation.MODIFY_ENTRY_METADATA, options=self.output_options)
|
|
@@ -200,16 +64,28 @@ class VariableValidation:
|
|
|
200
64
|
self._add_variables(PluginOperation.MODIFY_ENTRY, options=plugin_options)
|
|
201
65
|
|
|
202
66
|
# Validate that any formatter in the plugin options can resolve
|
|
203
|
-
validate_formatters(
|
|
67
|
+
resolved_subscription |= validate_formatters(
|
|
204
68
|
script=self.script,
|
|
205
69
|
unresolved_variables=self.unresolved_variables,
|
|
206
70
|
validator=plugin_options,
|
|
207
71
|
)
|
|
208
72
|
|
|
209
|
-
validate_formatters(
|
|
73
|
+
resolved_subscription |= validate_formatters(
|
|
210
74
|
script=self.script,
|
|
211
75
|
unresolved_variables=self.unresolved_variables,
|
|
212
76
|
validator=self.output_options,
|
|
213
77
|
)
|
|
214
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
|
+
|
|
215
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.
|
|
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.
|
|
32
|
+
return self._thumb_name
|
|
33
33
|
|
|
34
34
|
@property
|
|
35
35
|
def uid(self) -> OverridesStringFormatterValidator:
|
ytdl_sub/script/parser.py
CHANGED
|
@@ -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
|
ytdl_sub/script/script.py
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
238
|
+
to_function_name(function_key): parse(
|
|
254
239
|
text=function_value,
|
|
255
|
-
name=
|
|
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
|
|
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
|
|
256
|
+
if not is_function(variable_key)
|
|
272
257
|
}
|
|
273
258
|
self._validate()
|
|
274
259
|
|
|
@@ -485,12 +470,12 @@ class Script:
|
|
|
485
470
|
added_variables_to_validate: Set[str] = set()
|
|
486
471
|
|
|
487
472
|
functions_to_add = {
|
|
488
|
-
|
|
473
|
+
to_function_name(name): definition
|
|
489
474
|
for name, definition in variables.items()
|
|
490
|
-
if
|
|
475
|
+
if is_function(name)
|
|
491
476
|
}
|
|
492
477
|
variables_to_add = {
|
|
493
|
-
name: definition for name, definition in variables.items() if not
|
|
478
|
+
name: definition for name, definition in variables.items() if not is_function(name)
|
|
494
479
|
}
|
|
495
480
|
|
|
496
481
|
custom_function_names = set(self._functions.keys()) | functions_to_add.keys()
|
|
@@ -520,6 +505,46 @@ class Script:
|
|
|
520
505
|
|
|
521
506
|
return self
|
|
522
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
|
+
|
|
523
548
|
def resolve_once(
|
|
524
549
|
self,
|
|
525
550
|
variable_definitions: Dict[str, str],
|
|
@@ -560,6 +585,46 @@ class Script:
|
|
|
560
585
|
for name in variable_definitions.keys():
|
|
561
586
|
self._variables.pop(name, None)
|
|
562
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)
|
|
627
|
+
|
|
563
628
|
def get(self, variable_name: str) -> Resolvable:
|
|
564
629
|
"""
|
|
565
630
|
Parameters
|
|
@@ -605,4 +670,4 @@ class Script:
|
|
|
605
670
|
Set[str]
|
|
606
671
|
Names of all functions within the Script.
|
|
607
672
|
"""
|
|
608
|
-
return set(
|
|
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
|
-
|
|
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}"
|
|
@@ -8,12 +8,14 @@ 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 VariableValidation
|
|
11
12
|
from ytdl_sub.downloaders.url.validators import MultiUrlValidator
|
|
12
13
|
from ytdl_sub.entries.variables.override_variables import SubscriptionVariables
|
|
13
14
|
from ytdl_sub.utils.exceptions import SubscriptionPermissionError
|
|
14
15
|
from ytdl_sub.utils.file_handler import FileHandler
|
|
15
16
|
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
|
|
16
17
|
from ytdl_sub.utils.logger import Logger
|
|
18
|
+
from ytdl_sub.utils.yaml import dump_yaml
|
|
17
19
|
from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
|
|
18
20
|
|
|
19
21
|
logger = Logger.get("subscription")
|
|
@@ -76,6 +78,14 @@ class BaseSubscription(ABC):
|
|
|
76
78
|
}
|
|
77
79
|
)
|
|
78
80
|
|
|
81
|
+
# Validate after adding the subscription name
|
|
82
|
+
self._validated_dict = VariableValidation(
|
|
83
|
+
overrides=self.overrides,
|
|
84
|
+
downloader_options=self.downloader_options,
|
|
85
|
+
output_options=self.output_options,
|
|
86
|
+
plugins=self.plugins,
|
|
87
|
+
).ensure_proper_usage()
|
|
88
|
+
|
|
79
89
|
self._enhanced_download_archive: Optional[EnhancedDownloadArchive] = (
|
|
80
90
|
_initialize_download_archive(
|
|
81
91
|
output_options=self.output_options,
|
|
@@ -88,9 +98,9 @@ class BaseSubscription(ABC):
|
|
|
88
98
|
# Add post-archive variables
|
|
89
99
|
self.overrides.add(
|
|
90
100
|
{
|
|
91
|
-
SubscriptionVariables.subscription_has_download_archive():
|
|
92
|
-
|
|
93
|
-
|
|
101
|
+
SubscriptionVariables.subscription_has_download_archive(): (
|
|
102
|
+
f"{{%bool({self.download_archive.num_entries > 0})}}"
|
|
103
|
+
),
|
|
94
104
|
}
|
|
95
105
|
)
|
|
96
106
|
|
|
@@ -245,3 +255,11 @@ class BaseSubscription(ABC):
|
|
|
245
255
|
Subscription in yaml format
|
|
246
256
|
"""
|
|
247
257
|
return self._preset_options.yaml
|
|
258
|
+
|
|
259
|
+
def resolved_yaml(self) -> str:
|
|
260
|
+
"""
|
|
261
|
+
Returns
|
|
262
|
+
-------
|
|
263
|
+
Human-readable, condensed YAML definition of the subscription.
|
|
264
|
+
"""
|
|
265
|
+
return dump_yaml(self._validated_dict)
|
ytdl_sub/utils/script.py
CHANGED
|
@@ -4,7 +4,6 @@ from typing import Any
|
|
|
4
4
|
from typing import Dict
|
|
5
5
|
|
|
6
6
|
from ytdl_sub.script.parser import parse
|
|
7
|
-
from ytdl_sub.script.script import _is_function
|
|
8
7
|
from ytdl_sub.script.types.array import UnresolvedArray
|
|
9
8
|
from ytdl_sub.script.types.function import BuiltInFunction
|
|
10
9
|
from ytdl_sub.script.types.function import Function
|
|
@@ -15,8 +14,10 @@ from ytdl_sub.script.types.resolvable import Float
|
|
|
15
14
|
from ytdl_sub.script.types.resolvable import Integer
|
|
16
15
|
from ytdl_sub.script.types.resolvable import Lambda
|
|
17
16
|
from ytdl_sub.script.types.resolvable import String
|
|
17
|
+
from ytdl_sub.script.types.syntax_tree import SyntaxTree
|
|
18
18
|
from ytdl_sub.script.types.variable import Variable
|
|
19
19
|
from ytdl_sub.script.utils.exceptions import UNREACHABLE
|
|
20
|
+
from ytdl_sub.script.utils.name_validation import is_function
|
|
20
21
|
|
|
21
22
|
# pylint: disable=too-many-return-statements
|
|
22
23
|
|
|
@@ -30,10 +31,26 @@ class ScriptUtils:
|
|
|
30
31
|
sanitized_variables = {
|
|
31
32
|
f"{name}_sanitized": f"{{%sanitize({name})}}"
|
|
32
33
|
for name in variables.keys()
|
|
33
|
-
if not
|
|
34
|
+
if not is_function(name)
|
|
34
35
|
}
|
|
35
36
|
return dict(variables, **sanitized_variables)
|
|
36
37
|
|
|
38
|
+
@classmethod
|
|
39
|
+
def add_sanitized_parsed_variables(
|
|
40
|
+
cls, variables: Dict[str, SyntaxTree]
|
|
41
|
+
) -> Dict[str, SyntaxTree]:
|
|
42
|
+
"""
|
|
43
|
+
Helper to add sanitized variables to a Script
|
|
44
|
+
"""
|
|
45
|
+
sanitized_variables = {
|
|
46
|
+
f"{name}_sanitized": SyntaxTree(
|
|
47
|
+
ast=[BuiltInFunction(name="sanitize", args=[Variable(name)])]
|
|
48
|
+
)
|
|
49
|
+
for name in variables.keys()
|
|
50
|
+
if not is_function(name)
|
|
51
|
+
}
|
|
52
|
+
return variables | sanitized_variables
|
|
53
|
+
|
|
37
54
|
@classmethod
|
|
38
55
|
def to_script(cls, value: Any, sort_keys: bool = True) -> str:
|
|
39
56
|
"""
|
|
@@ -11,6 +11,7 @@ from ytdl_sub.script.types.syntax_tree import SyntaxTree
|
|
|
11
11
|
from ytdl_sub.script.utils.exceptions import RuntimeException
|
|
12
12
|
from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved
|
|
13
13
|
from ytdl_sub.script.utils.exceptions import UserException
|
|
14
|
+
from ytdl_sub.script.utils.exceptions import UserThrownRuntimeError
|
|
14
15
|
from ytdl_sub.utils.exceptions import StringFormattingVariableNotFoundException
|
|
15
16
|
from ytdl_sub.utils.script import ScriptUtils
|
|
16
17
|
from ytdl_sub.validators.validators import DictValidator
|
|
@@ -19,6 +20,8 @@ from ytdl_sub.validators.validators import LiteralDictValidator
|
|
|
19
20
|
from ytdl_sub.validators.validators import StringValidator
|
|
20
21
|
from ytdl_sub.validators.validators import Validator
|
|
21
22
|
|
|
23
|
+
# pylint: disable=protected-access
|
|
24
|
+
|
|
22
25
|
|
|
23
26
|
class StringFormatterValidator(StringValidator):
|
|
24
27
|
"""
|
|
@@ -51,7 +54,10 @@ class StringFormatterValidator(StringValidator):
|
|
|
51
54
|
def __init__(self, name, value: str):
|
|
52
55
|
super().__init__(name=name, value=value)
|
|
53
56
|
try:
|
|
54
|
-
|
|
57
|
+
self._parsed = parse(
|
|
58
|
+
text=str(value),
|
|
59
|
+
name=self.leaf_name,
|
|
60
|
+
)
|
|
55
61
|
except UserException as exc:
|
|
56
62
|
raise self._validation_exception(exc) from exc
|
|
57
63
|
|
|
@@ -65,6 +71,16 @@ class StringFormatterValidator(StringValidator):
|
|
|
65
71
|
"""
|
|
66
72
|
return self._value
|
|
67
73
|
|
|
74
|
+
@property
|
|
75
|
+
@final
|
|
76
|
+
def parsed(self) -> SyntaxTree:
|
|
77
|
+
"""
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
The parsed format string.
|
|
81
|
+
"""
|
|
82
|
+
return self._parsed
|
|
83
|
+
|
|
68
84
|
def post_process(self, resolved: str) -> str:
|
|
69
85
|
"""
|
|
70
86
|
Returns
|
|
@@ -167,9 +183,14 @@ class DictFormatterValidator(LiteralDictValidator):
|
|
|
167
183
|
|
|
168
184
|
@property
|
|
169
185
|
def dict_with_format_strings(self) -> Dict[str, str]:
|
|
170
|
-
"""Returns dict with the format strings themselves"""
|
|
186
|
+
"""Returns dict with the format strings themselves."""
|
|
171
187
|
return {key: string_formatter.format_string for key, string_formatter in self.dict.items()}
|
|
172
188
|
|
|
189
|
+
@property
|
|
190
|
+
def dict_with_parsed_format_strings(self) -> Dict[str, SyntaxTree]:
|
|
191
|
+
"""Returns dict with the parsed format strings."""
|
|
192
|
+
return {key: string_formatter.parsed for key, string_formatter in self.dict.items()}
|
|
193
|
+
|
|
173
194
|
|
|
174
195
|
class OverridesDictFormatterValidator(DictFormatterValidator):
|
|
175
196
|
"""
|
|
@@ -199,10 +220,8 @@ def to_variable_dependency_format_string(script: Script, parsed_format_string: S
|
|
|
199
220
|
dummy_format_string = ""
|
|
200
221
|
for var in parsed_format_string.variables:
|
|
201
222
|
dummy_format_string += f"{{ {var.name} }}"
|
|
202
|
-
# pylint: disable=protected-access
|
|
203
223
|
for variable_dependency in script._variables[var.name].variables:
|
|
204
224
|
dummy_format_string += f"{{ {variable_dependency.name} }}"
|
|
205
|
-
# pylint: enable=protected-access
|
|
206
225
|
return dummy_format_string
|
|
207
226
|
|
|
208
227
|
|
|
@@ -210,16 +229,15 @@ def _validate_formatter(
|
|
|
210
229
|
mock_script: Script,
|
|
211
230
|
unresolved_variables: Set[str],
|
|
212
231
|
formatter_validator: Union[StringFormatterValidator, OverridesStringFormatterValidator],
|
|
213
|
-
) ->
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
)
|
|
232
|
+
) -> str:
|
|
233
|
+
parsed = formatter_validator.parsed
|
|
234
|
+
if resolved := parsed.maybe_resolvable:
|
|
235
|
+
return resolved.native
|
|
236
|
+
|
|
237
|
+
is_static_formatter = isinstance(formatter_validator, OverridesStringFormatterValidator)
|
|
238
|
+
if is_static_formatter:
|
|
239
|
+
unresolved_variables = unresolved_variables.union({VARIABLES.entry_metadata.variable_name})
|
|
240
|
+
|
|
223
241
|
variable_names = {var.name for var in parsed.variables}
|
|
224
242
|
custom_function_names = {f"%{func.name}" for func in parsed.custom_functions}
|
|
225
243
|
|
|
@@ -233,21 +251,20 @@ def _validate_formatter(
|
|
|
233
251
|
"contains the following custom functions that do not exist: "
|
|
234
252
|
f"{', '.join(sorted(custom_function_names - mock_script.function_names))}"
|
|
235
253
|
)
|
|
236
|
-
if unresolved := variable_names.intersection(
|
|
254
|
+
if unresolved := variable_names.intersection(unresolved_variables):
|
|
237
255
|
raise StringFormattingVariableNotFoundException(
|
|
238
256
|
"contains the following variables that are unresolved when executing this "
|
|
239
257
|
f"formatter: {', '.join(sorted(unresolved))}"
|
|
240
258
|
)
|
|
241
259
|
try:
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
"tmp_var":
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
)
|
|
260
|
+
if is_static_formatter:
|
|
261
|
+
return mock_script.resolve_once_parsed(
|
|
262
|
+
{"tmp_var": formatter_validator.parsed},
|
|
263
|
+
unresolvable=unresolved_variables,
|
|
264
|
+
update=True,
|
|
265
|
+
)["tmp_var"].native
|
|
266
|
+
|
|
267
|
+
return formatter_validator.format_string
|
|
251
268
|
except RuntimeException as exc:
|
|
252
269
|
if isinstance(exc, ScriptVariableNotResolved) and is_static_formatter:
|
|
253
270
|
raise StringFormattingVariableNotFoundException(
|
|
@@ -255,45 +272,60 @@ def _validate_formatter(
|
|
|
255
272
|
"entry variables"
|
|
256
273
|
) from exc
|
|
257
274
|
raise StringFormattingVariableNotFoundException(exc) from exc
|
|
275
|
+
except UserThrownRuntimeError as exc:
|
|
276
|
+
# Errors are expected for non-static formatters due to missing entry
|
|
277
|
+
# data. Raise otherwise.
|
|
278
|
+
if not is_static_formatter:
|
|
279
|
+
return formatter_validator.format_string
|
|
280
|
+
raise exc
|
|
258
281
|
|
|
259
282
|
|
|
260
283
|
def validate_formatters(
|
|
261
284
|
script: Script,
|
|
262
285
|
unresolved_variables: Set[str],
|
|
263
286
|
validator: Validator,
|
|
264
|
-
) ->
|
|
287
|
+
) -> Dict:
|
|
265
288
|
"""
|
|
266
289
|
Ensure all OverridesStringFormatterValidator's only contain variables from the overrides
|
|
267
290
|
and resolve.
|
|
268
291
|
"""
|
|
292
|
+
resolved_dict: Dict = {}
|
|
293
|
+
|
|
269
294
|
if isinstance(validator, DictValidator):
|
|
270
|
-
|
|
295
|
+
resolved_dict[validator.leaf_name] = {}
|
|
271
296
|
# Usage of protected variables in other validators is fine. The reason to keep
|
|
272
297
|
# them protected is for readability when using them in subscriptions.
|
|
273
298
|
for validator_value in validator._validator_dict.values():
|
|
274
|
-
validate_formatters(
|
|
299
|
+
resolved_dict[validator.leaf_name] |= validate_formatters(
|
|
275
300
|
script=script,
|
|
276
301
|
unresolved_variables=unresolved_variables,
|
|
277
302
|
validator=validator_value,
|
|
278
303
|
)
|
|
279
|
-
# pylint: enable=protected-access
|
|
280
304
|
elif isinstance(validator, ListValidator):
|
|
305
|
+
resolved_dict[validator.leaf_name] = []
|
|
281
306
|
for list_value in validator.list:
|
|
282
|
-
validate_formatters(
|
|
307
|
+
list_output = validate_formatters(
|
|
283
308
|
script=script,
|
|
284
309
|
unresolved_variables=unresolved_variables,
|
|
285
310
|
validator=list_value,
|
|
286
311
|
)
|
|
312
|
+
assert len(list_output) == 1
|
|
313
|
+
resolved_dict[validator.leaf_name].append(list(list_output.values())[0])
|
|
287
314
|
elif isinstance(validator, (StringFormatterValidator, OverridesStringFormatterValidator)):
|
|
288
|
-
_validate_formatter(
|
|
315
|
+
resolved_dict[validator.leaf_name] = _validate_formatter(
|
|
289
316
|
mock_script=script,
|
|
290
317
|
unresolved_variables=unresolved_variables,
|
|
291
318
|
formatter_validator=validator,
|
|
292
319
|
)
|
|
293
320
|
elif isinstance(validator, (DictFormatterValidator, OverridesDictFormatterValidator)):
|
|
321
|
+
resolved_dict[validator.leaf_name] = {}
|
|
294
322
|
for validator_value in validator.dict.values():
|
|
295
|
-
_validate_formatter(
|
|
323
|
+
resolved_dict[validator.leaf_name] |= _validate_formatter(
|
|
296
324
|
mock_script=script,
|
|
297
325
|
unresolved_variables=unresolved_variables,
|
|
298
326
|
formatter_validator=validator_value,
|
|
299
327
|
)
|
|
328
|
+
else:
|
|
329
|
+
resolved_dict[validator.leaf_name] = validator._value
|
|
330
|
+
|
|
331
|
+
return resolved_dict
|
|
@@ -95,21 +95,11 @@ class Validator(ABC):
|
|
|
95
95
|
|
|
96
96
|
@final
|
|
97
97
|
@property
|
|
98
|
-
def
|
|
98
|
+
def leaf_name(self) -> str:
|
|
99
99
|
"""
|
|
100
100
|
Returns
|
|
101
101
|
-------
|
|
102
|
-
"
|
|
103
|
-
"""
|
|
104
|
-
return self._name.split(".")[0]
|
|
105
|
-
|
|
106
|
-
@final
|
|
107
|
-
@property
|
|
108
|
-
def _leaf_name(self) -> str:
|
|
109
|
-
"""
|
|
110
|
-
Returns
|
|
111
|
-
-------
|
|
112
|
-
"first" from the first.element.of.the.name
|
|
102
|
+
"name" from the first.element.of.the.name
|
|
113
103
|
"""
|
|
114
104
|
return self._name.split(".")[-1]
|
|
115
105
|
|
|
@@ -274,7 +264,7 @@ class DictValidator(Validator):
|
|
|
274
264
|
value=self._dict.get(key, default),
|
|
275
265
|
)
|
|
276
266
|
|
|
277
|
-
self.__validator_dict[
|
|
267
|
+
self.__validator_dict[key] = validator_instance
|
|
278
268
|
return validator_instance
|
|
279
269
|
|
|
280
270
|
@final
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
ytdl_sub/__init__.py,sha256
|
|
1
|
+
ytdl_sub/__init__.py,sha256=WrW7vK-7xEGvZMFgyjkq_NA4CjXzPSZv3q1IzcatKCI,79
|
|
2
2
|
ytdl_sub/main.py,sha256=4Rf9wXxSKW7IPnWqG5YtTZ814PjP1n9WtoFDivaainE,1004
|
|
3
3
|
ytdl_sub/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
ytdl_sub/cli/entrypoint.py,sha256=XXjUH4HiOP_BB2ZA_bNcyt5-o6YLAdZmj0EP3xtOtD8,9496
|
|
@@ -12,17 +12,17 @@ ytdl_sub/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
12
12
|
ytdl_sub/config/config_file.py,sha256=SQtVrMIUq2z3WwJVOed4y84JBMQ8aa4pbiBB0Y-YY4M,2781
|
|
13
13
|
ytdl_sub/config/config_validator.py,sha256=W1dvQD8wI7VOmGOHyaliu8DC6HOjfoGsgUw2MURTZOM,9797
|
|
14
14
|
ytdl_sub/config/defaults.py,sha256=NTwzlKDkks1LDGNjFMxh91fw5E6T6d_zGsCwODNYJxo,1152
|
|
15
|
-
ytdl_sub/config/overrides.py,sha256=
|
|
16
|
-
ytdl_sub/config/preset.py,sha256=
|
|
15
|
+
ytdl_sub/config/overrides.py,sha256=tCW0RvZ7365MIx5jbH4iDu0XhUP0RCRHGgBJiPiXB1s,10260
|
|
16
|
+
ytdl_sub/config/preset.py,sha256=Msacs60TyDWWzSlvkI3PurRil5vWOGQFK__Ev2HG1l0,9887
|
|
17
17
|
ytdl_sub/config/preset_options.py,sha256=PBinBBRLTaO4B5Q9bW2WUHAd0-x8fVIUqodOIm-py-c,14207
|
|
18
18
|
ytdl_sub/config/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
19
|
ytdl_sub/config/plugin/plugin.py,sha256=ugUyDnjLKcBy7lOpTbVKaGSuUMvzyyQx3DIcbv3eTkQ,4721
|
|
20
20
|
ytdl_sub/config/plugin/plugin_mapping.py,sha256=xx9K2EReIU7zdPlogjvIkowy8J2R4TEhMNJyR7_1aE0,7846
|
|
21
21
|
ytdl_sub/config/plugin/plugin_operation.py,sha256=evRLt-m7LI7q4oQvA4YqlCU0x3-i5MWEWdUn0p7UViw,169
|
|
22
|
-
ytdl_sub/config/plugin/preset_plugins.py,sha256=
|
|
22
|
+
ytdl_sub/config/plugin/preset_plugins.py,sha256=AbyYOJwWPK3hy93CfosD4toKJP7yiTVoA1sL4YrppV0,2877
|
|
23
23
|
ytdl_sub/config/validators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
24
|
ytdl_sub/config/validators/options.py,sha256=IweHvzrMWdCEn4oGItDl-X9PBK7L_CmKfD_wuhNctuI,2608
|
|
25
|
-
ytdl_sub/config/validators/variable_validation.py,sha256=
|
|
25
|
+
ytdl_sub/config/validators/variable_validation.py,sha256=oeNgo96XRR_hCB4vXcJujxPkxyoR6ibwJswHcy0pN08,3606
|
|
26
26
|
ytdl_sub/downloaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
27
|
ytdl_sub/downloaders/source_plugin.py,sha256=dqQpHgeFC_kXjUOAiJKzgtrzSzXPoL2Vvn8ez2sR7QA,2508
|
|
28
28
|
ytdl_sub/downloaders/ytdl_options_builder.py,sha256=k3cGBZmLMsdlnOj0PafHLJJofGqor7KQiv-B2TUc6mE,1553
|
|
@@ -31,7 +31,7 @@ ytdl_sub/downloaders/info_json/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
|
|
|
31
31
|
ytdl_sub/downloaders/info_json/info_json_downloader.py,sha256=_FOvM52GsaLhJy4_D9RkpMhsZZyygXrL62CqcxaO5Co,6619
|
|
32
32
|
ytdl_sub/downloaders/url/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
33
|
ytdl_sub/downloaders/url/downloader.py,sha256=wFtR7368t3-NJO0xM0svW9A272wsJY9ii36pfGpSW5U,20703
|
|
34
|
-
ytdl_sub/downloaders/url/validators.py,sha256=
|
|
34
|
+
ytdl_sub/downloaders/url/validators.py,sha256=ckDXzOPcO4oOdjP96dCJVib5ofe41xjOE3Evlq6vr08,13036
|
|
35
35
|
ytdl_sub/entries/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
36
|
ytdl_sub/entries/base_entry.py,sha256=x5CByJIMtwmsZkEe8TipBuQUQO8b2bWMYqyYvRlXGag,5403
|
|
37
37
|
ytdl_sub/entries/entry.py,sha256=_U0PryLO1QNLexYHG4bcQ1yMgOY1nxKjRNb99-PXg7A,10008
|
|
@@ -92,8 +92,8 @@ ytdl_sub/prebuilt_presets/tv_show/tv_show.yaml,sha256=qmAB3jsXY1VsqmtLnLXXte5rz_
|
|
|
92
92
|
ytdl_sub/prebuilt_presets/tv_show/tv_show_by_date.yaml,sha256=0OgIOzxSPNVqwcZnL6qT9_azplBhDXePcf0ASNTQJ5Q,7223
|
|
93
93
|
ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml,sha256=XCtt4i4-zAgyfh-p4SXSGd0FI56lWCrPOMmB6mdf-AE,238393
|
|
94
94
|
ytdl_sub/script/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
95
|
-
ytdl_sub/script/parser.py,sha256=
|
|
96
|
-
ytdl_sub/script/script.py,sha256=
|
|
95
|
+
ytdl_sub/script/parser.py,sha256=LUvmUIxg346njE8-uDx06nOW6ZDX5QS6aUtp4lsgwpk,22115
|
|
96
|
+
ytdl_sub/script/script.py,sha256=4fP5leu1GQdbbOQLzrWGEo_zaIUeZwGY0btFqdpQmpI,26582
|
|
97
97
|
ytdl_sub/script/script_output.py,sha256=5SIamnI-1D3xMA0qQzjf9xrIy8j6BVhGCKrl_Q1d2M8,1381
|
|
98
98
|
ytdl_sub/script/functions/__init__.py,sha256=rzl6O5G0IEqFYHQECIM9bRvcuQj-ytC_p-Xl2TTa6j0,2932
|
|
99
99
|
ytdl_sub/script/functions/array_functions.py,sha256=yg9rcZP67-aVzhu4oZZzUu3-AHiHltbQDWEs8GW1DJ4,7193
|
|
@@ -114,14 +114,14 @@ ytdl_sub/script/types/map.py,sha256=Y3zlm__IHcnycGFv0xbU_P7wgiQgtgddatPLQypjCvU,
|
|
|
114
114
|
ytdl_sub/script/types/resolvable.py,sha256=OinvpfKsVjgU_mnE0uYONQftUxNLa1zY2jA74bywwBQ,5302
|
|
115
115
|
ytdl_sub/script/types/syntax_tree.py,sha256=4xWluTMPJqA6yYuQ4XCcncSFt5cDZ9ZIT2AIOOH4bT0,2336
|
|
116
116
|
ytdl_sub/script/types/variable.py,sha256=aVJ3ocUr3WpDoolOq6y3NV71b3EQQYPAGrIT0FtIqc4,813
|
|
117
|
-
ytdl_sub/script/types/variable_dependency.py,sha256=
|
|
117
|
+
ytdl_sub/script/types/variable_dependency.py,sha256=J9XzVVrkyt-Ae2acHbb6xFnl6NIeOcL6wLOH9SvzKcA,6796
|
|
118
118
|
ytdl_sub/script/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
119
119
|
ytdl_sub/script/utils/exception_formatters.py,sha256=hZDX2w90vU4lY2xiEM-qxQofQz2dmqPwhkSeWaJRsQ4,4645
|
|
120
120
|
ytdl_sub/script/utils/exceptions.py,sha256=cag5ZLM6as1w-RsrOwO-oe4YFpwlFu_8U0QNGv_jQ68,2774
|
|
121
|
-
ytdl_sub/script/utils/name_validation.py,sha256=
|
|
121
|
+
ytdl_sub/script/utils/name_validation.py,sha256=jLdvHK--BAX2AEshydFwo3PAXfmEKqC_92cz1pMTgWU,2241
|
|
122
122
|
ytdl_sub/script/utils/type_checking.py,sha256=9EKWE1mWPOlmXWiWTZw-bCRV0FlYk22tNKlgEM6wU_0,10393
|
|
123
123
|
ytdl_sub/subscriptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
124
|
-
ytdl_sub/subscriptions/base_subscription.py,sha256=
|
|
124
|
+
ytdl_sub/subscriptions/base_subscription.py,sha256=sZH0U9Kp4jOMFR04sC97DFW7GNo7qWDUn23n0-G8gBk,7814
|
|
125
125
|
ytdl_sub/subscriptions/subscription.py,sha256=wfzOYVkzmsSH1KYbAMRi6Nhrb2pRaXdhILle7H0hcas,5127
|
|
126
126
|
ytdl_sub/subscriptions/subscription_download.py,sha256=-eLbClw88tleLdUgP7TtPtnqroE3Zzdx2XLQrgapMDc,18190
|
|
127
127
|
ytdl_sub/subscriptions/subscription_validators.py,sha256=tSN8YPLkIYXQbmWQ3M6U7xHpNjPn8rtNR9wSLIE2kzM,13606
|
|
@@ -138,7 +138,7 @@ ytdl_sub/utils/file_lock.py,sha256=VlDl8QjBEUAHDBHsfp7Q0tR1Me0fFYELqzMtlyZ0MO4,2
|
|
|
138
138
|
ytdl_sub/utils/file_path.py,sha256=7RWc4fGj7HH6srzGJ5ph1l5euJfWpIX-2cyC-03YXEs,2539
|
|
139
139
|
ytdl_sub/utils/logger.py,sha256=g_23ddk1WpQ3-_MOHz-Rlz7xCydK66BbtqkualX1zAQ,8593
|
|
140
140
|
ytdl_sub/utils/retry.py,sha256=eyhtrfmzwph4uGf4Bwk0UThEIGk4cobwLLuWowGHyK4,1313
|
|
141
|
-
ytdl_sub/utils/script.py,sha256=
|
|
141
|
+
ytdl_sub/utils/script.py,sha256=3sUWCZxVuk5inm6ou3ChFTfeDk7lRnhPSV1wQARj5UI,5777
|
|
142
142
|
ytdl_sub/utils/scriptable.py,sha256=mSMLVOnBzTAQLZXyBozx2spLwNi6llaT_c2zdS2l1rk,3365
|
|
143
143
|
ytdl_sub/utils/subtitles.py,sha256=0ICFw7C9H9BIZvsZf0YhD9ygwR68XCdOQWerspR2g3I,85
|
|
144
144
|
ytdl_sub/utils/system.py,sha256=I9dH46ZhRRpaO0pEPfeOKp968Ub3SAdyZASVavRHrvc,58
|
|
@@ -153,14 +153,14 @@ ytdl_sub/validators/regex_validator.py,sha256=jS8No927eg3zcpYEOv5g0gykePV0DCZaIr
|
|
|
153
153
|
ytdl_sub/validators/source_variable_validator.py,sha256=ziG4PVIyzj5ky-Okle0FB2d2P5DWs3-jYF81hMvBTEQ,922
|
|
154
154
|
ytdl_sub/validators/strict_dict_validator.py,sha256=RduK_3pOEbEQQuugAUeKKqM0Tv5x2vxSSb7vROyo2vQ,1661
|
|
155
155
|
ytdl_sub/validators/string_datetime.py,sha256=GpbBiZH1FHTMeo0Zk134-uMdTMk2JD3Re3TnvuPx2OU,1125
|
|
156
|
-
ytdl_sub/validators/string_formatter_validators.py,sha256=
|
|
156
|
+
ytdl_sub/validators/string_formatter_validators.py,sha256=dQ_8l1wl101vPkaOlfJuXprYI5EnfHQ6jLWjGk92raA,11858
|
|
157
157
|
ytdl_sub/validators/string_select_validator.py,sha256=KFXNKWX2J80WGt08m5gVYphPMHYxhHlgfcoXAQMq6zw,1086
|
|
158
|
-
ytdl_sub/validators/validators.py,sha256=
|
|
158
|
+
ytdl_sub/validators/validators.py,sha256=JC3-c9fSrozFADUY5jqZEhXpM2q3sfserlooQxT2DK8,9133
|
|
159
159
|
ytdl_sub/ytdl_additions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
160
160
|
ytdl_sub/ytdl_additions/enhanced_download_archive.py,sha256=Lsc0wjHdx9d8dYJCskZYAUGDAQ_QzQ-_xbQlyrBSzfk,24884
|
|
161
|
-
ytdl_sub-2025.12.
|
|
162
|
-
ytdl_sub-2025.12.
|
|
163
|
-
ytdl_sub-2025.12.
|
|
164
|
-
ytdl_sub-2025.12.
|
|
165
|
-
ytdl_sub-2025.12.
|
|
166
|
-
ytdl_sub-2025.12.
|
|
161
|
+
ytdl_sub-2025.12.31.post1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
162
|
+
ytdl_sub-2025.12.31.post1.dist-info/METADATA,sha256=aCbLYK2DK2szieUQ05O-SzJc0r5erfgVXBjnTGKrW0k,51426
|
|
163
|
+
ytdl_sub-2025.12.31.post1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
164
|
+
ytdl_sub-2025.12.31.post1.dist-info/entry_points.txt,sha256=K3T5235NlAI-WLmHCg5tzLZHqc33OLN5IY5fOGc9t10,48
|
|
165
|
+
ytdl_sub-2025.12.31.post1.dist-info/top_level.txt,sha256=6z-JWazl6jXspC2DNyxOnGnEqYyGzVbgcBDoXfbkUhI,9
|
|
166
|
+
ytdl_sub-2025.12.31.post1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|