Skip to content

Commit 1572eaf

Browse files
committed
feat(postgres/sqlite): update API to be fully async
Signed-off-by: Vaughn Dice <vdice@akamai.com>
1 parent 715e06b commit 1572eaf

File tree

4 files changed

+126
-44
lines changed

4 files changed

+126
-44
lines changed

examples/spin-postgres/app.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
from spin_sdk import http, postgres
1+
from spin_sdk import http, postgres, util
22
from spin_sdk.http import Request, Response
3-
from spin_sdk.postgres import RowSet, DbValue
4-
3+
from spin_sdk.postgres import Connection, RowSet, DbValue
54

65
def format_value(db_value: DbValue) -> str:
76
if hasattr(db_value, "value"):
87
return str(db_value.value)
98
return "NULL"
109

11-
1210
def format_rowset(rowset: RowSet) -> str:
1311
lines = []
1412
col_names = [col.name for col in rowset.columns]
@@ -19,11 +17,12 @@ def format_rowset(rowset: RowSet) -> str:
1917
lines.append(" | ".join(values))
2018
return "\n".join(lines)
2119

22-
2320
class HttpHandler(http.Handler):
2421
async def handle_request(self, request: Request) -> Response:
25-
with await postgres.open("user=postgres dbname=spin_dev host=localhost sslmode=disable password=password") as db:
26-
rowset = db.query("SELECT * FROM test", [])
22+
with await Connection.open("user=postgres dbname=spin_dev host=localhost sslmode=disable password=password") as db:
23+
columns, stream, future = await db.query("SELECT * FROM test", [])
24+
rows = await util.collect((stream, future))
25+
rowset = RowSet(columns, list(rows))
2726
result = format_rowset(rowset)
2827

2928
return Response(200, {"content-type": "text/plain"}, bytes(result, "utf-8"))

examples/spin-sqlite/app.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
from spin_sdk import http, sqlite
1+
from spin_sdk import http, util
22
from spin_sdk.http import Request, Response
3-
from spin_sdk.sqlite import Value_Integer
3+
from spin_sdk.sqlite import Connection, Value_Text, Value_Integer
44

