-
Notifications
You must be signed in to change notification settings - Fork 40
Invalid name generation when using python reserved keywords #89
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
base: main
Are you sure you want to change the base?
Conversation
…n reserved keywords Signed-off-by: QuentinN42 <quentin@lieumont.fr>
Signed-off-by: QuentinN42 <quentin@lieumont.fr>
Signed-off-by: QuentinN42 <quentin@lieumont.fr>
…figDict Signed-off-by: QuentinN42 <quentin@lieumont.fr>
Signed-off-by: QuentinN42 <quentin@lieumont.fr>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR addresses the handling of Python reserved keywords in generated code by transforming field names that conflict with keywords (e.g., class → class_) and adding appropriate Pydantic configuration for serialization/deserialization.
Key Changes:
- Introduced reserved keyword detection and field name transformation logic (
internal/poet/reserved.go) - Modified Pydantic models to include
model_configwithvalidate_by_alias=Trueandvalidate_by_name=True - Updated field generation to handle reserved keywords differently for Pydantic (with alias) vs dataclasses (simple rename)
Reviewed changes
Copilot reviewed 50 out of 52 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/poet/reserved.go | New file with reserved keyword list and transformation functions |
| internal/poet/reserved_test.go | Tests for reserved keyword detection and transformation |
| internal/poet/builders.go | Modified Name() builder to apply field name transformation |
| internal/gen.go | Updated pydanticNode() to add model_config and fieldNode() to handle reserved keywords |
| internal/gen_test.go | New unit tests for field node generation with reserved keywords |
| internal/endtoend/endtoend_test.go | Modified error message output in test |
| internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/* | New end-to-end test for Pydantic with reserved keywords |
| internal/endtoend/testdata/dataclasses_with_reserved_keywords/* | New end-to-end test for dataclasses with reserved keywords |
| internal/endtoend/testdata/emit_pydantic_models/db/models.py | Updated to include model_config in all Pydantic models |
| internal/endtoend/testdata/*/sqlc.yaml | Updated WASM SHA256 checksums |
| internal/endtoend/testdata//python/.py | Updated version comments from v1.28.0 to v1.29.0 |
| examples/src//.py | Updated version comments from v1.28.0 to v1.29.0 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Body: poet.Nodes( | ||
| &pyast.Assign{ | ||
| Targets: []*pyast.Node{ | ||
| { | ||
| Node: &pyast.Node_Name{ | ||
| Name: &pyast.Name{Id: "model_config"}, | ||
| }, | ||
| }, | ||
| }, | ||
| Value: poet.Node( | ||
| &pyast.Call{ | ||
| Func: &pyast.Node{ | ||
| Node: &pyast.Node_Attribute{ | ||
| Attribute: &pyast.Attribute{ | ||
| Value: &pyast.Node{ | ||
| Node: &pyast.Node_Name{ | ||
| Name: &pyast.Name{ | ||
| Id: "pydantic", | ||
| }, | ||
| }, | ||
| }, | ||
| Attr: "ConfigDict", | ||
| }, | ||
| }, | ||
| }, | ||
| Keywords: []*pyast.Keyword{ | ||
| { | ||
| Arg: "validate_by_alias", | ||
| Value: poet.Name("True"), | ||
| }, | ||
| { | ||
| Arg: "validate_by_name", | ||
| Value: poet.Name("True"), | ||
| }, | ||
| }, | ||
| }, | ||
| ), | ||
| }, | ||
| ), |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The model_config with validate_by_alias=True and validate_by_name=True is being added to ALL Pydantic models, even those without reserved keywords. This adds unnecessary validation overhead for models that don't need it. Consider only adding this configuration when the model actually contains fields with reserved keyword names, or at minimum make it conditional based on whether any field in the model uses reserved keywords.
| return &ast.Node{ | ||
| Node: &ast.Node_Name{ | ||
| Name: &ast.Name{Id: id}, | ||
| Name: &ast.Name{Id: FieldName(id)}, |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical bug: Applying FieldName(id) to ALL uses of poet.Name() will break code generation. The Name function is used for many purposes beyond field names, including:
- Built-in constants like "True", "False", "None" (lines 629, 633 in gen.go)
- Module names like "dataclasses", "enum" (lines 576, 782, 787)
- Type names like "str", "int" (lines 781, 1052, 1145)
- Variable names like "self", "conn", "row", "result" (lines 866, 868, 1007, 1031, etc.)
Once the reserved keywords list includes "True", "False", "None", "for", "return", "yield", "is", "not", "in", "or", "and", "as", "with", "if", "else", etc., this will transform all these identifiers incorrectly. This change should be reverted, and FieldName() should only be applied in specific contexts where field names are being generated.
| } | ||
| } | ||
|
|
||
| func Test_FieldName_ShouldUdateReservedKeywords(t *testing.T) { |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in function name: "ShouldUdateReservedKeywords" should be "ShouldUpdateReservedKeywords"
| var reservedKeywords = []string{ | ||
| "class", | ||
| "if", | ||
| "else", | ||
| "elif", | ||
| "not", | ||
| "for", | ||
| "and", | ||
| "in", | ||
| "is", | ||
| "or", | ||
| "with", | ||
| "as", | ||
| "assert", | ||
| "break", | ||
| "except", | ||
| "finally", | ||
| "try", | ||
| "raise", | ||
| "return", | ||
| "yield", | ||
| } |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reserved keywords list is incomplete. Python 3.x has 35 keywords, but only 17 are included here. Missing keywords include: False, None, True, async, await, continue, def, del, from, global, import, lambda, nonlocal, pass, while, and match/case (Python 3.10+). This could cause syntax errors when database columns use these names.
| got, err := cmd.CombinedOutput() | ||
| if diff := cmp.Diff(string(want), string(got)); diff != "" { | ||
| t.Errorf("sqlc diff mismatch (-want +got):\n%s", diff) | ||
| t.Errorf("sqlc diff mismatch (-want +got):\n%s", got) |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change breaks the test logic. The error message should show diff (the actual difference between want and got) to help debug test failures, not got (the raw output). This makes it harder to understand what went wrong when tests fail.
| t.Errorf("sqlc diff mismatch (-want +got):\n%s", got) | |
| t.Errorf("sqlc diff mismatch (-want +got):\n%s", diff) |
Fixes #88
To fix this issue, here is my proposition:
classreserved keyword byclass_.validate_by_nameto allow us to define the class usingAuthor(class_=...).validate_by_aliasto allow JSON serde.Changes overview
internal/poet/reserved.goto append_to reserved names.internal/gen.go::pydanticNodeto addmodel_config = pydantic.ConfigDictinternal/gen.go::fieldNode:_and addpydantic.Field(...)_Questions / possible issues
make testis it correct ?sqlc v1.28.0tosqlc v1.29.0is this intended ?internal/poet/reserved.goI hardcodedreservedKeywordswe should double check it and check if we want to keep it like that.