diff options
| author | dirkf <fieldhouse@gmx.net> | 2025-10-31 12:20:26 +0000 |
|---|---|---|
| committer | dirkf <fieldhouse@gmx.net> | 2025-11-21 01:52:11 +0000 |
| commit | 96419fa7064c7f77ccb1909e23150fde603f9f36 (patch) | |
| tree | fd85f62f91de11c2887dc7c102203bb8b3d1eab7 | |
| parent | cca41c9d2ca51fbfdc9a8c16f2f7b049b577300b (diff) | |
[utils] Support `filter` traversal key
Thx yt-dlp/yt-dlp#10653
| -rw-r--r-- | test/test_traversal.py | 8 | ||||
| -rw-r--r-- | youtube_dl/compat.py | 6 | ||||
| -rw-r--r-- | youtube_dl/utils.py | 8 |
3 files changed, 22 insertions, 0 deletions
diff --git a/test/test_traversal.py b/test/test_traversal.py index 2987970ba..21f81136f 100644 --- a/test/test_traversal.py +++ b/test/test_traversal.py @@ -473,6 +473,14 @@ class TestTraversal(_TestCase): self.assertIs(traverse_obj(morsel, [(None,), any]), morsel, msg='Morsel should not be implicitly changed to dict on usage') + def test_traversal_filter(self): + data = [None, False, True, 0, 1, 0.0, 1.1, '', 'str', {}, {0: 0}, [], [1]] + + self.assertEqual( + traverse_obj(data, (Ellipsis, filter)), + [True, 1, 1.1, 'str', {0: 0}, [1]], + '`filter` should filter falsy values') + def test_get_first(self): self.assertEqual(get_first([{'a': None}, {'a': 'spam'}], 'a'), 'spam') diff --git a/youtube_dl/compat.py b/youtube_dl/compat.py index ebe22bdf9..96b099a58 100644 --- a/youtube_dl/compat.py +++ b/youtube_dl/compat.py @@ -3452,6 +3452,8 @@ except ImportError: except ImportError: compat_map = map + +# compat_filter, compat_filter_fns try: from future_builtins import filter as compat_filter except ImportError: @@ -3459,6 +3461,9 @@ except ImportError: from itertools import ifilter as compat_filter except ImportError: compat_filter = filter +# "Is this function one or maybe the other filter()?" +compat_filter_fns = tuple(set((filter, compat_filter))) + # compat_zip try: @@ -3675,6 +3680,7 @@ __all__ = [ 'compat_etree_fromstring', 'compat_etree_iterfind', 'compat_filter', + 'compat_filter_fns', 'compat_get_terminal_size', 'compat_getenv', 'compat_getpass_getpass', diff --git a/youtube_dl/utils.py b/youtube_dl/utils.py index c4262936e..29d62130a 100644 --- a/youtube_dl/utils.py +++ b/youtube_dl/utils.py @@ -53,6 +53,8 @@ from .compat import ( compat_etree_fromstring, compat_etree_iterfind, compat_expanduser, + compat_filter as filter, + compat_filter_fns, compat_html_entities, compat_html_entities_html5, compat_http_client, @@ -6283,6 +6285,7 @@ def traverse_obj(obj, *paths, **kwargs): Read as: `{key: traverse_obj(obj, path) for key, path in dct.items()}`. - `any`-builtin: Take the first matching object and return it, resetting branching. - `all`-builtin: Take all matching objects and return them as a list, resetting branching. + - `filter`-builtin: Return the value if it is truthy, `None` otherwise. `tuple`, `list`, and `dict` all support nested paths and branches. @@ -6497,6 +6500,11 @@ def traverse_obj(obj, *paths, **kwargs): objs = (list(filtered_objs),) continue + # filter might be from __builtin__, future_builtins, or itertools.ifilter + if key in compat_filter_fns: + objs = filter(None, objs) + continue + if __debug__ and callable(key): # Verify function signature _try_bind_args(key, None, None) |
