Repository: RafaelWO/unparallel (language: python)

Issue:
Support single payload with multiple paths
## 🚀 Feature Request
Currently, one can pass multiple payloads (list of dictionaries) - to serve as a JSON body - with only one URI/path. In this case, the same URI will be used for all payloads. But it is not yet possible to do this the other way around, i.e. multiple paths with one payload.

This feature should be added.

## 🔈 Motivation
Use case, you might want to post the same data to multiple paths.

## 🛰 Alternatives

n/a

## 📎 Additional context

n/a



Discussion / hints:

Fix patch:
diff --git a/mkdocs.yml b/mkdocs.yml
index bb7e12d..d26aed2 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -11,25 +11,19 @@ theme:
   features:
     - content.code.copy
   palette:
-    # Palette toggle for automatic mode
-    - media: "(prefers-color-scheme)"
-      toggle:
-        icon: material/brightness-auto
-        name: Switch to light mode
-
     # Palette toggle for light mode
     - media: "(prefers-color-scheme: light)"
       scheme: default 
       toggle:
-        icon: material/brightness-7
-        name: Switch to dark mode
+        icon: octicons/moon-16
+        name: "Switch to dark mode"
 
     # Palette toggle for dark mode
     - media: "(prefers-color-scheme: dark)"
       scheme: slate
       toggle:
-        icon: material/brightness-4
-        name: Switch to system preference
+        icon: octicons/sun-16
+        name: "Switch to light mode"
 plugins:
   - search
   - mkdocstrings:
diff --git a/unparallel/unparallel.py b/unparallel/unparallel.py
index 52030a2..40cb505 100644
--- a/unparallel/unparallel.py
+++ b/unparallel/unparallel.py
@@ -190,7 +190,9 @@ async def up(
         headers (Optional[Dict[str, Any]], optional): A dictionary of headers to use.
             Defaults to None.
         payloads (Optional[Any], optional): A list of JSON payloads (dictionaries) e.g.
-            for HTTP post requests. Used together with paths. Defaults to None.
+            for HTTP post requests. Used together with paths. If one payload but
+            multiple paths are supplied, that payload is used for all requests.
+            Defaults to None.
         flatten_result (bool): If True and the response per request is a list,
             flatten that list of lists. This is useful when using paging.
             Defaults to False.
@@ -228,9 +230,14 @@ async def up(
     if payloads:
         if isinstance(paths, str):
             paths = [paths]
+        if not isinstance(payloads, list):
+            payloads = [payloads]
         if len(paths) == 1 and len(payloads) > 1:
             logging.info(f"Using path '{paths[0]}' for all {len(payloads)} payloads")
             paths = paths * len(payloads)
+        if len(payloads) == 1 and len(paths) > 1:
+            logging.info(f"Using payload '{payloads[0]}' for all {len(paths)} paths")
+            payloads = payloads * len(paths)
         if len(paths) != len(payloads):
             raise ValueError(
                 f"The number of paths does not match the number of payloads: "


Test patch:
diff --git a/tests/test_unparallel.py b/tests/test_unparallel.py
index 94cd750..db290af 100644
--- a/tests/test_unparallel.py
+++ b/tests/test_unparallel.py
@@ -96,7 +96,7 @@ async def test_up_get(caplog, respx_mock):
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("paths", ["/post", ["/post"], ["/post"] * 5])
-async def test_up_post(paths, respx_mock):
+async def test_up_post_single_vs_multi_path(paths, respx_mock):
     base_url = "http://test.com"
     payloads = [{"bar": i} for i in range(5)]
     respx_mock.post(base_url + "/post").mock(
@@ -110,15 +110,33 @@ async def test_up_post(paths, respx_mock):
 
 
 @pytest.mark.asyncio
+@pytest.mark.parametrize(
+    "payloads", [{"bar": 1}, [{"bar": 1}], [{"bar": i} for i in range(5)]]
+)
+async def test_up_post_single_vs_multi_payload(payloads, respx_mock):
+    base_url = "http://test.com"
+    paths = [f"/post/{i}" for i in range(5)]
+    for path in paths:
+        respx_mock.post(base_url + path).mock(return_value=Response(200))
+
+    results = await up(base_url, paths, "post", payloads=payloads)
+    assert len(results) == len(paths)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize(
+    "flatten, expected",
+    [(False, [[1, 2, 3], [4, 5, 6]]), (True, [1, 2, 3, 4, 5, 6])],
+    ids=["not-flat", "flat"],
+)
 @mock.patch(
     "unparallel.unparallel.single_request", side_effect=[(1, [1, 2, 3]), (2, [4, 5, 6])]
 )
-async def test_request_urls_flat(patched_fetch):
+async def test_request_urls_flat(patched_fetch, flatten, expected):
     results = await request_urls(
-        "http://test.com", paths=["/a", "/b"], method="get", flatten_result=True
+        "http://test.com", paths=["/a", "/b"], method="get", flatten_result=flatten
     )
-    # without flatten, result would be: [[1, 2, 3], [4, 5, 6]]
-    assert results == [1, 2, 3, 4, 5, 6]
+    assert results == expected
 
 
 @pytest.mark.asyncio