55
class HttpHandler(http.Handler):
66
async def handle_request(self, request: Request) -> Response:
7-
with await sqlite.open_default() as db:
8-
result = db.execute("SELECT * FROM todos WHERE id > (?);", [Value_Integer(1)])
9-
rows = result.rows
10-
7+
with await Connection.open_default() as db:
8+
await db.execute("INSERT INTO todos (description, due) VALUES (?, ?)", [Value_Text("Try out Spin SQLite"), Value_Text("Friday")])
9+
columns, stream, future = await db.execute("SELECT * FROM todos WHERE id > (?);", [Value_Integer(1)])
10+
rows = await util.collect((stream, future))
11+
1112
return Response(
1213
200,
1314
{"content-type": "text/plain"},

src/spin_sdk/postgres.py

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,58 @@
11
"""Module for interacting with a Postgres database"""
22

3+
from typing import List, Tuple
4+
from types import TracebackType
5+
from componentize_py_types import Result
6+
from componentize_py_async_support.streams import StreamReader
7+
from componentize_py_async_support.futures import FutureReader
38
from spin_sdk.wit.imports import spin_postgres_postgres_4_2_0 as pg
49

5-
Connection = pg.Connection
610
RowSet = pg.RowSet
711
DbValue = pg.DbValue
12+
ParameterValue = pg.ParameterValue
13+
Column = pg.Column
14+
Error = pg.Error
815

9-
async def open(connection_string: str) -> Connection:
10-
"""
11-
Open a connection with a Postgres database.
12-
13-
The connection_string is the Postgres URL connection string.
14-
15-
A `componentize_py_types.Err(Error_ConnectionFailed(str))` when a connection fails.
16-
17-
A `componentize_py_types.Err(Error_Other(str))` when some other error occurs.
18-
"""
19-
return await Connection.open_async(connection_string)
16+
class Connection:
17+
def __init__(self, connection: pg.Connection) -> None:
18+
self.connection = connection
19+
20+
def __enter__(self) -> Connection:
21+
return self
22+
23+
def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None:
24+
return self.connection.__exit__(exc_type, exc_value, traceback)
25+
26+
@classmethod
27+
async def open(cls, connection_string: str) -> Connection:
28+
"""
29+
Open a connection with a Postgres database.
30+
31+
The connection_string is the Postgres URL connection string.
32+
33+
A `componentize_py_types.Err(Error_ConnectionFailed(str))` when a connection fails.
34+
35+
A `componentize_py_types.Err(Error_Other(str))` when some other error occurs.
36+
"""
37+
return Connection(await pg.Connection.open_async(connection_string))
38+
39+
async def query(self, statement: str, params: List[ParameterValue]) -> Tuple[List[Column], StreamReader[List[DbValue]], FutureReader[Result[None, Error]]]:
40+
"""
41+
Query the Postgres database.
42+
43+
Returns a Tuple containing a List of Columns, a List of DbValues encapsulated in an asynchronous iterator (`componentize_py_async_support.streams.StreamReader`),
44+
and a future (`componentize_py_async_support.futures.FutureReader`) containing the result of the operation.
45+
46+
Raises: `componentize_py_types.Err(spin_sdk.wit.imports.spin_postgres_postgres_4_2_0.Error)`
47+
"""
48+
return await self.connection.query_async(statement, params)
49+
50+
async def execute(self, statement: str, params: List[ParameterValue]) -> int:
51+
"""
52+
Execute command to the database.
53+
54+
Returns the number of affected rows as an int.
55+
56+
Raises: `componentize_py_types.Err(spin_sdk.wit.imports.spin_postgres_postgres_4_2_0.Error)`
57+
"""
58+
return await self.connection.execute_async(statement, params)

src/spin_sdk/sqlite.py

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,77 @@
11
"""Module for interacting with an SQLite database"""
22

3-
from typing import List
3+
from typing import List, Tuple
4+
from types import TracebackType
5+
from componentize_py_types import Result
6+
from componentize_py_async_support.streams import StreamReader
7+
from componentize_py_async_support.futures import FutureReader
48
from spin_sdk.wit.imports import spin_sqlite_sqlite_3_1_0 as sqlite
59

6-
Connection = sqlite.Connection
10+
Error = sqlite.Error
11+
Value = sqlite.Value
712
Value_Integer = sqlite.Value_Integer
813
Value_Real = sqlite.Value_Real
914
Value_Text = sqlite.Value_Text
1015
Value_Blob = sqlite.Value_Blob
16+
RowResult = sqlite.RowResult
1117

12-
async def open(name: str) -> Connection:
13-
"""Open a connection to a named database instance.
18+
class Connection:
19+
def __init__(self, connection: sqlite.Connection) -> None:
20+
self.connection = connection
1421

15-
If `database` is "default", the default instance is opened.
22+
def __enter__(self) -> Connection:
23+
return self
1624

17-
A `componentize_py_types.Err(spin_sdk.wit.imports.spin_sqlite_sqlite_3_1_0.Error_AccessDenied)` will be raised when the component does not have access to the specified database.
25+
def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None:
26+
return self.connection.__exit__(exc_type, exc_value, traceback)
1827

19-
A `componentize_py_types.Err(spin_sdk.wit.imports.spin_sqlite_sqlite_3_1_0.Error_NoSuchDatabase)` will be raised when the host does not recognize the database name requested.
20-
21-
A `componentize_py_types.Err(spin_sdk.wit.imports.spin_sqlite_sqlite_3_1_0.Error_InvalidConnection)` will be raised when the provided connection string is not valid.
22-
23-
A `componentize_py_types.Err(spin_sdk.wit.imports.spin_sqlite_sqlite_3_1_0.Error_Io(str))` will be raised when implementation-specific error occured (e.g. I/O)
24-
"""
25-
return await Connection.open_async(name)
28+
@classmethod
29+
async def open(cls, name: str) -> Connection:
30+
"""Open a connection to a named database instance.
2631
27-
async def open_default() -> Connection:
28-
"""Open the default store.
32+
If `database` is "default", the default instance is opened.
2933
30-
A `componentize_py_types.Err(spin_sdk.wit.imports.spin_sqlite_sqlite_3_1_0.Error_AccessDenied)` will be raised when the component does not have access to the default database.
34+
A `componentize_py_types.Err(spin_sdk.wit.imports.spin_sqlite_sqlite_3_1_0.Error_AccessDenied)` will be raised when the component does not have access to the specified database.
3135
32-
A `componentize_py_types.Err(spin_sdk.wit.imports.spin_sqlite_sqlite_3_1_0.Error_Io(str))` will be raised when implementation-specific error occured (e.g. I/O)
33-
"""
34-
return await Connection.open_async("default")
36+
A `componentize_py_types.Err(spin_sdk.wit.imports.spin_sqlite_sqlite_3_1_0.Error_NoSuchDatabase)` will be raised when the host does not recognize the database name requested.
37+
38+
A `componentize_py_types.Err(spin_sdk.wit.imports.spin_sqlite_sqlite_3_1_0.Error_InvalidConnection)` will be raised when the provided connection string is not valid.
39+
40+
A `componentize_py_types.Err(spin_sdk.wit.imports.spin_sqlite_sqlite_3_1_0.Error_Io(str))` will be raised when implementation-specific error occured (e.g. I/O)
41+
"""
42+
return Connection(await sqlite.Connection.open_async(name))
43+
44+
@classmethod
45+
async def open_default(cls) -> Connection:
46+
"""Open the default store.
47+
48+
A `componentize_py_types.Err(spin_sdk.wit.imports.spin_sqlite_sqlite_3_1_0.Error_AccessDenied)` will be raised when the component does not have access to the default database.
49+
50+
A `componentize_py_types.Err(spin_sdk.wit.imports.spin_sqlite_sqlite_3_1_0.Error_Io(str))` will be raised when implementation-specific error occured (e.g. I/O)
51+
"""
52+
return Connection(await sqlite.Connection.open_async("default"))
53+
54+
async def execute(self, statement: str, params: List[Value]) -> Tuple[List[str], StreamReader[RowResult], FutureReader[Result[None, Error]]]:
55+
"""
56+
Execute a command to the database.
57+
58+
Returns a Tuple containing a List of columns, a List of RowResults encapsulated in an asynchronous iterator (`componentize_py_async_support.streams.StreamReader`),
59+
and a future (`componentize_py_async_support.futures.FutureReader`) containing the result of the operation.
60+
61+
Raises: `componentize_py_types.Err(spin_sdk.wit.imports.spin_sqlite_sqlite_3_1_0.Error)`
62+
"""
63+
return await self.connection.execute_async(statement, params)
64+
65+
async def last_insert_rowid(self) -> int:
66+
"""
67+
The SQLite rowid of the most recent successful INSERT on the connection, or 0 if
68+
there has not yet been an INSERT on the connection.
69+
"""
70+
return await self.connection.last_insert_rowid_async()
71+
72+
async def changes(self) -> int:
73+
"""
74+
The number of rows modified, inserted or deleted by the most recently completed
75+
INSERT, UPDATE or DELETE statement on the connection.
76+
"""
77+
return await self.connection.changes_async()

0 commit comments

Comments
 (0)