Skip to content

Commit 9604f32

Browse files
authored
Merge pull request #1 from zztkm/feat-sqlalchemy-model
feat: sqlalchemy model
2 parents 62dc0e7 + 33bc14c commit 9604f32

21 files changed

+66622
-25
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist

Makefile

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,38 @@
1-
all: sqlc-gen-python sqlc-gen-python.wasm
1+
VERSION := $$(make -s show-version)
2+
CURRENT_REVISION := $(shell git rev-parse --short HEAD)
3+
BUILD_LDFLAGS := "-s -w -X main.revision=$(CURRENT_REVISION)"
4+
GOBIN ?= $(shell go env GOPATH)/bin
25

3-
sqlc-gen-python:
4-
cd plugin && go build -o ~/bin/sqlc-gen-python ./main.go
6+
.PHONY: show-version
7+
show-version: $(GOBIN)/gobump
8+
@gobump show -r cmd/sqlc-gen-python-orm
59

6-
sqlc-gen-python.wasm:
7-
cd plugin && GOOS=wasip1 GOARCH=wasm go build -o sqlc-gen-python.wasm main.go
8-
openssl sha256 plugin/sqlc-gen-python.wasm
10+
$(GOBIN)/gobump:
11+
@go install github.com/x-motemen/gobump/cmd/gobump@latest
12+
13+
.PHONY: compile
14+
compile:
15+
sqlc compile
16+
17+
.PHONY: generate
18+
generate: sqlc.yaml
19+
sqlc generate
20+
21+
22+
.PHONY: release
23+
release: dist/sqlc-gen-ts-d1.wasm dist/sqlc-gen-ts-d1.wasm.sha256
24+
gh release delete -y --cleanup-tag "v${VERSION}"
25+
gh release create "v${VERSION}" dist/sqlc-gen-python-orm.wasm dist/sqlc-gen-python-orm.wasm.sha256
26+
27+
.PHONY: clean
28+
clean:
29+
rm -rf ./_examples/gen
30+
31+
sqlc.yaml: dist/sqlc-gen-python-orm.wasm.sha256 _sqlc.yaml
32+
cat _sqlc.yaml | WASM_SHA256=$$(cat $<) envsubst > $@
33+
34+
dist/sqlc-gen-python-orm.wasm.sha256: dist/sqlc-gen-python-orm.wasm
35+
openssl sha256 $< | awk '{print $$2}' > $@
36+
37+
dist/sqlc-gen-python-orm.wasm: internal/*
38+
GOOS=wasip1 GOARCH=wasm go build -o $@ ./cmd/sqlc-gen-python-orm/main.go

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# sqlc-gen-python-orm
2+
3+
sqlc-gen-python-orm is a plugin for [sqlc](https://sqlc.dev/) that generates an ORM (now, support SQLAlchemy only) for Python.
4+
15
## Usage
26

37
```yaml
@@ -19,3 +23,7 @@ sql:
1923
emit_sync_querier: true
2024
emit_async_querier: true
2125
```
26+
27+
## Refs
28+
29+
- [sqlc plugin を書こう - 薄いブログ](https://orisano.hatenablog.com/entry/2023/09/06/010926)

_examples/gen/sqlc/models.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Code generated by sqlc. DO NOT EDIT.
2+
# versions:
3+
# sqlc v1.20.0
4+
import pydantic
5+
from typing import Optional
6+
7+
8+
class Author(pydantic.BaseModel):
9+
id: int
10+
name: str
11+
age: int
12+
bio: Optional[str]
13+
is_active: bool

_examples/gen/sqlc/orm.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Code generated by sqlc. DO NOT EDIT.
2+
# versions:
3+
# sqlc v1.20.0
4+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
5+
from typing import Optional
6+
7+
8+
class Base(DeclarativeBase):
9+
pass
10+
11+
12+
class Author(Base):
13+
__tablename__ = "authors"
14+
id: Mapped[int]
15+
name: Mapped[str]
16+
age: Mapped[int]
17+
bio: Mapped[Optional[str]]
18+
is_active: Mapped[bool]

_examples/gen/sqlc/query.py

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# Code generated by sqlc. DO NOT EDIT.
2+
# versions:
3+
# sqlc v1.20.0
4+
# source: query.sql
5+
from typing import AsyncIterator, Iterator, Optional
6+
7+
import sqlalchemy
8+
import sqlalchemy.ext.asyncio
9+
10+
from . import models
11+
12+
13+
CREATE_AUTHOR = """-- name: create_author \\:one
14+
insert into authors (
15+
name, bio, age
16+
) values (
17+
:p1, :p2, :p3
18+
)
19+
returning id, name, age, bio, is_active
20+
"""
21+
22+
23+
DELETE_AUTHOR = """-- name: delete_author \\:exec
24+
delete from authors
25+
where id = :p1
26+
"""
27+
28+
29+
GET_AUTHOR = """-- name: get_author \\:one
30+
select id, name, age, bio, is_active from authors
31+
where id = :p1 LIMIT 1
32+
"""
33+
34+
35+
LIST_AUTHORS = """-- name: list_authors \\:many
36+
select id, name, age, bio, is_active from authors
37+
order by name
38+
"""
39+
40+
41+
LOCK_AUTHOR = """-- name: lock_author \\:one
42+
select id, name, age, bio, is_active from authors
43+
where id = :p1 LIMIT 1 FOR UPDATE NOWAIT
44+
"""
45+
46+
47+
UPDATE_AUTHOR = """-- name: update_author \\:one
48+
update authors
49+
set name = :p2,
50+
bio = :p3,
51+
age = :p4
52+
where id = :p1
53+
returning id, name, age, bio, is_active
54+
"""
55+
56+
57+
class Querier:
58+
def __init__(self, conn: sqlalchemy.engine.Connection):
59+
self._conn = conn
60+
61+
def create_author(self, *, name: str, bio: Optional[str], age: int) -> Optional[models.Author]:
62+
row = self._conn.execute(sqlalchemy.text(CREATE_AUTHOR), {"p1": name, "p2": bio, "p3": age}).first()
63+
if row is None:
64+
return None
65+
return models.Author(
66+
id=row[0],
67+
name=row[1],
68+
age=row[2],
69+
bio=row[3],
70+
is_active=row[4],
71+
)
72+
73+
def delete_author(self, *, id: int) -> None:
74+
self._conn.execute(sqlalchemy.text(DELETE_AUTHOR), {"p1": id})
75+
76+
def get_author(self, *, id: int) -> Optional[models.Author]:
77+
row = self._conn.execute(sqlalchemy.text(GET_AUTHOR), {"p1": id}).first()
78+
if row is None:
79+
return None
80+
return models.Author(
81+
id=row[0],
82+
name=row[1],
83+
age=row[2],
84+
bio=row[3],
85+
is_active=row[4],
86+
)
87+
88+
def list_authors(self) -> Iterator[models.Author]:
89+
result = self._conn.execute(sqlalchemy.text(LIST_AUTHORS))
90+
for row in result:
91+
yield models.Author(
92+
id=row[0],
93+
name=row[1],
94+
age=row[2],
95+
bio=row[3],
96+
is_active=row[4],
97+
)
98+
99+
def lock_author(self, *, id: int) -> Optional[models.Author]:
100+
row = self._conn.execute(sqlalchemy.text(LOCK_AUTHOR), {"p1": id}).first()
101+
if row is None:
102+
return None
103+
return models.Author(
104+
id=row[0],
105+
name=row[1],
106+
age=row[2],
107+
bio=row[3],
108+
is_active=row[4],
109+
)
110+
111+
def update_author(self, *, id: int, name: str, bio: Optional[str], age: int) -> Optional[models.Author]:
112+
row = self._conn.execute(sqlalchemy.text(UPDATE_AUTHOR), {
113+
"p1": id,
114+
"p2": name,
115+
"p3": bio,
116+
"p4": age,
117+
}).first()
118+
if row is None:
119+
return None
120+
return models.Author(
121+
id=row[0],
122+
name=row[1],
123+
age=row[2],
124+
bio=row[3],
125+
is_active=row[4],
126+
)
127+
128+
129+
class AsyncQuerier:
130+
def __init__(self, conn: sqlalchemy.ext.asyncio.AsyncConnection):
131+
self._conn = conn
132+
133+
async def create_author(self, *, name: str, bio: Optional[str], age: int) -> Optional[models.Author]:
134+
row = (await self._conn.execute(sqlalchemy.text(CREATE_AUTHOR), {"p1": name, "p2": bio, "p3": age})).first()
135+
if row is None:
136+
return None
137+
return models.Author(
138+
id=row[0],
139+
name=row[1],
140+
age=row[2],
141+
bio=row[3],
142+
is_active=row[4],
143+
)
144+
145+
async def delete_author(self, *, id: int) -> None:
146+
await self._conn.execute(sqlalchemy.text(DELETE_AUTHOR), {"p1": id})
147+
148+
async def get_author(self, *, id: int) -> Optional[models.Author]:
149+
row = (await self._conn.execute(sqlalchemy.text(GET_AUTHOR), {"p1": id})).first()
150+
if row is None:
151+
return None
152+
return models.Author(
153+
id=row[0],
154+
name=row[1],
155+
age=row[2],
156+
bio=row[3],
157+
is_active=row[4],
158+
)
159+
160+
async def list_authors(self) -> AsyncIterator[models.Author]:
161+
result = await self._conn.stream(sqlalchemy.text(LIST_AUTHORS))
162+
async for row in result:
163+
yield models.Author(
164+
id=row[0],
165+
name=row[1],
166+
age=row[2],
167+
bio=row[3],
168+
is_active=row[4],
169+
)
170+
171+
async def lock_author(self, *, id: int) -> Optional[models.Author]:
172+
row = (await self._conn.execute(sqlalchemy.text(LOCK_AUTHOR), {"p1": id})).first()
173+
if row is None:
174+
return None
175+
return models.Author(
176+
id=row[0],
177+
name=row[1],
178+
age=row[2],
179+
bio=row[3],
180+
is_active=row[4],
181+
)
182+
183+
async def update_author(self, *, id: int, name: str, bio: Optional[str], age: int) -> Optional[models.Author]:
184+
row = (await self._conn.execute(sqlalchemy.text(UPDATE_AUTHOR), {
185+
"p1": id,
186+
"p2": name,
187+
"p3": bio,
188+
"p4": age,
189+
})).first()
190+
if row is None:
191+
return None
192+
return models.Author(
193+
id=row[0],
194+
name=row[1],
195+
age=row[2],
196+
bio=row[3],
197+
is_active=row[4],
198+
)

_examples/query.sql

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
-- name: GetAuthor :one
2+
select * from authors
3+
where id = $1 LIMIT 1;
4+
5+
-- name: LockAuthor :one
6+
select * from authors
7+
where id = $1 LIMIT 1 FOR UPDATE NOWAIT;
8+
9+
-- name: ListAuthors :many
10+
select * from authors
11+
order by name;
12+
13+
-- name: CreateAuthor :one
14+
insert into authors (
15+
name, bio, age
16+
) values (
17+
$1, $2, $3
18+
)
19+
returning *;
20+
21+
-- name: UpdateAuthor :one
22+
update authors
23+
set name = $2,
24+
bio = $3,
25+
age = $4
26+
where id = $1
27+
returning *;
28+
29+
-- name: DeleteAuthor :exec
30+
delete from authors
31+
where id = $1;

_examples/schema.sql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
create table authors (
2+
id BIGSERIAL PRIMARY KEY,
3+
name text not null,
4+
age int not null default 0,
5+
bio text,
6+
is_active boolean not null default true
7+
);

_sqlc.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
version: '2'
2+
plugins:
3+
- name: py
4+
wasm:
5+
url: file://dist/sqlc-gen-python-orm.wasm
6+
sha256: $WASM_SHA256
7+
sql:
8+
- schema: "_examples/schema.sql"
9+
queries: "_examples/query.sql"
10+
engine: postgresql
11+
gen:
12+
json:
13+
out: "."
14+
codegen:
15+
- out: "_examples/gen/sqlc"
16+
plugin: py
17+
options:
18+
package: .
19+
emit_sync_querier: true
20+
emit_async_querier: true
21+
emit_pydantic_models: true
22+
emit_sqlalchemy_models: true

plugin/main.go renamed to cmd/sqlc-gen-python-orm/main.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package main
33
import (
44
"github.com/sqlc-dev/sqlc-go/codegen"
55

6-
python "github.com/sqlc-dev/sqlc-gen-python/internal"
6+
python "github.com/zztkm/sqlc-gen-python-orm/internal"
77
)
88

9+
const version = "0.0.1"
10+
911
func main() {
1012
codegen.Run(python.Generate)
1113
}

0 commit comments

Comments
 (0)