Skip to content

Commit 7b63041

Browse files
authored
Merge pull request #37 from community-of-python/fix-merging-arbitrary-types-in-pydantic-configs
Make sure `Any` types are not converted to dict if applicable when merging Pydantic configs
2 parents 5e66f60 + ba1e02e commit 7b63041

File tree

3 files changed

+42
-7
lines changed

3 files changed

+42
-7
lines changed

microbootstrap/helpers.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,13 @@ def merge_pydantic_configs(
3636
config_to_merge: PydanticConfigT,
3737
config_with_changes: PydanticConfigT,
3838
) -> PydanticConfigT:
39-
config_class: typing.Final = config_to_merge.__class__
40-
resulting_dict_config: typing.Final = merge_dict_configs(
41-
config_to_merge.model_dump(exclude_defaults=True, exclude_unset=True),
42-
config_with_changes.model_dump(exclude_defaults=True, exclude_unset=True),
43-
)
44-
return config_class(**resulting_dict_config)
39+
initial_fields: typing.Final = dict(config_to_merge)
40+
changed_fields: typing.Final = {
41+
one_field_name: getattr(config_with_changes, one_field_name)
42+
for one_field_name in config_with_changes.model_fields_set
43+
}
44+
merged_fields: typing.Final = merge_dict_configs(initial_fields, changed_fields)
45+
return config_to_merge.model_copy(update=merged_fields)
4546

4647

4748
def merge_dataclasses_configs(

tests/instruments/test_swagger.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import litestar
55
from httpx import AsyncClient
66
from litestar import openapi, status_codes
7+
from litestar.openapi import spec as litestar_openapi
78
from litestar.openapi.plugins import ScalarRenderPlugin
89
from litestar.static_files import StaticFilesConfig
910
from litestar.testing import AsyncTestClient
@@ -52,7 +53,7 @@ def test_litestar_swagger_bootstrap_online_docs(minimal_swagger_config: SwaggerC
5253
assert "static_files_config" not in bootstrap_result
5354

5455

55-
def test_litestar_swagger_bootstrap_with_overriden_render_plugins(minimal_swagger_config: SwaggerConfig) -> None:
56+
def test_litestar_swagger_bootstrap_with_overridden_render_plugins(minimal_swagger_config: SwaggerConfig) -> None:
5657
new_render_plugins: typing.Final = [ScalarRenderPlugin()]
5758
minimal_swagger_config.swagger_extra_params["render_plugins"] = new_render_plugins
5859

@@ -64,6 +65,21 @@ def test_litestar_swagger_bootstrap_with_overriden_render_plugins(minimal_swagge
6465
assert bootstrap_result["openapi_config"].render_plugins is new_render_plugins
6566

6667

68+
def test_litestar_swagger_bootstrap_extra_params_have_correct_types(minimal_swagger_config: SwaggerConfig) -> None:
69+
swagger_instrument: typing.Final = LitestarSwaggerInstrument(minimal_swagger_config)
70+
new_components: typing.Final = litestar_openapi.Components(
71+
security_schemes={"Bearer": litestar_openapi.SecurityScheme(type="http", scheme="Bearer")}
72+
)
73+
swagger_instrument.configure_instrument(
74+
minimal_swagger_config.model_copy(update={"swagger_extra_params": {"components": new_components}})
75+
)
76+
bootstrap_result: typing.Final = swagger_instrument.bootstrap_before()
77+
78+
assert "openapi_config" in bootstrap_result
79+
assert isinstance(bootstrap_result["openapi_config"], openapi.OpenAPIConfig)
80+
assert type(bootstrap_result["openapi_config"].components) is litestar_openapi.Components
81+
82+
6783
def test_litestar_swagger_bootstrap_offline_docs(minimal_swagger_config: SwaggerConfig) -> None:
6884
minimal_swagger_config.swagger_offline_docs = True
6985
swagger_instrument: typing.Final = LitestarSwaggerInstrument(minimal_swagger_config)

tests/test_helpers.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ class PydanticConfig(pydantic.BaseModel):
4949
dict_field: dict[str, typing.Any] = pydantic.Field(default_factory=dict)
5050

5151

52+
@dataclasses.dataclass
53+
class InnerDataclass:
54+
string_field: str
55+
56+
5257
@pytest.mark.parametrize(
5358
("first_model", "second_model", "result"),
5459
[
@@ -76,6 +81,19 @@ class PydanticConfig(pydantic.BaseModel):
7681
dict_field={"value1": 1, "value2": 2, "value3": 4},
7782
),
7883
),
84+
(
85+
PydanticConfig(string_field="value1", array_field=[1, 2], dict_field={"value1": 1, "value3": 3}),
86+
PydanticConfig(
87+
string_field="value2",
88+
array_field=[InnerDataclass(string_field="hi")],
89+
dict_field={"value1": 1, "value2": 2, "value3": InnerDataclass(string_field="there")},
90+
),
91+
PydanticConfig(
92+
string_field="value2",
93+
array_field=[1, 2, InnerDataclass(string_field="hi")],
94+
dict_field={"value1": 1, "value2": 2, "value3": InnerDataclass(string_field="there")},
95+
),
96+
),
7997
],
8098
)
8199
def test_merge_pydantic_configs(

0 commit comments

Comments
 (0)