From 592c1d6a77ed4e58d7f8e6eb394da00190c45feb Mon Sep 17 00:00:00 2001 From: Oliver Lambson Date: Tue, 11 Mar 2025 16:34:04 -0700 Subject: [PATCH 01/16] feat: add overrides config option --- README.md | 44 +++++++++ internal/config.go | 25 +++-- .../testdata/emit_pydantic_models/sqlc.yaml | 2 +- .../endtoend/testdata/emit_str_enum/sqlc.yaml | 2 +- .../testdata/emit_type_overrides/db/models.py | 11 +++ .../testdata/emit_type_overrides/db/query.py | 92 +++++++++++++++++++ .../emit_type_overrides/my_lib/__init__.py | 0 .../emit_type_overrides/my_lib/models.py | 7 ++ .../testdata/emit_type_overrides/query.sql | 12 +++ .../testdata/emit_type_overrides/schema.sql | 4 + .../testdata/emit_type_overrides/sqlc.yaml | 22 +++++ .../endtoend/testdata/exec_result/sqlc.yaml | 2 +- .../endtoend/testdata/exec_rows/sqlc.yaml | 2 +- .../inflection_exclude_table_names/sqlc.yaml | 2 +- .../query_parameter_limit_two/sqlc.yaml | 2 +- .../query_parameter_limit_undefined/sqlc.yaml | 2 +- .../query_parameter_limit_zero/sqlc.yaml | 2 +- .../query_parameter_no_limit/sqlc.yaml | 2 +- internal/gen.go | 34 +++++++ internal/imports.go | 28 ++++++ 20 files changed, 279 insertions(+), 18 deletions(-) create mode 100644 internal/endtoend/testdata/emit_type_overrides/db/models.py create mode 100644 internal/endtoend/testdata/emit_type_overrides/db/query.py create mode 100644 internal/endtoend/testdata/emit_type_overrides/my_lib/__init__.py create mode 100644 internal/endtoend/testdata/emit_type_overrides/my_lib/models.py create mode 100644 internal/endtoend/testdata/emit_type_overrides/query.sql create mode 100644 internal/endtoend/testdata/emit_type_overrides/schema.sql create mode 100644 internal/endtoend/testdata/emit_type_overrides/sqlc.yaml diff --git a/README.md b/README.md index c9f2531..5f7ecee 100644 --- a/README.md +++ b/README.md @@ -76,3 +76,47 @@ class Status(str, enum.Enum): OPEN = "op!en" CLOSED = "clo@sed" ``` + +### Override Column Types + +Option: `overrides` + +You can override the SQL to Python type mapping for specific columns using the `overrides` option. This is useful for columns with JSON data or other custom types. + +Example configuration: + +```yaml +options: + package: authors + emit_pydantic_models: true + overrides: + - column: "some_table.payload" + py_import: "my_lib.models" + py_type: "Payload" +``` + +This will: +1. Override the column `payload` in `some_table` to use the type `Payload` +2. Add an import for `my_lib.models` to the models file + +Example output: + +```python +# Code generated by sqlc. DO NOT EDIT. +# versions: +# sqlc v1.28.0 + +import datetime +import pydantic +from typing import Any + +import my_lib.models + + +class SomeTable(pydantic.BaseModel): + id: int + created_at: datetime.datetime + payload: my_lib.models.Payload +``` + +This is similar to the [overrides functionality in the Go version of sqlc](https://docs.sqlc.dev/en/stable/howto/overrides.html#overriding-types). diff --git a/internal/config.go b/internal/config.go index 1a8a565..e78112c 100644 --- a/internal/config.go +++ b/internal/config.go @@ -1,13 +1,20 @@ package python +type OverrideColumn struct { + Column string `json:"column"` + PyType string `json:"py_type"` + PyImport string `json:"py_import"` +} + type Config struct { - EmitExactTableNames bool `json:"emit_exact_table_names"` - EmitSyncQuerier bool `json:"emit_sync_querier"` - EmitAsyncQuerier bool `json:"emit_async_querier"` - Package string `json:"package"` - Out string `json:"out"` - EmitPydanticModels bool `json:"emit_pydantic_models"` - EmitStrEnum bool `json:"emit_str_enum"` - QueryParameterLimit *int32 `json:"query_parameter_limit"` - InflectionExcludeTableNames []string `json:"inflection_exclude_table_names"` + EmitExactTableNames bool `json:"emit_exact_table_names"` + EmitSyncQuerier bool `json:"emit_sync_querier"` + EmitAsyncQuerier bool `json:"emit_async_querier"` + Package string `json:"package"` + Out string `json:"out"` + EmitPydanticModels bool `json:"emit_pydantic_models"` + EmitStrEnum bool `json:"emit_str_enum"` + QueryParameterLimit *int32 `json:"query_parameter_limit"` + InflectionExcludeTableNames []string `json:"inflection_exclude_table_names"` + Overrides []OverrideColumn `json:"overrides"` } diff --git a/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml b/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml index beae200..62ec488 100644 --- a/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml +++ b/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "31717935ced1923fdaea102da0d345776c173df7fa6668120be8f7900a7fe938" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_str_enum/sqlc.yaml b/internal/endtoend/testdata/emit_str_enum/sqlc.yaml index 04e3feb..56fe8bf 100644 --- a/internal/endtoend/testdata/emit_str_enum/sqlc.yaml +++ b/internal/endtoend/testdata/emit_str_enum/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "31717935ced1923fdaea102da0d345776c173df7fa6668120be8f7900a7fe938" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_type_overrides/db/models.py b/internal/endtoend/testdata/emit_type_overrides/db/models.py new file mode 100644 index 0000000..1decb3d --- /dev/null +++ b/internal/endtoend/testdata/emit_type_overrides/db/models.py @@ -0,0 +1,11 @@ +# Code generated by sqlc. DO NOT EDIT. +# versions: +# sqlc v1.28.0 +import pydantic + +import my_lib.models + + +class Book(pydantic.BaseModel): + id: int + payload: my_lib.models.Payload diff --git a/internal/endtoend/testdata/emit_type_overrides/db/query.py b/internal/endtoend/testdata/emit_type_overrides/db/query.py new file mode 100644 index 0000000..0486a35 --- /dev/null +++ b/internal/endtoend/testdata/emit_type_overrides/db/query.py @@ -0,0 +1,92 @@ +# Code generated by sqlc. DO NOT EDIT. +# versions: +# sqlc v1.28.0 +# source: query.sql +from typing import AsyncIterator, Iterator, Optional + +import my_lib.models +import sqlalchemy +import sqlalchemy.ext.asyncio + +from db import models + + +CREATE_BOOK = """-- name: create_book \\:one +INSERT INTO books (payload) +VALUES (:p1) +RETURNING id, payload +""" + + +GET_BOOK = """-- name: get_book \\:one +SELECT id, payload FROM books +WHERE id = :p1 LIMIT 1 +""" + + +LIST_BOOKS = """-- name: list_books \\:many +SELECT id, payload FROM books +ORDER BY id +""" + + +class Querier: + def __init__(self, conn: sqlalchemy.engine.Connection): + self._conn = conn + + def create_book(self, *, payload: my_lib.models.Payload) -> Optional[models.Book]: + row = self._conn.execute(sqlalchemy.text(CREATE_BOOK), {"p1": payload}).first() + if row is None: + return None + return models.Book( + id=row[0], + payload=row[1], + ) + + def get_book(self, *, id: int) -> Optional[models.Book]: + row = self._conn.execute(sqlalchemy.text(GET_BOOK), {"p1": id}).first() + if row is None: + return None + return models.Book( + id=row[0], + payload=row[1], + ) + + def list_books(self) -> Iterator[models.Book]: + result = self._conn.execute(sqlalchemy.text(LIST_BOOKS)) + for row in result: + yield models.Book( + id=row[0], + payload=row[1], + ) + + +class AsyncQuerier: + def __init__(self, conn: sqlalchemy.ext.asyncio.AsyncConnection): + self._conn = conn + + async def create_book(self, *, payload: my_lib.models.Payload) -> Optional[models.Book]: + row = (await self._conn.execute(sqlalchemy.text(CREATE_BOOK), {"p1": payload})).first() + if row is None: + return None + return models.Book( + id=row[0], + payload=row[1], + ) + + async def get_book(self, *, id: int) -> Optional[models.Book]: + row = (await self._conn.execute(sqlalchemy.text(GET_BOOK), {"p1": id})).first() + if row is None: + return None + return models.Book( + id=row[0], + payload=row[1], + ) + + async def list_books(self) -> AsyncIterator[models.Book]: + result = await self._conn.stream(sqlalchemy.text(LIST_BOOKS)) + async for row in result: + yield models.Book( + id=row[0], + payload=row[1], + ) diff --git a/internal/endtoend/testdata/emit_type_overrides/my_lib/__init__.py b/internal/endtoend/testdata/emit_type_overrides/my_lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/internal/endtoend/testdata/emit_type_overrides/my_lib/models.py b/internal/endtoend/testdata/emit_type_overrides/my_lib/models.py new file mode 100644 index 0000000..1f1a052 --- /dev/null +++ b/internal/endtoend/testdata/emit_type_overrides/my_lib/models.py @@ -0,0 +1,7 @@ +from datetime import date + +from pydantic import BaseModel + +class Payload(BaseModel): + name: str + release_date: date diff --git a/internal/endtoend/testdata/emit_type_overrides/query.sql b/internal/endtoend/testdata/emit_type_overrides/query.sql new file mode 100644 index 0000000..ab1a3c1 --- /dev/null +++ b/internal/endtoend/testdata/emit_type_overrides/query.sql @@ -0,0 +1,12 @@ +-- name: GetBook :one +SELECT * FROM books +WHERE id = $1 LIMIT 1; + +-- name: ListBooks :many +SELECT * FROM books +ORDER BY id; + +-- name: CreateBook :one +INSERT INTO books (payload) +VALUES (sqlc.arg(payload)) +RETURNING *; diff --git a/internal/endtoend/testdata/emit_type_overrides/schema.sql b/internal/endtoend/testdata/emit_type_overrides/schema.sql new file mode 100644 index 0000000..51997ea --- /dev/null +++ b/internal/endtoend/testdata/emit_type_overrides/schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE books ( + id SERIAL PRIMARY KEY, + payload JSONB NOT NULL +); diff --git a/internal/endtoend/testdata/emit_type_overrides/sqlc.yaml b/internal/endtoend/testdata/emit_type_overrides/sqlc.yaml new file mode 100644 index 0000000..e70c41a --- /dev/null +++ b/internal/endtoend/testdata/emit_type_overrides/sqlc.yaml @@ -0,0 +1,22 @@ +version: "2" +plugins: + - name: py + wasm: + url: file://../../../../bin/sqlc-gen-python.wasm + sha256: "31717935ced1923fdaea102da0d345776c173df7fa6668120be8f7900a7fe938" +sql: + - schema: schema.sql + queries: query.sql + engine: postgresql + codegen: + - plugin: py + out: db + options: + package: db + emit_pydantic_models: true + emit_sync_querier: true + emit_async_querier: true + overrides: + - column: "books.payload" + py_import: "my_lib.models" + py_type: "Payload" diff --git a/internal/endtoend/testdata/exec_result/sqlc.yaml b/internal/endtoend/testdata/exec_result/sqlc.yaml index ddffc83..e7fe6ff 100644 --- a/internal/endtoend/testdata/exec_result/sqlc.yaml +++ b/internal/endtoend/testdata/exec_result/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "31717935ced1923fdaea102da0d345776c173df7fa6668120be8f7900a7fe938" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/exec_rows/sqlc.yaml b/internal/endtoend/testdata/exec_rows/sqlc.yaml index ddffc83..e7fe6ff 100644 --- a/internal/endtoend/testdata/exec_rows/sqlc.yaml +++ b/internal/endtoend/testdata/exec_rows/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "31717935ced1923fdaea102da0d345776c173df7fa6668120be8f7900a7fe938" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml b/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml index efbb150..030d33e 100644 --- a/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml +++ b/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "31717935ced1923fdaea102da0d345776c173df7fa6668120be8f7900a7fe938" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml index 336bca7..018e2db 100644 --- a/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "31717935ced1923fdaea102da0d345776c173df7fa6668120be8f7900a7fe938" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml index c20cd57..91a7c07 100644 --- a/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "31717935ced1923fdaea102da0d345776c173df7fa6668120be8f7900a7fe938" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml index 6e2cdeb..56644ee 100644 --- a/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "31717935ced1923fdaea102da0d345776c173df7fa6668120be8f7900a7fe938" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml b/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml index c432e4f..2b8d205 100644 --- a/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "31717935ced1923fdaea102da0d345776c173df7fa6668120be8f7900a7fe938" sql: - schema: schema.sql queries: query.sql diff --git a/internal/gen.go b/internal/gen.go index 6e50fae..9cd35b3 100644 --- a/internal/gen.go +++ b/internal/gen.go @@ -181,6 +181,40 @@ func (q Query) ArgDictNode() *pyast.Node { } func makePyType(req *plugin.GenerateRequest, col *plugin.Column) pyType { + // Parse the configuration + var conf Config + if len(req.PluginOptions) > 0 { + if err := json.Unmarshal(req.PluginOptions, &conf); err != nil { + log.Printf("failed to parse plugin options: %s", err) + } + } + + // Check for overrides + if len(conf.Overrides) > 0 && col.Table != nil { + tableName := col.Table.Name + if col.Table.Schema != "" && col.Table.Schema != req.Catalog.DefaultSchema { + tableName = col.Table.Schema + "." + tableName + } + + // Look for a matching override + for _, override := range conf.Overrides { + overrideKey := tableName + "." + col.Name + if override.Column == overrideKey { + // Found a match, use the override + typeStr := override.PyType + if override.PyImport != "" && !strings.Contains(typeStr, ".") { + typeStr = override.PyImport + "." + override.PyType + } + return pyType{ + InnerType: typeStr, + IsArray: col.IsArray, + IsNull: !col.NotNull, + } + } + } + } + + // No override found, use the standard type mapping typ := pyInnerType(req, col) return pyType{ InnerType: typ, diff --git a/internal/imports.go b/internal/imports.go index b88c58c..454eefd 100644 --- a/internal/imports.go +++ b/internal/imports.go @@ -97,6 +97,20 @@ func (i *importer) modelImportSpecs() (map[string]importSpec, map[string]importS pkg := make(map[string]importSpec) + // Add custom imports from overrides + for _, override := range i.C.Overrides { + if override.PyImport != "" { + // Check if it's a standard module or a package import + if strings.Contains(override.PyImport, ".") { + // It's a package import + pkg[override.PyImport] = importSpec{Module: override.PyImport} + } else { + // It's a standard import + std[override.PyImport] = importSpec{Module: override.PyImport} + } + } + } + return std, pkg } @@ -167,6 +181,20 @@ func (i *importer) queryImportSpecs(fileName string) (map[string]importSpec, map } } + // Add custom imports from overrides for query files + for _, override := range i.C.Overrides { + if override.PyImport != "" { + // Check if it's a standard module or a package import + if strings.Contains(override.PyImport, ".") { + // It's a package import + pkg[override.PyImport] = importSpec{Module: override.PyImport} + } else { + // It's a standard import + std[override.PyImport] = importSpec{Module: override.PyImport} + } + } + } + return std, pkg } From 19e8d56813a83664bc19c12a3121e84e8bc7af66 Mon Sep 17 00:00:00 2001 From: QuentinN42 Date: Fri, 25 Jul 2025 15:37:07 +0200 Subject: [PATCH 02/16] feat: added invalid test for invalid name generation when using python reserved keywords Signed-off-by: QuentinN42 --- examples/src/authors/models.py | 2 +- examples/src/authors/query.py | 2 +- examples/src/booktest/models.py | 2 +- examples/src/booktest/query.py | 2 +- examples/src/jets/models.py | 2 +- examples/src/jets/query-building.py | 2 +- examples/src/ondeck/city.py | 2 +- examples/src/ondeck/models.py | 2 +- examples/src/ondeck/venue.py | 2 +- internal/endtoend/endtoend_test.go | 2 +- .../emit_pydantic_models/db/models.py | 2 +- .../testdata/emit_pydantic_models/db/query.py | 2 +- .../testdata/emit_pydantic_models/sqlc.yaml | 2 +- .../db/__init__.py | 0 .../db/models.py | 9 ++++ .../db/query.py | 44 +++++++++++++++++++ .../query.sql | 3 ++ .../schema.sql | 4 ++ .../sqlc.yaml | 18 ++++++++ .../testdata/emit_str_enum/db/models.py | 2 +- .../testdata/emit_str_enum/db/query.py | 2 +- .../endtoend/testdata/emit_str_enum/sqlc.yaml | 2 +- .../testdata/exec_result/python/models.py | 2 +- .../testdata/exec_result/python/query.py | 2 +- .../endtoend/testdata/exec_result/sqlc.yaml | 2 +- .../testdata/exec_rows/python/models.py | 2 +- .../testdata/exec_rows/python/query.py | 2 +- .../endtoend/testdata/exec_rows/sqlc.yaml | 2 +- .../python/models.py | 2 +- .../python/query.py | 2 +- .../inflection_exclude_table_names/sqlc.yaml | 2 +- .../python/models.py | 2 +- .../query_parameter_limit_two/python/query.py | 2 +- .../query_parameter_limit_two/sqlc.yaml | 2 +- .../python/models.py | 2 +- .../python/query.py | 2 +- .../query_parameter_limit_undefined/sqlc.yaml | 2 +- .../python/models.py | 2 +- .../python/query.py | 2 +- .../query_parameter_limit_zero/sqlc.yaml | 2 +- .../query_parameter_no_limit/sqlc.yaml | 2 +- 41 files changed, 113 insertions(+), 35 deletions(-) create mode 100644 internal/endtoend/testdata/emit_pydantic_models_with_fields/db/__init__.py create mode 100644 internal/endtoend/testdata/emit_pydantic_models_with_fields/db/models.py create mode 100644 internal/endtoend/testdata/emit_pydantic_models_with_fields/db/query.py create mode 100644 internal/endtoend/testdata/emit_pydantic_models_with_fields/query.sql create mode 100644 internal/endtoend/testdata/emit_pydantic_models_with_fields/schema.sql create mode 100644 internal/endtoend/testdata/emit_pydantic_models_with_fields/sqlc.yaml diff --git a/examples/src/authors/models.py b/examples/src/authors/models.py index 96553a5..b3b9554 100644 --- a/examples/src/authors/models.py +++ b/examples/src/authors/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 import dataclasses from typing import Optional diff --git a/examples/src/authors/query.py b/examples/src/authors/query.py index 019f877..b932e35 100644 --- a/examples/src/authors/query.py +++ b/examples/src/authors/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 # source: query.sql from typing import AsyncIterator, Iterator, Optional diff --git a/examples/src/booktest/models.py b/examples/src/booktest/models.py index d7ee131..dcfbc20 100644 --- a/examples/src/booktest/models.py +++ b/examples/src/booktest/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 import dataclasses import datetime import enum diff --git a/examples/src/booktest/query.py b/examples/src/booktest/query.py index bc71f22..12d3717 100644 --- a/examples/src/booktest/query.py +++ b/examples/src/booktest/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 # source: query.sql import dataclasses import datetime diff --git a/examples/src/jets/models.py b/examples/src/jets/models.py index 0d4eb5d..fc5464b 100644 --- a/examples/src/jets/models.py +++ b/examples/src/jets/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 import dataclasses diff --git a/examples/src/jets/query-building.py b/examples/src/jets/query-building.py index 7651116..adcdcdb 100644 --- a/examples/src/jets/query-building.py +++ b/examples/src/jets/query-building.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 # source: query-building.sql from typing import AsyncIterator, Optional diff --git a/examples/src/ondeck/city.py b/examples/src/ondeck/city.py index 5af93e9..2f2da93 100644 --- a/examples/src/ondeck/city.py +++ b/examples/src/ondeck/city.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 # source: city.sql from typing import AsyncIterator, Optional diff --git a/examples/src/ondeck/models.py b/examples/src/ondeck/models.py index 1161408..a32fea2 100644 --- a/examples/src/ondeck/models.py +++ b/examples/src/ondeck/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 import dataclasses import datetime import enum diff --git a/examples/src/ondeck/venue.py b/examples/src/ondeck/venue.py index 6159bf6..1911cb3 100644 --- a/examples/src/ondeck/venue.py +++ b/examples/src/ondeck/venue.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 # source: venue.sql import dataclasses from typing import AsyncIterator, List, Optional diff --git a/internal/endtoend/endtoend_test.go b/internal/endtoend/endtoend_test.go index bd66c27..09544c1 100644 --- a/internal/endtoend/endtoend_test.go +++ b/internal/endtoend/endtoend_test.go @@ -101,7 +101,7 @@ func TestGenerate(t *testing.T) { cmd.Dir = dir 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) } if len(want) == 0 && err != nil { t.Error(err) diff --git a/internal/endtoend/testdata/emit_pydantic_models/db/models.py b/internal/endtoend/testdata/emit_pydantic_models/db/models.py index 7676e5c..61ad3eb 100644 --- a/internal/endtoend/testdata/emit_pydantic_models/db/models.py +++ b/internal/endtoend/testdata/emit_pydantic_models/db/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 import pydantic from typing import Optional diff --git a/internal/endtoend/testdata/emit_pydantic_models/db/query.py b/internal/endtoend/testdata/emit_pydantic_models/db/query.py index 6f5b76f..cc36118 100644 --- a/internal/endtoend/testdata/emit_pydantic_models/db/query.py +++ b/internal/endtoend/testdata/emit_pydantic_models/db/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 # source: query.sql from typing import AsyncIterator, Iterator, Optional diff --git a/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml b/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml index beae200..6178534 100644 --- a/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml +++ b/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/__init__.py b/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/models.py b/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/models.py new file mode 100644 index 0000000..c653489 --- /dev/null +++ b/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/models.py @@ -0,0 +1,9 @@ +# Code generated by sqlc. DO NOT EDIT. +# versions: +# sqlc v1.29.0 +import pydantic + + +class Author(pydantic.BaseModel): + id: int + class: str diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/query.py b/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/query.py new file mode 100644 index 0000000..337492e --- /dev/null +++ b/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/query.py @@ -0,0 +1,44 @@ +# Code generated by sqlc. DO NOT EDIT. +# versions: +# sqlc v1.29.0 +# source: query.sql +from typing import Optional + +import sqlalchemy +import sqlalchemy.ext.asyncio + +from db import models + + +GET_AUTHOR = """-- name: get_author \\:one +SELECT id, class FROM authors +WHERE id = :p1 LIMIT 1 +""" + + +class Querier: + def __init__(self, conn: sqlalchemy.engine.Connection): + self._conn = conn + + def get_author(self, *, id: int) -> Optional[models.Author]: + row = self._conn.execute(sqlalchemy.text(GET_AUTHOR), {"p1": id}).first() + if row is None: + return None + return models.Author( + id=row[0], + class=row[1], + ) + + +class AsyncQuerier: + def __init__(self, conn: sqlalchemy.ext.asyncio.AsyncConnection): + self._conn = conn + + async def get_author(self, *, id: int) -> Optional[models.Author]: + row = (await self._conn.execute(sqlalchemy.text(GET_AUTHOR), {"p1": id})).first() + if row is None: + return None + return models.Author( + id=row[0], + class=row[1], + ) diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_fields/query.sql b/internal/endtoend/testdata/emit_pydantic_models_with_fields/query.sql new file mode 100644 index 0000000..6c0b1c8 --- /dev/null +++ b/internal/endtoend/testdata/emit_pydantic_models_with_fields/query.sql @@ -0,0 +1,3 @@ +-- name: GetAuthor :one +SELECT * FROM authors +WHERE id = $1 LIMIT 1; diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_fields/schema.sql b/internal/endtoend/testdata/emit_pydantic_models_with_fields/schema.sql new file mode 100644 index 0000000..0d611d3 --- /dev/null +++ b/internal/endtoend/testdata/emit_pydantic_models_with_fields/schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE authors ( + id BIGSERIAL PRIMARY KEY, + class text NOT NULL +); diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_fields/sqlc.yaml b/internal/endtoend/testdata/emit_pydantic_models_with_fields/sqlc.yaml new file mode 100644 index 0000000..6178534 --- /dev/null +++ b/internal/endtoend/testdata/emit_pydantic_models_with_fields/sqlc.yaml @@ -0,0 +1,18 @@ +version: '2' +plugins: +- name: py + wasm: + url: file://../../../../bin/sqlc-gen-python.wasm + sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" +sql: +- schema: schema.sql + queries: query.sql + engine: postgresql + codegen: + - plugin: py + out: db + options: + package: db + emit_sync_querier: true + emit_async_querier: true + emit_pydantic_models: true \ No newline at end of file diff --git a/internal/endtoend/testdata/emit_str_enum/db/models.py b/internal/endtoend/testdata/emit_str_enum/db/models.py index 5fdf754..aa43ab1 100644 --- a/internal/endtoend/testdata/emit_str_enum/db/models.py +++ b/internal/endtoend/testdata/emit_str_enum/db/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 import dataclasses import enum from typing import Optional diff --git a/internal/endtoend/testdata/emit_str_enum/db/query.py b/internal/endtoend/testdata/emit_str_enum/db/query.py index 8082889..5ea0264 100644 --- a/internal/endtoend/testdata/emit_str_enum/db/query.py +++ b/internal/endtoend/testdata/emit_str_enum/db/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 # source: query.sql from typing import AsyncIterator, Iterator, Optional diff --git a/internal/endtoend/testdata/emit_str_enum/sqlc.yaml b/internal/endtoend/testdata/emit_str_enum/sqlc.yaml index 04e3feb..d1f2b2a 100644 --- a/internal/endtoend/testdata/emit_str_enum/sqlc.yaml +++ b/internal/endtoend/testdata/emit_str_enum/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/exec_result/python/models.py b/internal/endtoend/testdata/exec_result/python/models.py index 034fb2d..6d3e9f5 100644 --- a/internal/endtoend/testdata/exec_result/python/models.py +++ b/internal/endtoend/testdata/exec_result/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 import dataclasses diff --git a/internal/endtoend/testdata/exec_result/python/query.py b/internal/endtoend/testdata/exec_result/python/query.py index b68ce39..c9c6e21 100644 --- a/internal/endtoend/testdata/exec_result/python/query.py +++ b/internal/endtoend/testdata/exec_result/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 # source: query.sql import sqlalchemy import sqlalchemy.ext.asyncio diff --git a/internal/endtoend/testdata/exec_result/sqlc.yaml b/internal/endtoend/testdata/exec_result/sqlc.yaml index ddffc83..724ba96 100644 --- a/internal/endtoend/testdata/exec_result/sqlc.yaml +++ b/internal/endtoend/testdata/exec_result/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/exec_rows/python/models.py b/internal/endtoend/testdata/exec_rows/python/models.py index 034fb2d..6d3e9f5 100644 --- a/internal/endtoend/testdata/exec_rows/python/models.py +++ b/internal/endtoend/testdata/exec_rows/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 import dataclasses diff --git a/internal/endtoend/testdata/exec_rows/python/query.py b/internal/endtoend/testdata/exec_rows/python/query.py index 7a9b2a6..a678f3d 100644 --- a/internal/endtoend/testdata/exec_rows/python/query.py +++ b/internal/endtoend/testdata/exec_rows/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 # source: query.sql import sqlalchemy import sqlalchemy.ext.asyncio diff --git a/internal/endtoend/testdata/exec_rows/sqlc.yaml b/internal/endtoend/testdata/exec_rows/sqlc.yaml index ddffc83..724ba96 100644 --- a/internal/endtoend/testdata/exec_rows/sqlc.yaml +++ b/internal/endtoend/testdata/exec_rows/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/inflection_exclude_table_names/python/models.py b/internal/endtoend/testdata/inflection_exclude_table_names/python/models.py index 8ba8803..fc76620 100644 --- a/internal/endtoend/testdata/inflection_exclude_table_names/python/models.py +++ b/internal/endtoend/testdata/inflection_exclude_table_names/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 import dataclasses diff --git a/internal/endtoend/testdata/inflection_exclude_table_names/python/query.py b/internal/endtoend/testdata/inflection_exclude_table_names/python/query.py index 1e1e161..1fc92fd 100644 --- a/internal/endtoend/testdata/inflection_exclude_table_names/python/query.py +++ b/internal/endtoend/testdata/inflection_exclude_table_names/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 # source: query.sql from typing import Optional diff --git a/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml b/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml index efbb150..126afe4 100644 --- a/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml +++ b/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_two/python/models.py b/internal/endtoend/testdata/query_parameter_limit_two/python/models.py index 059675d..89c0f8d 100644 --- a/internal/endtoend/testdata/query_parameter_limit_two/python/models.py +++ b/internal/endtoend/testdata/query_parameter_limit_two/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 import dataclasses diff --git a/internal/endtoend/testdata/query_parameter_limit_two/python/query.py b/internal/endtoend/testdata/query_parameter_limit_two/python/query.py index e8b723e..0d9bd97 100644 --- a/internal/endtoend/testdata/query_parameter_limit_two/python/query.py +++ b/internal/endtoend/testdata/query_parameter_limit_two/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 # source: query.sql import sqlalchemy import sqlalchemy.ext.asyncio diff --git a/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml index 336bca7..b371af4 100644 --- a/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_undefined/python/models.py b/internal/endtoend/testdata/query_parameter_limit_undefined/python/models.py index 30e80db..dc09dab 100644 --- a/internal/endtoend/testdata/query_parameter_limit_undefined/python/models.py +++ b/internal/endtoend/testdata/query_parameter_limit_undefined/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 import dataclasses diff --git a/internal/endtoend/testdata/query_parameter_limit_undefined/python/query.py b/internal/endtoend/testdata/query_parameter_limit_undefined/python/query.py index 5a1fbbc..49b7bd1 100644 --- a/internal/endtoend/testdata/query_parameter_limit_undefined/python/query.py +++ b/internal/endtoend/testdata/query_parameter_limit_undefined/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 # source: query.sql import sqlalchemy import sqlalchemy.ext.asyncio diff --git a/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml index c20cd57..d928aa5 100644 --- a/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_zero/python/models.py b/internal/endtoend/testdata/query_parameter_limit_zero/python/models.py index 059675d..89c0f8d 100644 --- a/internal/endtoend/testdata/query_parameter_limit_zero/python/models.py +++ b/internal/endtoend/testdata/query_parameter_limit_zero/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 import dataclasses diff --git a/internal/endtoend/testdata/query_parameter_limit_zero/python/query.py b/internal/endtoend/testdata/query_parameter_limit_zero/python/query.py index 47bd6a9..38e0efb 100644 --- a/internal/endtoend/testdata/query_parameter_limit_zero/python/query.py +++ b/internal/endtoend/testdata/query_parameter_limit_zero/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 # source: query.sql import dataclasses diff --git a/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml index 6e2cdeb..adf3de1 100644 --- a/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml b/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml index c432e4f..25f27f6 100644 --- a/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" sql: - schema: schema.sql queries: query.sql From f92824439ae12b72d107d31b4cffcfcb92cb79f8 Mon Sep 17 00:00:00 2001 From: QuentinN42 Date: Fri, 25 Jul 2025 17:55:11 +0200 Subject: [PATCH 03/16] fix: added reserved python keywords Signed-off-by: QuentinN42 --- .../testdata/emit_pydantic_models/sqlc.yaml | 2 +- .../sqlc.yaml | 2 +- .../endtoend/testdata/emit_str_enum/sqlc.yaml | 2 +- .../endtoend/testdata/exec_result/sqlc.yaml | 2 +- .../endtoend/testdata/exec_rows/sqlc.yaml | 2 +- .../inflection_exclude_table_names/sqlc.yaml | 2 +- .../query_parameter_limit_two/sqlc.yaml | 2 +- .../query_parameter_limit_undefined/sqlc.yaml | 2 +- .../query_parameter_limit_zero/sqlc.yaml | 2 +- .../query_parameter_no_limit/sqlc.yaml | 2 +- internal/gen.go | 81 +++++++++++++++++-- internal/gen_test.go | 53 ++++++++++++ internal/poet/builders.go | 2 +- internal/poet/reserved.go | 38 +++++++++ 14 files changed, 178 insertions(+), 16 deletions(-) create mode 100644 internal/gen_test.go create mode 100644 internal/poet/reserved.go diff --git a/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml b/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml index 6178534..5cae61b 100644 --- a/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml +++ b/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" + sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_fields/sqlc.yaml b/internal/endtoend/testdata/emit_pydantic_models_with_fields/sqlc.yaml index 6178534..5cae61b 100644 --- a/internal/endtoend/testdata/emit_pydantic_models_with_fields/sqlc.yaml +++ b/internal/endtoend/testdata/emit_pydantic_models_with_fields/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" + sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_str_enum/sqlc.yaml b/internal/endtoend/testdata/emit_str_enum/sqlc.yaml index d1f2b2a..bb85822 100644 --- a/internal/endtoend/testdata/emit_str_enum/sqlc.yaml +++ b/internal/endtoend/testdata/emit_str_enum/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" + sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/exec_result/sqlc.yaml b/internal/endtoend/testdata/exec_result/sqlc.yaml index 724ba96..58de1ac 100644 --- a/internal/endtoend/testdata/exec_result/sqlc.yaml +++ b/internal/endtoend/testdata/exec_result/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" + sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/exec_rows/sqlc.yaml b/internal/endtoend/testdata/exec_rows/sqlc.yaml index 724ba96..58de1ac 100644 --- a/internal/endtoend/testdata/exec_rows/sqlc.yaml +++ b/internal/endtoend/testdata/exec_rows/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" + sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml b/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml index 126afe4..2f60c56 100644 --- a/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml +++ b/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" + sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml index b371af4..fc188a5 100644 --- a/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" + sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml index d928aa5..0c0b4d0 100644 --- a/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" + sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml index adf3de1..3147f3c 100644 --- a/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" + sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml b/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml index 25f27f6..e5692fc 100644 --- a/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "b220b970c2b429b3d3cdde49d2f49c5db73d85c98253ec053533822ab2b58640" + sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" sql: - schema: schema.sql queries: query.sql diff --git a/internal/gen.go b/internal/gen.go index 6e50fae..b99e59a 100644 --- a/internal/gen.go +++ b/internal/gen.go @@ -601,11 +601,82 @@ func pydanticNode(name string) *pyast.ClassDef { } } -func fieldNode(f Field) *pyast.Node { +func fieldNode(f Field, emitPydanticModels bool) *pyast.Node { + if !poet.IsReserved(f.Name) { + return &pyast.Node{ + Node: &pyast.Node_AnnAssign{ + AnnAssign: &pyast.AnnAssign{ + Target: &pyast.Name{Id: f.Name}, + Annotation: f.Type.Annotation(), + Comment: f.Comment, + }, + }, + } + } + + // At this point the field name is a reserved python keyword, so we need to + // update the field name to be a valid python identifier. + if emitPydanticModels { + // On Pydantic add `_` at the end of the field name to make it valid + // Also add a `= pydantic.Field(alias=...)` to the field to allow clean serde + return &pyast.Node{ + Node: &pyast.Node_Assign{ + Assign: &pyast.Assign{ + Targets: []*pyast.Node{ + { + Node: &pyast.Node_AnnAssign{ + AnnAssign: &pyast.AnnAssign{ + Target: &pyast.Name{Id: poet.FieldName(f.Name)}, + Annotation: f.Type.Annotation(), + Comment: f.Comment, + }, + }, + }, + }, + Value: &pyast.Node{ + Node: &pyast.Node_Call{ + Call: &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: "Field", + }, + }, + }, + Keywords: []*pyast.Keyword{ + { + Arg: "alias", + Value: &pyast.Node{ + Node: &pyast.Node_Constant{ + Constant: &pyast.Constant{ + Value: &pyast.Constant_Str{ + Str: f.Name, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + } + + // On dataclasses add `_` at the end of the field name to make it valid return &pyast.Node{ Node: &pyast.Node_AnnAssign{ AnnAssign: &pyast.AnnAssign{ - Target: &pyast.Name{Id: f.Name}, + Target: &pyast.Name{Id: poet.FieldName(f.Name)}, Annotation: f.Type.Annotation(), Comment: f.Comment, }, @@ -731,7 +802,7 @@ func buildModelsTree(ctx *pyTmplCtx, i *importer) *pyast.Node { }) } for _, f := range m.Fields { - def.Body = append(def.Body, fieldNode(f)) + def.Body = append(def.Body, fieldNode(f, ctx.C.EmitPydanticModels)) } mod.Body = append(mod.Body, &pyast.Node{ Node: &pyast.Node_ClassDef{ @@ -857,7 +928,7 @@ func buildQueryTree(ctx *pyTmplCtx, i *importer, source string) *pyast.Node { def = dataclassNode(arg.Struct.Name) } for _, f := range arg.Struct.Fields { - def.Body = append(def.Body, fieldNode(f)) + def.Body = append(def.Body, fieldNode(f, ctx.C.EmitPydanticModels)) } mod.Body = append(mod.Body, poet.Node(def)) } @@ -870,7 +941,7 @@ func buildQueryTree(ctx *pyTmplCtx, i *importer, source string) *pyast.Node { def = dataclassNode(q.Ret.Struct.Name) } for _, f := range q.Ret.Struct.Fields { - def.Body = append(def.Body, fieldNode(f)) + def.Body = append(def.Body, fieldNode(f, ctx.C.EmitPydanticModels)) } mod.Body = append(mod.Body, poet.Node(def)) } diff --git a/internal/gen_test.go b/internal/gen_test.go new file mode 100644 index 0000000..f850eda --- /dev/null +++ b/internal/gen_test.go @@ -0,0 +1,53 @@ +package python + +import ( + "strconv" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/sqlc-dev/sqlc-gen-python/internal/printer" +) + +func Test_fieldNode(t *testing.T) { + cases := []struct { + Expected string + EmitPydanticModels bool + Field Field + }{ + { + Expected: "class_: int", + Field: Field{ + Name: "class", + Type: pyType{ + InnerType: "int", + IsNull: false, + IsArray: false, + }, + }, + EmitPydanticModels: false, + }, + { + Expected: "class_: int = pydantic.Field(\n alias=\"class\",\n)", + Field: Field{ + Name: "class", + Type: pyType{ + InnerType: "int", + IsNull: false, + IsArray: false, + }, + }, + EmitPydanticModels: true, + }, + } + + for _, tc := range cases { + t.Run(tc.Expected+" "+strconv.FormatBool(tc.EmitPydanticModels), func(t *testing.T) { + res := fieldNode(tc.Field, tc.EmitPydanticModels) + result := printer.Print(res, printer.Options{}) + if diff := cmp.Diff(strings.TrimSpace(tc.Expected), strings.TrimSpace(string(result.Python))); diff != "" { + t.Errorf("node to python code mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/internal/poet/builders.go b/internal/poet/builders.go index d38ed7c..4a36606 100644 --- a/internal/poet/builders.go +++ b/internal/poet/builders.go @@ -64,7 +64,7 @@ func Is() *ast.Node { func Name(id string) *ast.Node { return &ast.Node{ Node: &ast.Node_Name{ - Name: &ast.Name{Id: id}, + Name: &ast.Name{Id: FieldName(id)}, }, } } diff --git a/internal/poet/reserved.go b/internal/poet/reserved.go new file mode 100644 index 0000000..d9d3198 --- /dev/null +++ b/internal/poet/reserved.go @@ -0,0 +1,38 @@ +package poet + +import "slices" + +// TODO(quentin@escape.tech): check if this is complete +var reservedKeywords = []string{ + "class", + "if", + "else", + "elif", + "not", + "for", + "and", + "in", + "is", + "or", + "with", + "as", + "assert", + "break", + "except", + "finally", + "try", + "raise", + "return", + "yield", +} + +func IsReserved(name string) bool { + return slices.Contains(reservedKeywords, name) +} + +func FieldName(name string) string { + if IsReserved(name) { + return name + "_" + } + return name +} From 24fcd39f834e82f95563e5e23a31519d7e48bc88 Mon Sep 17 00:00:00 2001 From: QuentinN42 Date: Fri, 25 Jul 2025 18:07:43 +0200 Subject: [PATCH 04/16] refactor: use poet.Node Signed-off-by: QuentinN42 --- .../testdata/emit_pydantic_models/sqlc.yaml | 2 +- .../db/models.py | 4 +- .../db/query.py | 4 +- .../sqlc.yaml | 2 +- .../endtoend/testdata/emit_str_enum/sqlc.yaml | 2 +- .../endtoend/testdata/exec_result/sqlc.yaml | 2 +- .../endtoend/testdata/exec_rows/sqlc.yaml | 2 +- .../inflection_exclude_table_names/sqlc.yaml | 2 +- .../query_parameter_limit_two/sqlc.yaml | 2 +- .../query_parameter_limit_undefined/sqlc.yaml | 2 +- .../query_parameter_limit_zero/sqlc.yaml | 2 +- .../query_parameter_no_limit/sqlc.yaml | 2 +- internal/gen.go | 60 +++++++------------ 13 files changed, 38 insertions(+), 50 deletions(-) diff --git a/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml b/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml index 5cae61b..1ca3d7c 100644 --- a/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml +++ b/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" + sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/models.py b/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/models.py index c653489..353611d 100644 --- a/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/models.py +++ b/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/models.py @@ -6,4 +6,6 @@ class Author(pydantic.BaseModel): id: int - class: str + class_: str = pydantic.Field( + alias="class", + ) diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/query.py b/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/query.py index 337492e..1a0ac38 100644 --- a/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/query.py +++ b/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/query.py @@ -26,7 +26,7 @@ def get_author(self, *, id: int) -> Optional[models.Author]: return None return models.Author( id=row[0], - class=row[1], + class_=row[1], ) @@ -40,5 +40,5 @@ async def get_author(self, *, id: int) -> Optional[models.Author]: return None return models.Author( id=row[0], - class=row[1], + class_=row[1], ) diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_fields/sqlc.yaml b/internal/endtoend/testdata/emit_pydantic_models_with_fields/sqlc.yaml index 5cae61b..1ca3d7c 100644 --- a/internal/endtoend/testdata/emit_pydantic_models_with_fields/sqlc.yaml +++ b/internal/endtoend/testdata/emit_pydantic_models_with_fields/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" + sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_str_enum/sqlc.yaml b/internal/endtoend/testdata/emit_str_enum/sqlc.yaml index bb85822..f994e65 100644 --- a/internal/endtoend/testdata/emit_str_enum/sqlc.yaml +++ b/internal/endtoend/testdata/emit_str_enum/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" + sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/exec_result/sqlc.yaml b/internal/endtoend/testdata/exec_result/sqlc.yaml index 58de1ac..57290d7 100644 --- a/internal/endtoend/testdata/exec_result/sqlc.yaml +++ b/internal/endtoend/testdata/exec_result/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" + sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/exec_rows/sqlc.yaml b/internal/endtoend/testdata/exec_rows/sqlc.yaml index 58de1ac..57290d7 100644 --- a/internal/endtoend/testdata/exec_rows/sqlc.yaml +++ b/internal/endtoend/testdata/exec_rows/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" + sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml b/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml index 2f60c56..49466ee 100644 --- a/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml +++ b/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" + sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml index fc188a5..d58d023 100644 --- a/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" + sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml index 0c0b4d0..91d9a96 100644 --- a/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" + sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml index 3147f3c..8172f8d 100644 --- a/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" + sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml b/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml index e5692fc..6b18de5 100644 --- a/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "30dc4a9deda7f954e6444652e98763b3ad587ea672d88235b2c0f45ba8a4d03b" + sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" sql: - schema: schema.sql queries: query.sql diff --git a/internal/gen.go b/internal/gen.go index b99e59a..63fe590 100644 --- a/internal/gen.go +++ b/internal/gen.go @@ -107,7 +107,7 @@ func (v QueryValue) RowNode(rowVar string) *pyast.Node { } for i, f := range v.Struct.Fields { call.Keywords = append(call.Keywords, &pyast.Keyword{ - Arg: f.Name, + Arg: poet.FieldName(f.Name), Value: subscriptNode( rowVar, constantInt(i), @@ -622,51 +622,37 @@ func fieldNode(f Field, emitPydanticModels bool) *pyast.Node { return &pyast.Node{ Node: &pyast.Node_Assign{ Assign: &pyast.Assign{ - Targets: []*pyast.Node{ - { - Node: &pyast.Node_AnnAssign{ - AnnAssign: &pyast.AnnAssign{ - Target: &pyast.Name{Id: poet.FieldName(f.Name)}, - Annotation: f.Type.Annotation(), - Comment: f.Comment, - }, - }, + Targets: poet.Nodes( + &pyast.AnnAssign{ + Target: &pyast.Name{Id: poet.FieldName(f.Name)}, + Annotation: f.Type.Annotation(), + Comment: f.Comment, }, - }, - Value: &pyast.Node{ - Node: &pyast.Node_Call{ - Call: &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: "Field", - }, - }, - }, - Keywords: []*pyast.Keyword{ - { - Arg: "alias", + ), + Value: poet.Node( + &pyast.Call{ + Func: &pyast.Node{ + Node: &pyast.Node_Attribute{ + Attribute: &pyast.Attribute{ Value: &pyast.Node{ - Node: &pyast.Node_Constant{ - Constant: &pyast.Constant{ - Value: &pyast.Constant_Str{ - Str: f.Name, - }, + Node: &pyast.Node_Name{ + Name: &pyast.Name{ + Id: "pydantic", }, }, }, + Attr: "Field", }, }, }, + Keywords: []*pyast.Keyword{ + { + Arg: "alias", + Value: poet.Constant(f.Name), + }, + }, }, - }, + ), }, }, } From a5d1ee84533fc7db53fb358b3869e91eb80ea756 Mon Sep 17 00:00:00 2001 From: QuentinN42 Date: Fri, 25 Jul 2025 18:38:20 +0200 Subject: [PATCH 05/16] test: added more tests to the poet pkg and generated the pydantic.ConfigDict Signed-off-by: QuentinN42 --- .../emit_pydantic_models/db/models.py | 4 ++ .../testdata/emit_pydantic_models/sqlc.yaml | 2 +- .../db/models.py | 4 ++ .../sqlc.yaml | 2 +- .../endtoend/testdata/emit_str_enum/sqlc.yaml | 2 +- .../endtoend/testdata/exec_result/sqlc.yaml | 2 +- .../endtoend/testdata/exec_rows/sqlc.yaml | 2 +- .../inflection_exclude_table_names/sqlc.yaml | 2 +- .../query_parameter_limit_two/sqlc.yaml | 2 +- .../query_parameter_limit_undefined/sqlc.yaml | 2 +- .../query_parameter_limit_zero/sqlc.yaml | 2 +- .../query_parameter_no_limit/sqlc.yaml | 2 +- internal/gen.go | 39 ++++++++++++++++++ internal/poet/reserved_test.go | 41 +++++++++++++++++++ 14 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 internal/poet/reserved_test.go diff --git a/internal/endtoend/testdata/emit_pydantic_models/db/models.py b/internal/endtoend/testdata/emit_pydantic_models/db/models.py index 61ad3eb..5c6947e 100644 --- a/internal/endtoend/testdata/emit_pydantic_models/db/models.py +++ b/internal/endtoend/testdata/emit_pydantic_models/db/models.py @@ -6,6 +6,10 @@ class Author(pydantic.BaseModel): + model_config = pydantic.ConfigDict( + validate_by_alias=True, + validate_by_name=True, + ) id: int name: str bio: Optional[str] diff --git a/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml b/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml index 1ca3d7c..bc03370 100644 --- a/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml +++ b/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" + sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/models.py b/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/models.py index 353611d..1403269 100644 --- a/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/models.py +++ b/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/models.py @@ -5,6 +5,10 @@ class Author(pydantic.BaseModel): + model_config = pydantic.ConfigDict( + validate_by_alias=True, + validate_by_name=True, + ) id: int class_: str = pydantic.Field( alias="class", diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_fields/sqlc.yaml b/internal/endtoend/testdata/emit_pydantic_models_with_fields/sqlc.yaml index 1ca3d7c..bc03370 100644 --- a/internal/endtoend/testdata/emit_pydantic_models_with_fields/sqlc.yaml +++ b/internal/endtoend/testdata/emit_pydantic_models_with_fields/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" + sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_str_enum/sqlc.yaml b/internal/endtoend/testdata/emit_str_enum/sqlc.yaml index f994e65..1a80fbc 100644 --- a/internal/endtoend/testdata/emit_str_enum/sqlc.yaml +++ b/internal/endtoend/testdata/emit_str_enum/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" + sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/exec_result/sqlc.yaml b/internal/endtoend/testdata/exec_result/sqlc.yaml index 57290d7..9c6a7d3 100644 --- a/internal/endtoend/testdata/exec_result/sqlc.yaml +++ b/internal/endtoend/testdata/exec_result/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" + sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/exec_rows/sqlc.yaml b/internal/endtoend/testdata/exec_rows/sqlc.yaml index 57290d7..9c6a7d3 100644 --- a/internal/endtoend/testdata/exec_rows/sqlc.yaml +++ b/internal/endtoend/testdata/exec_rows/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" + sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml b/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml index 49466ee..369271f 100644 --- a/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml +++ b/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" + sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml index d58d023..6d7d162 100644 --- a/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" + sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml index 91d9a96..4ba6c57 100644 --- a/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" + sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml index 8172f8d..4c1abd2 100644 --- a/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" + sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml b/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml index 6b18de5..270c290 100644 --- a/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "10f4fa000db23558eba8758926bab44a5e50118d3c892ecc4bd64a1fb88f678c" + sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" sql: - schema: schema.sql queries: query.sql diff --git a/internal/gen.go b/internal/gen.go index 63fe590..d339650 100644 --- a/internal/gen.go +++ b/internal/gen.go @@ -598,6 +598,45 @@ func pydanticNode(name string) *pyast.ClassDef { }, }, }, + 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"), + }, + }, + }, + ), + }, + ), } } diff --git a/internal/poet/reserved_test.go b/internal/poet/reserved_test.go new file mode 100644 index 0000000..3449090 --- /dev/null +++ b/internal/poet/reserved_test.go @@ -0,0 +1,41 @@ +package poet + +import "testing" + +func Test_FieldName_ShouldNotChangeNonReservedKeywords(t *testing.T) { + cases := []string{ + "hello", + "world", + "iff", + "not_reserved", + "class_", + } + + for _, key := range cases { + t.Run(key, func(t *testing.T) { + got := FieldName(key) + if got != key { + t.Errorf("FieldName(%q) = %q, want %q", key, got, key) + } + }) + } +} + +func Test_FieldName_ShouldUdateReservedKeywords(t *testing.T) { + cases := []string{ + "if", + "class", + } + + for _, key := range cases { + t.Run(key, func(t *testing.T) { + if !IsReserved(key) { + t.Errorf("%s should be reserved", key) + } + got := FieldName(key) + if IsReserved(got) { + t.Errorf("FieldName(%s) = %s, should not be reserved", key, got) + } + }) + } +} From 068fd251596b60ba31d51fb8ffd3a5b057413269 Mon Sep 17 00:00:00 2001 From: QuentinN42 Date: Fri, 25 Jul 2025 18:53:01 +0200 Subject: [PATCH 06/16] feat: added dataclasses tests Signed-off-by: QuentinN42 --- .../db/__init__.py | 0 .../db/models.py | 10 +++++ .../db/query.py | 0 .../query.sql | 0 .../schema.sql | 0 .../sqlc.yaml | 18 ++++++++ .../db/__init__.py | 0 .../db/models.py | 0 .../db/query.py | 44 +++++++++++++++++++ .../query.sql | 3 ++ .../schema.sql | 4 ++ .../sqlc.yaml | 0 12 files changed, 79 insertions(+) rename internal/endtoend/testdata/{emit_pydantic_models_with_fields => dataclasses_with_reserved_keywords}/db/__init__.py (100%) create mode 100644 internal/endtoend/testdata/dataclasses_with_reserved_keywords/db/models.py rename internal/endtoend/testdata/{emit_pydantic_models_with_fields => dataclasses_with_reserved_keywords}/db/query.py (100%) rename internal/endtoend/testdata/{emit_pydantic_models_with_fields => dataclasses_with_reserved_keywords}/query.sql (100%) rename internal/endtoend/testdata/{emit_pydantic_models_with_fields => dataclasses_with_reserved_keywords}/schema.sql (100%) create mode 100644 internal/endtoend/testdata/dataclasses_with_reserved_keywords/sqlc.yaml create mode 100644 internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/db/__init__.py rename internal/endtoend/testdata/{emit_pydantic_models_with_fields => emit_pydantic_models_with_reserved_keywords}/db/models.py (100%) create mode 100644 internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/db/query.py create mode 100644 internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/query.sql create mode 100644 internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/schema.sql rename internal/endtoend/testdata/{emit_pydantic_models_with_fields => emit_pydantic_models_with_reserved_keywords}/sqlc.yaml (100%) diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/__init__.py b/internal/endtoend/testdata/dataclasses_with_reserved_keywords/db/__init__.py similarity index 100% rename from internal/endtoend/testdata/emit_pydantic_models_with_fields/db/__init__.py rename to internal/endtoend/testdata/dataclasses_with_reserved_keywords/db/__init__.py diff --git a/internal/endtoend/testdata/dataclasses_with_reserved_keywords/db/models.py b/internal/endtoend/testdata/dataclasses_with_reserved_keywords/db/models.py new file mode 100644 index 0000000..f2dd802 --- /dev/null +++ b/internal/endtoend/testdata/dataclasses_with_reserved_keywords/db/models.py @@ -0,0 +1,10 @@ +# Code generated by sqlc. DO NOT EDIT. +# versions: +# sqlc v1.29.0 +import dataclasses + + +@dataclasses.dataclass() +class Author: + id: int + class_: str diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/query.py b/internal/endtoend/testdata/dataclasses_with_reserved_keywords/db/query.py similarity index 100% rename from internal/endtoend/testdata/emit_pydantic_models_with_fields/db/query.py rename to internal/endtoend/testdata/dataclasses_with_reserved_keywords/db/query.py diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_fields/query.sql b/internal/endtoend/testdata/dataclasses_with_reserved_keywords/query.sql similarity index 100% rename from internal/endtoend/testdata/emit_pydantic_models_with_fields/query.sql rename to internal/endtoend/testdata/dataclasses_with_reserved_keywords/query.sql diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_fields/schema.sql b/internal/endtoend/testdata/dataclasses_with_reserved_keywords/schema.sql similarity index 100% rename from internal/endtoend/testdata/emit_pydantic_models_with_fields/schema.sql rename to internal/endtoend/testdata/dataclasses_with_reserved_keywords/schema.sql diff --git a/internal/endtoend/testdata/dataclasses_with_reserved_keywords/sqlc.yaml b/internal/endtoend/testdata/dataclasses_with_reserved_keywords/sqlc.yaml new file mode 100644 index 0000000..a7cfc22 --- /dev/null +++ b/internal/endtoend/testdata/dataclasses_with_reserved_keywords/sqlc.yaml @@ -0,0 +1,18 @@ +version: "2" +plugins: + - name: py + wasm: + url: file://../../../../bin/sqlc-gen-python.wasm + sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" +sql: + - schema: schema.sql + queries: query.sql + engine: postgresql + codegen: + - plugin: py + out: db + options: + package: db + emit_sync_querier: true + emit_async_querier: true + emit_pydantic_models: false diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/db/__init__.py b/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_fields/db/models.py b/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/db/models.py similarity index 100% rename from internal/endtoend/testdata/emit_pydantic_models_with_fields/db/models.py rename to internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/db/models.py diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/db/query.py b/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/db/query.py new file mode 100644 index 0000000..1a0ac38 --- /dev/null +++ b/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/db/query.py @@ -0,0 +1,44 @@ +# Code generated by sqlc. DO NOT EDIT. +# versions: +# sqlc v1.29.0 +# source: query.sql +from typing import Optional + +import sqlalchemy +import sqlalchemy.ext.asyncio + +from db import models + + +GET_AUTHOR = """-- name: get_author \\:one +SELECT id, class FROM authors +WHERE id = :p1 LIMIT 1 +""" + + +class Querier: + def __init__(self, conn: sqlalchemy.engine.Connection): + self._conn = conn + + def get_author(self, *, id: int) -> Optional[models.Author]: + row = self._conn.execute(sqlalchemy.text(GET_AUTHOR), {"p1": id}).first() + if row is None: + return None + return models.Author( + id=row[0], + class_=row[1], + ) + + +class AsyncQuerier: + def __init__(self, conn: sqlalchemy.ext.asyncio.AsyncConnection): + self._conn = conn + + async def get_author(self, *, id: int) -> Optional[models.Author]: + row = (await self._conn.execute(sqlalchemy.text(GET_AUTHOR), {"p1": id})).first() + if row is None: + return None + return models.Author( + id=row[0], + class_=row[1], + ) diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/query.sql b/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/query.sql new file mode 100644 index 0000000..6c0b1c8 --- /dev/null +++ b/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/query.sql @@ -0,0 +1,3 @@ +-- name: GetAuthor :one +SELECT * FROM authors +WHERE id = $1 LIMIT 1; diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/schema.sql b/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/schema.sql new file mode 100644 index 0000000..0d611d3 --- /dev/null +++ b/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE authors ( + id BIGSERIAL PRIMARY KEY, + class text NOT NULL +); diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_fields/sqlc.yaml b/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/sqlc.yaml similarity index 100% rename from internal/endtoend/testdata/emit_pydantic_models_with_fields/sqlc.yaml rename to internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/sqlc.yaml From 62ab2e7566e807a5db02f40eb985300f0a13c294 Mon Sep 17 00:00:00 2001 From: Merlin Ran Date: Fri, 28 Nov 2025 19:12:02 -0500 Subject: [PATCH 07/16] fix: escape double quotes if they appear in constant str --- internal/printer/printer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/printer/printer.go b/internal/printer/printer.go index 0660c6a..eca3080 100644 --- a/internal/printer/printer.go +++ b/internal/printer/printer.go @@ -282,7 +282,7 @@ func (w *writer) printConstant(c *ast.Constant, indent int32) { str = `"""` } w.print(str) - w.print(n.Str) + w.print(strings.ReplaceAll(n.Str, `"`, `\"`)) w.print(str) default: From 692ef0ca86f6bb0e54330a397568f27423c12661 Mon Sep 17 00:00:00 2001 From: Merlin Ran Date: Fri, 28 Nov 2025 20:30:52 -0500 Subject: [PATCH 08/16] more fixes to docstring and comments in general --- internal/printer/printer.go | 57 ++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/internal/printer/printer.go b/internal/printer/printer.go index eca3080..c5f2c4e 100644 --- a/internal/printer/printer.go +++ b/internal/printer/printer.go @@ -37,6 +37,22 @@ func (w *writer) printIndent(indent int32) { } } +func (w *writer) printCommentText(text string, indent int32) { + lines := strings.Split(text, "\n") + for _, line := range lines { + w.print("#") + // trim right space which is usually unintended, + // but leave left space untouched in case if it's intentionally formatted. + trimmed := strings.TrimRight(line, " ") + if trimmed != "" { + w.print(" ") + w.print(trimmed) + } + w.print("\n") + w.printIndent(indent) + } +} + func (w *writer) printNode(node *ast.Node, indent int32) { switch n := node.Node.(type) { @@ -132,10 +148,7 @@ func (w *writer) printNode(node *ast.Node, indent int32) { func (w *writer) printAnnAssign(aa *ast.AnnAssign, indent int32) { if aa.Comment != "" { - w.print("# ") - w.print(aa.Comment) - w.print("\n") - w.printIndent(indent) + w.printCommentText(aa.Comment, indent) } w.printName(aa.Target, indent) w.print(": ") @@ -255,10 +268,7 @@ func (w *writer) printClassDef(cd *ast.ClassDef, indent int32) { if i == 0 { if e, ok := node.Node.(*ast.Node_Expr); ok { if c, ok := e.Expr.Value.Node.(*ast.Node_Constant); ok { - w.print(`""`) - w.printConstant(c.Constant, indent) - w.print(`""`) - w.print("\n") + w.printDocString(c.Constant, indent) continue } } @@ -268,6 +278,33 @@ func (w *writer) printClassDef(cd *ast.ClassDef, indent int32) { } } +func (w *writer) printDocString(c *ast.Constant, indent int32) { + switch n := c.Value.(type) { + case *ast.Constant_Str: + w.print(`"""`) + lines := strings.Split(n.Str, "\n") + printedN := 0 + for n, line := range lines { + // trim right space which is usually unintended, + // but leave left space untouched in case if it's intentionally formatted. + trimmed := strings.TrimRight(line, " ") + if trimmed == "" { + continue + } + if printedN > 0 && n < len(lines)-1 { + w.print("\n") + w.printIndent(indent) + } + w.print(strings.ReplaceAll(trimmed, `"`, `\"`)) + printedN++ + } + w.print(`"""`) + w.print("\n") + default: + panic(n) + } +} + func (w *writer) printConstant(c *ast.Constant, indent int32) { switch n := c.Value.(type) { case *ast.Constant_Int: @@ -291,9 +328,7 @@ func (w *writer) printConstant(c *ast.Constant, indent int32) { } func (w *writer) printComment(c *ast.Comment, indent int32) { - w.print("# ") - w.print(c.Text) - w.print("\n") + w.printCommentText(c.Text, 0) } func (w *writer) printCompare(c *ast.Compare, indent int32) { From 1967bfa8923b825270555f240f783cdb3d7902b3 Mon Sep 17 00:00:00 2001 From: Merlin Ran Date: Fri, 28 Nov 2025 20:52:50 -0500 Subject: [PATCH 09/16] add more python keywords --- internal/poet/reserved.go | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/internal/poet/reserved.go b/internal/poet/reserved.go index d9d3198..6077ae6 100644 --- a/internal/poet/reserved.go +++ b/internal/poet/reserved.go @@ -4,25 +4,37 @@ import "slices" // TODO(quentin@escape.tech): check if this is complete var reservedKeywords = []string{ - "class", - "if", - "else", - "elif", - "not", - "for", "and", - "in", - "is", - "or", - "with", "as", "assert", + "async", + "await", "break", + "class", + "continue", + "def", + "del", + "elif", + "else", "except", "finally", - "try", + "for", + "from", + "global", + "if", + "import", + "in", + "is", + "lambda", + "nonlocal", + "not", + "or", + "pass", "raise", "return", + "try", + "while", + "with", "yield", } From eeed5d4e0a52bb98b557da198b1215ba1f248bd1 Mon Sep 17 00:00:00 2001 From: Merlin Ran Date: Sun, 30 Nov 2025 18:51:49 -0500 Subject: [PATCH 10/16] strings.Replace->strings.ReplaceAll --- internal/gen.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/gen.go b/internal/gen.go index d339650..edaefd4 100644 --- a/internal/gen.go +++ b/internal/gen.go @@ -219,9 +219,9 @@ func methodName(name string) string { var pyIdentPattern = regexp.MustCompile("[^a-zA-Z0-9_]+") func pyEnumValueName(value string) string { - id := strings.Replace(value, "-", "_", -1) - id = strings.Replace(id, ":", "_", -1) - id = strings.Replace(id, "/", "_", -1) + id := strings.ReplaceAll(value, "-", "_") + id = strings.ReplaceAll(id, ":", "_") + id = strings.ReplaceAll(id, "/", "_") id = pyIdentPattern.ReplaceAllString(id, "") return strings.ToUpper(id) } From 95289ea5f95757956486668c27d5c461357d0c27 Mon Sep 17 00:00:00 2001 From: Merlin Ran Date: Sun, 30 Nov 2025 19:42:40 -0500 Subject: [PATCH 11/16] params and return type should be CamelCased --- internal/gen.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/gen.go b/internal/gen.go index edaefd4..ca18c21 100644 --- a/internal/gen.go +++ b/internal/gen.go @@ -406,7 +406,7 @@ func buildQueries(conf Config, req *plugin.GenerateRequest, structs []Struct) ([ gq.Args = []QueryValue{{ Emit: true, Name: "arg", - Struct: columnsToStruct(req, query.Name+"Params", cols), + Struct: columnsToStruct(req, modelName(query.Name+"Params", req.Settings), cols), }} } else { args := make([]QueryValue, 0, len(query.Params)) @@ -461,7 +461,7 @@ func buildQueries(conf Config, req *plugin.GenerateRequest, structs []Struct) ([ Column: c, }) } - gs = columnsToStruct(req, query.Name+"Row", columns) + gs = columnsToStruct(req, modelName(query.Name+"Row", req.Settings), columns) emit = true } gq.Ret = QueryValue{ From 266fab8c3849fe6fcc83177e10768fe2a4c55004 Mon Sep 17 00:00:00 2001 From: Merlin Ran Date: Fri, 5 Dec 2025 17:03:49 -0500 Subject: [PATCH 12/16] fix generating enum for db enums --- internal/postgresql_type.go | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/internal/postgresql_type.go b/internal/postgresql_type.go index 3d0891b..df5da14 100644 --- a/internal/postgresql_type.go +++ b/internal/postgresql_type.go @@ -8,7 +8,7 @@ import ( ) func postgresType(req *plugin.GenerateRequest, col *plugin.Column) string { - columnType := sdk.DataType(col.Type) + columnType := sdk.DataType(col.Type) switch columnType { case "serial", "serial4", "pg_catalog.serial4", "bigserial", "serial8", "pg_catalog.serial8", "smallserial", "serial2", "pg_catalog.serial2", "integer", "int", "int4", "pg_catalog.int4", "bigint", "int8", "pg_catalog.int8", "smallint", "int2", "pg_catalog.int2": @@ -42,21 +42,22 @@ func postgresType(req *plugin.GenerateRequest, col *plugin.Column) string { return "str" case "ltree", "lquery", "ltxtquery": return "str" - default: - for _, schema := range req.Catalog.Schemas { - if schema.Name == "pg_catalog" || schema.Name == "information_schema" { - continue - } - for _, enum := range schema.Enums { - if columnType == enum.Name { - if schema.Name == req.Catalog.DefaultSchema { - return "models." + modelName(enum.Name, req.Settings) - } - return "models." + modelName(schema.Name+"_"+enum.Name, req.Settings) - } - } - } - log.Printf("unknown PostgreSQL type: %s\n", columnType) - return "Any" - } + default: + for _, schema := range req.Catalog.Schemas { + if schema.Name == "pg_catalog" || schema.Name == "information_schema" { + continue + } + for _, enum := range schema.Enums { + // Match both unqualified and schema-qualified enum type names + if columnType == enum.Name || columnType == schema.Name+"."+enum.Name { + if schema.Name == req.Catalog.DefaultSchema { + return "models." + modelName(enum.Name, req.Settings) + } + return "models." + modelName(schema.Name+"_"+enum.Name, req.Settings) + } + } + } + log.Printf("unknown PostgreSQL type: %s\n", columnType) + return "Any" + } } From 0806323c92e6873d47c9e323cf4f2fd77bec4843 Mon Sep 17 00:00:00 2001 From: Merlin Ran Date: Fri, 5 Dec 2025 17:20:04 -0500 Subject: [PATCH 13/16] add config option to control if have schema prefix for types or not --- README.md | 23 +++++++++++++++ internal/config.go | 26 +++++++++++------ internal/gen.go | 56 +++++++++++++++++++++++++++++-------- internal/postgresql_type.go | 9 +++--- 4 files changed, 90 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index c9f2531..d10bd8d 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,29 @@ sql: emit_async_querier: true ``` +### Configuration Options + +These are the supported `options` for the `py` plugin. Add them under the `codegen[].options` section of your `sqlc.yaml`. + +- package: Module path used for imports in generated query files (e.g., `from import models`). +- emit_sync_querier: Emit a synchronous `Querier` class using `sqlalchemy.engine.Connection`. +- emit_async_querier: Emit an asynchronous `AsyncQuerier` class using `sqlalchemy.ext.asyncio.AsyncConnection`. +- emit_pydantic_models: Emit Pydantic models instead of `dataclasses` for models.py. See the section below. +- emit_str_enum: Emit enums as `enum.StrEnum` (Python >=3.11). When false, emit `(str, enum.Enum)`. See the section below. +- emit_schema_name_prefix: When true, prefix non-default schema to generated types to avoid name collisions. Examples: + - false (default): `Book`, `BookStatus` + - true: `MySchemaBook`, `MySchemaBookStatus` when the objects live in schema `my_schema`. +- emit_exact_table_names: When true, do not singularize table names for model class names. +- query_parameter_limit: Integer controlling when query params are grouped into a single struct argument. + - If the number of parameters exceeds this value, a single `Params` struct is emitted. + - Set to 0 to always emit a struct; omit or set to a large value to keep separate parameters. +- inflection_exclude_table_names: A list of table names to exclude from singularization when `emit_exact_table_names` is false. +- overrides: Column type overrides; see the section below. + +Notes +- out: Controlled by `codegen[].out` at the sqlc level. The plugin’s `out` option is not used; prefer the top-level `out` value. + + ### Emit Pydantic Models instead of `dataclasses` Option: `emit_pydantic_models` diff --git a/internal/config.go b/internal/config.go index 1a8a565..8fc6a0a 100644 --- a/internal/config.go +++ b/internal/config.go @@ -1,13 +1,21 @@ package python +type OverrideColumn struct { + Column string `json:"column"` + PyType string `json:"py_type"` + PyImport string `json:"py_import"` +} + type Config struct { - EmitExactTableNames bool `json:"emit_exact_table_names"` - EmitSyncQuerier bool `json:"emit_sync_querier"` - EmitAsyncQuerier bool `json:"emit_async_querier"` - Package string `json:"package"` - Out string `json:"out"` - EmitPydanticModels bool `json:"emit_pydantic_models"` - EmitStrEnum bool `json:"emit_str_enum"` - QueryParameterLimit *int32 `json:"query_parameter_limit"` - InflectionExcludeTableNames []string `json:"inflection_exclude_table_names"` + EmitExactTableNames bool `json:"emit_exact_table_names"` + EmitSyncQuerier bool `json:"emit_sync_querier"` + EmitAsyncQuerier bool `json:"emit_async_querier"` + Package string `json:"package"` + Out string `json:"out"` + EmitPydanticModels bool `json:"emit_pydantic_models"` + EmitStrEnum bool `json:"emit_str_enum"` + EmitSchemaNamePrefix bool `json:"emit_schema_name_prefix"` + QueryParameterLimit *int32 `json:"query_parameter_limit"` + InflectionExcludeTableNames []string `json:"inflection_exclude_table_names"` + Overrides []OverrideColumn `json:"overrides"` } diff --git a/internal/gen.go b/internal/gen.go index ca18c21..0c145ea 100644 --- a/internal/gen.go +++ b/internal/gen.go @@ -181,7 +181,41 @@ func (q Query) ArgDictNode() *pyast.Node { } func makePyType(req *plugin.GenerateRequest, col *plugin.Column) pyType { - typ := pyInnerType(req, col) + // Parse the configuration + var conf Config + if len(req.PluginOptions) > 0 { + if err := json.Unmarshal(req.PluginOptions, &conf); err != nil { + log.Printf("failed to parse plugin options: %s", err) + } + } + + // Check for overrides + if len(conf.Overrides) > 0 && col.Table != nil { + tableName := col.Table.Name + if col.Table.Schema != "" && col.Table.Schema != req.Catalog.DefaultSchema { + tableName = col.Table.Schema + "." + tableName + } + + // Look for a matching override + for _, override := range conf.Overrides { + overrideKey := tableName + "." + col.Name + if override.Column == overrideKey { + // Found a match, use the override + typeStr := override.PyType + if override.PyImport != "" && !strings.Contains(typeStr, ".") { + typeStr = override.PyImport + "." + override.PyType + } + return pyType{ + InnerType: typeStr, + IsArray: col.IsArray, + IsNull: !col.NotNull, + } + } + } + } + + // No override found, use the standard type mapping + typ := pyInnerType(conf, req, col) return pyType{ InnerType: typ, IsArray: col.IsArray, @@ -189,10 +223,10 @@ func makePyType(req *plugin.GenerateRequest, col *plugin.Column) pyType { } } -func pyInnerType(req *plugin.GenerateRequest, col *plugin.Column) string { +func pyInnerType(conf Config, req *plugin.GenerateRequest, col *plugin.Column) string { switch req.Settings.Engine { case "postgresql": - return postgresType(req, col) + return postgresType(conf, req, col) default: log.Println("unsupported engine type") return "Any" @@ -226,7 +260,7 @@ func pyEnumValueName(value string) string { return strings.ToUpper(id) } -func buildEnums(req *plugin.GenerateRequest) []Enum { +func buildEnums(conf Config, req *plugin.GenerateRequest) []Enum { var enums []Enum for _, schema := range req.Catalog.Schemas { if schema.Name == "pg_catalog" || schema.Name == "information_schema" { @@ -234,10 +268,10 @@ func buildEnums(req *plugin.GenerateRequest) []Enum { } for _, enum := range schema.Enums { var enumName string - if schema.Name == req.Catalog.DefaultSchema { - enumName = enum.Name - } else { + if conf.EmitSchemaNamePrefix && schema.Name != req.Catalog.DefaultSchema { enumName = schema.Name + "_" + enum.Name + } else { + enumName = enum.Name } e := Enum{ Name: modelName(enumName, req.Settings), @@ -267,10 +301,10 @@ func buildModels(conf Config, req *plugin.GenerateRequest) []Struct { } for _, table := range schema.Tables { var tableName string - if schema.Name == req.Catalog.DefaultSchema { - tableName = table.Rel.Name - } else { + if conf.EmitSchemaNamePrefix && schema.Name != req.Catalog.DefaultSchema { tableName = schema.Name + "_" + table.Rel.Name + } else { + tableName = table.Rel.Name } structName := tableName if !conf.EmitExactTableNames { @@ -1185,7 +1219,7 @@ func Generate(_ context.Context, req *plugin.GenerateRequest) (*plugin.GenerateR } } - enums := buildEnums(req) + enums := buildEnums(conf, req) models := buildModels(conf, req) queries, err := buildQueries(conf, req, models) if err != nil { diff --git a/internal/postgresql_type.go b/internal/postgresql_type.go index df5da14..dfc0800 100644 --- a/internal/postgresql_type.go +++ b/internal/postgresql_type.go @@ -7,7 +7,7 @@ import ( "github.com/sqlc-dev/plugin-sdk-go/sdk" ) -func postgresType(req *plugin.GenerateRequest, col *plugin.Column) string { +func postgresType(conf Config, req *plugin.GenerateRequest, col *plugin.Column) string { columnType := sdk.DataType(col.Type) switch columnType { @@ -50,10 +50,11 @@ func postgresType(req *plugin.GenerateRequest, col *plugin.Column) string { for _, enum := range schema.Enums { // Match both unqualified and schema-qualified enum type names if columnType == enum.Name || columnType == schema.Name+"."+enum.Name { - if schema.Name == req.Catalog.DefaultSchema { - return "models." + modelName(enum.Name, req.Settings) + name := enum.Name + if conf.EmitSchemaNamePrefix && schema.Name != req.Catalog.DefaultSchema { + name = schema.Name + "_" + enum.Name } - return "models." + modelName(schema.Name+"_"+enum.Name, req.Settings) + return "models." + modelName(name, req.Settings) } } } From 0a9dcb769588ef94786c23354d664cfcee2c0415 Mon Sep 17 00:00:00 2001 From: Merlin Ran Date: Tue, 24 Feb 2026 19:04:57 +0000 Subject: [PATCH 14/16] Support type overrides with test coveragfixtures Also updated tests to the latest sqlc version --- README.md | 48 +++++++ examples/src/authors/models.py | 2 +- examples/src/authors/query.py | 2 +- examples/src/booktest/models.py | 2 +- examples/src/booktest/query.py | 2 +- examples/src/jets/models.py | 2 +- examples/src/jets/query-building.py | 2 +- examples/src/ondeck/city.py | 2 +- examples/src/ondeck/models.py | 2 +- examples/src/ondeck/venue.py | 2 +- internal/config.go | 1 + .../db/models.py | 2 +- .../db/query.py | 2 +- .../sqlc.yaml | 2 +- .../emit_pydantic_models/db/models.py | 2 +- .../testdata/emit_pydantic_models/db/query.py | 2 +- .../testdata/emit_pydantic_models/sqlc.yaml | 2 +- .../db/models.py | 2 +- .../db/query.py | 2 +- .../sqlc.yaml | 2 +- .../testdata/emit_str_enum/db/models.py | 2 +- .../testdata/emit_str_enum/db/query.py | 2 +- .../endtoend/testdata/emit_str_enum/sqlc.yaml | 2 +- .../testdata/emit_type_overrides/db/models.py | 24 ++++ .../testdata/emit_type_overrides/db/query.py | 133 ++++++++++++++++++ .../testdata/emit_type_overrides/query.sql | 18 +++ .../testdata/emit_type_overrides/schema.sql | 10 ++ .../testdata/emit_type_overrides/sqlc.yaml | 25 ++++ .../testdata/exec_result/python/models.py | 2 +- .../testdata/exec_result/python/query.py | 2 +- .../endtoend/testdata/exec_result/sqlc.yaml | 2 +- .../testdata/exec_rows/python/models.py | 2 +- .../testdata/exec_rows/python/query.py | 2 +- .../endtoend/testdata/exec_rows/sqlc.yaml | 2 +- .../python/models.py | 2 +- .../python/query.py | 2 +- .../inflection_exclude_table_names/sqlc.yaml | 2 +- .../python/models.py | 2 +- .../query_parameter_limit_two/python/query.py | 2 +- .../query_parameter_limit_two/sqlc.yaml | 2 +- .../python/models.py | 2 +- .../python/query.py | 2 +- .../query_parameter_limit_undefined/sqlc.yaml | 2 +- .../python/models.py | 2 +- .../python/query.py | 2 +- .../query_parameter_limit_zero/sqlc.yaml | 2 +- .../query_parameter_no_limit/sqlc.yaml | 2 +- internal/gen.go | 37 +++-- 48 files changed, 328 insertions(+), 48 deletions(-) create mode 100644 internal/endtoend/testdata/emit_type_overrides/db/models.py create mode 100644 internal/endtoend/testdata/emit_type_overrides/db/query.py create mode 100644 internal/endtoend/testdata/emit_type_overrides/query.sql create mode 100644 internal/endtoend/testdata/emit_type_overrides/schema.sql create mode 100644 internal/endtoend/testdata/emit_type_overrides/sqlc.yaml diff --git a/README.md b/README.md index d10bd8d..d2fe13f 100644 --- a/README.md +++ b/README.md @@ -99,3 +99,51 @@ class Status(str, enum.Enum): OPEN = "op!en" CLOSED = "clo@sed" ``` + +### Override Column Types + +Option: `overrides` + +You can override the SQL to Python type mapping for specific columns or database types using the `overrides` option. This is useful for columns with JSON data or other custom types. + +Example configuration: + +```yaml +options: + package: authors + emit_pydantic_models: true + overrides: + - column: "some_table.payload" + py_import: "my_lib.models" + py_type: "Payload" + - db_type: "jsonb" + py_import: "my_lib.models" + py_type: "Payload" +``` + +This will: +1. Override the column `payload` in `some_table` to use the type `Payload` +2. Override any column with the database type `jsonb` to use the type `Payload` +3. Add an import for `my_lib.models` to the models file + +Example output: + +```python +# Code generated by sqlc. DO NOT EDIT. +# versions: +# sqlc v1.30.0 + +import datetime +import pydantic +from typing import Any + +import my_lib.models + + +class SomeTable(pydantic.BaseModel): + id: int + created_at: datetime.datetime + payload: my_lib.models.Payload +``` + +This is similar to the [overrides functionality in the Go version of sqlc](https://docs.sqlc.dev/en/stable/howto/overrides.html#overriding-types). diff --git a/examples/src/authors/models.py b/examples/src/authors/models.py index b3b9554..007ea19 100644 --- a/examples/src/authors/models.py +++ b/examples/src/authors/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 import dataclasses from typing import Optional diff --git a/examples/src/authors/query.py b/examples/src/authors/query.py index b932e35..48513ed 100644 --- a/examples/src/authors/query.py +++ b/examples/src/authors/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 # source: query.sql from typing import AsyncIterator, Iterator, Optional diff --git a/examples/src/booktest/models.py b/examples/src/booktest/models.py index dcfbc20..a882d02 100644 --- a/examples/src/booktest/models.py +++ b/examples/src/booktest/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 import dataclasses import datetime import enum diff --git a/examples/src/booktest/query.py b/examples/src/booktest/query.py index 12d3717..6e71192 100644 --- a/examples/src/booktest/query.py +++ b/examples/src/booktest/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 # source: query.sql import dataclasses import datetime diff --git a/examples/src/jets/models.py b/examples/src/jets/models.py index fc5464b..7d9a81e 100644 --- a/examples/src/jets/models.py +++ b/examples/src/jets/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 import dataclasses diff --git a/examples/src/jets/query-building.py b/examples/src/jets/query-building.py index adcdcdb..51d69a5 100644 --- a/examples/src/jets/query-building.py +++ b/examples/src/jets/query-building.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 # source: query-building.sql from typing import AsyncIterator, Optional diff --git a/examples/src/ondeck/city.py b/examples/src/ondeck/city.py index 2f2da93..951d95b 100644 --- a/examples/src/ondeck/city.py +++ b/examples/src/ondeck/city.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 # source: city.sql from typing import AsyncIterator, Optional diff --git a/examples/src/ondeck/models.py b/examples/src/ondeck/models.py index a32fea2..bc05e8a 100644 --- a/examples/src/ondeck/models.py +++ b/examples/src/ondeck/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 import dataclasses import datetime import enum diff --git a/examples/src/ondeck/venue.py b/examples/src/ondeck/venue.py index 1911cb3..87f7992 100644 --- a/examples/src/ondeck/venue.py +++ b/examples/src/ondeck/venue.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 # source: venue.sql import dataclasses from typing import AsyncIterator, List, Optional diff --git a/internal/config.go b/internal/config.go index 8fc6a0a..6f0f669 100644 --- a/internal/config.go +++ b/internal/config.go @@ -2,6 +2,7 @@ package python type OverrideColumn struct { Column string `json:"column"` + DbType string `json:"db_type"` PyType string `json:"py_type"` PyImport string `json:"py_import"` } diff --git a/internal/endtoend/testdata/dataclasses_with_reserved_keywords/db/models.py b/internal/endtoend/testdata/dataclasses_with_reserved_keywords/db/models.py index f2dd802..50ed88f 100644 --- a/internal/endtoend/testdata/dataclasses_with_reserved_keywords/db/models.py +++ b/internal/endtoend/testdata/dataclasses_with_reserved_keywords/db/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 import dataclasses diff --git a/internal/endtoend/testdata/dataclasses_with_reserved_keywords/db/query.py b/internal/endtoend/testdata/dataclasses_with_reserved_keywords/db/query.py index 1a0ac38..5ee401b 100644 --- a/internal/endtoend/testdata/dataclasses_with_reserved_keywords/db/query.py +++ b/internal/endtoend/testdata/dataclasses_with_reserved_keywords/db/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 # source: query.sql from typing import Optional diff --git a/internal/endtoend/testdata/dataclasses_with_reserved_keywords/sqlc.yaml b/internal/endtoend/testdata/dataclasses_with_reserved_keywords/sqlc.yaml index a7cfc22..56d1cac 100644 --- a/internal/endtoend/testdata/dataclasses_with_reserved_keywords/sqlc.yaml +++ b/internal/endtoend/testdata/dataclasses_with_reserved_keywords/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" + sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_pydantic_models/db/models.py b/internal/endtoend/testdata/emit_pydantic_models/db/models.py index 5c6947e..8b96b15 100644 --- a/internal/endtoend/testdata/emit_pydantic_models/db/models.py +++ b/internal/endtoend/testdata/emit_pydantic_models/db/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 import pydantic from typing import Optional diff --git a/internal/endtoend/testdata/emit_pydantic_models/db/query.py b/internal/endtoend/testdata/emit_pydantic_models/db/query.py index cc36118..946674d 100644 --- a/internal/endtoend/testdata/emit_pydantic_models/db/query.py +++ b/internal/endtoend/testdata/emit_pydantic_models/db/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 # source: query.sql from typing import AsyncIterator, Iterator, Optional diff --git a/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml b/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml index bc03370..5cb5d25 100644 --- a/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml +++ b/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" + sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/db/models.py b/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/db/models.py index 1403269..8003649 100644 --- a/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/db/models.py +++ b/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/db/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 import pydantic diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/db/query.py b/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/db/query.py index 1a0ac38..5ee401b 100644 --- a/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/db/query.py +++ b/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/db/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 # source: query.sql from typing import Optional diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/sqlc.yaml b/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/sqlc.yaml index bc03370..5cb5d25 100644 --- a/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/sqlc.yaml +++ b/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" + sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_str_enum/db/models.py b/internal/endtoend/testdata/emit_str_enum/db/models.py index aa43ab1..5fd5508 100644 --- a/internal/endtoend/testdata/emit_str_enum/db/models.py +++ b/internal/endtoend/testdata/emit_str_enum/db/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 import dataclasses import enum from typing import Optional diff --git a/internal/endtoend/testdata/emit_str_enum/db/query.py b/internal/endtoend/testdata/emit_str_enum/db/query.py index 5ea0264..c02a9ec 100644 --- a/internal/endtoend/testdata/emit_str_enum/db/query.py +++ b/internal/endtoend/testdata/emit_str_enum/db/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 # source: query.sql from typing import AsyncIterator, Iterator, Optional diff --git a/internal/endtoend/testdata/emit_str_enum/sqlc.yaml b/internal/endtoend/testdata/emit_str_enum/sqlc.yaml index 1a80fbc..a79fdd2 100644 --- a/internal/endtoend/testdata/emit_str_enum/sqlc.yaml +++ b/internal/endtoend/testdata/emit_str_enum/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" + sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_type_overrides/db/models.py b/internal/endtoend/testdata/emit_type_overrides/db/models.py new file mode 100644 index 0000000..10f1b05 --- /dev/null +++ b/internal/endtoend/testdata/emit_type_overrides/db/models.py @@ -0,0 +1,24 @@ +# Code generated by sqlc. DO NOT EDIT. +# versions: +# sqlc v1.30.0 +import enum +import pydantic +from typing import Optional + + +class BookStatus(str, enum.Enum): + AVAILABLE = "available" + CHECKED_OUT = "checked_out" + OVERDUE = "overdue" + + +class Book(pydantic.BaseModel): + model_config = pydantic.ConfigDict( + validate_by_alias=True, + validate_by_name=True, + ) + id: int + title: str + status: Optional[BookStatus] + payload: my_lib.models.BookPayload + metadata: Optional[my_lib.models.JsonValue] diff --git a/internal/endtoend/testdata/emit_type_overrides/db/query.py b/internal/endtoend/testdata/emit_type_overrides/db/query.py new file mode 100644 index 0000000..e60e74e --- /dev/null +++ b/internal/endtoend/testdata/emit_type_overrides/db/query.py @@ -0,0 +1,133 @@ +# Code generated by sqlc. DO NOT EDIT. +# versions: +# sqlc v1.30.0 +# source: query.sql +from typing import AsyncIterator, Iterator, Optional + +import sqlalchemy +import sqlalchemy.ext.asyncio + +from db import models + + +CREATE_BOOK = """-- name: create_book \\:one +INSERT INTO books ( + title, status, payload, metadata +) VALUES ( + :p1, :p2, :p3, :p4 +) RETURNING id, title, status, payload, metadata +""" + + +DELETE_BOOK = """-- name: delete_book \\:exec +DELETE FROM books +WHERE id = :p1 +""" + + +GET_BOOK = """-- name: get_book \\:one +SELECT id, title, status, payload, metadata FROM books +WHERE id = :p1 LIMIT 1 +""" + + +LIST_BOOKS = """-- name: list_books \\:many +SELECT id, title, status, payload, metadata FROM books +ORDER BY title +""" + + +class Querier: + def __init__(self, conn: sqlalchemy.engine.Connection): + self._conn = conn + + def create_book(self, *, title: str, status: Optional[models.BookStatus], payload: my_lib.models.BookPayload, metadata: Optional[my_lib.models.JsonValue]) -> Optional[models.Book]: + row = self._conn.execute(sqlalchemy.text(CREATE_BOOK), { + "p1": title, + "p2": status, + "p3": payload, + "p4": metadata, + }).first() + if row is None: + return None + return models.Book( + id=row[0], + title=row[1], + status=row[2], + payload=row[3], + metadata=row[4], + ) + + def delete_book(self, *, id: int) -> None: + self._conn.execute(sqlalchemy.text(DELETE_BOOK), {"p1": id}) + + def get_book(self, *, id: int) -> Optional[models.Book]: + row = self._conn.execute(sqlalchemy.text(GET_BOOK), {"p1": id}).first() + if row is None: + return None + return models.Book( + id=row[0], + title=row[1], + status=row[2], + payload=row[3], + metadata=row[4], + ) + + def list_books(self) -> Iterator[models.Book]: + result = self._conn.execute(sqlalchemy.text(LIST_BOOKS)) + for row in result: + yield models.Book( + id=row[0], + title=row[1], + status=row[2], + payload=row[3], + metadata=row[4], + ) + + +class AsyncQuerier: + def __init__(self, conn: sqlalchemy.ext.asyncio.AsyncConnection): + self._conn = conn + + async def create_book(self, *, title: str, status: Optional[models.BookStatus], payload: my_lib.models.BookPayload, metadata: Optional[my_lib.models.JsonValue]) -> Optional[models.Book]: + row = (await self._conn.execute(sqlalchemy.text(CREATE_BOOK), { + "p1": title, + "p2": status, + "p3": payload, + "p4": metadata, + })).first() + if row is None: + return None + return models.Book( + id=row[0], + title=row[1], + status=row[2], + payload=row[3], + metadata=row[4], + ) + + async def delete_book(self, *, id: int) -> None: + await self._conn.execute(sqlalchemy.text(DELETE_BOOK), {"p1": id}) + + async def get_book(self, *, id: int) -> Optional[models.Book]: + row = (await self._conn.execute(sqlalchemy.text(GET_BOOK), {"p1": id})).first() + if row is None: + return None + return models.Book( + id=row[0], + title=row[1], + status=row[2], + payload=row[3], + metadata=row[4], + ) + + async def list_books(self) -> AsyncIterator[models.Book]: + result = await self._conn.stream(sqlalchemy.text(LIST_BOOKS)) + async for row in result: + yield models.Book( + id=row[0], + title=row[1], + status=row[2], + payload=row[3], + metadata=row[4], + ) diff --git a/internal/endtoend/testdata/emit_type_overrides/query.sql b/internal/endtoend/testdata/emit_type_overrides/query.sql new file mode 100644 index 0000000..7214d43 --- /dev/null +++ b/internal/endtoend/testdata/emit_type_overrides/query.sql @@ -0,0 +1,18 @@ +-- name: GetBook :one +SELECT * FROM books +WHERE id = $1 LIMIT 1; + +-- name: ListBooks :many +SELECT * FROM books +ORDER BY title; + +-- name: CreateBook :one +INSERT INTO books ( + title, status, payload, metadata +) VALUES ( + $1, $2, $3, $4 +) RETURNING *; + +-- name: DeleteBook :exec +DELETE FROM books +WHERE id = $1; diff --git a/internal/endtoend/testdata/emit_type_overrides/schema.sql b/internal/endtoend/testdata/emit_type_overrides/schema.sql new file mode 100644 index 0000000..51c1c9f --- /dev/null +++ b/internal/endtoend/testdata/emit_type_overrides/schema.sql @@ -0,0 +1,10 @@ +CREATE TYPE book_status AS ENUM ('available', 'checked_out', 'overdue'); + + +CREATE TABLE books ( + id BIGSERIAL PRIMARY KEY, + title text NOT NULL, + status book_status DEFAULT 'available', + payload jsonb NOT NULL, + metadata jsonb +); diff --git a/internal/endtoend/testdata/emit_type_overrides/sqlc.yaml b/internal/endtoend/testdata/emit_type_overrides/sqlc.yaml new file mode 100644 index 0000000..75d9869 --- /dev/null +++ b/internal/endtoend/testdata/emit_type_overrides/sqlc.yaml @@ -0,0 +1,25 @@ +version: "2" +plugins: + - name: py + wasm: + url: file://../../../../bin/sqlc-gen-python.wasm + sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" +sql: + - schema: schema.sql + queries: query.sql + engine: postgresql + codegen: + - plugin: py + out: db + options: + package: db + emit_pydantic_models: true + emit_sync_querier: true + emit_async_querier: true + overrides: + - column: "books.payload" + py_import: "my_lib.models" + py_type: "BookPayload" + - db_type: "jsonb" + py_import: "my_lib.models" + py_type: "JsonValue" diff --git a/internal/endtoend/testdata/exec_result/python/models.py b/internal/endtoend/testdata/exec_result/python/models.py index 6d3e9f5..ced3715 100644 --- a/internal/endtoend/testdata/exec_result/python/models.py +++ b/internal/endtoend/testdata/exec_result/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 import dataclasses diff --git a/internal/endtoend/testdata/exec_result/python/query.py b/internal/endtoend/testdata/exec_result/python/query.py index c9c6e21..c063868 100644 --- a/internal/endtoend/testdata/exec_result/python/query.py +++ b/internal/endtoend/testdata/exec_result/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 # source: query.sql import sqlalchemy import sqlalchemy.ext.asyncio diff --git a/internal/endtoend/testdata/exec_result/sqlc.yaml b/internal/endtoend/testdata/exec_result/sqlc.yaml index 9c6a7d3..14eb4ef 100644 --- a/internal/endtoend/testdata/exec_result/sqlc.yaml +++ b/internal/endtoend/testdata/exec_result/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" + sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/exec_rows/python/models.py b/internal/endtoend/testdata/exec_rows/python/models.py index 6d3e9f5..ced3715 100644 --- a/internal/endtoend/testdata/exec_rows/python/models.py +++ b/internal/endtoend/testdata/exec_rows/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 import dataclasses diff --git a/internal/endtoend/testdata/exec_rows/python/query.py b/internal/endtoend/testdata/exec_rows/python/query.py index a678f3d..c5a936d 100644 --- a/internal/endtoend/testdata/exec_rows/python/query.py +++ b/internal/endtoend/testdata/exec_rows/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 # source: query.sql import sqlalchemy import sqlalchemy.ext.asyncio diff --git a/internal/endtoend/testdata/exec_rows/sqlc.yaml b/internal/endtoend/testdata/exec_rows/sqlc.yaml index 9c6a7d3..14eb4ef 100644 --- a/internal/endtoend/testdata/exec_rows/sqlc.yaml +++ b/internal/endtoend/testdata/exec_rows/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" + sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/inflection_exclude_table_names/python/models.py b/internal/endtoend/testdata/inflection_exclude_table_names/python/models.py index fc76620..0614ac0 100644 --- a/internal/endtoend/testdata/inflection_exclude_table_names/python/models.py +++ b/internal/endtoend/testdata/inflection_exclude_table_names/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 import dataclasses diff --git a/internal/endtoend/testdata/inflection_exclude_table_names/python/query.py b/internal/endtoend/testdata/inflection_exclude_table_names/python/query.py index 1fc92fd..8b9eb26 100644 --- a/internal/endtoend/testdata/inflection_exclude_table_names/python/query.py +++ b/internal/endtoend/testdata/inflection_exclude_table_names/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 # source: query.sql from typing import Optional diff --git a/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml b/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml index 369271f..dc0208b 100644 --- a/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml +++ b/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" + sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_two/python/models.py b/internal/endtoend/testdata/query_parameter_limit_two/python/models.py index 89c0f8d..2ddf019 100644 --- a/internal/endtoend/testdata/query_parameter_limit_two/python/models.py +++ b/internal/endtoend/testdata/query_parameter_limit_two/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 import dataclasses diff --git a/internal/endtoend/testdata/query_parameter_limit_two/python/query.py b/internal/endtoend/testdata/query_parameter_limit_two/python/query.py index 0d9bd97..5a97c59 100644 --- a/internal/endtoend/testdata/query_parameter_limit_two/python/query.py +++ b/internal/endtoend/testdata/query_parameter_limit_two/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 # source: query.sql import sqlalchemy import sqlalchemy.ext.asyncio diff --git a/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml index 6d7d162..d837147 100644 --- a/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" + sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_undefined/python/models.py b/internal/endtoend/testdata/query_parameter_limit_undefined/python/models.py index dc09dab..77bdfe5 100644 --- a/internal/endtoend/testdata/query_parameter_limit_undefined/python/models.py +++ b/internal/endtoend/testdata/query_parameter_limit_undefined/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 import dataclasses diff --git a/internal/endtoend/testdata/query_parameter_limit_undefined/python/query.py b/internal/endtoend/testdata/query_parameter_limit_undefined/python/query.py index 49b7bd1..6380dce 100644 --- a/internal/endtoend/testdata/query_parameter_limit_undefined/python/query.py +++ b/internal/endtoend/testdata/query_parameter_limit_undefined/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 # source: query.sql import sqlalchemy import sqlalchemy.ext.asyncio diff --git a/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml index 4ba6c57..7d1f505 100644 --- a/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" + sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_zero/python/models.py b/internal/endtoend/testdata/query_parameter_limit_zero/python/models.py index 89c0f8d..2ddf019 100644 --- a/internal/endtoend/testdata/query_parameter_limit_zero/python/models.py +++ b/internal/endtoend/testdata/query_parameter_limit_zero/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 import dataclasses diff --git a/internal/endtoend/testdata/query_parameter_limit_zero/python/query.py b/internal/endtoend/testdata/query_parameter_limit_zero/python/query.py index 38e0efb..5edcd9c 100644 --- a/internal/endtoend/testdata/query_parameter_limit_zero/python/query.py +++ b/internal/endtoend/testdata/query_parameter_limit_zero/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 # source: query.sql import dataclasses diff --git a/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml index 4c1abd2..7f66fa2 100644 --- a/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" + sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml b/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml index 270c290..798e0f8 100644 --- a/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "24b0da217e85c9b952a4c746476aa761e9b293a4a68bef8409d97edc1c003016" + sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" sql: - schema: schema.sql queries: query.sql diff --git a/internal/gen.go b/internal/gen.go index 0c145ea..ffd64d7 100644 --- a/internal/gen.go +++ b/internal/gen.go @@ -190,17 +190,38 @@ func makePyType(req *plugin.GenerateRequest, col *plugin.Column) pyType { } // Check for overrides - if len(conf.Overrides) > 0 && col.Table != nil { - tableName := col.Table.Name - if col.Table.Schema != "" && col.Table.Schema != req.Catalog.DefaultSchema { - tableName = col.Table.Schema + "." + tableName + if len(conf.Overrides) > 0 { + if col.Table != nil { + tableName := col.Table.Name + if col.Table.Schema != "" && col.Table.Schema != req.Catalog.DefaultSchema { + tableName = col.Table.Schema + "." + tableName + } + + // Look for a matching column override first + for _, override := range conf.Overrides { + overrideKey := tableName + "." + col.Name + if override.Column == overrideKey { + // Found a match, use the override + typeStr := override.PyType + if override.PyImport != "" && !strings.Contains(typeStr, ".") { + typeStr = override.PyImport + "." + override.PyType + } + return pyType{ + InnerType: typeStr, + IsArray: col.IsArray, + IsNull: !col.NotNull, + } + } + } } - // Look for a matching override + // Then look for a matching db_type override + columnType := strings.ToLower(sdk.DataType(col.Type)) for _, override := range conf.Overrides { - overrideKey := tableName + "." + col.Name - if override.Column == overrideKey { - // Found a match, use the override + if override.DbType == "" { + continue + } + if strings.EqualFold(override.DbType, columnType) { typeStr := override.PyType if override.PyImport != "" && !strings.Contains(typeStr, ".") { typeStr = override.PyImport + "." + override.PyType From 86a9e4e6a519e1e781c18dff7276411af68117f6 Mon Sep 17 00:00:00 2001 From: Merlin Ran Date: Tue, 24 Feb 2026 20:23:03 +0000 Subject: [PATCH 15/16] fail for unknown yaml fields --- internal/config.go | 23 +++++++++++++ internal/config_test.go | 32 +++++++++++++++++++ .../sqlc.yaml | 2 +- .../testdata/emit_pydantic_models/sqlc.yaml | 2 +- .../sqlc.yaml | 2 +- .../endtoend/testdata/emit_str_enum/sqlc.yaml | 2 +- .../testdata/emit_type_overrides/db/query.py | 29 +++++++++++++++++ .../testdata/emit_type_overrides/query.sql | 5 +++ .../testdata/emit_type_overrides/sqlc.yaml | 2 +- .../endtoend/testdata/exec_result/sqlc.yaml | 2 +- .../endtoend/testdata/exec_rows/sqlc.yaml | 2 +- .../inflection_exclude_table_names/sqlc.yaml | 2 +- .../query_parameter_limit_two/sqlc.yaml | 2 +- .../query_parameter_limit_undefined/sqlc.yaml | 2 +- .../query_parameter_limit_zero/sqlc.yaml | 2 +- .../query_parameter_no_limit/sqlc.yaml | 2 +- internal/gen.go | 15 ++++----- 17 files changed, 108 insertions(+), 20 deletions(-) create mode 100644 internal/config_test.go diff --git a/internal/config.go b/internal/config.go index 6f0f669..a024dce 100644 --- a/internal/config.go +++ b/internal/config.go @@ -1,5 +1,12 @@ package python +import ( + "bytes" + json "encoding/json" + "fmt" + "strings" +) + type OverrideColumn struct { Column string `json:"column"` DbType string `json:"db_type"` @@ -20,3 +27,19 @@ type Config struct { InflectionExcludeTableNames []string `json:"inflection_exclude_table_names"` Overrides []OverrideColumn `json:"overrides"` } + +func parseConfig(raw []byte) (Config, error) { + var conf Config + if len(raw) == 0 { + return conf, nil + } + + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&conf); err != nil { + msg := strings.TrimPrefix(err.Error(), "json: ") + return Config{}, fmt.Errorf("invalid plugin options: %s", msg) + } + + return conf, nil +} diff --git a/internal/config_test.go b/internal/config_test.go new file mode 100644 index 0000000..c7d515f --- /dev/null +++ b/internal/config_test.go @@ -0,0 +1,32 @@ +package python + +import ( + "strings" + "testing" +) + +func TestParseConfigDisallowUnknownFields(t *testing.T) { + _, err := parseConfig([]byte(`{"emit_sync_querier":true,"db_typ":"jsonb"}`)) + if err == nil { + t.Fatal("expected unknown field error, got nil") + } + if !strings.Contains(err.Error(), "invalid plugin options") { + t.Fatalf("expected error to reference plugin options, got: %v", err) + } + if !strings.Contains(err.Error(), `unknown field "db_typ"`) { + t.Fatalf("expected unknown field in error, got: %v", err) + } +} + +func TestParseConfigValid(t *testing.T) { + conf, err := parseConfig([]byte(`{"emit_sync_querier":true,"overrides":[{"db_type":"jsonb","py_type":"str"}]}`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !conf.EmitSyncQuerier { + t.Fatal("expected emit_sync_querier to be true") + } + if len(conf.Overrides) != 1 || conf.Overrides[0].DbType != "jsonb" || conf.Overrides[0].PyType != "str" { + t.Fatal("unexpected parsed overrides") + } +} diff --git a/internal/endtoend/testdata/dataclasses_with_reserved_keywords/sqlc.yaml b/internal/endtoend/testdata/dataclasses_with_reserved_keywords/sqlc.yaml index 56d1cac..bb7f0e0 100644 --- a/internal/endtoend/testdata/dataclasses_with_reserved_keywords/sqlc.yaml +++ b/internal/endtoend/testdata/dataclasses_with_reserved_keywords/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" + sha256: "a5d1ad0ead1ecadb0608684d7c7a04327762299b769e6cca9c8f838c5df89788" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml b/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml index 5cb5d25..483362d 100644 --- a/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml +++ b/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" + sha256: "a5d1ad0ead1ecadb0608684d7c7a04327762299b769e6cca9c8f838c5df89788" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/sqlc.yaml b/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/sqlc.yaml index 5cb5d25..483362d 100644 --- a/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/sqlc.yaml +++ b/internal/endtoend/testdata/emit_pydantic_models_with_reserved_keywords/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" + sha256: "a5d1ad0ead1ecadb0608684d7c7a04327762299b769e6cca9c8f838c5df89788" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_str_enum/sqlc.yaml b/internal/endtoend/testdata/emit_str_enum/sqlc.yaml index a79fdd2..4e08a4b 100644 --- a/internal/endtoend/testdata/emit_str_enum/sqlc.yaml +++ b/internal/endtoend/testdata/emit_str_enum/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" + sha256: "a5d1ad0ead1ecadb0608684d7c7a04327762299b769e6cca9c8f838c5df89788" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_type_overrides/db/query.py b/internal/endtoend/testdata/emit_type_overrides/db/query.py index e60e74e..d160b11 100644 --- a/internal/endtoend/testdata/emit_type_overrides/db/query.py +++ b/internal/endtoend/testdata/emit_type_overrides/db/query.py @@ -37,6 +37,13 @@ """ +SEARCH_BOOKS = """-- name: search_books \\:many +SELECT id, title, status, payload, metadata FROM books +WHERE payload @> COALESCE(:p1\\:\\:jsonb, '{}'\\:\\:jsonb) +ORDER BY title +""" + + class Querier: def __init__(self, conn: sqlalchemy.engine.Connection): self._conn = conn @@ -84,6 +91,17 @@ def list_books(self) -> Iterator[models.Book]: metadata=row[4], ) + def search_books(self, *, payload_filter: Optional[my_lib.models.JsonValue]) -> Iterator[models.Book]: + result = self._conn.execute(sqlalchemy.text(SEARCH_BOOKS), {"p1": payload_filter}) + for row in result: + yield models.Book( + id=row[0], + title=row[1], + status=row[2], + payload=row[3], + metadata=row[4], + ) + class AsyncQuerier: def __init__(self, conn: sqlalchemy.ext.asyncio.AsyncConnection): @@ -131,3 +149,14 @@ async def list_books(self) -> AsyncIterator[models.Book]: payload=row[3], metadata=row[4], ) + + async def search_books(self, *, payload_filter: Optional[my_lib.models.JsonValue]) -> AsyncIterator[models.Book]: + result = await self._conn.stream(sqlalchemy.text(SEARCH_BOOKS), {"p1": payload_filter}) + async for row in result: + yield models.Book( + id=row[0], + title=row[1], + status=row[2], + payload=row[3], + metadata=row[4], + ) diff --git a/internal/endtoend/testdata/emit_type_overrides/query.sql b/internal/endtoend/testdata/emit_type_overrides/query.sql index 7214d43..27240c2 100644 --- a/internal/endtoend/testdata/emit_type_overrides/query.sql +++ b/internal/endtoend/testdata/emit_type_overrides/query.sql @@ -16,3 +16,8 @@ INSERT INTO books ( -- name: DeleteBook :exec DELETE FROM books WHERE id = $1; + +-- name: SearchBooks :many +SELECT * FROM books +WHERE payload @> COALESCE(sqlc.narg('payload_filter')::jsonb, '{}'::jsonb) +ORDER BY title; diff --git a/internal/endtoend/testdata/emit_type_overrides/sqlc.yaml b/internal/endtoend/testdata/emit_type_overrides/sqlc.yaml index 75d9869..a254cc1 100644 --- a/internal/endtoend/testdata/emit_type_overrides/sqlc.yaml +++ b/internal/endtoend/testdata/emit_type_overrides/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" + sha256: "a5d1ad0ead1ecadb0608684d7c7a04327762299b769e6cca9c8f838c5df89788" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/exec_result/sqlc.yaml b/internal/endtoend/testdata/exec_result/sqlc.yaml index 14eb4ef..ad2de60 100644 --- a/internal/endtoend/testdata/exec_result/sqlc.yaml +++ b/internal/endtoend/testdata/exec_result/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" + sha256: "a5d1ad0ead1ecadb0608684d7c7a04327762299b769e6cca9c8f838c5df89788" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/exec_rows/sqlc.yaml b/internal/endtoend/testdata/exec_rows/sqlc.yaml index 14eb4ef..ad2de60 100644 --- a/internal/endtoend/testdata/exec_rows/sqlc.yaml +++ b/internal/endtoend/testdata/exec_rows/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" + sha256: "a5d1ad0ead1ecadb0608684d7c7a04327762299b769e6cca9c8f838c5df89788" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml b/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml index dc0208b..00c46bc 100644 --- a/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml +++ b/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" + sha256: "a5d1ad0ead1ecadb0608684d7c7a04327762299b769e6cca9c8f838c5df89788" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml index d837147..6818f81 100644 --- a/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" + sha256: "a5d1ad0ead1ecadb0608684d7c7a04327762299b769e6cca9c8f838c5df89788" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml index 7d1f505..c274b43 100644 --- a/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" + sha256: "a5d1ad0ead1ecadb0608684d7c7a04327762299b769e6cca9c8f838c5df89788" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml index 7f66fa2..61ade44 100644 --- a/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" + sha256: "a5d1ad0ead1ecadb0608684d7c7a04327762299b769e6cca9c8f838c5df89788" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml b/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml index 798e0f8..a1301c7 100644 --- a/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "77e9665cec6efe4e1871218c27a09143eb0f23fb509855c881757022383813e0" + sha256: "a5d1ad0ead1ecadb0608684d7c7a04327762299b769e6cca9c8f838c5df89788" sql: - schema: schema.sql queries: query.sql diff --git a/internal/gen.go b/internal/gen.go index ffd64d7..5132c9e 100644 --- a/internal/gen.go +++ b/internal/gen.go @@ -2,7 +2,6 @@ package python import ( "context" - json "encoding/json" "errors" "fmt" "log" @@ -181,11 +180,13 @@ func (q Query) ArgDictNode() *pyast.Node { } func makePyType(req *plugin.GenerateRequest, col *plugin.Column) pyType { - // Parse the configuration var conf Config if len(req.PluginOptions) > 0 { - if err := json.Unmarshal(req.PluginOptions, &conf); err != nil { + parsed, err := parseConfig(req.PluginOptions) + if err != nil { log.Printf("failed to parse plugin options: %s", err) + } else { + conf = parsed } } @@ -1233,11 +1234,9 @@ func HashComment(s string) string { } func Generate(_ context.Context, req *plugin.GenerateRequest) (*plugin.GenerateResponse, error) { - var conf Config - if len(req.PluginOptions) > 0 { - if err := json.Unmarshal(req.PluginOptions, &conf); err != nil { - return nil, err - } + conf, err := parseConfig(req.PluginOptions) + if err != nil { + return nil, err } enums := buildEnums(conf, req) From 619f15bcf28d438d29d8db4714e6008f61dba819 Mon Sep 17 00:00:00 2001 From: Merlin Ran Date: Tue, 24 Feb 2026 20:28:11 +0000 Subject: [PATCH 16/16] fix ci --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea4c783..56df2c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: go-version: '1.23.5' - uses: sqlc-dev/setup-sqlc@v4 with: - sqlc-version: '1.29.0' + sqlc-version: '1.30.0' - run: make - run: make test - run: sqlc diff