Skip to content

Commit ea2451d

Browse files
authored
Merge branch 'main' into support-mysql-type
2 parents d3d1c06 + 0431a69 commit ea2451d

File tree

21 files changed

+66660
-19
lines changed

21 files changed

+66660
-19
lines changed

.gitignore

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

LICENSE

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
MIT License
22

3+
Copyright (c) 2023 zztkm
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
22+
23+
24+
Below is the original software license.
25+
326
Copyright (c) 2022 Kyle Conroy
427

528
Permission is hereby granted, free of charge, to any person obtaining a copy

Makefile

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,42 @@
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-python-orm.wasm dist/sqlc-gen-python-orm.wasm.sha256
24+
gh release create "v${VERSION}" dist/sqlc-gen-python-orm.wasm dist/sqlc-gen-python-orm.wasm.sha256
25+
26+
.PHONY: release
27+
release-overwrite: dist/sqlc-gen-python-orm.wasm dist/sqlc-gen-python-orm.wasm.sha256
28+
gh release delete -y --cleanup-tag "v${VERSION}"
29+
gh release create "v${VERSION}" dist/sqlc-gen-python-orm.wasm dist/sqlc-gen-python-orm.wasm.sha256
30+
31+
.PHONY: clean
32+
clean:
33+
rm -rf ./_examples/gen
34+
35+
sqlc.yaml: dist/sqlc-gen-python-orm.wasm.sha256 _sqlc.yaml
36+
cat _sqlc.yaml | WASM_SHA256=$$(cat $<) envsubst > $@
37+
38+
dist/sqlc-gen-python-orm.wasm.sha256: dist/sqlc-gen-python-orm.wasm
39+
openssl sha256 $< | awk '{print $$2}' > $@
40+
41+
dist/sqlc-gen-python-orm.wasm: internal/*
42+
GOOS=wasip1 GOARCH=wasm go build -o $@ ./cmd/sqlc-gen-python-orm/main.go

README.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
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+
5+
This softwaer forked from [sqlc-gen-python](https://github.com/sqlc-dev/sqlc-gen-python) and modified to generate ORM.
6+
17
## Usage
28

9+
get sha256 hash of wasm file
10+
11+
```bash
12+
curl -sSL https://github.com/veltiosoft/sqlc-gen-python-orm/releases/download/v0.0.1/sqlc-gen-python-orm.wasm.sha256
13+
```
14+
15+
add plugin to sqlc.yaml
316
```yaml
417
version: '2'
518
plugins:
619
- name: py
720
wasm:
8-
url: https://downloads.sqlc.dev/plugin/sqlc-gen-python_1.1.0.wasm
9-
sha256: ef58f143a8c116781091441770c7166caaf361dd645f62b8f05f462e9f95c3b2
21+
url: https://github.com/veltiosoft/sqlc-gen-python-orm/releases/download/v0.0.1/sqlc-gen-python-orm.wasm
22+
sha256: <sha256 hash>
1023
sql:
1124
- schema: "schema.sql"
1225
queries: "query.sql"
@@ -15,7 +28,11 @@ sql:
1528
- out: src/authors
1629
plugin: py
1730
options:
18-
package: authors
31+
package: .
1932
emit_sync_querier: true
2033
emit_async_querier: true
2134
```
35+
36+
## Refs
37+
38+
- [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+
);

0 commit comments

Comments
 (0)