Skip to content

Commit 4a5e03a

Browse files
fix: preserve field aliases during Block serialization with include_secrets (#21306)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: alex.s <alex.s@prefect.io> Co-authored-by: alex.s <ajstreed1@gmail.com>
1 parent af02e07 commit 4a5e03a

File tree

2 files changed

+25
-12
lines changed

2 files changed

+25
-12
lines changed

src/prefect/blocks/core.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -428,21 +428,28 @@ def ser_model(
428428
if (ctx := info.context) and ctx.get("include_secrets") is True:
429429
# Add serialization mode to context so handle_secret_render knows how to process nested models
430430
ctx["serialization_mode"] = info.mode
431+
ctx["by_alias"] = info.by_alias
431432

432-
for field_name in type(self).model_fields:
433+
for field_name, field_info in type(self).model_fields.items():
433434
field_value = getattr(self, field_name)
434435

435-
# In JSON mode, skip fields that don't contain secrets
436+
# Determine the correct key in the serialized dict,
437+
# respecting alias settings to avoid creating duplicate
438+
# entries (e.g. both "schema" and "schema_" for a field
439+
# defined as `schema_: str = Field(alias="schema")`).
440+
alias = field_info.serialization_alias or field_info.alias
441+
if info.by_alias and alias:
442+
key = alias
443+
else:
444+
key = field_name
445+
446+
# Skip fields that don't contain secrets
436447
# as they're already properly serialized by the handler
437-
if (
438-
info.mode == "json"
439-
and field_name in jsonable_self
440-
and not self._field_has_secrets(field_name)
441-
):
448+
if key in jsonable_self and not self._field_has_secrets(field_name):
442449
continue
443450

444451
# For all other fields, use visit_collection with handle_secret_render
445-
jsonable_self[field_name] = visit_collection(
452+
jsonable_self[key] = visit_collection(
446453
expr=field_value,
447454
visit_fn=partial(handle_secret_render, context=ctx),
448455
return_data=True,

src/prefect/utilities/pydantic.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -350,21 +350,27 @@ def handle_secret_render(value: object, context: dict[str, Any]) -> object:
350350
elif isinstance(value, BaseModel):
351351
# Pass the serialization mode if available in context
352352
mode = context.get("serialization_mode", "python")
353+
by_alias = context.get("by_alias", False)
353354
if mode == "json":
354355
# For JSON mode with nested models, we need to recursively process fields
355356
# because regular Pydantic models don't understand include_secrets
356357

357-
json_data = value.model_dump(mode="json")
358-
for field_name in type(value).model_fields:
358+
json_data = value.model_dump(mode="json", by_alias=by_alias)
359+
for field_name, field_info in type(value).model_fields.items():
359360
field_value = getattr(value, field_name)
360-
json_data[field_name] = visit_collection(
361+
alias = field_info.serialization_alias or field_info.alias
362+
if by_alias and alias:
363+
key = alias
364+
else:
365+
key = field_name
366+
json_data[key] = visit_collection(
361367
expr=field_value,
362368
visit_fn=partial(handle_secret_render, context=context),
363369
return_data=True,
364370
)
365371
return json_data
366372
else:
367-
return value.model_dump(context=context)
373+
return value.model_dump(by_alias=by_alias, context=context)
368374
return value
369375

370376

0 commit comments

Comments
 (0)