Skip to content
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

database adapters for native asyncio bindings #78

Merged
merged 51 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
5cac193
start moving in a more generic, non-twisted direction
glyph Mar 15, 2024
5ba469d
get a mysql-connector adapter type-checking too
glyph Mar 15, 2024
6b2ff47
more recent mypy needs this
glyph Mar 15, 2024
8b004af
missing annotations import
glyph Mar 16, 2024
ca6886e
friendlier syntax for old python
glyph Mar 16, 2024
68431dc
pin more deps
glyph Mar 16, 2024
d38063c
oops
glyph Mar 16, 2024
36ecdad
let's see if we can test a postgres
glyph Mar 16, 2024
2f8d6ae
attempt to run the tests
glyph Mar 16, 2024
d7c75b4
CI debugging
glyph Mar 16, 2024
9adbd38
what even is yaml
glyph Mar 16, 2024
61b500d
okay so we're not seeing PGPORT
glyph Mar 16, 2024
6a502e6
environment variables
glyph Mar 16, 2024
a176b5e
optimize services
glyph Mar 16, 2024
76b7bb0
someone please help me budget this my CI is dying
glyph Mar 16, 2024
f4f6076
line up auth
glyph Mar 16, 2024
0bf6761
try try try again
glyph Mar 16, 2024
1a58b5a
test docstring
glyph Mar 16, 2024
bca1bc9
move things around and weaken the Twisted dependency somewhat
glyph Mar 16, 2024
b4e3d91
address moves
glyph Mar 16, 2024
5dd08d3
psycopg-binary only supports cpython
glyph Mar 16, 2024
3c48f1c
add some postgres tests for real
glyph Mar 16, 2024
22db5b9
cite explanation of the weird image-naming convention
glyph Mar 16, 2024
e5ff04e
explicitly describe the mapping
glyph Mar 16, 2024
2949c03
rename, export a public name
glyph Mar 17, 2024
c191ecd
more renaming, cleanups, public name
glyph Mar 17, 2024
dfda063
attempt to test mysql
glyph Mar 17, 2024
02df454
address coverage gap that dropping assertNoResult dependency introduced
glyph Mar 18, 2024
4be1032
oh hey that one _is_ actually an async test case
glyph Mar 18, 2024
76765a6
don't wipe out the failures. gosh there were a ton of these
glyph Mar 18, 2024
41b1c1a
I can't believe it's not postgres
glyph Mar 18, 2024
79b6832
these are only for catching misconfigurations of the test run itself
glyph Mar 18, 2024
f444dd0
deduplicate a little
glyph Mar 18, 2024
827fb6d
add in a call to `close` so as not to leak cursors and to cover drive…
glyph Mar 18, 2024
bc77f2b
just use `dict` since it's more amenable to driver interfaces
glyph Mar 19, 2024
5bca84e
test out psycopg description
glyph Mar 19, 2024
30d17ef
annotations
glyph Mar 19, 2024
67c500a
refactor: connection pooling should not couple to threading
glyph Mar 19, 2024
f77d881
oh hey I already built a connection pool, let's use it
glyph Mar 19, 2024
2353c69
python version of README example for easier checking
glyph Mar 19, 2024
139cb00
update README, with easier-reading version of imports
glyph Mar 19, 2024
64a0e8f
3.8
glyph Mar 19, 2024
05790b5
comment out executemany. not needed for accessors, not really tested …
glyph Mar 19, 2024
6abea38
start building out common adapter tests
glyph Mar 19, 2024
75effbd
test coverage, drop as-yet-unused fetchmany()
glyph Mar 20, 2024
e68f93d
add skips
glyph Mar 20, 2024
c3411f9
annotations
glyph Mar 20, 2024
327ca06
not entirely sure what the difference is here, but there's a difference
glyph Mar 20, 2024
e19e599
follow the configuration I set out in my own config file
glyph Mar 20, 2024
ef66478
just use asserts, we're already in except blocks
glyph Mar 20, 2024
41f1080
these lists are never empty
glyph Mar 20, 2024
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
3 changes: 3 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ extend-ignore =

######## WARNINGS ABOVE SHOULD BE FIXED ########

# module level import not at top of file
E402,

# Invalid first argument used for instance method
B902,

