Skip to content

Instantly share code, notes, and snippets.

@Jacoby6000
Created August 5, 2026 13:12
Show Gist options
  • Select an option

  • Save Jacoby6000/9eb303eb2f71ba6fd04511f941ff63bb to your computer and use it in GitHub Desktop.

Select an option

Save Jacoby6000/9eb303eb2f71ba6fd04511f941ff63bb to your computer and use it in GitHub Desktop.
Minimal Smithplates SQL union Pydantic warning reproduction

Smithplates SQL union/Pydantic warning reproduction

This standalone model stores a Smithy union in an @sqlJson column. A custom query decodes the JSON column to a mapping and constructs the generated SQL row dataclass. Serializing that generated row through Pydantic emits PydanticSerializationUnexpectedValue for every union variant, even though the mapping has the correct Smithy wire shape.

Reproduce

Requirements: Java, the Smithy CLI 1.71.0, and Python 3.12 or newer.

smithy build
python -m venv .venv
.venv/bin/pip install -r requirements.txt
PYTHONPATH=build/smithy/source/smithplates/src .venv/bin/python reproduce.py

Tested with Smithplates 0.3.2 and Pydantic 2.13.4.

The script also demonstrates the warning-free workaround: validate the decoded mapping with TypeAdapter(models.Action).validate_python(...) before placing it in the generated row dataclass.

$version: "2.0"
namespace example.repro
use smithplates.codegen.sql#DerivedStruct
use smithplates.codegen.sql#sqlDeriveSelectOne
use smithplates.codegen.sql#sqlJson
use smithplates.codegen.sql#sqlPrimaryKey
use smithplates.codegen.sql#sqlService
use smithplates.codegen.sql#sqlTable
use smithy.api#required
structure CopyAction {
@required
source: String
}
structure DeleteAction {
@required
target: String
}
union Action {
copy: CopyAction
delete: DeleteAction
}
@sqlTable(name: "jobs")
structure Job {
@required
@sqlPrimaryKey
id: String
@required
@sqlJson
action: Action
}
@sqlDeriveSelectOne(targetTable: "example.repro#Job")
operation GetJob {
input: DerivedStruct
output: Job
}
@sqlService
service Repository {
version: "1"
operations: [GetJob]
}
from __future__ import annotations
import warnings
from pydantic import TypeAdapter
from generated.example.repro.models import repository_models as models
decoded_json = {"copy": {"source": "s3://example/input"}}
# This is the shape returned by json.loads() in a custom SELECT mapper. Generated
# SQL dataclasses do not validate constructor arguments, so the mapping remains a
# dict even though its wire shape is valid for the generated Action union.
job = models.Job(id="job-1", action=decoded_json) # type: ignore[arg-type]
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
dumped = TypeAdapter(models.Job).dump_python(job, mode="json")
assert dumped == {"id": "job-1", "action": decoded_json}
assert len(caught) == 1
assert "PydanticSerializationUnexpectedValue" in str(caught[0].message)
print(caught[0].message)
# The warning disappears if the mapping is validated into the generated variant
# before constructing the generated row. SQL codegen does not expose a public
# validate_action helper analogous to HTTP codegen's union helper.
validated_action = TypeAdapter(models.Action).validate_python(decoded_json)
validated_job = models.Job(id="job-1", action=validated_action)
with warnings.catch_warnings(record=True) as validated_caught:
warnings.simplefilter("always")
assert TypeAdapter(models.Job).dump_python(validated_job, mode="json") == dumped
assert validated_caught == []
pydantic==2.13.4
{
"version": "1.0",
"sources": ["model"],
"maven": {
"repositories": [
{ "url": "https://repo1.maven.org/maven2/" }
],
"dependencies": [
"com.jacoby6000:smithplates-plugin:0.3.2",
"software.amazon.smithy:smithy-cli:1.71.0"
]
},
"plugins": {
"smithplates": {
"python": {
"sourceOutputDir": "src/generated",
"testOutputDir": "tests/generated",
"sql": {
"sqlite": {
"enable": true,
"migrationLocation": "db/migrations/sqlite"
},
"rootNamespace": "generated"
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment