Skip to content

Commit

Permalink
Add object_to_multipart_dict utility
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuatz committed Sep 23, 2024
1 parent ce8f2ac commit 8fc0d96
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ tasks:
# Clear cache files
rm -rf $PACKAGE_DIR/testing/.pytest_run_cache
# Run pytest
poetry run pytest -n auto
poetry run pytest -n auto {{.CLI_ARGS}}
#============================================================#
#================= SECTION_HEADING ==========================#
#============================================================#
41 changes: 41 additions & 0 deletions django_utils_lib/requests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from typing import Dict, Optional


def object_to_multipart_dict(obj: Dict, existing_multipart_dict: Optional[dict] = None, key_prefix="") -> Dict:
"""
This is basically the inverse of a multi-part form parser, which can additionally
handle nested entries.
The main use-case for this is constructing requests in Python that emulate
a multipart FormData payload that would normally be sent by the frontend.
Nested entries get flattened / hoisted, so that the final dict is a flat
key-value map, with bracket notation used for nested entries. List items are
also hoisted up, with indices put within leading brackets.
Warning: values are not stringified (but would be in a true multipart payload)
@example
```
nested_dict = {"a": 1, "multi": [{"id": "abc"}, {"id": "123"}]}
print(object_to_multipart_dict(nested_dict))
# > {'a': 1, 'multi[0][id]': 'abc', 'multi[1][id]': '123'}
```
"""
result = existing_multipart_dict or {}
for _key, val in obj.items():
# If this is a nested child, we need to wrap key in brackets
_key = f"[{_key}]" if existing_multipart_dict else _key
key = key_prefix + _key
if isinstance(val, dict):
object_to_multipart_dict(val, result, key)
elif isinstance(val, (list, tuple)):
for i, sub_val in enumerate(val):
sub_key = f"{key}[{i}]"
if isinstance(sub_val, dict):
object_to_multipart_dict(sub_val, result, sub_key)
else:
result[sub_key] = sub_val
else:
result[key] = val
return result
2 changes: 1 addition & 1 deletion django_utils_lib/testing/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
import xdist
import xdist.dsession
import xdist.workermanage
from constants import PACKAGE_NAME
from filelock import FileLock
from typing_extensions import NotRequired, TypedDict

from django_utils_lib.constants import PACKAGE_NAME
from django_utils_lib.logger import build_heading_block, pkg_logger
from django_utils_lib.testing.utils import PytestNodeID, is_main_pytest_runner, validate_requirement_tagging

Expand Down
22 changes: 22 additions & 0 deletions django_utils_lib/tests/test_requests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from django_utils_lib.requests import object_to_multipart_dict


def test_object_to_multipart_dict():
regular_dict = {
"a": 1,
"b": ("tuple_a", "tuple_b"),
"c": ["list_a", "list_b"],
"nested_dict": {"nested_a_b": {"d": "d test"}, "e": "e test", "f": 24.1},
"nested_objs_list": [{"name": "nested obj a"}, {"name": "nested obj b"}],
}
multipart_dict = object_to_multipart_dict(regular_dict)
assert multipart_dict.get("a") == 1
assert multipart_dict.get("b[0]") == "tuple_a"
assert multipart_dict.get("b[1]") == "tuple_b"
assert multipart_dict.get("c[0]") == "list_a"
assert multipart_dict.get("c[1]") == "list_b"
assert multipart_dict.get("nested_dict[nested_a_b][d]") == "d test"
assert multipart_dict.get("nested_dict[e]") == "e test"
assert multipart_dict.get("nested_dict[f]") == 24.1
assert multipart_dict.get("nested_objs_list[0][name]") == "nested obj a"
assert multipart_dict.get("nested_objs_list[1][name]") == "nested obj b"

0 comments on commit 8fc0d96

Please sign in to comment.