Database Driver Adapters
Database driver adapters in aiosql allow extension of the library to support
additional database drivers.
If you are using a driver other than the ones currently supported by built-in
driver adapters (sqlite3
, apsw
, aiosqlite
, psycopg
,
psycopg2
, pg8000
, pygresql
, asyncpg
, pymysql
,
mysqlclient
, mysql-connector
, duckdb
, pymssql
),
first check whether your driver supports pyformat or named paramstyles.
If so, check (manually) whether the default PEP 249 drivers work:
import acmedb # your PEP 249 driver
import aiosql
conn = acmedb.connect("…")
queries = aiosql.from_str("-- name: add42$\nSELECT :n + 42;\n", acmedb)
assert queries.add42(conn, n=18) == 60
If this simplistic test works, do more tests involving all operators (see the pytest tests), then create an issue to notify that your driver works out of the box so it can be advertised from the readme.
If it does not work or if you have an asynchronous driver, you will need to make
your own.
Good news, it should be very close to the existing supported drivers.
A database driver adapter is a duck-typed class that follows either of the
Protocol
types defined in
aiosql/types.py:
class SyncDriverAdapterProtocol(Protocol):
def process_sql(
self, query_name: str, op_type: SQLOperationType, sql: str
) -> str: ... # pragma: no cover
def select(
self,
conn: Any,
query_name: str,
sql: str,
parameters: ParamType,
record_class: Optional[Callable],
) -> Generator[Any, None, None]: ... # pragma: no cover
def select_one(
self,
conn: Any,
query_name: str,
sql: str,
parameters: ParamType,
record_class: Optional[Callable],
) -> Optional[Tuple[Any, ...]]: ... # pragma: no cover
def select_value(
self, conn: Any, query_name: str, sql: str, parameters: ParamType
) -> Optional[Any]: ... # pragma: no cover
def select_cursor(
self, conn: Any, query_name: str, sql: str, parameters: ParamType
) -> ContextManager[Any]: ... # pragma: no cover
def insert_update_delete(
self, conn: Any, query_name: str, sql: str, parameters: ParamType
) -> int: ... # pragma: no cover
def insert_update_delete_many(
self, conn: Any, query_name: str, sql: str, parameters: ParamType
) -> int: ... # pragma: no cover
def insert_returning(
self, conn: Any, query_name: str, sql: str, parameters: ParamType
) -> Optional[Any]: ... # pragma: no cover
class AsyncDriverAdapterProtocol(Protocol):
def process_sql(
self, query_name: str, op_type: SQLOperationType, sql: str
) -> str: ... # pragma: no cover
async def select(
self,
conn: Any,
query_name: str,
sql: str,
parameters: ParamType,
record_class: Optional[Callable],
) -> List: ... # pragma: no cover
async def select_one(
self,
conn: Any,
query_name: str,
sql: str,
parameters: ParamType,
record_class: Optional[Callable],
) -> Optional[Any]: ... # pragma: no cover
async def select_value(
self, conn: Any, query_name: str, sql: str, parameters: ParamType
) -> Optional[Any]: ... # pragma: no cover
async def select_cursor(
self, conn: Any, query_name: str, sql: str, parameters: ParamType
) -> AsyncContextManager[Any]: ... # pragma: no cover
# TODO: Next major version introduce a return? Optional return?
async def insert_update_delete(
self, conn: Any, query_name: str, sql: str, parameters: ParamType
) -> None: ... # pragma: no cover
# TODO: Next major version introduce a return? Optional return?
async def insert_update_delete_many(
self, conn: Any, query_name: str, sql: str, parameters: ParamType
) -> None: ... # pragma: no cover
async def insert_returning(
self, conn: Any, query_name: str, sql: str, parameters: ParamType
) -> Optional[Any]: ... # pragma: no cover
Some comments about these classes, one for synchronous queries (PEP 249) and the other for asynchronous queries:
_cursor
is an internal method to generate a cursor, as some drivers need to pass parameters at this phase.process_sql
is used to preprocess SQL queries so has to handle named parameters as they are managed by the target driver.select
,select_one
,insert_update_delete
,insert_update_delete_many
,insert_returning
andexecute_script
implement all operations.select_cursor
returns the raw cursor from aselect
.
There isn’t much difference between these two protocols besides the
async def
syntax for the method definition.
There is one more sneaky difference, the aiosql code expects async adapters to
have a static class field is_aio_driver = True
so it can tell when to use
await
for method returns.
Looking at the source of the builtin
adapters/
is a great place to start seeing how you may write your own database driver adapter.
For a PEP 249 driver, consider inheriting from aiosql.adapters.Generic
if you can.
To use the adapter pass its constructor or factory as the driver_adapter
argument when building Queries:
queries = aiosql.from_path("foo.sql", driver_adapter=AcmeAdapter)
Alternatively, an adapter can be registered or overriden:
# in AcmeAdapter provider, eg module "acmedb_aiosql"
import aiosql
aiosql.register_adapter("acmedb", AcmeAdapter)
# then use it elsewhere
import aiosql
queries = aiosql.from_path("some.sql", "acmedb")
Please ask questions on GitHub Issues. If the community makes additional adapter add-ons it will be listed from the doc.