Skip to content

✨ Add aioodbc driver support #163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/test-sqlite.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ on:
- main

jobs:

test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ '3.8', '3.9', '3.10', '3.11', '3.12' ]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

steps:
- name: checkout
Expand All @@ -25,6 +24,11 @@ jobs:
with:
python-version: ${{ matrix.python-version }}

- name: install SQLite3 ODBC Driver
run: |
sudo apt-get update
sudo apt-get install -y unixodbc unixodbc-dev

- name: install poetry
env:
POETRY_VERSION: "1.7.1"
Expand Down
35 changes: 32 additions & 3 deletions docs/database_support/mssql.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# [Microsoft SQL Server](https://www.microsoft.com/en-us/sql-server/sql-server-2019)
Supported drivers:

| dbapi | default | driver | connection class |
|------------------------------------|------------|-----------------|-------------------------------|
| [pymssql](https://www.pymssql.org) | :thumbsup: | `mssql+pymssql` | `pymssql._pymssql.Connection` |
| dbapi | default | driver | connection class |
| ---------------------------------------------- | ------------ | --------------- | ------------------------------- |
| [pymssql](https://www.pymssql.org) | :thumbsup: | `mssql+pymssql` | `pymssql._pymssql.Connection` |
| [aioodbc](https://github.com/aio-libs/aioodbc) | :thumbsdown: | `mssql+aioodbc` | `aioodbc.connection.Connection` |

## pymssql
`pymssql` is the default dbapi driver for Microsoft SQL Server in *pydapper*.
Expand Down Expand Up @@ -49,3 +50,31 @@ Use *pydapper* with a custom connection pool.
```python
{!docs/../docs_src/connections/pymssql_using.py!}
```

## aioodbc
`aioodbc` supports async methods for Microsoft SQL Server and other ODBC-compatible databases.
It is based on [pyodbc](https://github.com/mkleehammer/pyodbc).

### Installation
=== "pip"
```console
pip install pydapper[aioodbc]
```

=== "poetry"
```console
poetry add pydapper -E aioodbc
```

### Example - `connect_async`
To use async with MSSQL you need to use `aioodbc` driver.
Please see the [pyodbc docs](https://github.com/mkleehammer/pyodbc/wiki) for a full description about connecting.
```python
{!docs/../docs_src/connections/aioodbc_mssql_connect.py!}
```

### Example - `using_async`
Use *pydapper* with a `aioodbc` connection pool.
```python
{!docs/../docs_src/connections/aioodbc_mssql_using.py!}
```
30 changes: 29 additions & 1 deletion docs/database_support/sqlite.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Supported drivers:

| dbapi | default | driver | connection class |
|-----------------------------------------------------------|------------|------------------|----------------------|
| --------------------------------------------------------- | ---------- | ---------------- | -------------------- |
| [sqlite3](https://docs.python.org/3/library/sqlite3.html) | :thumbsup: | `sqlite+sqlite3` | `sqlite3.Connection` |

## sqlite3
Expand Down Expand Up @@ -49,3 +49,31 @@ Use *pydapper* with a custom connection pool.
```python
{!docs/../docs_src/connections/sqlite3_using.py!}
```

## aioodbc
`aioodbc` supports async methods for ODBC-compatible databases. It is based on [pyodbc](https://github.com/mkleehammer/pyodbc).
You may need to install [SQLite3 ODBC Driver](http://www.ch-werner.de/sqliteodbc/).

### Installation
=== "pip"
```console
pip install pydapper[aioodbc]
```

=== "poetry"
```console
poetry add pydapper -E aioodbc
```

### Example - `connect_async`
To use async with SQLite you can use `aioodbc` driver.
Please see the [pyodbc docs](https://github.com/mkleehammer/pyodbc/wiki) for a full description about connecting.
```python
{!docs/../docs_src/connections/aioodbc_sqlite_connect.py!}
```

### Example - `using_async`
Use *pydapper* with a `aioodbc` connection pool.
```python
{!docs/../docs_src/connections/aioodbc_sqlite_using.py!}
```
19 changes: 19 additions & 0 deletions docs_src/connections/aioodbc_mssql_connect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import asyncio

import pydapper


async def main():
async with pydapper.connect_async("mssql+aioodbc://sa:pydapper@localhost/pydapper") as commands:
print(type(commands))
# <class 'pydapper.odbc.aioodbc.AioodbcCommands'>

print(type(commands.connection))
# <class 'aioodbc.connection.Connection'>

async with commands.cursor() as raw_cursor:
print(type(raw_cursor))
# <class 'aioodbc.cursor.Cursor'>


asyncio.run(main())
21 changes: 21 additions & 0 deletions docs_src/connections/aioodbc_mssql_using.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import asyncio

import aioodbc

import pydapper


async def main():
async with aioodbc.create_pool(
dsn="DRIVER={ODBC Driver 17 for SQL Server};SERVER=localhost,1433;DATABASE=pydapper;UID=sa;PWD=pydapper"
) as pool:
conn = await pool.acquire()
async with pydapper.using_async(conn) as commands:
print(type(commands))
# <class 'pydapper.odbc.aioodbc.AioodbcCommands'>

print(type(commands.connection))
# <class 'aioodbc.connection.Connection'>


asyncio.run(main())
19 changes: 19 additions & 0 deletions docs_src/connections/aioodbc_sqlite_connect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import asyncio

import pydapper


async def main():
async with pydapper.connect_async("sqlite+aioodbc://pydapper.db") as commands:
print(type(commands))
# <class 'pydapper.odbc.aioodbc.AioodbcCommands'>

print(type(commands.connection))
# <class 'aioodbc.connection.Connection'>

async with commands.cursor() as raw_cursor:
print(type(raw_cursor))
# <class 'aioodbc.cursor.Cursor'>


asyncio.run(main())
19 changes: 19 additions & 0 deletions docs_src/connections/aioodbc_sqlite_using.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import asyncio

import aioodbc

import pydapper


async def main():
async with aioodbc.create_pool(dsn="DRIVER={{SQLite3 ODBC Driver}};DATABASE=pydapper.db") as pool:
conn = await pool.acquire()
async with pydapper.using_async(conn) as commands:
print(type(commands))
# <class 'pydapper.odbc.aioodbc.AioodbcCommands'>

print(type(commands.connection))
# <class 'aioodbc.connection.Connection'>


asyncio.run(main())
59 changes: 57 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pydapper/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .main import using_async
from .mssql import PymssqlCommands as _PymssqlCommands
from .mysql import MySqlConnectorPythonCommands as _MySqlConnectorPythonCommands
from .odbc import AioodbcCommands as _AioodbcCommands
from .oracle import CxOracleCommands as _CxOracleCommands
from .postgresql import AiopgCommands as _AioPgCommand
from .postgresql import Psycopg2Commands as _Psycopg2Commands
Expand Down
1 change: 1 addition & 0 deletions pydapper/odbc/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .aioodbc import AioodbcCommands
62 changes: 62 additions & 0 deletions pydapper/odbc/aioodbc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from typing import TYPE_CHECKING

from pydapper.commands import CommandsAsync
from pydapper.commands import DefaultSqlParamHandler
from pydapper.main import register_async
from pydapper.types import AsyncCursorType
from pydapper.utils import import_dbapi_module

if TYPE_CHECKING:
from ..dsn_parser import PydapperParseResult


_DRIVERS = {
"mssql": "{ODBC Driver 17 for SQL Server}",
"mysql": "{MySQL ODBC 8.0 Unicode Driver}",
"postgresql": "{PostgreSQL Unicode}",
"sqlite": "{SQLite3 ODBC Driver}",
}


def pydapper_dsn_to_odbc(parsed_dsn: "PydapperParseResult") -> str:
chunks = {}

Check warning on line 22 in pydapper/odbc/aioodbc.py

View check run for this annotation

Codecov / codecov/patch

pydapper/odbc/aioodbc.py#L22

Added line #L22 was not covered by tests

server = parsed_dsn.host

Check warning on line 24 in pydapper/odbc/aioodbc.py

View check run for this annotation

Codecov / codecov/patch

pydapper/odbc/aioodbc.py#L24

Added line #L24 was not covered by tests
if parsed_dsn.port:
server += f",{parsed_dsn.port}"

Check warning on line 26 in pydapper/odbc/aioodbc.py

View check run for this annotation

Codecov / codecov/patch

pydapper/odbc/aioodbc.py#L26

Added line #L26 was not covered by tests

if server:
chunks["server"] = server

Check warning on line 29 in pydapper/odbc/aioodbc.py

View check run for this annotation

Codecov / codecov/patch

pydapper/odbc/aioodbc.py#L29

Added line #L29 was not covered by tests

if parsed_dsn.username:
chunks["uid"] = parsed_dsn.username

Check warning on line 32 in pydapper/odbc/aioodbc.py

View check run for this annotation

Codecov / codecov/patch

pydapper/odbc/aioodbc.py#L32

Added line #L32 was not covered by tests

if parsed_dsn.password:
chunks["pwd"] = parsed_dsn.password

Check warning on line 35 in pydapper/odbc/aioodbc.py

View check run for this annotation

Codecov / codecov/patch

pydapper/odbc/aioodbc.py#L35

Added line #L35 was not covered by tests

if parsed_dsn.dbname:
chunks["database"] = parsed_dsn.dbname

Check warning on line 38 in pydapper/odbc/aioodbc.py

View check run for this annotation

Codecov / codecov/patch

pydapper/odbc/aioodbc.py#L38

Added line #L38 was not covered by tests

if parsed_dsn.dbms:
chunks["driver"] = _DRIVERS.get(parsed_dsn.dbms, "")

Check warning on line 41 in pydapper/odbc/aioodbc.py

View check run for this annotation

Codecov / codecov/patch

pydapper/odbc/aioodbc.py#L41

Added line #L41 was not covered by tests

chunks = {**chunks, **parsed_dsn.query}

Check warning on line 43 in pydapper/odbc/aioodbc.py

View check run for this annotation

Codecov / codecov/patch

pydapper/odbc/aioodbc.py#L43

Added line #L43 was not covered by tests

return ";".join([f"{k.upper()}={v}" for k, v in chunks.items()])


@register_async("aioodbc")
class AioodbcCommands(CommandsAsync):
class SqlParamHandler(DefaultSqlParamHandler):
def get_param_placeholder(self, param_name: str) -> str:
return "(?)"

Check warning on line 52 in pydapper/odbc/aioodbc.py

View check run for this annotation

Codecov / codecov/patch

pydapper/odbc/aioodbc.py#L52

Added line #L52 was not covered by tests

@classmethod
async def connect_async(cls, parsed_dsn: "PydapperParseResult", **connect_kwargs) -> "CommandsAsync":
aioodbc = import_dbapi_module("aioodbc")
dsn = pydapper_dsn_to_odbc(parsed_dsn)
conn = await aioodbc.connect(

Check warning on line 58 in pydapper/odbc/aioodbc.py

View check run for this annotation

Codecov / codecov/patch

pydapper/odbc/aioodbc.py#L56-L58

Added lines #L56 - L58 were not covered by tests
dsn=dsn,
**connect_kwargs,
)
return cls(conn)

Check warning on line 62 in pydapper/odbc/aioodbc.py

View check run for this annotation

Codecov / codecov/patch

pydapper/odbc/aioodbc.py#L62

Added line #L62 was not covered by tests
Loading
Loading