Repository: yt-dlp/yt-dlp
Pull request #11437 (2024-11-02 20:15:29)

[utils] Allow partial application for even more functions
**IMPORTANT**: PRs without the template will be CLOSED

### Description of your *pull request* and other information

<!--

Explanation of your *pull request* in arbitrary form goes here. Please **make sure the description explains the purpose and effect** of your *pull request* and is worded well enough to be understood. Provide as much **context and examples** as possible

-->

Fixes b6dc2c49e8793c6dfa21275e61caf49ec1148b81


<details open><summary>Template</summary> <!-- OPEN is intentional -->

<!--

# PLEASE FOLLOW THE GUIDE BELOW

- You will be asked some questions, please read them **carefully** and answer honestly
- Put an `x` into all the boxes `[ ]` relevant to your *pull request* (like [x])
- Use *Preview* tab to see how your *pull request* will actually look like

-->

### Before submitting a *pull request* make sure you have:
- [x] At least skimmed through [contributing guidelines](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#developer-instructions) including [yt-dlp coding conventions](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#yt-dlp-coding-conventions)
- [x] [Searched](https://github.com/yt-dlp/yt-dlp/search?q=is%3Apr&type=Issues) the bugtracker for similar pull requests

### In order to be accepted and merged into yt-dlp each piece of code must be in public domain or released under [Unlicense](http://unlicense.org/). Check all of the following options that apply:
- [x] I am the original author of this code and I am willing to release it under [Unlicense](http://unlicense.org/)

### What is the purpose of your *pull request*?
- [x] Core bug fix/improvement

</details>



Patch:
diff --git a/yt_dlp/utils/_utils.py b/yt_dlp/utils/_utils.py
index e30008e93..2f4c0a00f 100644
--- a/yt_dlp/utils/_utils.py
+++ b/yt_dlp/utils/_utils.py
@@ -5294,6 +5294,7 @@ def make_archive_id(ie, video_id):
     return f'{ie_key.lower()} {video_id}'
 
 
+@partial_application
 def truncate_string(s, left, right=0):
     assert left > 3 and right >= 0
     if s is None or len(s) <= left + right:
diff --git a/yt_dlp/utils/traversal.py b/yt_dlp/utils/traversal.py
index dd9b4690b..bc313d5c4 100644
--- a/yt_dlp/utils/traversal.py
+++ b/yt_dlp/utils/traversal.py
@@ -449,6 +449,14 @@ def trim(s):
     return trim
 
 
+def unpack(func):
+    @functools.wraps(func)
+    def inner(items, **kwargs):
+        return func(*items, **kwargs)
+
+    return inner
+
+
 def get_first(obj, *paths, **kwargs):
     return traverse_obj(obj, *((..., *variadic(keys)) for keys in paths), **kwargs, get_all=False)
 


Test patch:
diff --git a/test/test_traversal.py b/test/test_traversal.py
index f1d123bd6..1c0cc5362 100644
--- a/test/test_traversal.py
+++ b/test/test_traversal.py
@@ -9,6 +9,7 @@
     determine_ext,
     dict_get,
     int_or_none,
+    join_nonempty,
     str_or_none,
 )
 from yt_dlp.utils.traversal import (
@@ -16,6 +17,7 @@
     subs_list_to_dict,
     traverse_obj,
     trim_str,
+    unpack,
 )
 
 _TEST_DATA = {
@@ -510,6 +512,15 @@ def test_trim_str(self):
         assert trim_str(start='abc', end='abc')('abc') == ''
         assert trim_str(start='', end='')('abc') == 'abc'
 
+    def test_unpack(self):
+        assert unpack(lambda *x: ''.join(map(str, x)))([1, 2, 3]) == '123'
+        assert unpack(join_nonempty)([1, 2, 3]) == '1-2-3'
+        assert unpack(join_nonempty(delim=' '))([1, 2, 3]) == '1 2 3'
+        with pytest.raises(TypeError):
+            unpack(join_nonempty)()
+        with pytest.raises(TypeError):
+            unpack()
+
 
 class TestDictGet:
     def test_dict_get(self):