Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improvement: python handles arrays of deep object query parameters #4304

Merged
merged 4 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
51 changes: 38 additions & 13 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)

return {query_key: query_value}


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
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

encoded_values.extend(single_query_encoder(query_key, obj_dict))
else:
encoded_values.append((query_key, value))

return encoded_values

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.

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
Loading