diff --git a/README.md b/README.md index cfe68ff..e8b10df 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ asyncpg). Some options raise the minimum Python version; see each option. | `emit_str_enum` | `false` | `enum.StrEnum` (Python 3.11+) instead of `(str, enum.Enum)` | | `emit_exact_table_names` | `false` | Don't singularize table names for model classes | | `inflection_exclude_table_names` | `[]` | Table names never singularized | -| `query_parameter_limit` | `4` | Params above this become a params class; `0` always uses one | +| `query_parameter_limit` | `4` | Params above this become a keyword-only `arg` params class; `0` always uses one | | `emit_modern_types` | `false` | `X \| None`, `list[X]`, `collections.abc` (Python 3.10+) | | `emit_generic_querier` | `false` | PEP 695 generic queriers (Python 3.12+) | | `emit_aware_datetime` | `false` | `timestamptz` as `pydantic.AwareDatetime` (needs `emit_pydantic_models`) | @@ -177,7 +177,8 @@ class Querier[_ConnT: Union[sqlalchemy.engine.Connection, sqlalchemy.orm.Session Commands: `:copyfrom`, `:batchexec`, `:batchone`, `:batchmany` All four always generate a params class, whatever `query_parameter_limit` is, -and take one positional `Sequence` of it. An empty sequence runs nothing. +and take it as a keyword-only `args: Sequence[Params]`. An empty sequence runs +nothing. - `:copyfrom` runs a single `executemany` and returns the row count, as reported by the driver (some report `-1`). @@ -203,12 +204,12 @@ class CreateAuthorsParams: class Querier: # ... - def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> None: - if not arg: + def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> None: + if not args: return None self._conn.execute( sqlalchemy.text(CREATE_AUTHORS), - [{"p1": a.name, "p2": a.bio} for a in arg], + [{"p1": a.name, "p2": a.bio} for a in args], ) ``` @@ -474,15 +475,18 @@ Leaves the `# versions:` / `# sqlc vX.Y.Z` lines out of file headers. unchanged and new behaviour is opt-in. Expect diffs where upstream produced invalid code (keyword names, empty classes, enum names) or `Any` (`varchar`, `timestamp`, …), plus the annotation-only `cast()` and - `_conn: Union[...]` changes. + `_conn: Union[...]` changes. Params-class methods take a keyword-only + `arg=` instead of a positional argument. - From [asavoy/alt-sqlc-gen-python][asavoy]: - Set `emit_modern_types`, `emit_aware_datetime` and `emit_generic_querier` to get its output style; here they are opt-in. - `emit_query_errors` keeps the same class names, constructor, `query_name` and `cause`; `constraint_name` is new. - `overrides` accepts its `py_type` + `py_import` form unchanged. - - `:batchexec` takes one positional `arg: Sequence[Params]` instead of a - keyword-only `args: list[Params]`. + - `:batchexec` (like `:copyfrom`, `:batchone` and `:batchmany`) takes a + keyword-only `args: Sequence[Params]` instead of `args: list[Params]`. + - Other params-class methods take a keyword-only `arg=` instead of a + positional argument. ## Development diff --git a/examples/src/booktest/query.py b/examples/src/booktest/query.py index 21ce54a..b4bb71d 100644 --- a/examples/src/booktest/query.py +++ b/examples/src/booktest/query.py @@ -151,7 +151,7 @@ async def create_author(self, *, name: str) -> Optional[models.Author]: name=cast(str, row[1]), ) - async def create_book(self, arg: CreateBookParams) -> Optional[models.Book]: + async def create_book(self, *, arg: CreateBookParams) -> Optional[models.Book]: row = (await self._conn.execute(sqlalchemy.text(CREATE_BOOK), { "p1": arg.author_id, "p2": arg.isbn, diff --git a/examples/src/library/query.py b/examples/src/library/query.py index 418c151..f4f653f 100644 --- a/examples/src/library/query.py +++ b/examples/src/library/query.py @@ -109,21 +109,21 @@ def create_author(self, *, name: str, bio: Optional[str]) -> Optional[models.Aut def create_author_at(self, *, name: str, created_at: datetime.datetime) -> Optional[models.Author]: ... - def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: ... + def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: ... def create_book(self, *, author_id: int, title: str) -> Optional[models.Book]: ... - def create_books(self, arg: Sequence[CreateBooksParams]) -> None: ... + def create_books(self, *, args: Sequence[CreateBooksParams]) -> None: ... - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: ... + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: ... def get_book_with_author(self, *, id: int) -> Optional[GetBookWithAuthorRow]: ... - def insert_authors_returning(self, arg: Sequence[InsertAuthorsReturningParams]) -> Iterator[Optional[models.Author]]: ... + def insert_authors_returning(self, *, args: Sequence[InsertAuthorsReturningParams]) -> Iterator[Optional[models.Author]]: ... def list_authors(self) -> Iterator[models.Author]: ... - def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> Iterator[List[models.Book]]: ... + def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> Iterator[List[models.Book]]: ... def set_book_title(self, *, id: int, title: str) -> None: ... @@ -133,21 +133,21 @@ async def create_author(self, *, name: str, bio: Optional[str]) -> Optional[mode async def create_author_at(self, *, name: str, created_at: datetime.datetime) -> Optional[models.Author]: ... - async def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: ... + async def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: ... async def create_book(self, *, author_id: int, title: str) -> Optional[models.Book]: ... - async def create_books(self, arg: Sequence[CreateBooksParams]) -> None: ... + async def create_books(self, *, args: Sequence[CreateBooksParams]) -> None: ... - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[Optional[models.Author]]: ... + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[Optional[models.Author]]: ... async def get_book_with_author(self, *, id: int) -> Optional[GetBookWithAuthorRow]: ... - def insert_authors_returning(self, arg: Sequence[InsertAuthorsReturningParams]) -> AsyncIterator[Optional[models.Author]]: ... + def insert_authors_returning(self, *, args: Sequence[InsertAuthorsReturningParams]) -> AsyncIterator[Optional[models.Author]]: ... def list_authors(self) -> AsyncIterator[models.Author]: ... - def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[List[models.Book]]: ... + def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[List[models.Book]]: ... async def set_book_title(self, *, id: int, title: str) -> None: ... @@ -183,11 +183,11 @@ def create_author_at(self, *, name: str, created_at: datetime.datetime) -> Optio created_at=cast(datetime.datetime, row[3]), ) - def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: + def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: with errors._wrap_errors("create_authors"): - if not arg: + if not args: return 0 - result = self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in arg]) + result = self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in args]) return cast("sqlalchemy.engine.CursorResult[Any]", result).rowcount def create_book(self, *, author_id: int, title: str) -> Optional[models.Book]: @@ -202,15 +202,15 @@ def create_book(self, *, author_id: int, title: str) -> Optional[models.Book]: status=cast(models.BookStatus, row[3]), ) - def create_books(self, arg: Sequence[CreateBooksParams]) -> None: + def create_books(self, *, args: Sequence[CreateBooksParams]) -> None: with errors._wrap_errors("create_books"): - if not arg: + if not args: return None - self._conn.execute(sqlalchemy.text(CREATE_BOOKS), [{"p1": a.author_id, "p2": a.title, "p3": a.status} for a in arg]) + self._conn.execute(sqlalchemy.text(CREATE_BOOKS), [{"p1": a.author_id, "p2": a.title, "p3": a.status} for a in args]) - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: with errors._wrap_errors("get_authors_by_id"): - for a in arg: + for a in args: row = self._conn.execute(sqlalchemy.text(GET_AUTHORS_BY_ID), {"p1": a.id}).first() if row is None: yield None @@ -242,9 +242,9 @@ def get_book_with_author(self, *, id: int) -> Optional[GetBookWithAuthorRow]: ), ) - def insert_authors_returning(self, arg: Sequence[InsertAuthorsReturningParams]) -> Iterator[Optional[models.Author]]: + def insert_authors_returning(self, *, args: Sequence[InsertAuthorsReturningParams]) -> Iterator[Optional[models.Author]]: with errors._wrap_errors("insert_authors_returning"): - for a in arg: + for a in args: row = self._conn.execute(sqlalchemy.text(INSERT_AUTHORS_RETURNING), {"p1": a.name}).first() if row is None: yield None @@ -267,9 +267,9 @@ def list_authors(self) -> Iterator[models.Author]: created_at=cast(datetime.datetime, row[3]), ) - def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> Iterator[List[models.Book]]: + def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> Iterator[List[models.Book]]: with errors._wrap_errors("list_books_by_author"): - for a in arg: + for a in args: yield [models.Book( id=cast(int, row[0]), author_id=cast(int, row[1]), @@ -313,11 +313,11 @@ async def create_author_at(self, *, name: str, created_at: datetime.datetime) -> created_at=cast(datetime.datetime, row[3]), ) - async def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: + async def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: with errors._wrap_errors("create_authors"): - if not arg: + if not args: return 0 - result = await self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in arg]) + result = await self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in args]) return cast("sqlalchemy.engine.CursorResult[Any]", result).rowcount async def create_book(self, *, author_id: int, title: str) -> Optional[models.Book]: @@ -332,15 +332,15 @@ async def create_book(self, *, author_id: int, title: str) -> Optional[models.Bo status=cast(models.BookStatus, row[3]), ) - async def create_books(self, arg: Sequence[CreateBooksParams]) -> None: + async def create_books(self, *, args: Sequence[CreateBooksParams]) -> None: with errors._wrap_errors("create_books"): - if not arg: + if not args: return None - await self._conn.execute(sqlalchemy.text(CREATE_BOOKS), [{"p1": a.author_id, "p2": a.title, "p3": a.status} for a in arg]) + await self._conn.execute(sqlalchemy.text(CREATE_BOOKS), [{"p1": a.author_id, "p2": a.title, "p3": a.status} for a in args]) - async def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[Optional[models.Author]]: + async def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[Optional[models.Author]]: with errors._wrap_errors("get_authors_by_id"): - for a in arg: + for a in args: row = (await self._conn.execute(sqlalchemy.text(GET_AUTHORS_BY_ID), {"p1": a.id})).first() if row is None: yield None @@ -372,9 +372,9 @@ async def get_book_with_author(self, *, id: int) -> Optional[GetBookWithAuthorRo ), ) - async def insert_authors_returning(self, arg: Sequence[InsertAuthorsReturningParams]) -> AsyncIterator[Optional[models.Author]]: + async def insert_authors_returning(self, *, args: Sequence[InsertAuthorsReturningParams]) -> AsyncIterator[Optional[models.Author]]: with errors._wrap_errors("insert_authors_returning"): - for a in arg: + for a in args: row = (await self._conn.execute(sqlalchemy.text(INSERT_AUTHORS_RETURNING), {"p1": a.name})).first() if row is None: yield None @@ -397,9 +397,9 @@ async def list_authors(self) -> AsyncIterator[models.Author]: created_at=cast(datetime.datetime, row[3]), ) - async def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[List[models.Book]]: + async def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[List[models.Book]]: with errors._wrap_errors("list_books_by_author"): - for a in arg: + for a in args: yield [models.Book( id=cast(int, row[0]), author_id=cast(int, row[1]), diff --git a/examples/src/library_modern/query.py b/examples/src/library_modern/query.py index 0985dee..e6edb40 100644 --- a/examples/src/library_modern/query.py +++ b/examples/src/library_modern/query.py @@ -103,21 +103,21 @@ def create_author(self, *, name: str, bio: str | None) -> models.Author | None: def create_author_at(self, *, name: str, created_at: pydantic.AwareDatetime) -> models.Author | None: ... - def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: ... + def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: ... def create_book(self, *, author_id: int, title: str) -> models.Book | None: ... - def create_books(self, arg: Sequence[CreateBooksParams]) -> None: ... + def create_books(self, *, args: Sequence[CreateBooksParams]) -> None: ... - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> Iterator[models.Author | None]: ... + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> Iterator[models.Author | None]: ... def get_book_with_author(self, *, id: int) -> GetBookWithAuthorRow | None: ... - def insert_authors_returning(self, arg: Sequence[InsertAuthorsReturningParams]) -> Iterator[models.Author | None]: ... + def insert_authors_returning(self, *, args: Sequence[InsertAuthorsReturningParams]) -> Iterator[models.Author | None]: ... def list_authors(self) -> Iterator[models.Author]: ... - def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> Iterator[list[models.Book]]: ... + def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> Iterator[list[models.Book]]: ... def set_book_title(self, *, id: int, title: str) -> None: ... @@ -127,21 +127,21 @@ async def create_author(self, *, name: str, bio: str | None) -> models.Author | async def create_author_at(self, *, name: str, created_at: pydantic.AwareDatetime) -> models.Author | None: ... - async def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: ... + async def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: ... async def create_book(self, *, author_id: int, title: str) -> models.Book | None: ... - async def create_books(self, arg: Sequence[CreateBooksParams]) -> None: ... + async def create_books(self, *, args: Sequence[CreateBooksParams]) -> None: ... - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[models.Author | None]: ... + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[models.Author | None]: ... async def get_book_with_author(self, *, id: int) -> GetBookWithAuthorRow | None: ... - def insert_authors_returning(self, arg: Sequence[InsertAuthorsReturningParams]) -> AsyncIterator[models.Author | None]: ... + def insert_authors_returning(self, *, args: Sequence[InsertAuthorsReturningParams]) -> AsyncIterator[models.Author | None]: ... def list_authors(self) -> AsyncIterator[models.Author]: ... - def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[list[models.Book]]: ... + def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[list[models.Book]]: ... async def set_book_title(self, *, id: int, title: str) -> None: ... @@ -177,11 +177,11 @@ def create_author_at(self, *, name: str, created_at: pydantic.AwareDatetime) -> created_at=cast(pydantic.AwareDatetime, row[3]), ) - def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: + def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: with errors._wrap_errors("create_authors"): - if not arg: + if not args: return 0 - result = self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in arg]) + result = self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in args]) return cast("sqlalchemy.engine.CursorResult[Any]", result).rowcount def create_book(self, *, author_id: int, title: str) -> models.Book | None: @@ -196,15 +196,15 @@ def create_book(self, *, author_id: int, title: str) -> models.Book | None: status=cast(models.BookStatus, row[3]), ) - def create_books(self, arg: Sequence[CreateBooksParams]) -> None: + def create_books(self, *, args: Sequence[CreateBooksParams]) -> None: with errors._wrap_errors("create_books"): - if not arg: + if not args: return None - self._conn.execute(sqlalchemy.text(CREATE_BOOKS), [{"p1": a.author_id, "p2": a.title, "p3": a.status} for a in arg]) + self._conn.execute(sqlalchemy.text(CREATE_BOOKS), [{"p1": a.author_id, "p2": a.title, "p3": a.status} for a in args]) - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> Iterator[models.Author | None]: + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> Iterator[models.Author | None]: with errors._wrap_errors("get_authors_by_id"): - for a in arg: + for a in args: row = self._conn.execute(sqlalchemy.text(GET_AUTHORS_BY_ID), {"p1": a.id}).first() if row is None: yield None @@ -236,9 +236,9 @@ def get_book_with_author(self, *, id: int) -> GetBookWithAuthorRow | None: ), ) - def insert_authors_returning(self, arg: Sequence[InsertAuthorsReturningParams]) -> Iterator[models.Author | None]: + def insert_authors_returning(self, *, args: Sequence[InsertAuthorsReturningParams]) -> Iterator[models.Author | None]: with errors._wrap_errors("insert_authors_returning"): - for a in arg: + for a in args: row = self._conn.execute(sqlalchemy.text(INSERT_AUTHORS_RETURNING), {"p1": a.name}).first() if row is None: yield None @@ -261,9 +261,9 @@ def list_authors(self) -> Iterator[models.Author]: created_at=cast(pydantic.AwareDatetime, row[3]), ) - def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> Iterator[list[models.Book]]: + def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> Iterator[list[models.Book]]: with errors._wrap_errors("list_books_by_author"): - for a in arg: + for a in args: yield [models.Book( id=cast(int, row[0]), author_id=cast(int, row[1]), @@ -307,11 +307,11 @@ async def create_author_at(self, *, name: str, created_at: pydantic.AwareDatetim created_at=cast(pydantic.AwareDatetime, row[3]), ) - async def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: + async def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: with errors._wrap_errors("create_authors"): - if not arg: + if not args: return 0 - result = await self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in arg]) + result = await self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in args]) return cast("sqlalchemy.engine.CursorResult[Any]", result).rowcount async def create_book(self, *, author_id: int, title: str) -> models.Book | None: @@ -326,15 +326,15 @@ async def create_book(self, *, author_id: int, title: str) -> models.Book | None status=cast(models.BookStatus, row[3]), ) - async def create_books(self, arg: Sequence[CreateBooksParams]) -> None: + async def create_books(self, *, args: Sequence[CreateBooksParams]) -> None: with errors._wrap_errors("create_books"): - if not arg: + if not args: return None - await self._conn.execute(sqlalchemy.text(CREATE_BOOKS), [{"p1": a.author_id, "p2": a.title, "p3": a.status} for a in arg]) + await self._conn.execute(sqlalchemy.text(CREATE_BOOKS), [{"p1": a.author_id, "p2": a.title, "p3": a.status} for a in args]) - async def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[models.Author | None]: + async def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[models.Author | None]: with errors._wrap_errors("get_authors_by_id"): - for a in arg: + for a in args: row = (await self._conn.execute(sqlalchemy.text(GET_AUTHORS_BY_ID), {"p1": a.id})).first() if row is None: yield None @@ -366,9 +366,9 @@ async def get_book_with_author(self, *, id: int) -> GetBookWithAuthorRow | None: ), ) - async def insert_authors_returning(self, arg: Sequence[InsertAuthorsReturningParams]) -> AsyncIterator[models.Author | None]: + async def insert_authors_returning(self, *, args: Sequence[InsertAuthorsReturningParams]) -> AsyncIterator[models.Author | None]: with errors._wrap_errors("insert_authors_returning"): - for a in arg: + for a in args: row = (await self._conn.execute(sqlalchemy.text(INSERT_AUTHORS_RETURNING), {"p1": a.name})).first() if row is None: yield None @@ -391,9 +391,9 @@ async def list_authors(self) -> AsyncIterator[models.Author]: created_at=cast(pydantic.AwareDatetime, row[3]), ) - async def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[list[models.Book]]: + async def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[list[models.Book]]: with errors._wrap_errors("list_books_by_author"): - for a in arg: + for a in args: yield [models.Book( id=cast(int, row[0]), author_id=cast(int, row[1]), diff --git a/examples/src/ondeck/venue.py b/examples/src/ondeck/venue.py index 28157ef..88a5d55 100644 --- a/examples/src/ondeck/venue.py +++ b/examples/src/ondeck/venue.py @@ -97,7 +97,7 @@ class AsyncQuerier: def __init__(self, conn: Union[sqlalchemy.ext.asyncio.AsyncConnection, sqlalchemy.ext.asyncio.AsyncSession]): self._conn = conn - async def create_venue(self, arg: CreateVenueParams) -> Optional[int]: + async def create_venue(self, *, arg: CreateVenueParams) -> Optional[int]: row = (await self._conn.execute(sqlalchemy.text(CREATE_VENUE), { "p1": arg.slug, "p2": arg.name, diff --git a/examples/src/tests/test_booktest.py b/examples/src/tests/test_booktest.py index 19959ae..e508cd2 100644 --- a/examples/src/tests/test_booktest.py +++ b/examples/src/tests/test_booktest.py @@ -18,7 +18,7 @@ async def test_books(async_db: sqlalchemy.ext.asyncio.AsyncConnection): assert author is not None now = datetime.datetime.now() - await querier.create_book(query.CreateBookParams( + await querier.create_book(arg=query.CreateBookParams( author_id=author.author_id, isbn="1", title="my book title", @@ -28,7 +28,7 @@ async def test_books(async_db: sqlalchemy.ext.asyncio.AsyncConnection): tags=[], )) - b1 = await querier.create_book(query.CreateBookParams( + b1 = await querier.create_book(arg=query.CreateBookParams( author_id=author.author_id, isbn="2", title="the second book", @@ -40,7 +40,7 @@ async def test_books(async_db: sqlalchemy.ext.asyncio.AsyncConnection): await querier.update_book(book_id=b1.book_id, title="changed second title", tags=["cool", "disastor"]) - b3 = await querier.create_book(query.CreateBookParams( + b3 = await querier.create_book(arg=query.CreateBookParams( author_id=author.author_id, isbn="3", title="the third book", @@ -50,7 +50,7 @@ async def test_books(async_db: sqlalchemy.ext.asyncio.AsyncConnection): tags=["cool"], )) - b4 = await querier.create_book(query.CreateBookParams( + b4 = await querier.create_book(arg=query.CreateBookParams( author_id=author.author_id, isbn="4", title="4th place finisher", diff --git a/examples/src/tests/test_library.py b/examples/src/tests/test_library.py index ede6678..9983688 100644 --- a/examples/src/tests/test_library.py +++ b/examples/src/tests/test_library.py @@ -41,18 +41,18 @@ async def aq(async_db: sqlalchemy.ext.asyncio.AsyncConnection) -> query.AsyncQue def test_copyfrom_and_batchexec_use_one_executemany(db, q): log = StatementLog(db) - assert q.create_authors([]) == 0 - q.create_books([]) + assert q.create_authors(args=[]) == 0 + q.create_books(args=[]) assert log.statements == [] - count = q.create_authors([ + count = q.create_authors(args=[ query.CreateAuthorsParams(name="Ursula", bio=None), query.CreateAuthorsParams(name="Octavia", bio="Kindred"), ]) assert count == 2 authors = {a.name: a for a in q.list_authors()} log.statements.clear() - q.create_books([ + q.create_books(args=[ query.CreateBooksParams(author_id=authors["Ursula"].id, title="The Dispossessed", status=models.BookStatus.IN_PRINT), query.CreateBooksParams(author_id=authors["Octavia"].id, title="Kindred", status=models.BookStatus.DRAFT), ]) @@ -65,7 +65,7 @@ def test_batchone_is_lazy_and_ordered(db, q): assert a is not None and b is not None log = StatementLog(db) - results = q.get_authors_by_id([ + results = q.get_authors_by_id(args=[ query.GetAuthorsByIDParams(id=a.id), query.GetAuthorsByIDParams(id=-1), query.GetAuthorsByIDParams(id=b.id), @@ -84,7 +84,7 @@ def test_batchmany_yields_a_list_per_input(q): first = q.create_book(author_id=a.id, title="The Dispossessed") second = q.create_book(author_id=a.id, title="The Lathe of Heaven") - results = list(q.list_books_by_author([ + results = list(q.list_books_by_author(args=[ query.ListBooksByAuthorParams(author_id=a.id), query.ListBooksByAuthorParams(author_id=b.id), ])) @@ -134,11 +134,11 @@ def test_typed_errors(db, q): with pytest.raises(errors.QueryError): with db.begin_nested(): - q.create_authors([query.CreateAuthorsParams(name="Ursula", bio=None)]) + q.create_authors(args=[query.CreateAuthorsParams(name="Ursula", bio=None)]) def test_error_while_iterating_is_wrapped(db, q): - results = q.insert_authors_returning([ + results = q.insert_authors_returning(args=[ query.InsertAuthorsReturningParams(name="Ursula"), query.InsertAuthorsReturningParams(name="Ursula"), ]) @@ -155,7 +155,7 @@ async def test_async_error_while_iterating_is_wrapped(async_db, aq): names = [query.InsertAuthorsReturningParams(name="Ursula")] * 2 with pytest.raises(errors.UniqueViolationError): async with async_db.begin_nested(): - async for _ in aq.insert_authors_returning(names): + async for _ in aq.insert_authors_returning(args=names): pass @@ -163,17 +163,17 @@ def test_other_errors_pass_through(db, q): with pytest.raises(sqlalchemy.exc.ProgrammingError): with db.begin_nested(): db.execute(sqlalchemy.text("DROP TABLE books")) - list(q.list_books_by_author([query.ListBooksByAuthorParams(author_id=1)])) + list(q.list_books_by_author(args=[query.ListBooksByAuthorParams(author_id=1)])) async def test_async_batches_and_errors(async_db, aq): a = await aq.create_author(name="Ursula", bio=None) assert a is not None - assert await aq.create_authors([query.CreateAuthorsParams(name="Octavia", bio=None)]) == 1 + assert await aq.create_authors(args=[query.CreateAuthorsParams(name="Octavia", bio=None)]) == 1 - found = [x async for x in aq.get_authors_by_id([query.GetAuthorsByIDParams(id=a.id), query.GetAuthorsByIDParams(id=-1)])] + found = [x async for x in aq.get_authors_by_id(args=[query.GetAuthorsByIDParams(id=a.id), query.GetAuthorsByIDParams(id=-1)])] assert found == [a, None] - books = [x async for x in aq.list_books_by_author([query.ListBooksByAuthorParams(author_id=a.id)])] + books = [x async for x in aq.list_books_by_author(args=[query.ListBooksByAuthorParams(author_id=a.id)])] assert books == [[]] with pytest.raises(errors.UniqueViolationError) as unique: @@ -183,7 +183,7 @@ async def test_async_batches_and_errors(async_db, aq): with pytest.raises(errors.ForeignKeyViolationError) as fk: async with async_db.begin_nested(): - await aq.create_books([query.CreateBooksParams(author_id=-1, title="Orphan", status=models.BookStatus.DRAFT)]) + await aq.create_books(args=[query.CreateBooksParams(author_id=-1, title="Orphan", status=models.BookStatus.DRAFT)]) assert fk.value.constraint_name == "books_author_id_fkey" with pytest.raises(errors.NotNullViolationError): @@ -197,9 +197,9 @@ def test_session_backed_querier(db): q = query.Querier(session) a = q.create_author(name="Ursula", bio=None) assert a is not None - assert q.create_authors([query.CreateAuthorsParams(name="Octavia", bio=None)]) == 1 + assert q.create_authors(args=[query.CreateAuthorsParams(name="Octavia", bio=None)]) == 1 assert [x.name for x in q.list_authors()] == ["Octavia", "Ursula"] - assert list(q.get_authors_by_id([query.GetAuthorsByIDParams(id=a.id)])) == [a] + assert list(q.get_authors_by_id(args=[query.GetAuthorsByIDParams(id=a.id)])) == [a] class FakeQuerier: @@ -217,12 +217,12 @@ def create_author_at(self, *, name: str, created_at: datetime.datetime) -> Optio def list_authors(self) -> Iterator[models.Author]: yield from sorted(self.authors.values(), key=lambda a: a.name) - def create_authors(self, arg: Sequence[query.CreateAuthorsParams]) -> int: - for a in arg: + def create_authors(self, *, args: Sequence[query.CreateAuthorsParams]) -> int: + for a in args: self.create_author(name=a.name, bio=a.bio) - return len(arg) + return len(args) - def create_books(self, arg: Sequence[query.CreateBooksParams]) -> None: + def create_books(self, *, args: Sequence[query.CreateBooksParams]) -> None: raise NotImplementedError def create_book(self, *, author_id: int, title: str) -> Optional[models.Book]: @@ -231,18 +231,18 @@ def create_book(self, *, author_id: int, title: str) -> Optional[models.Book]: def set_book_title(self, *, id: int, title: str) -> None: raise NotImplementedError - def get_authors_by_id(self, arg: Sequence[query.GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: - for a in arg: + def get_authors_by_id(self, *, args: Sequence[query.GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: + for a in args: yield self.authors.get(a.id) - def list_books_by_author(self, arg: Sequence[query.ListBooksByAuthorParams]) -> Iterator[List[models.Book]]: - for _ in arg: + def list_books_by_author(self, *, args: Sequence[query.ListBooksByAuthorParams]) -> Iterator[List[models.Book]]: + for _ in args: yield [] def get_book_with_author(self, *, id: int) -> Optional[query.GetBookWithAuthorRow]: return None - def insert_authors_returning(self, arg: Sequence[query.InsertAuthorsReturningParams]) -> Iterator[Optional[models.Author]]: + def insert_authors_returning(self, *, args: Sequence[query.InsertAuthorsReturningParams]) -> Iterator[Optional[models.Author]]: raise NotImplementedError @@ -252,7 +252,7 @@ def author_names(q: query.QuerierProtocol) -> List[str]: def test_fake_satisfies_protocol(): fake = FakeQuerier() - fake.create_authors([query.CreateAuthorsParams(name="b", bio=None), query.CreateAuthorsParams(name="a", bio=None)]) + fake.create_authors(args=[query.CreateAuthorsParams(name="b", bio=None), query.CreateAuthorsParams(name="a", bio=None)]) assert author_names(fake) == ["a", "b"] diff --git a/examples/src/tests/test_ondeck.py b/examples/src/tests/test_ondeck.py index 20185a8..55492cb 100644 --- a/examples/src/tests/test_ondeck.py +++ b/examples/src/tests/test_ondeck.py @@ -19,7 +19,7 @@ async def test_ondeck(async_db: sqlalchemy.ext.asyncio.AsyncConnection): city = await city_querier.create_city(slug="san-francisco", name="San Francisco") assert city is not None - venue_id = await venue_querier.create_venue(venue_queries.CreateVenueParams( + venue_id = await venue_querier.create_venue(arg=venue_queries.CreateVenueParams( slug="the-fillmore", name="The Fillmore", city=city.slug, diff --git a/internal/endtoend/testdata/batch_exec/db/query.py b/internal/endtoend/testdata/batch_exec/db/query.py index 06df908..df2177a 100644 --- a/internal/endtoend/testdata/batch_exec/db/query.py +++ b/internal/endtoend/testdata/batch_exec/db/query.py @@ -39,15 +39,15 @@ class Querier: def __init__(self, conn: Union[sqlalchemy.engine.Connection, sqlalchemy.orm.Session]): self._conn = conn - def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> None: - if not arg: + def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> None: + if not args: return None - self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in arg]) + self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in args]) - def delete_authors(self, arg: Sequence[DeleteAuthorsParams]) -> None: - if not arg: + def delete_authors(self, *, args: Sequence[DeleteAuthorsParams]) -> None: + if not args: return None - self._conn.execute(sqlalchemy.text(DELETE_AUTHORS), [{"p1": a.id} for a in arg]) + self._conn.execute(sqlalchemy.text(DELETE_AUTHORS), [{"p1": a.id} for a in args]) class AsyncQuerier: @@ -56,12 +56,12 @@ class AsyncQuerier: def __init__(self, conn: Union[sqlalchemy.ext.asyncio.AsyncConnection, sqlalchemy.ext.asyncio.AsyncSession]): self._conn = conn - async def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> None: - if not arg: + async def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> None: + if not args: return None - await self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in arg]) + await self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in args]) - async def delete_authors(self, arg: Sequence[DeleteAuthorsParams]) -> None: - if not arg: + async def delete_authors(self, *, args: Sequence[DeleteAuthorsParams]) -> None: + if not args: return None - await self._conn.execute(sqlalchemy.text(DELETE_AUTHORS), [{"p1": a.id} for a in arg]) + await self._conn.execute(sqlalchemy.text(DELETE_AUTHORS), [{"p1": a.id} for a in args]) diff --git a/internal/endtoend/testdata/batch_exec/sqlc.yaml b/internal/endtoend/testdata/batch_exec/sqlc.yaml index 819e198..f2a0004 100644 --- a/internal/endtoend/testdata/batch_exec/sqlc.yaml +++ b/internal/endtoend/testdata/batch_exec/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/batch_one_many/classic/query.py b/internal/endtoend/testdata/batch_one_many/classic/query.py index 6bcff2c..aaa11a9 100644 --- a/internal/endtoend/testdata/batch_one_many/classic/query.py +++ b/internal/endtoend/testdata/batch_one_many/classic/query.py @@ -65,16 +65,16 @@ class Querier: def __init__(self, conn: Union[sqlalchemy.engine.Connection, sqlalchemy.orm.Session]): self._conn = conn - def get_author_names(self, arg: Sequence[GetAuthorNamesParams]) -> Iterator[Optional[str]]: - for a in arg: + def get_author_names(self, *, args: Sequence[GetAuthorNamesParams]) -> Iterator[Optional[str]]: + for a in args: row = self._conn.execute(sqlalchemy.text(GET_AUTHOR_NAMES), {"p1": a.id}).first() if row is None: yield None else: yield cast(str, row[0]) - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: - for a in arg: + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: + for a in args: row = self._conn.execute(sqlalchemy.text(GET_AUTHORS_BY_ID), {"p1": a.id}).first() if row is None: yield None @@ -85,15 +85,15 @@ def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> Iterator[Opt bio=cast(Optional[str], row[2]), ) - def list_book_titles(self, arg: Sequence[ListBookTitlesParams]) -> Iterator[List[ListBookTitlesRow]]: - for a in arg: + def list_book_titles(self, *, args: Sequence[ListBookTitlesParams]) -> Iterator[List[ListBookTitlesRow]]: + for a in args: yield [ListBookTitlesRow( id=cast(int, row[0]), title=cast(str, row[1]), ) for row in self._conn.execute(sqlalchemy.text(LIST_BOOK_TITLES), {"p1": a.author_id, "p2": a.title})] - def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> Iterator[List[models.Book]]: - for a in arg: + def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> Iterator[List[models.Book]]: + for a in args: yield [models.Book( id=cast(int, row[0]), author_id=cast(int, row[1]), @@ -107,16 +107,16 @@ class AsyncQuerier: def __init__(self, conn: Union[sqlalchemy.ext.asyncio.AsyncConnection, sqlalchemy.ext.asyncio.AsyncSession]): self._conn = conn - async def get_author_names(self, arg: Sequence[GetAuthorNamesParams]) -> AsyncIterator[Optional[str]]: - for a in arg: + async def get_author_names(self, *, args: Sequence[GetAuthorNamesParams]) -> AsyncIterator[Optional[str]]: + for a in args: row = (await self._conn.execute(sqlalchemy.text(GET_AUTHOR_NAMES), {"p1": a.id})).first() if row is None: yield None else: yield cast(str, row[0]) - async def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[Optional[models.Author]]: - for a in arg: + async def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[Optional[models.Author]]: + for a in args: row = (await self._conn.execute(sqlalchemy.text(GET_AUTHORS_BY_ID), {"p1": a.id})).first() if row is None: yield None @@ -127,15 +127,15 @@ async def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> AsyncI bio=cast(Optional[str], row[2]), ) - async def list_book_titles(self, arg: Sequence[ListBookTitlesParams]) -> AsyncIterator[List[ListBookTitlesRow]]: - for a in arg: + async def list_book_titles(self, *, args: Sequence[ListBookTitlesParams]) -> AsyncIterator[List[ListBookTitlesRow]]: + for a in args: yield [ListBookTitlesRow( id=cast(int, row[0]), title=cast(str, row[1]), ) for row in await self._conn.execute(sqlalchemy.text(LIST_BOOK_TITLES), {"p1": a.author_id, "p2": a.title})] - async def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[List[models.Book]]: - for a in arg: + async def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[List[models.Book]]: + for a in args: yield [models.Book( id=cast(int, row[0]), author_id=cast(int, row[1]), diff --git a/internal/endtoend/testdata/batch_one_many/modern/query.py b/internal/endtoend/testdata/batch_one_many/modern/query.py index a9ee1f5..5d894da 100644 --- a/internal/endtoend/testdata/batch_one_many/modern/query.py +++ b/internal/endtoend/testdata/batch_one_many/modern/query.py @@ -66,16 +66,16 @@ class Querier: def __init__(self, conn: sqlalchemy.engine.Connection | sqlalchemy.orm.Session): self._conn = conn - def get_author_names(self, arg: Sequence[GetAuthorNamesParams]) -> Iterator[str | None]: - for a in arg: + def get_author_names(self, *, args: Sequence[GetAuthorNamesParams]) -> Iterator[str | None]: + for a in args: row = self._conn.execute(sqlalchemy.text(GET_AUTHOR_NAMES), {"p1": a.id}).first() if row is None: yield None else: yield cast(str, row[0]) - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> Iterator[models.Author | None]: - for a in arg: + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> Iterator[models.Author | None]: + for a in args: row = self._conn.execute(sqlalchemy.text(GET_AUTHORS_BY_ID), {"p1": a.id}).first() if row is None: yield None @@ -86,15 +86,15 @@ def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> Iterator[mod bio=cast(str | None, row[2]), ) - def list_book_titles(self, arg: Sequence[ListBookTitlesParams]) -> Iterator[list[ListBookTitlesRow]]: - for a in arg: + def list_book_titles(self, *, args: Sequence[ListBookTitlesParams]) -> Iterator[list[ListBookTitlesRow]]: + for a in args: yield [ListBookTitlesRow( id=cast(int, row[0]), title=cast(str, row[1]), ) for row in self._conn.execute(sqlalchemy.text(LIST_BOOK_TITLES), {"p1": a.author_id, "p2": a.title})] - def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> Iterator[list[models.Book]]: - for a in arg: + def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> Iterator[list[models.Book]]: + for a in args: yield [models.Book( id=cast(int, row[0]), author_id=cast(int, row[1]), @@ -108,16 +108,16 @@ class AsyncQuerier: def __init__(self, conn: sqlalchemy.ext.asyncio.AsyncConnection | sqlalchemy.ext.asyncio.AsyncSession): self._conn = conn - async def get_author_names(self, arg: Sequence[GetAuthorNamesParams]) -> AsyncIterator[str | None]: - for a in arg: + async def get_author_names(self, *, args: Sequence[GetAuthorNamesParams]) -> AsyncIterator[str | None]: + for a in args: row = (await self._conn.execute(sqlalchemy.text(GET_AUTHOR_NAMES), {"p1": a.id})).first() if row is None: yield None else: yield cast(str, row[0]) - async def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[models.Author | None]: - for a in arg: + async def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[models.Author | None]: + for a in args: row = (await self._conn.execute(sqlalchemy.text(GET_AUTHORS_BY_ID), {"p1": a.id})).first() if row is None: yield None @@ -128,15 +128,15 @@ async def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> AsyncI bio=cast(str | None, row[2]), ) - async def list_book_titles(self, arg: Sequence[ListBookTitlesParams]) -> AsyncIterator[list[ListBookTitlesRow]]: - for a in arg: + async def list_book_titles(self, *, args: Sequence[ListBookTitlesParams]) -> AsyncIterator[list[ListBookTitlesRow]]: + for a in args: yield [ListBookTitlesRow( id=cast(int, row[0]), title=cast(str, row[1]), ) for row in await self._conn.execute(sqlalchemy.text(LIST_BOOK_TITLES), {"p1": a.author_id, "p2": a.title})] - async def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[list[models.Book]]: - for a in arg: + async def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[list[models.Book]]: + for a in args: yield [models.Book( id=cast(int, row[0]), author_id=cast(int, row[1]), diff --git a/internal/endtoend/testdata/batch_one_many/sqlc.yaml b/internal/endtoend/testdata/batch_one_many/sqlc.yaml index 68320c3..f918331 100644 --- a/internal/endtoend/testdata/batch_one_many/sqlc.yaml +++ b/internal/endtoend/testdata/batch_one_many/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/constant_name_conflict/sqlc.yaml b/internal/endtoend/testdata/constant_name_conflict/sqlc.yaml index 4c8bb8b..f1bcd4d 100644 --- a/internal/endtoend/testdata/constant_name_conflict/sqlc.yaml +++ b/internal/endtoend/testdata/constant_name_conflict/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/copyfrom/db/query.py b/internal/endtoend/testdata/copyfrom/db/query.py index e31e6e3..8a23747 100644 --- a/internal/endtoend/testdata/copyfrom/db/query.py +++ b/internal/endtoend/testdata/copyfrom/db/query.py @@ -29,10 +29,10 @@ class Querier: def __init__(self, conn: Union[sqlalchemy.engine.Connection, sqlalchemy.orm.Session]): self._conn = conn - def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: - if not arg: + def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: + if not args: return 0 - result = self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in arg]) + result = self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in args]) return cast("sqlalchemy.engine.CursorResult[Any]", result).rowcount @@ -42,8 +42,8 @@ class AsyncQuerier: def __init__(self, conn: Union[sqlalchemy.ext.asyncio.AsyncConnection, sqlalchemy.ext.asyncio.AsyncSession]): self._conn = conn - async def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: - if not arg: + async def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: + if not args: return 0 - result = await self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in arg]) + result = await self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in args]) return cast("sqlalchemy.engine.CursorResult[Any]", result).rowcount diff --git a/internal/endtoend/testdata/copyfrom/sqlc.yaml b/internal/endtoend/testdata/copyfrom/sqlc.yaml index 819e198..f2a0004 100644 --- a/internal/endtoend/testdata/copyfrom/sqlc.yaml +++ b/internal/endtoend/testdata/copyfrom/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_aware_datetime/sqlc.yaml b/internal/endtoend/testdata/emit_aware_datetime/sqlc.yaml index b83b8a3..09a4fb9 100644 --- a/internal/endtoend/testdata/emit_aware_datetime/sqlc.yaml +++ b/internal/endtoend/testdata/emit_aware_datetime/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_aware_datetime_no_pydantic/sqlc.yaml b/internal/endtoend/testdata/emit_aware_datetime_no_pydantic/sqlc.yaml index 7332b66..33cc01a 100644 --- a/internal/endtoend/testdata/emit_aware_datetime_no_pydantic/sqlc.yaml +++ b/internal/endtoend/testdata/emit_aware_datetime_no_pydantic/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_generic_querier/sqlc.yaml b/internal/endtoend/testdata/emit_generic_querier/sqlc.yaml index 6fa44a2..2147f78 100644 --- a/internal/endtoend/testdata/emit_generic_querier/sqlc.yaml +++ b/internal/endtoend/testdata/emit_generic_querier/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_modern_types/sqlc.yaml b/internal/endtoend/testdata/emit_modern_types/sqlc.yaml index 61873d5..565bfb7 100644 --- a/internal/endtoend/testdata/emit_modern_types/sqlc.yaml +++ b/internal/endtoend/testdata/emit_modern_types/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" 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 b592da4..1445fe3 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: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_querier_protocol/_check.py b/internal/endtoend/testdata/emit_querier_protocol/_check.py index 3dcf901..a00123f 100644 --- a/internal/endtoend/testdata/emit_querier_protocol/_check.py +++ b/internal/endtoend/testdata/emit_querier_protocol/_check.py @@ -16,11 +16,11 @@ class FakeQuerier: def __init__(self) -> None: self.authors: dict[int, models.Author] = {} - def create_authors(self, arg: Sequence[query.CreateAuthorsParams]) -> int: - for a in arg: + def create_authors(self, *, args: Sequence[query.CreateAuthorsParams]) -> int: + for a in args: id = len(self.authors) + 1 self.authors[id] = models.Author(id=id, name=a.name, bio=a.bio) - return len(arg) + return len(args) def delete_author(self, *, id: int) -> None: self.authors.pop(id, None) @@ -28,15 +28,15 @@ def delete_author(self, *, id: int) -> None: def get_author(self, *, id: int) -> Optional[models.Author]: return self.authors.get(id) - def get_authors_by_id(self, arg: Sequence[query.GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: - for a in arg: + def get_authors_by_id(self, *, args: Sequence[query.GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: + for a in args: yield self.authors.get(a.id) def list_authors(self) -> Iterator[models.Author]: yield from self.authors.values() - def list_books_by_author(self, arg: Sequence[query.ListBooksByAuthorParams]) -> Iterator[list[models.Book]]: - for _ in arg: + def list_books_by_author(self, *, args: Sequence[query.ListBooksByAuthorParams]) -> Iterator[list[models.Book]]: + for _ in args: yield [] diff --git a/internal/endtoend/testdata/emit_querier_protocol/db/query.py b/internal/endtoend/testdata/emit_querier_protocol/db/query.py index 9989e2b..8570e5c 100644 --- a/internal/endtoend/testdata/emit_querier_protocol/db/query.py +++ b/internal/endtoend/testdata/emit_querier_protocol/db/query.py @@ -59,31 +59,31 @@ class ListBooksByAuthorParams: class QuerierProtocol(Protocol): - def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: ... + def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: ... def delete_author(self, *, id: int) -> None: ... def get_author(self, *, id: int) -> Optional[models.Author]: ... - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: ... + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: ... def list_authors(self) -> Iterator[models.Author]: ... - def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> Iterator[List[models.Book]]: ... + def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> Iterator[List[models.Book]]: ... class AsyncQuerierProtocol(Protocol): - async def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: ... + async def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: ... async def delete_author(self, *, id: int) -> None: ... async def get_author(self, *, id: int) -> Optional[models.Author]: ... - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[Optional[models.Author]]: ... + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[Optional[models.Author]]: ... def list_authors(self) -> AsyncIterator[models.Author]: ... - def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[List[models.Book]]: ... + def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[List[models.Book]]: ... class Querier: @@ -92,10 +92,10 @@ class Querier: def __init__(self, conn: Union[sqlalchemy.engine.Connection, sqlalchemy.orm.Session]): self._conn = conn - def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: - if not arg: + def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: + if not args: return 0 - result = self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in arg]) + result = self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in args]) return cast("sqlalchemy.engine.CursorResult[Any]", result).rowcount def delete_author(self, *, id: int) -> None: @@ -111,8 +111,8 @@ def get_author(self, *, id: int) -> Optional[models.Author]: bio=cast(Optional[str], row[2]), ) - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: - for a in arg: + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: + for a in args: row = self._conn.execute(sqlalchemy.text(GET_AUTHORS_BY_ID), {"p1": a.id}).first() if row is None: yield None @@ -132,8 +132,8 @@ def list_authors(self) -> Iterator[models.Author]: bio=cast(Optional[str], row[2]), ) - def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> Iterator[List[models.Book]]: - for a in arg: + def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> Iterator[List[models.Book]]: + for a in args: yield [models.Book( id=cast(int, row[0]), author_id=cast(int, row[1]), @@ -147,10 +147,10 @@ class AsyncQuerier: def __init__(self, conn: Union[sqlalchemy.ext.asyncio.AsyncConnection, sqlalchemy.ext.asyncio.AsyncSession]): self._conn = conn - async def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: - if not arg: + async def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: + if not args: return 0 - result = await self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in arg]) + result = await self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in args]) return cast("sqlalchemy.engine.CursorResult[Any]", result).rowcount async def delete_author(self, *, id: int) -> None: @@ -166,8 +166,8 @@ async def get_author(self, *, id: int) -> Optional[models.Author]: bio=cast(Optional[str], row[2]), ) - async def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[Optional[models.Author]]: - for a in arg: + async def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[Optional[models.Author]]: + for a in args: row = (await self._conn.execute(sqlalchemy.text(GET_AUTHORS_BY_ID), {"p1": a.id})).first() if row is None: yield None @@ -187,8 +187,8 @@ async def list_authors(self) -> AsyncIterator[models.Author]: bio=cast(Optional[str], row[2]), ) - async def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[List[models.Book]]: - for a in arg: + async def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[List[models.Book]]: + for a in args: yield [models.Book( id=cast(int, row[0]), author_id=cast(int, row[1]), diff --git a/internal/endtoend/testdata/emit_querier_protocol/sqlc.yaml b/internal/endtoend/testdata/emit_querier_protocol/sqlc.yaml index cb1f8e8..0b231d1 100644 --- a/internal/endtoend/testdata/emit_querier_protocol/sqlc.yaml +++ b/internal/endtoend/testdata/emit_querier_protocol/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_query_errors/classic/query.py b/internal/endtoend/testdata/emit_query_errors/classic/query.py index 427b089..0ed8b29 100644 --- a/internal/endtoend/testdata/emit_query_errors/classic/query.py +++ b/internal/endtoend/testdata/emit_query_errors/classic/query.py @@ -102,9 +102,9 @@ def delete_author(self, *, id: int) -> None: with errors._wrap_errors("delete_author"): self._conn.execute(sqlalchemy.text(DELETE_AUTHOR), {"p1": id}) - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: with errors._wrap_errors("get_authors_by_id"): - for a in arg: + for a in args: row = self._conn.execute(sqlalchemy.text(GET_AUTHORS_BY_ID), {"p1": a.id}).first() if row is None: yield None @@ -205,9 +205,9 @@ async def delete_author(self, *, id: int) -> None: with errors._wrap_errors("delete_author"): await self._conn.execute(sqlalchemy.text(DELETE_AUTHOR), {"p1": id}) - async def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[Optional[models.Author]]: + async def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[Optional[models.Author]]: with errors._wrap_errors("get_authors_by_id"): - for a in arg: + for a in args: row = (await self._conn.execute(sqlalchemy.text(GET_AUTHORS_BY_ID), {"p1": a.id})).first() if row is None: yield None diff --git a/internal/endtoend/testdata/emit_query_errors/modern/query.py b/internal/endtoend/testdata/emit_query_errors/modern/query.py index b4cd9e1..5157ac4 100644 --- a/internal/endtoend/testdata/emit_query_errors/modern/query.py +++ b/internal/endtoend/testdata/emit_query_errors/modern/query.py @@ -103,9 +103,9 @@ def delete_author(self, *, id: int) -> None: with errors._wrap_errors("delete_author"): self._conn.execute(sqlalchemy.text(DELETE_AUTHOR), {"p1": id}) - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> Iterator[models.Author | None]: + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> Iterator[models.Author | None]: with errors._wrap_errors("get_authors_by_id"): - for a in arg: + for a in args: row = self._conn.execute(sqlalchemy.text(GET_AUTHORS_BY_ID), {"p1": a.id}).first() if row is None: yield None @@ -206,9 +206,9 @@ async def delete_author(self, *, id: int) -> None: with errors._wrap_errors("delete_author"): await self._conn.execute(sqlalchemy.text(DELETE_AUTHOR), {"p1": id}) - async def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[models.Author | None]: + async def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[models.Author | None]: with errors._wrap_errors("get_authors_by_id"): - for a in arg: + for a in args: row = (await self._conn.execute(sqlalchemy.text(GET_AUTHORS_BY_ID), {"p1": a.id})).first() if row is None: yield None diff --git a/internal/endtoend/testdata/emit_query_errors/sqlc.yaml b/internal/endtoend/testdata/emit_query_errors/sqlc.yaml index ac42352..f2108bd 100644 --- a/internal/endtoend/testdata/emit_query_errors/sqlc.yaml +++ b/internal/endtoend/testdata/emit_query_errors/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_query_errors_conflict/sqlc.yaml b/internal/endtoend/testdata/emit_query_errors_conflict/sqlc.yaml index b9459f4..2f8299c 100644 --- a/internal/endtoend/testdata/emit_query_errors_conflict/sqlc.yaml +++ b/internal/endtoend/testdata/emit_query_errors_conflict/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: errors.sql diff --git a/internal/endtoend/testdata/emit_str_enum/sqlc.yaml b/internal/endtoend/testdata/emit_str_enum/sqlc.yaml index 9a068ff..432a94e 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: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/empty_params_class/db/query.py b/internal/endtoend/testdata/empty_params_class/db/query.py index 2a12edf..88ff4b8 100644 --- a/internal/endtoend/testdata/empty_params_class/db/query.py +++ b/internal/endtoend/testdata/empty_params_class/db/query.py @@ -37,13 +37,13 @@ class Querier: def __init__(self, conn: Union[sqlalchemy.engine.Connection, sqlalchemy.orm.Session]): self._conn = conn - def count_authors(self, arg: CountAuthorsParams) -> Optional[int]: + def count_authors(self, *, arg: CountAuthorsParams) -> Optional[int]: row = self._conn.execute(sqlalchemy.text(COUNT_AUTHORS)).first() if row is None: return None return cast(int, row[0]) - def list_authors(self, arg: ListAuthorsParams) -> Iterator[models.Author]: + def list_authors(self, *, arg: ListAuthorsParams) -> Iterator[models.Author]: result = self._conn.execute(sqlalchemy.text(LIST_AUTHORS)) for row in result: yield models.Author( diff --git a/internal/endtoend/testdata/empty_params_class/sqlc.yaml b/internal/endtoend/testdata/empty_params_class/sqlc.yaml index c524cbc..1daae2a 100644 --- a/internal/endtoend/testdata/empty_params_class/sqlc.yaml +++ b/internal/endtoend/testdata/empty_params_class/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/enum_member_names/sqlc.yaml b/internal/endtoend/testdata/enum_member_names/sqlc.yaml index 6eec1c3..2f080a9 100644 --- a/internal/endtoend/testdata/enum_member_names/sqlc.yaml +++ b/internal/endtoend/testdata/enum_member_names/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" 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 81613ef..0687822 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: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" 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 81613ef..0687822 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: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/feature_matrix_classic/_check.py b/internal/endtoend/testdata/feature_matrix_classic/_check.py index c5a8f6f..93a26e8 100644 --- a/internal/endtoend/testdata/feature_matrix_classic/_check.py +++ b/internal/endtoend/testdata/feature_matrix_classic/_check.py @@ -23,15 +23,15 @@ def sync(conn: sqlalchemy.engine.Connection, session: sqlalchemy.orm.Session) -> if row is not None: author: models.Author = row.authors book: models.Book = row.books - found: Iterator[Optional[models.Author]] = q.get_authors_by_id([query.GetAuthorsByIDParams(id=1)]) - books: Iterator[List[models.Book]] = q.list_books_by_author([query.ListBooksByAuthorParams(author_id=1)]) - count: int = q.create_authors([query.CreateAuthorsParams(name="b", bio=None)]) + found: Iterator[Optional[models.Author]] = q.get_authors_by_id(args=[query.GetAuthorsByIDParams(id=1)]) + books: Iterator[List[models.Book]] = q.list_books_by_author(args=[query.ListBooksByAuthorParams(author_id=1)]) + count: int = q.create_authors(args=[query.CreateAuthorsParams(name="b", bio=None)]) status: models.BookStatus = models.BookStatus.IN_PRINT async def asynchronous(conn: sqlalchemy.ext.asyncio.AsyncConnection) -> None: q: query.AsyncQuerierProtocol = query.AsyncQuerier(conn) - async for author in q.get_authors_by_id([query.GetAuthorsByIDParams(id=1)]): + async for author in q.get_authors_by_id(args=[query.GetAuthorsByIDParams(id=1)]): if author is not None: print(author.name) - await q.create_books([query.CreateBooksParams(author_id=1, title="t", status=models.BookStatus.DRAFT, from_=None)]) + await q.create_books(args=[query.CreateBooksParams(author_id=1, title="t", status=models.BookStatus.DRAFT, from_=None)]) diff --git a/internal/endtoend/testdata/feature_matrix_classic/db/query.py b/internal/endtoend/testdata/feature_matrix_classic/db/query.py index 927664e..dbbb5ab 100644 --- a/internal/endtoend/testdata/feature_matrix_classic/db/query.py +++ b/internal/endtoend/testdata/feature_matrix_classic/db/query.py @@ -115,21 +115,21 @@ def count_authors(self) -> Optional[int]: ... def create_author(self, *, name: str, bio: Optional[str]) -> Optional[models.Author]: ... - def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: ... + def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: ... - def create_books(self, arg: Sequence[CreateBooksParams]) -> None: ... + def create_books(self, *, args: Sequence[CreateBooksParams]) -> None: ... def delete_author(self, *, id: int) -> int: ... def get_author(self, *, id: int) -> Optional[models.Author]: ... - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: ... + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: ... def get_book_with_author(self, *, id: int) -> Optional[GetBookWithAuthorRow]: ... def list_authors(self) -> Iterator[models.Author]: ... - def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> Iterator[List[models.Book]]: ... + def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> Iterator[List[models.Book]]: ... def list_books_by_status(self, *, status: models.BookStatus, class_: int) -> Iterator[ListBooksByStatusRow]: ... @@ -139,21 +139,21 @@ async def count_authors(self) -> Optional[int]: ... async def create_author(self, *, name: str, bio: Optional[str]) -> Optional[models.Author]: ... - async def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: ... + async def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: ... - async def create_books(self, arg: Sequence[CreateBooksParams]) -> None: ... + async def create_books(self, *, args: Sequence[CreateBooksParams]) -> None: ... async def delete_author(self, *, id: int) -> int: ... async def get_author(self, *, id: int) -> Optional[models.Author]: ... - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[Optional[models.Author]]: ... + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[Optional[models.Author]]: ... async def get_book_with_author(self, *, id: int) -> Optional[GetBookWithAuthorRow]: ... def list_authors(self) -> AsyncIterator[models.Author]: ... - def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[List[models.Book]]: ... + def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[List[models.Book]]: ... def list_books_by_status(self, *, status: models.BookStatus, class_: int) -> AsyncIterator[ListBooksByStatusRow]: ... @@ -184,23 +184,23 @@ def create_author(self, *, name: str, bio: Optional[str]) -> Optional[models.Aut created_at=cast(datetime.datetime, row[3]), ) - def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: + def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: with errors._wrap_errors("create_authors"): - if not arg: + if not args: return 0 - result = self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in arg]) + result = self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in args]) return cast("sqlalchemy.engine.CursorResult[Any]", result).rowcount - def create_books(self, arg: Sequence[CreateBooksParams]) -> None: + def create_books(self, *, args: Sequence[CreateBooksParams]) -> None: with errors._wrap_errors("create_books"): - if not arg: + if not args: return None self._conn.execute(sqlalchemy.text(CREATE_BOOKS), [{ "p1": a.author_id, "p2": a.title, "p3": a.status, "p4": a.from_, - } for a in arg]) + } for a in args]) def delete_author(self, *, id: int) -> int: with errors._wrap_errors("delete_author"): @@ -219,10 +219,10 @@ def get_author(self, *, id: int) -> Optional[models.Author]: created_at=cast(datetime.datetime, row[3]), ) - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> Iterator[Optional[models.Author]]: """Look up several authors at once.""" with errors._wrap_errors("get_authors_by_id"): - for a in arg: + for a in args: row = self._conn.execute(sqlalchemy.text(GET_AUTHORS_BY_ID), {"p1": a.id}).first() if row is None: yield None @@ -267,9 +267,9 @@ def list_authors(self) -> Iterator[models.Author]: created_at=cast(datetime.datetime, row[3]), ) - def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> Iterator[List[models.Book]]: + def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> Iterator[List[models.Book]]: with errors._wrap_errors("list_books_by_author"): - for a in arg: + for a in args: yield [models.Book( id=cast(int, row[0]), author_id=cast(int, row[1]), @@ -317,23 +317,23 @@ async def create_author(self, *, name: str, bio: Optional[str]) -> Optional[mode created_at=cast(datetime.datetime, row[3]), ) - async def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: + async def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: with errors._wrap_errors("create_authors"): - if not arg: + if not args: return 0 - result = await self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in arg]) + result = await self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in args]) return cast("sqlalchemy.engine.CursorResult[Any]", result).rowcount - async def create_books(self, arg: Sequence[CreateBooksParams]) -> None: + async def create_books(self, *, args: Sequence[CreateBooksParams]) -> None: with errors._wrap_errors("create_books"): - if not arg: + if not args: return None await self._conn.execute(sqlalchemy.text(CREATE_BOOKS), [{ "p1": a.author_id, "p2": a.title, "p3": a.status, "p4": a.from_, - } for a in arg]) + } for a in args]) async def delete_author(self, *, id: int) -> int: with errors._wrap_errors("delete_author"): @@ -352,10 +352,10 @@ async def get_author(self, *, id: int) -> Optional[models.Author]: created_at=cast(datetime.datetime, row[3]), ) - async def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[Optional[models.Author]]: + async def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[Optional[models.Author]]: """Look up several authors at once.""" with errors._wrap_errors("get_authors_by_id"): - for a in arg: + for a in args: row = (await self._conn.execute(sqlalchemy.text(GET_AUTHORS_BY_ID), {"p1": a.id})).first() if row is None: yield None @@ -400,9 +400,9 @@ async def list_authors(self) -> AsyncIterator[models.Author]: created_at=cast(datetime.datetime, row[3]), ) - async def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[List[models.Book]]: + async def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[List[models.Book]]: with errors._wrap_errors("list_books_by_author"): - for a in arg: + for a in args: yield [models.Book( id=cast(int, row[0]), author_id=cast(int, row[1]), diff --git a/internal/endtoend/testdata/feature_matrix_classic/sqlc.yaml b/internal/endtoend/testdata/feature_matrix_classic/sqlc.yaml index c9d0ade..b925b7f 100644 --- a/internal/endtoend/testdata/feature_matrix_classic/sqlc.yaml +++ b/internal/endtoend/testdata/feature_matrix_classic/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/feature_matrix_modern/_check.py b/internal/endtoend/testdata/feature_matrix_modern/_check.py index 05d34d0..d7fd7a5 100644 --- a/internal/endtoend/testdata/feature_matrix_modern/_check.py +++ b/internal/endtoend/testdata/feature_matrix_modern/_check.py @@ -21,8 +21,8 @@ def sync(conn: sqlalchemy.engine.Connection, session: sqlalchemy.orm.Session) -> return if created is not None: assert_type(created.created_at, pydantic.AwareDatetime) - found: Iterator[models.Author | None] = q.get_authors_by_id([query.GetAuthorsByIDParams(id=1)]) - books: Iterator[list[models.Book]] = q.list_books_by_author([query.ListBooksByAuthorParams(author_id=1)]) + found: Iterator[models.Author | None] = q.get_authors_by_id(args=[query.GetAuthorsByIDParams(id=1)]) + books: Iterator[list[models.Book]] = q.list_books_by_author(args=[query.ListBooksByAuthorParams(author_id=1)]) status: models.BookStatus = models.BookStatus.OUT_OF_PRINT @@ -30,6 +30,6 @@ async def asynchronous(session: sqlalchemy.ext.asyncio.AsyncSession) -> None: q = query.AsyncQuerier(session) assert_type(q._conn, sqlalchemy.ext.asyncio.AsyncSession) p: query.AsyncQuerierProtocol = q - async for books in q.list_books_by_author([query.ListBooksByAuthorParams(author_id=1)]): + async for books in q.list_books_by_author(args=[query.ListBooksByAuthorParams(author_id=1)]): for b in books: print(b.title, b.from_) diff --git a/internal/endtoend/testdata/feature_matrix_modern/db/query.py b/internal/endtoend/testdata/feature_matrix_modern/db/query.py index 3cdeee8..a6529ae 100644 --- a/internal/endtoend/testdata/feature_matrix_modern/db/query.py +++ b/internal/endtoend/testdata/feature_matrix_modern/db/query.py @@ -135,15 +135,15 @@ def count_authors(self) -> int | None: ... def create_author(self, *, name: str, bio: str | None) -> models.Author | None: ... - def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: ... + def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: ... - def create_books(self, arg: Sequence[CreateBooksParams]) -> None: ... + def create_books(self, *, args: Sequence[CreateBooksParams]) -> None: ... def delete_author(self, *, id: int) -> int: ... def get_author(self, *, id: int) -> models.Author | None: ... - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> Iterator[models.Author | None]: ... + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> Iterator[models.Author | None]: ... def get_book_with_author(self, *, id: int) -> GetBookWithAuthorRow | None: ... @@ -153,7 +153,7 @@ def list_(self) -> Iterator[list[str] | None]: ... def list_authors(self) -> Iterator[models.Author]: ... - def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> Iterator[list[models.Book]]: ... + def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> Iterator[list[models.Book]]: ... def list_books_by_status(self, *, status: models.BookStatus, class_: int) -> Iterator[ListBooksByStatusRow]: ... @@ -169,15 +169,15 @@ async def count_authors(self) -> int | None: ... async def create_author(self, *, name: str, bio: str | None) -> models.Author | None: ... - async def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: ... + async def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: ... - async def create_books(self, arg: Sequence[CreateBooksParams]) -> None: ... + async def create_books(self, *, args: Sequence[CreateBooksParams]) -> None: ... async def delete_author(self, *, id: int) -> int: ... async def get_author(self, *, id: int) -> models.Author | None: ... - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[models.Author | None]: ... + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[models.Author | None]: ... async def get_book_with_author(self, *, id: int) -> GetBookWithAuthorRow | None: ... @@ -187,7 +187,7 @@ def list_(self) -> AsyncIterator[list[str] | None]: ... def list_authors(self) -> AsyncIterator[models.Author]: ... - def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[list[models.Book]]: ... + def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[list[models.Book]]: ... def list_books_by_status(self, *, status: models.BookStatus, class_: int) -> AsyncIterator[ListBooksByStatusRow]: ... @@ -224,23 +224,23 @@ def create_author(self, *, name: str, bio: str | None) -> models.Author | None: created_at=cast(pydantic.AwareDatetime, row[3]), ) - def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: + def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: with errors._wrap_errors("create_authors"): - if not arg: + if not args: return 0 - result = self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in arg]) + result = self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in args]) return cast("sqlalchemy.engine.CursorResult[Any]", result).rowcount - def create_books(self, arg: Sequence[CreateBooksParams]) -> None: + def create_books(self, *, args: Sequence[CreateBooksParams]) -> None: with errors._wrap_errors("create_books"): - if not arg: + if not args: return None self._conn.execute(sqlalchemy.text(CREATE_BOOKS), [{ "p1": a.author_id, "p2": a.title, "p3": a.status, "p4": a.from_, - } for a in arg]) + } for a in args]) def delete_author(self, *, id: int) -> int: with errors._wrap_errors("delete_author"): @@ -259,10 +259,10 @@ def get_author(self, *, id: int) -> models.Author | None: created_at=cast(pydantic.AwareDatetime, row[3]), ) - def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> Iterator[models.Author | None]: + def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> Iterator[models.Author | None]: """Look up several authors at once.""" with errors._wrap_errors("get_authors_by_id"): - for a in arg: + for a in args: row = self._conn.execute(sqlalchemy.text(GET_AUTHORS_BY_ID), {"p1": a.id}).first() if row is None: yield None @@ -318,9 +318,9 @@ def list_authors(self) -> Iterator[models.Author]: created_at=cast(pydantic.AwareDatetime, row[3]), ) - def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> Iterator[list[models.Book]]: + def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> Iterator[list[models.Book]]: with errors._wrap_errors("list_books_by_author"): - for a in arg: + for a in args: yield [models.Book( id=cast(int, row[0]), author_id=cast(int, row[1]), @@ -398,23 +398,23 @@ async def create_author(self, *, name: str, bio: str | None) -> models.Author | created_at=cast(pydantic.AwareDatetime, row[3]), ) - async def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int: + async def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int: with errors._wrap_errors("create_authors"): - if not arg: + if not args: return 0 - result = await self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in arg]) + result = await self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), [{"p1": a.name, "p2": a.bio} for a in args]) return cast("sqlalchemy.engine.CursorResult[Any]", result).rowcount - async def create_books(self, arg: Sequence[CreateBooksParams]) -> None: + async def create_books(self, *, args: Sequence[CreateBooksParams]) -> None: with errors._wrap_errors("create_books"): - if not arg: + if not args: return None await self._conn.execute(sqlalchemy.text(CREATE_BOOKS), [{ "p1": a.author_id, "p2": a.title, "p3": a.status, "p4": a.from_, - } for a in arg]) + } for a in args]) async def delete_author(self, *, id: int) -> int: with errors._wrap_errors("delete_author"): @@ -433,10 +433,10 @@ async def get_author(self, *, id: int) -> models.Author | None: created_at=cast(pydantic.AwareDatetime, row[3]), ) - async def get_authors_by_id(self, arg: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[models.Author | None]: + async def get_authors_by_id(self, *, args: Sequence[GetAuthorsByIDParams]) -> AsyncIterator[models.Author | None]: """Look up several authors at once.""" with errors._wrap_errors("get_authors_by_id"): - for a in arg: + for a in args: row = (await self._conn.execute(sqlalchemy.text(GET_AUTHORS_BY_ID), {"p1": a.id})).first() if row is None: yield None @@ -492,9 +492,9 @@ async def list_authors(self) -> AsyncIterator[models.Author]: created_at=cast(pydantic.AwareDatetime, row[3]), ) - async def list_books_by_author(self, arg: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[list[models.Book]]: + async def list_books_by_author(self, *, args: Sequence[ListBooksByAuthorParams]) -> AsyncIterator[list[models.Book]]: with errors._wrap_errors("list_books_by_author"): - for a in arg: + for a in args: yield [models.Book( id=cast(int, row[0]), author_id=cast(int, row[1]), diff --git a/internal/endtoend/testdata/feature_matrix_modern/sqlc.yaml b/internal/endtoend/testdata/feature_matrix_modern/sqlc.yaml index 688d01b..7673322 100644 --- a/internal/endtoend/testdata/feature_matrix_modern/sqlc.yaml +++ b/internal/endtoend/testdata/feature_matrix_modern/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" 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 bbcb74f..1547ca0 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: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/method_name_conflict/sqlc.yaml b/internal/endtoend/testdata/method_name_conflict/sqlc.yaml index 4c8bb8b..f1bcd4d 100644 --- a/internal/endtoend/testdata/method_name_conflict/sqlc.yaml +++ b/internal/endtoend/testdata/method_name_conflict/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/method_name_conflict_nfkc/sqlc.yaml b/internal/endtoend/testdata/method_name_conflict_nfkc/sqlc.yaml index 4c8bb8b..f1bcd4d 100644 --- a/internal/endtoend/testdata/method_name_conflict_nfkc/sqlc.yaml +++ b/internal/endtoend/testdata/method_name_conflict_nfkc/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/method_name_per_file/sqlc.yaml b/internal/endtoend/testdata/method_name_per_file/sqlc.yaml index de585b6..2602cfd 100644 --- a/internal/endtoend/testdata/method_name_per_file/sqlc.yaml +++ b/internal/endtoend/testdata/method_name_per_file/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query/ diff --git a/internal/endtoend/testdata/models_conflict/sqlc.yaml b/internal/endtoend/testdata/models_conflict/sqlc.yaml index c5596f0..7c61e6c 100644 --- a/internal/endtoend/testdata/models_conflict/sqlc.yaml +++ b/internal/endtoend/testdata/models_conflict/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: models.sql diff --git a/internal/endtoend/testdata/multidim_arrays/sqlc.yaml b/internal/endtoend/testdata/multidim_arrays/sqlc.yaml index 819e198..f2a0004 100644 --- a/internal/endtoend/testdata/multidim_arrays/sqlc.yaml +++ b/internal/endtoend/testdata/multidim_arrays/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/multiline_comments/sqlc.yaml b/internal/endtoend/testdata/multiline_comments/sqlc.yaml index 6eec1c3..2f080a9 100644 --- a/internal/endtoend/testdata/multiline_comments/sqlc.yaml +++ b/internal/endtoend/testdata/multiline_comments/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/omit_sqlc_version/sqlc.yaml b/internal/endtoend/testdata/omit_sqlc_version/sqlc.yaml index aad83c0..8e9f00b 100644 --- a/internal/endtoend/testdata/omit_sqlc_version/sqlc.yaml +++ b/internal/endtoend/testdata/omit_sqlc_version/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/omit_unused_structs/sqlc.yaml b/internal/endtoend/testdata/omit_unused_structs/sqlc.yaml index 387ef43..de39519 100644 --- a/internal/endtoend/testdata/omit_unused_structs/sqlc.yaml +++ b/internal/endtoend/testdata/omit_unused_structs/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/overrides/sqlc.yaml b/internal/endtoend/testdata/overrides/sqlc.yaml index 1eb9dd7..30983dd 100644 --- a/internal/endtoend/testdata/overrides/sqlc.yaml +++ b/internal/endtoend/testdata/overrides/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: queries diff --git a/internal/endtoend/testdata/overrides_global/sqlc.yaml b/internal/endtoend/testdata/overrides_global/sqlc.yaml index eaa8239..c865848 100644 --- a/internal/endtoend/testdata/overrides_global/sqlc.yaml +++ b/internal/endtoend/testdata/overrides_global/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/postgresql_type_aliases/db/query.py b/internal/endtoend/testdata/postgresql_type_aliases/db/query.py index 252bae0..966e1ee 100644 --- a/internal/endtoend/testdata/postgresql_type_aliases/db/query.py +++ b/internal/endtoend/testdata/postgresql_type_aliases/db/query.py @@ -66,7 +66,7 @@ class Querier: def __init__(self, conn: Union[sqlalchemy.engine.Connection, sqlalchemy.orm.Session]): self._conn = conn - def cast_aliases(self, arg: CastAliasesParams) -> Optional[CastAliasesRow]: + def cast_aliases(self, *, arg: CastAliasesParams) -> Optional[CastAliasesRow]: row = self._conn.execute(sqlalchemy.text(CAST_ALIASES), { "p1": arg.column_1, "p2": arg.column_2, diff --git a/internal/endtoend/testdata/postgresql_type_aliases/sqlc.yaml b/internal/endtoend/testdata/postgresql_type_aliases/sqlc.yaml index 4c8bb8b..f1bcd4d 100644 --- a/internal/endtoend/testdata/postgresql_type_aliases/sqlc.yaml +++ b/internal/endtoend/testdata/postgresql_type_aliases/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/python_keywords/sqlc.yaml b/internal/endtoend/testdata/python_keywords/sqlc.yaml index 819e198..f2a0004 100644 --- a/internal/endtoend/testdata/python_keywords/sqlc.yaml +++ b/internal/endtoend/testdata/python_keywords/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_comments/sqlc.yaml b/internal/endtoend/testdata/query_comments/sqlc.yaml index 819e198..f2a0004 100644 --- a/internal/endtoend/testdata/query_comments/sqlc.yaml +++ b/internal/endtoend/testdata/query_comments/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" 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 d7184f4..f4bcd3f 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: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" 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 81613ef..0687822 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: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql 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 4c0dc35..d3f9fa7 100644 --- a/internal/endtoend/testdata/query_parameter_limit_zero/python/query.py +++ b/internal/endtoend/testdata/query_parameter_limit_zero/python/query.py @@ -39,11 +39,11 @@ class Querier: def __init__(self, conn: Union[sqlalchemy.engine.Connection, sqlalchemy.orm.Session]): self._conn = conn - def delete_bar_by_id(self, arg: DeleteBarByIDParams) -> int: + def delete_bar_by_id(self, *, arg: DeleteBarByIDParams) -> int: result = self._conn.execute(sqlalchemy.text(DELETE_BAR_BY_ID), {"p1": arg.id}) return cast("sqlalchemy.engine.CursorResult[Any]", result).rowcount - def delete_bar_by_id_and_name(self, arg: DeleteBarByIDAndNameParams) -> int: + def delete_bar_by_id_and_name(self, *, arg: DeleteBarByIDAndNameParams) -> int: result = self._conn.execute(sqlalchemy.text(DELETE_BAR_BY_ID_AND_NAME), {"p1": arg.id, "p2": arg.name}) return cast("sqlalchemy.engine.CursorResult[Any]", result).rowcount @@ -54,10 +54,10 @@ class AsyncQuerier: def __init__(self, conn: Union[sqlalchemy.ext.asyncio.AsyncConnection, sqlalchemy.ext.asyncio.AsyncSession]): self._conn = conn - async def delete_bar_by_id(self, arg: DeleteBarByIDParams) -> int: + async def delete_bar_by_id(self, *, arg: DeleteBarByIDParams) -> int: result = await self._conn.execute(sqlalchemy.text(DELETE_BAR_BY_ID), {"p1": arg.id}) return cast("sqlalchemy.engine.CursorResult[Any]", result).rowcount - async def delete_bar_by_id_and_name(self, arg: DeleteBarByIDAndNameParams) -> int: + async def delete_bar_by_id_and_name(self, *, arg: DeleteBarByIDAndNameParams) -> int: result = await self._conn.execute(sqlalchemy.text(DELETE_BAR_BY_ID_AND_NAME), {"p1": arg.id, "p2": arg.name}) return cast("sqlalchemy.engine.CursorResult[Any]", result).rowcount diff --git a/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml index c469832..e720810 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: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" 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 889a9d2..9d7840d 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: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/rename/sqlc.yaml b/internal/endtoend/testdata/rename/sqlc.yaml index edc033e..8229bdd 100644 --- a/internal/endtoend/testdata/rename/sqlc.yaml +++ b/internal/endtoend/testdata/rename/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/sqlc_embed/sqlc.yaml b/internal/endtoend/testdata/sqlc_embed/sqlc.yaml index c97acf5..e237482 100644 --- a/internal/endtoend/testdata/sqlc_embed/sqlc.yaml +++ b/internal/endtoend/testdata/sqlc_embed/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "a9550c28fc0bb7eb327516023a276181ff9336c46323526e275d14ad66c6ffd6" + sha256: "c2ce8a7db011038a60cbfe9cc54f432775a4acae76affdaac9520076a3301335" sql: - schema: schema.sql queries: query.sql diff --git a/internal/gen.go b/internal/gen.go index fcc310a..817121c 100644 --- a/internal/gen.go +++ b/internal/gen.go @@ -112,22 +112,14 @@ func takesSequence(cmd string) bool { } func (q Query) addArgs(f *pyFile, args *pyast.Arguments) { - // A single struct arg does not need to be passed as a keyword argument - if len(q.Args) == 1 && q.Args[0].IsStruct() { - ann := q.Args[0].annotation(f) - if takesSequence(q.Cmd) { + for _, a := range q.Args { + ann := a.annotation(f) + if a.IsStruct() && takesSequence(q.Cmd) { ann = f.generic("Sequence", ann) } - args.Args = append(args.Args, &pyast.Arg{ - Arg: q.Args[0].Name, - Annotation: ann, - }) - return - } - for _, a := range q.Args { args.KwOnlyArgs = append(args.KwOnlyArgs, &pyast.Arg{ Arg: a.Name, - Annotation: a.annotation(f), + Annotation: ann, }) } } @@ -452,6 +444,10 @@ func buildQueries(conf Config, req *plugin.GenerateRequest, structs []Struct) ([ return nil, errors.New("invalid query parameter limit") } if len(query.Params) > qpl || qpl == 0 || takesSequence(query.Cmd) { + name := "arg" + if takesSequence(query.Cmd) { + name = "args" + } var cols []pyColumn for _, p := range query.Params { cols = append(cols, pyColumn{ @@ -461,7 +457,7 @@ func buildQueries(conf Config, req *plugin.GenerateRequest, structs []Struct) ([ } gq.Args = []QueryValue{{ Emit: true, - Name: "arg", + Name: name, Struct: columnsToStruct(req, conf, query.Name+"Params", cols, structs), }} } else { @@ -924,13 +920,13 @@ func isNone(name string) *pyast.Node { } // sequenceParams is the executemany parameter list for a sequence-taking -// command: one dict per item of arg. +// command: one dict per item of the sequence argument. func sequenceParams(q Query) *pyast.Node { dict := q.argDictNode("a") if dict == nil { dict = poet.Node(&pyast.Dict{}) } - return poet.ListComp(dict, poet.Name("a"), poet.Name("arg")) + return poet.ListComp(dict, poet.Name("a"), poet.Name(q.Args[0].Name)) } func (b *querierBuilder) method(q Query) (querierMethod, error) { @@ -981,7 +977,7 @@ func (b *querierBuilder) method(q Query) (querierMethod, error) { case metadata.CmdCopyFrom: body = append(body, poet.Node(&pyast.If{ - Test: poet.Not(poet.Name("arg")), + Test: poet.Not(poet.Name(q.Args[0].Name)), Body: []*pyast.Node{poet.Return(poet.Constant(0))}, }), assignNode("result", b.execute(q, sequenceParams(q))), @@ -991,7 +987,7 @@ func (b *querierBuilder) method(q Query) (querierMethod, error) { case metadata.CmdBatchExec: body = append(body, poet.Node(&pyast.If{ - Test: poet.Not(poet.Name("arg")), + Test: poet.Not(poet.Name(q.Args[0].Name)), Body: []*pyast.Node{poet.Return(poet.Constant(nil))}, }), b.execute(q, sequenceParams(q)), @@ -1000,7 +996,7 @@ func (b *querierBuilder) method(q Query) (querierMethod, error) { case metadata.CmdBatchOne: body = append(body, poet.Node(&pyast.For{ Target: poet.Name("a"), - Iter: poet.Name("arg"), + Iter: poet.Name(q.Args[0].Name), Body: []*pyast.Node{ assignNode("row", poet.Node(&pyast.Call{ Func: poet.Attribute(b.execute(q, q.argDictNode("a")), "first"), @@ -1017,7 +1013,7 @@ func (b *querierBuilder) method(q Query) (querierMethod, error) { case metadata.CmdBatchMany: body = append(body, poet.Node(&pyast.For{ Target: poet.Name("a"), - Iter: poet.Name("arg"), + Iter: poet.Name(q.Args[0].Name), Body: []*pyast.Node{ poet.Expr(poet.Yield(poet.ListComp( b.rowNode(q.Ret, "row"), diff --git a/openspec/changes/archive/2026-10-20-keyword-only-params-arg/.openspec.yaml b/openspec/changes/archive/2026-10-20-keyword-only-params-arg/.openspec.yaml new file mode 100644 index 0000000..c1bc902 --- /dev/null +++ b/openspec/changes/archive/2026-10-20-keyword-only-params-arg/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-19 diff --git a/openspec/changes/archive/2026-10-20-keyword-only-params-arg/design.md b/openspec/changes/archive/2026-10-20-keyword-only-params-arg/design.md new file mode 100644 index 0000000..d63c18f --- /dev/null +++ b/openspec/changes/archive/2026-10-20-keyword-only-params-arg/design.md @@ -0,0 +1,44 @@ +# Design + +## Context + +`addArgs` in `internal/gen.go` special-cases a single struct argument and +appends it to positional `Args`; every other parameter goes to `KwOnlyArgs`. The +struct's `QueryValue` is always named `arg`, and the sequence-command bodies +hard-code `arg` (`if not arg`, `for a in arg`). Querier protocols are built from +the same `pyast.Arguments`, so they follow automatically. + +## Goals / Non-Goals + +**Goals:** +- One rule for every generated parameter: keyword-only. +- Name follows the command shape: `args` for sequence-taking commands, `arg` + otherwise. + +**Non-Goals:** +- Changing the annotation to alt's `list[Params]`; `Sequence` stays, since it + accepts lists and tuples and is already what callers type against. +- A config flag to keep the old positional form. + +## Decisions + +- **Drop the struct special case in `addArgs`; put it in `KwOnlyArgs`.** The + `Sequence` wrapping stays. Alternative: a separate code path per command — + rejected, the existing `takesSequence(q.Cmd)` predicate already splits the + two shapes. +- **Pick the name where the params `QueryValue` is built** (`"args"` when + `takesSequence(query.Cmd)`, else `"arg"`), and have the sequence-command + bodies read `q.Args[0].Name` instead of the literal `"arg"`. Keeps the name in + one place. +- **No collision handling needed.** The params-class argument is the method's + only parameter besides `self`, and `arg`/`args` are not module-level names the + body reads. + +## Risks / Trade-offs + +- [Breaking for every caller of a params-class method] → Called out in the + README migration section; the change lands before a tagged release that + documents the old form as stable. +- [Hand-written protocol fakes in tests drift] → `_check.py` and + `examples/src/tests/test_library.py` are type-checked against the protocols, + so a missed fake fails the check. diff --git a/openspec/changes/archive/2026-10-20-keyword-only-params-arg/proposal.md b/openspec/changes/archive/2026-10-20-keyword-only-params-arg/proposal.md new file mode 100644 index 0000000..81723d8 --- /dev/null +++ b/openspec/changes/archive/2026-10-20-keyword-only-params-arg/proposal.md @@ -0,0 +1,42 @@ +# Proposal + +## Why + +When a query's parameters are packed into a params class (more parameters than +`query_parameter_limit`, a limit of `0`, or a sequence-taking command), the +generated method takes it as one positional `arg`. Every other generated +parameter is keyword-only, so this one is the odd case out, and the batch form +also diverges from asavoy/alt-sqlc-gen-python, which uses a keyword-only `args`. + +## What Changes + +- **BREAKING**: A params-class argument is keyword-only. Callers write + `q.update_author(arg=...)`, not `q.update_author(...)`. +- **BREAKING**: For sequence-taking commands (`:copyfrom`, `:batchexec`, + `:batchone`, `:batchmany`) the argument is named `args`: + `def create_authors(self, *, args: Sequence[CreateAuthorsParams])`. +- Non-sequence commands keep the name `arg`, now keyword-only: + `def update_author(self, *, arg: UpdateAuthorParams)`. +- Annotation stays `Sequence[Params]` (not alt's `list[Params]`). +- Querier protocols mirror the new signatures. +- README examples and the alt-sqlc-gen-python migration note are updated. + +## Capabilities + +### New Capabilities + +### Modified Capabilities + +- `query-codegen`: Copyfrom and Batch commands requirements change from one + positional argument to keyword-only `args`; a new requirement pins the + keyword-only `arg` for non-sequence params-class methods. + +## Impact + +- `internal/gen.go`: `addArgs`, the params-class `QueryValue` name, and the + sequence-command bodies (`if not arg`, `for a in arg`). +- Regenerated `internal/endtoend/testdata/**` and `examples/src/**` output; + hand-written `_check.py` fakes and `examples/src/tests` that call these methods. +- `README.md`. +- Downstream callers that pass the params object positionally or by `arg=` on + batch/copyfrom methods must update. diff --git a/openspec/changes/archive/2026-10-20-keyword-only-params-arg/specs/query-codegen/spec.md b/openspec/changes/archive/2026-10-20-keyword-only-params-arg/specs/query-codegen/spec.md new file mode 100644 index 0000000..ba9360e --- /dev/null +++ b/openspec/changes/archive/2026-10-20-keyword-only-params-arg/specs/query-codegen/spec.md @@ -0,0 +1,107 @@ +# Spec Delta + +## ADDED Requirements + +### Requirement: Params-class argument + +When a non-sequence query's parameters are packed into a params class (more +parameters than `query_parameter_limit`, or `query_parameter_limit: 0`), the +sync and async querier methods and protocol methods SHALL take that params +class as one keyword-only argument named `arg`. + +#### Scenario: Over the limit + +- **WHEN** `query_parameter_limit: 2` and `UpdateAuthor :exec` has three + parameters +- **THEN** the querier has + `def update_author(self, *, arg: UpdateAuthorParams) -> None`, and calling + `q.update_author(UpdateAuthorParams(...))` positionally is a `TypeError` + +#### Scenario: Limit of zero + +- **WHEN** `query_parameter_limit: 0` and `GetAuthor :one` has one parameter +- **THEN** the querier has + `def get_author(self, *, arg: GetAuthorParams) -> Optional[models.Author]` + +## MODIFIED Requirements + +### Requirement: Copyfrom + +A `:copyfrom` query SHALL generate a params class holding every query parameter, +even when there are fewer parameters than `query_parameter_limit`. The sync and +async querier methods SHALL take one keyword-only argument named `args`, a +`Sequence` of that params class. Each method SHALL run the statement once over +all the items as a single `executemany` call and return the driver-reported row +count as `int`. Generation SHALL no longer fail for `:copyfrom`. + +#### Scenario: Bulk insert + +- **WHEN** a query is `-- name: CreateAuthors :copyfrom` with + `INSERT INTO authors (name, bio) VALUES ($1, $2)` +- **THEN** the querier has + `def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int` + (and an `async def` form), which passes a list of parameter dicts, one per + item, to a single `execute` call + +#### Scenario: Empty input + +- **WHEN** `create_authors` is called with `args=[]` +- **THEN** it returns `0` without running the statement + +### Requirement: Batch commands + +A `:batchone`, `:batchmany`, or `:batchexec` query SHALL generate a params class +holding every query parameter, as `:copyfrom` does, whatever +`query_parameter_limit` is. Each method SHALL take one keyword-only argument +named `args`, a `Sequence` of that params class. An empty sequence SHALL run no +statements. + +`:batchexec` SHALL run the statement once as a single `executemany` over all +items, and SHALL return `None` (awaitable on `AsyncQuerier`). + +`:batchone` and `:batchmany` SHALL run the statement once per item, in order, on +the querier's connection. They SHALL produce results lazily, as the caller +consumes them: + +| Command | Sync return | Async return | Value per input item | +|---|---|---|---| +| `:batchone` | `Iterator[Row \| None]` | `AsyncIterator[Row \| None]` | the first row, or `None` when there is no row | +| `:batchmany` | `Iterator[list[Row]]` | `AsyncIterator[list[Row]]` | all rows, possibly empty | + +`Row` here means the query's row class, the reused model, or the scalar type, +chosen by the same rules as `:one` and `:many`. The annotations above use modern +spelling. Generated code SHALL spell them in the active type syntax +(`Optional[Row]` and `List[Row]` when `emit_modern_types` is off). + +#### Scenario: Batchone yields per input + +- **WHEN** `GetAuthorsByID :batchone` is called with three params objects and + the second id does not exist +- **THEN** iterating the result yields a row, then `None`, then a row + +#### Scenario: Batchmany yields a list per input + +- **WHEN** `ListBooksByAuthor :batchmany` is called with two params objects +- **THEN** iterating the result yields exactly two lists, one per params object, + in input order + +#### Scenario: Lazy execution + +- **WHEN** a `:batchone` method is called and the caller stops after the first + yielded result +- **THEN** only the first item's statement has run + +#### Scenario: Batchexec uses one executemany + +- **WHEN** `CreateAuthors :batchexec` + (`INSERT INTO authors (name, bio) VALUES ($1, $2)`) is called with two params + objects +- **THEN** the method makes one `execute` call with a list of two parameter + dicts, and returns `None` once both rows are written + +#### Scenario: Keyword-only args + +- **WHEN** a `:batchexec` query `CreateAuthors` is generated +- **THEN** the method signature is + `def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> None`, + and passing the sequence positionally is a `TypeError` diff --git a/openspec/changes/archive/2026-10-20-keyword-only-params-arg/tasks.md b/openspec/changes/archive/2026-10-20-keyword-only-params-arg/tasks.md new file mode 100644 index 0000000..58dda4f --- /dev/null +++ b/openspec/changes/archive/2026-10-20-keyword-only-params-arg/tasks.md @@ -0,0 +1,19 @@ +# Tasks + +## 1. Generator + +- [x] 1.1 In `internal/gen.go`, name the params `QueryValue` `args` when `takesSequence(query.Cmd)`, else `arg`; verify with `go build ./...` +- [x] 1.2 Remove the positional struct special case in `addArgs` so the params argument goes to `KwOnlyArgs` (keeping the `Sequence` wrap); verify with `go build ./...` +- [x] 1.3 Replace the literal `"arg"` in `sequenceParams` and the `:copyfrom` / `:batchexec` / `:batchone` / `:batchmany` bodies with `q.Args[0].Name`; verify with `go build ./...` + +## 2. Regenerated output and hand-written callers + +- [x] 2.1 Rebuild the plugin (`mise exec -- make all`) and run `sqlc generate` in each `internal/endtoend/testdata/*` dir; verify every params-class method reads `(self, *, arg: ...)` or `(self, *, args: Sequence[...])` with `grep -rn "self, arg" internal/endtoend/testdata` returning nothing +- [x] 2.2 Update `internal/endtoend/testdata/emit_querier_protocol/_check.py` fakes to `(self, *, args: Sequence[...])`; verify the testdata's type check (mypy/pyright as run for that dir) passes +- [x] 2.3 Regenerate `examples/` and update `examples/src/tests/test_library.py` fakes and call sites to `args=[...]`; verify `sqlc diff` in `examples/` is clean and `pytest src/tests` passes against Postgres +- [x] 2.4 Verify `mise exec -- make test` passes + +## 3. Docs + +- [x] 3.1 Update the README `:batchexec` example to `def create_authors(self, *, args: Sequence[CreateAuthorsParams])` / `for a in args`, and any other params-class example; verify `grep -n "self, arg" README.md` returns nothing +- [x] 3.2 Rewrite the alt-sqlc-gen-python migration bullet: batch/copyfrom methods take keyword-only `args: Sequence[Params]` (alt: `args: list[Params]`); note that non-sequence params-class methods now take keyword-only `arg` diff --git a/openspec/specs/query-codegen/spec.md b/openspec/specs/query-codegen/spec.md index 1fa063d..d5ccbb2 100644 --- a/openspec/specs/query-codegen/spec.md +++ b/openspec/specs/query-codegen/spec.md @@ -50,25 +50,46 @@ line, in order. Queries without comments SHALL produce no docstring. A `:copyfrom` query SHALL generate a params class holding every query parameter, even when there are fewer parameters than `query_parameter_limit`. The sync and -async querier methods SHALL take one positional argument, a `Sequence` of that -params class. Each method SHALL run the statement once over all the items as a -single `executemany` call and return the driver-reported row count as `int`. -Generation SHALL no longer fail for `:copyfrom`. +async querier methods SHALL take one keyword-only argument named `args`, a +`Sequence` of that params class. Each method SHALL run the statement once over +all the items as a single `executemany` call and return the driver-reported row +count as `int`. Generation SHALL no longer fail for `:copyfrom`. #### Scenario: Bulk insert - **WHEN** a query is `-- name: CreateAuthors :copyfrom` with `INSERT INTO authors (name, bio) VALUES ($1, $2)` - **THEN** the querier has - `def create_authors(self, arg: Sequence[CreateAuthorsParams]) -> int` (and an - `async def` form), which passes a list of parameter dicts, one per item, to a - single `execute` call + `def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> int` + (and an `async def` form), which passes a list of parameter dicts, one per + item, to a single `execute` call #### Scenario: Empty input -- **WHEN** `create_authors` is called with an empty sequence +- **WHEN** `create_authors` is called with `args=[]` - **THEN** it returns `0` without running the statement +### Requirement: Params-class argument + +When a non-sequence query's parameters are packed into a params class (more +parameters than `query_parameter_limit`, or `query_parameter_limit: 0`), the +sync and async querier methods and protocol methods SHALL take that params +class as one keyword-only argument named `arg`. + +#### Scenario: Over the limit + +- **WHEN** `query_parameter_limit: 2` and `UpdateAuthor :exec` has three + parameters +- **THEN** the querier has + `def update_author(self, *, arg: UpdateAuthorParams) -> None`, and calling + `q.update_author(UpdateAuthorParams(...))` positionally is a `TypeError` + +#### Scenario: Limit of zero + +- **WHEN** `query_parameter_limit: 0` and `GetAuthor :one` has one parameter +- **THEN** the querier has + `def get_author(self, *, arg: GetAuthorParams) -> Optional[models.Author]` + ### Requirement: Multi-dimensional arrays A column or parameter with N array dimensions SHALL be annotated with N nested @@ -183,8 +204,9 @@ models, and embedded models. It SHALL have no runtime effect. A `:batchone`, `:batchmany`, or `:batchexec` query SHALL generate a params class holding every query parameter, as `:copyfrom` does, whatever -`query_parameter_limit` is. Each method SHALL take one positional argument, a -`Sequence` of that params class. An empty sequence SHALL run no statements. +`query_parameter_limit` is. Each method SHALL take one keyword-only argument +named `args`, a `Sequence` of that params class. An empty sequence SHALL run no +statements. `:batchexec` SHALL run the statement once as a single `executemany` over all items, and SHALL return `None` (awaitable on `AsyncQuerier`). @@ -229,6 +251,13 @@ spelling. Generated code SHALL spell them in the active type syntax - **THEN** the method makes one `execute` call with a list of two parameter dicts, and returns `None` once both rows are written +#### Scenario: Keyword-only args + +- **WHEN** a `:batchexec` query `CreateAuthors` is generated +- **THEN** the method signature is + `def create_authors(self, *, args: Sequence[CreateAuthorsParams]) -> None`, + and passing the sequence positionally is a `TypeError` + ### Requirement: Querier protocols With `emit_querier_protocol: true`, each query file SHALL also define