Expand Down
67 changes: 65 additions & 2 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -183,18 +183,54 @@ jobs:


unit:
name: "Py:${{ matrix.python-version }} - Tw:${{ matrix.twisted-version }} - ${{ matrix.os }}"
name: "${{ matrix.tox-suffix }}Py:${{ matrix.python-version }} - Tw:${{ matrix.twisted-version }} - ${{ matrix.os }}"

runs-on: ${{ matrix.os }}
timeout-minutes: 30
continue-on-error: ${{ matrix.optional }}

services:
mysql:
image: "${{ matrix.mysql-image }}"
env:
MYSQL_ROOT_PASSWORD: "ghamysqlpassword"
MYSQL_DATABASE: "ghamysqldb"
ports:
- "3306:3306"
options: >-
--health-cmd "mysqladmin ping -ppass"
--health-interval 10s
--health-start-period 10s
--health-timeout 5s
--health-retries 10

postgres16:

# https://github.com/actions/runner/issues/822
image: "${{ matrix.pg-image }}"

env:
POSTGRES_DB: "ghapgdb"
POSTGRES_USER: "ghapguser"
POSTGRES_PASSWORD: "ghapgpass"
POSTGRES_PORT: "5432"
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

strategy:
matrix:
os: ["ubuntu-latest"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
twisted-version: ["21.2", "22.1", "23.10"]
tox-prefix: ["coverage"]
tox-suffix: [""]
optional: [false]
pg-image: [""]
include:
- os: "ubuntu-latest"
python-version: "pypy-3.8"
Expand All @@ -208,6 +244,20 @@ jobs:
twisted-version: "current"
tox-prefix: "coverage"
optional: false
- os: "ubuntu-latest"
python-version: "3.12"
twisted-version: "23.10"
tox-prefix: "coverage"
tox-suffix: "-postgres"
pg-image: "postgres:16"
optional: false
- os: "ubuntu-latest"
python-version: "3.12"
twisted-version: "23.10"
tox-prefix: "coverage"
tox-suffix: "-mysql"
mysql-image: "mysql:8.0.29"
optional: false

steps:

Expand Down Expand Up @@ -239,7 +289,7 @@ jobs:
tw = "${{ matrix.twisted-version }}"
tw = tw.replace(".", "")

env = f"${{ matrix.tox-prefix }}-py{py}-tw{tw}"
env = f"${{ matrix.tox-prefix }}-py{py}-tw{tw}${{ matrix.tox-suffix }}"

print(f"TOX_ENV={env}")

Expand All @@ -259,6 +309,19 @@ jobs:

- name: Run unit tests
run: tox run -e ${TOX_ENV}
env:
PGHOST: "localhost"
PGPORT: "5432"

PGUSER: "ghapguser"
PGDATABASE: "ghapgdb"
PGPASSWORD: "ghapgpass"

MYSQL_USER: "root"
MYSQL_PWD: "ghamysqlpassword"
MYSQL_DATABASE: "ghamysqldb"
MYSQL_HOST: "localhost"
MYSQL_PORT: "3306"

- name: Combine coverage
run: tox run -e coverage_combine,coverage_report
Expand Down
4 changes: 4 additions & 0 deletions .isort.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ profile = black
# Put two blank lines after imports
lines_after_imports = 2

# not sure why this is a non-default, let's not make 'as' imports randomly way
# more verbose
combine_as_imports = true

# Match max-line-length in Flake8 config
line_length = 80

Expand Down
1 change: 0 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ repos:
- id: python-check-blanket-noqa
- id: python-check-blanket-type-ignore
- id: python-no-eval
- id: python-use-type-annotations
- id: rst-backticks
- id: rst-directive-colons
- id: rst-inline-touching-normal
Expand Down
41 changes: 23 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,40 +80,45 @@ drivers and pytest is definitely on the roadmap.)
Using it looks like this:

