Skip to content

Commit

Permalink
improvement: python handles arrays of deep object query parameters (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
armandobelardo authored Aug 14, 2024
1 parent c6a7748 commit a9e5871
Show file tree
Hide file tree
Showing 49 changed files with 1,396 additions and 411 deletions.
45 changes: 35 additions & 10 deletions generators/python/core_utilities/sdk/query_encoder.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,56 @@
from collections import ChainMap
from typing import Any, Dict, Optional
from typing import Any, Dict, List, Optional, Tuple

import pydantic


# Flattens dicts to be of the form {"key[subkey][subkey2]": value} where value is not a dict
def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = None) -> Dict[str, Any]:
result = {}
def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = None) -> List[Tuple[str, Any]]:
result = []
for k, v in dict_flat.items():
key = f"{key_prefix}[{k}]" if key_prefix is not None else k
if isinstance(v, dict):
result.update(traverse_query_dict(v, key))
result.extend(traverse_query_dict(v, key))
elif isinstance(v, list):
for arr_v in v:
if isinstance(arr_v, dict):
result.extend(traverse_query_dict(arr_v, key))
else:
result.append((key, arr_v))
else:
result[key] = v
result.append((key, v))
return result


def single_query_encoder(query_key: str, query_value: Any) -> Dict[str, Any]:
def single_query_encoder(query_key: str, query_value: Any) -> List[Tuple[str, Any]]:
if isinstance(query_value, pydantic.BaseModel) or isinstance(query_value, dict):
if isinstance(query_value, pydantic.BaseModel):
obj_dict = query_value.dict(by_alias=True)
else:
obj_dict = query_value
return traverse_query_dict(obj_dict, query_key)
elif isinstance(query_value, list):
encoded_values: List[Tuple[str, Any]] = []
for value in query_value:
if isinstance(value, pydantic.BaseModel) or isinstance(value, dict):
if isinstance(value, pydantic.BaseModel):
obj_dict = value.dict(by_alias=True)
elif isinstance(value, dict):
obj_dict = value

return {query_key: query_value}
encoded_values.extend(single_query_encoder(query_key, obj_dict))
else:
encoded_values.append((query_key, value))

return encoded_values

def encode_query(query: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
return dict(ChainMap(*[single_query_encoder(k, v) for k, v in query.items()])) if query is not None else None
return [(query_key, query_value)]


def encode_query(query: Optional[Dict[str, Any]]) -> Optional[List[Tuple[str, Any]]]:
if query is None:
return None

encoded_query = []
for k, v in query.items():
encoded_query.extend(single_query_encoder(k, v))
return encoded_query
4 changes: 4 additions & 0 deletions generators/python/sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.10.3] - 2024-08-14

- Fix: Query encoding now appropriately takes arrays of deep objects into account.

## [3.10.2] - 2024-08-13

- Fix: Unions with utils now update forward refs again, a regression that was introduced in version 3.7.0
Expand Down
2 changes: 1 addition & 1 deletion generators/python/sdk/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.10.2
3.10.3
12 changes: 8 additions & 4 deletions generators/python/tests/utils/test_query_encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
from core_utilities.sdk.query_encoder import encode_query


def test_query_encoding() -> None:
assert encode_query({"hello world": "hello world"}) == {"hello world": "hello world"}
assert encode_query({"hello_world": {"hello" : "world"}}) == {"hello_world[hello]": "world"}
assert encode_query({"hello_world": {"hello" : {"world" : "today"}, "test": "this"}, "hi": "there"}) == {"hello_world[hello][world]": "today", "hello_world[test]": "this", "hi": "there"}
def test_query_encoding_deep_objects() -> None:
assert encode_query({"hello world": "hello world"}) == [("hello world", "hello world")]
assert encode_query({"hello_world": {"hello" : "world"}}) == [("hello_world[hello]", "world")]
assert encode_query({"hello_world": {"hello" : {"world" : "today"}, "test": "this"}, "hi": "there"}) == [("hello_world[hello][world]", "today"), ("hello_world[test]", "this"), ("hi", "there")]

def test_query_encoding_deep_object_arrays() -> None:
assert encode_query({"objects": [{"key": "hello", "value": "world"}, {"key": "foo", "value": "bar"}]}) == [("objects[key]", "hello"), ("objects[value]", "world"), ("objects[key]", "foo"), ("objects[value]", "bar")]
assert encode_query({"users": [{"name": "string", "tags": ["string"]}, {"name": "string2", "tags": ["string2", "string3"]}]}) == [("users[name]", "string"), ("users[tags]", "string"), ("users[name]", "string2"), ("users[tags]", "string2"), ("users[tags]", "string3")]

def test_encode_query_with_none() -> None:
encoded = encode_query(None)
Expand Down
45 changes: 35 additions & 10 deletions seed/python-sdk/alias-extends/src/seed/core/query_encoder.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 26 additions & 8 deletions seed/python-sdk/alias-extends/tests/utils/test_query_encoding.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 35 additions & 10 deletions seed/python-sdk/alias/src/seed/core/query_encoder.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 26 additions & 8 deletions seed/python-sdk/alias/tests/utils/test_query_encoding.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 35 additions & 10 deletions seed/python-sdk/api-wide-base-path/src/seed/core/query_encoder.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a9e5871

Please sign in to comment.