```python
@dataclass
class Quote:
db: QuoteDB
id: int
contents: str

from dbxs import query, one, many
from dbxs.dbapi_async import adaptSynchronousDriver
from dbxs import accessor, many, one, query, statement

class QuoteDB(Protocol):
@query(
sql="select id, contents from quote where id = {id}",
load=one(Quote),
)
async def quoteByID(self, id: int) -> Quote:
...
@query(sql="SELECT id, contents FROM quote WHERE id={id}", load=one(Quote))
async def quoteByID(self, id: int) -> Quote: ...

@query(sql="SELECT id, contents FROM quote", load=many(Quote))
def allQuotes(self) -> AsyncIterable[Quote]: ...

@statement(sql="INSERT INTO quote (contents) VALUES ({text})")
async def addQuote(self, text: str) -> None: ...

@query(
sql="select id, contents from quote",
load=many(Quote),
from dbxs.dbapi import DBAPIConnection
def sqliteWithSchema() -> DBAPIConnection:
c = connect(":memory:")
c.execute(
"CREATE TABLE quote (contents, id INTEGER PRIMARY KEY AUTOINCREMENT)"
)
def allQuotes(self) -> AsyncIterable[Quote]:
...
c.commit()
return c

from dbxs.adapters.dbapi_twisted import adaptSynchronousDriver
driver = adaptSynchronousDriver(sqliteWithSchema, paramstyle)
quotes = accessor(QuoteDB)

driver = adaptSynchronousDriver(lambda: sqlite3.connect(...))

async def main() -> None:
from dbxs.async_dbapi import transaction
async with transaction(driver) as t:
quotedb: QuoteDB = quotes(t)
print("quote 1", (await quotedb.quoteByID(1)).contents)
await quotedb.addQuote("hello, world")
async for quote in quotedb.allQuotes():
print("quote", quote.id, quote.contents)

matched = (await quotedb.quoteByID(quote.id)) == quote
print(f"quote ({quote.id}) {quote.contents!r} {matched}")
```

### Previous SQL Interface Solutions
Expand Down
63 changes: 63 additions & 0 deletions check_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from __future__ import annotations

from dataclasses import dataclass
from sqlite3 import connect, paramstyle
from typing import AsyncIterable, Protocol


@dataclass
class Quote:
db: QuoteDB
id: int
contents: str


from dbxs import accessor, many, one, query, statement
from dbxs.adapters.dbapi_twisted import adaptSynchronousDriver
from dbxs.async_dbapi import transaction
from dbxs.dbapi import DBAPIConnection


class QuoteDB(Protocol):
@query(sql="SELECT id, contents FROM quote WHERE id={id}", load=one(Quote))
async def quoteByID(self, id: int) -> Quote:
...

@query(sql="SELECT id, contents FROM quote", load=many(Quote))
def allQuotes(self) -> AsyncIterable[Quote]:
...

@statement(sql="INSERT INTO quote (contents) VALUES ({text})")
async def addQuote(self, text: str) -> None:
...


def sqliteWithSchema() -> DBAPIConnection:
c = connect(":memory:")
c.execute(
"CREATE TABLE quote (contents, id INTEGER PRIMARY KEY AUTOINCREMENT)"
)
c.commit()
return c


driver = adaptSynchronousDriver(sqliteWithSchema, paramstyle)
quotes = accessor(QuoteDB)


async def main() -> None:
async with transaction(driver) as t:
quotedb: QuoteDB = quotes(t)
await quotedb.addQuote("hello, world")
async for quote in quotedb.allQuotes():
matched = (await quotedb.quoteByID(quote.id)) == quote
print(f"quote ({quote.id}) {quote.contents!r} {matched}")


if __name__ == "__main__":
from twisted.internet.defer import Deferred
from twisted.internet.task import react

@react
def run(reactor: object) -> Deferred[None]:
return Deferred.fromCoroutine(main())
2 changes: 2 additions & 0 deletions requirements/mypy.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
-r postgres.txt
mypy==1.8.0
mypy-zope==1.0.3
mysql-connector-python==8.3.0
types-click==7.1.8
1 change: 1 addition & 0 deletions requirements/mysql.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mysql-connector-python==8.3.0
2 changes: 2 additions & 0 deletions requirements/postgres.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
psycopg==3.1.18
psycopg-binary==3.1.18 ; implementation_name == "cpython"
2 changes: 1 addition & 1 deletion requirements/tox-pin-base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ hyperlink==21.0.0
idna==3.4
incremental==22.10.0
six==1.16.0
typing_extensions==4.9.0
typing-extensions==4.10.0
zope.interface==6.1
Loading
Loading