dinao 2.2.0.dev178__tar.gz → 2.2.0.dev182__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dinao-2.2.0.dev178/dinao.egg-info → dinao-2.2.0.dev182}/PKG-INFO +2 -1
- dinao-2.2.0.dev182/dinao/__version__.py +2 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/__init__.py +6 -1
- dinao-2.2.0.dev182/dinao/backend/sqlite/__init__.py +9 -0
- dinao-2.2.0.dev182/dinao/backend/sqlite/aiosqlite.py +96 -0
- dinao-2.2.0.dev182/dinao/backend/sqlite/base.py +15 -0
- dinao-2.2.0.dev178/dinao/backend/sqlite.py → dinao-2.2.0.dev182/dinao/backend/sqlite/stdlib.py +4 -7
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/binding/mappers.py +17 -2
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182/dinao.egg-info}/PKG-INFO +2 -1
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao.egg-info/SOURCES.txt +4 -1
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao.egg-info/requires.txt +1 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/pyproject.toml +1 -0
- dinao-2.2.0.dev178/dinao/__version__.py +0 -2
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/AUTHORS +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/LICENSE +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/README.md +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/__init__.py +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/base.py +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/errors.py +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/mariadb.py +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/mysql.py +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/postgres/__init__.py +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/postgres/asyncpg.py +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/postgres/base.py +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/postgres/psycopg2.py +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/postgres/psycopg3.py +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/binding/__init__.py +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/binding/binders/__init__.py +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/binding/binders/async_.py +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/binding/binders/base.py +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/binding/binders/sync.py +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/binding/errors.py +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/binding/templating.py +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/mung.py +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao.egg-info/dependency_links.txt +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao.egg-info/top_level.txt +0 -0
- {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dinao
|
|
3
|
-
Version: 2.2.0.
|
|
3
|
+
Version: 2.2.0.dev182
|
|
4
4
|
Summary: A simple database API
|
|
5
5
|
Author-email: Jim Carreer <jim.carreer+dinao@gmail.com>
|
|
6
6
|
License-Expression: ISC
|
|
@@ -24,6 +24,7 @@ Provides-Extra: tests
|
|
|
24
24
|
Requires-Dist: pytest; extra == "tests"
|
|
25
25
|
Requires-Dist: pytest-asyncio; extra == "tests"
|
|
26
26
|
Requires-Dist: pytest-cov; extra == "tests"
|
|
27
|
+
Requires-Dist: aiosqlite; extra == "tests"
|
|
27
28
|
Requires-Dist: asyncpg; extra == "tests"
|
|
28
29
|
Requires-Dist: psycopg2-binary; extra == "tests"
|
|
29
30
|
Requires-Dist: psycopg[binary]; extra == "tests"
|
|
@@ -21,7 +21,7 @@ from dinao.backend.postgres import (
|
|
|
21
21
|
ConnectionPoolPSQLPsycopg2,
|
|
22
22
|
ConnectionPoolPSQLPsycopg3,
|
|
23
23
|
)
|
|
24
|
-
from dinao.backend.sqlite import ConnectionPoolSQLite3
|
|
24
|
+
from dinao.backend.sqlite import AsyncConnectionPoolAiosqlite, ConnectionPoolSQLite3
|
|
25
25
|
|
|
26
26
|
ENGINE_DEFAULTS = {"postgresql": "psycopg2", "sqlite3": None, "mariadb": "mariadbconnector", "mysql": "mysqlconnector"}
|
|
27
27
|
MODE_DEFAULTS = {
|
|
@@ -29,6 +29,7 @@ MODE_DEFAULTS = {
|
|
|
29
29
|
("postgresql", "psycopg"): "sync",
|
|
30
30
|
("postgresql", "asyncpg"): "async",
|
|
31
31
|
("sqlite3", None): "sync",
|
|
32
|
+
("sqlite3", "aiosqlite"): "async",
|
|
32
33
|
("mariadb", "mariadbconnector"): "sync",
|
|
33
34
|
("mysql", "mysqlconnector"): "sync",
|
|
34
35
|
}
|
|
@@ -94,6 +95,10 @@ def create_connection_pool(db_url: str) -> ConnectionPoolBase:
|
|
|
94
95
|
if mode == "async":
|
|
95
96
|
return AsyncConnectionPoolPSQLPsycopg3(db_url)
|
|
96
97
|
return ConnectionPoolPSQLPsycopg3(db_url)
|
|
98
|
+
if backend == "sqlite3" and engine == "aiosqlite":
|
|
99
|
+
if mode == "sync":
|
|
100
|
+
raise UnsupportedBackendError("The aiosqlite driver does not support sync mode")
|
|
101
|
+
return AsyncConnectionPoolAiosqlite(db_url)
|
|
97
102
|
if backend == "sqlite3":
|
|
98
103
|
if mode == "async":
|
|
99
104
|
raise UnsupportedBackendError("The sqlite3 backend does not support async mode")
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Implementation of SQLite backend using aiosqlite."""
|
|
2
|
+
|
|
3
|
+
from contextlib import asynccontextmanager
|
|
4
|
+
|
|
5
|
+
from dinao.backend.base import AsyncConnection, AsyncConnectionPool, AsyncResultSet
|
|
6
|
+
from dinao.backend.errors import BackendNotInstalledError
|
|
7
|
+
from dinao.backend.sqlite.base import ConnectionPoolSQLiteMixin
|
|
8
|
+
from dinao.mung import StaticMungSymbolProvider
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ConnectionAiosqlite(AsyncConnection):
|
|
12
|
+
"""Async Connection implementation wrapping an aiosqlite connection."""
|
|
13
|
+
|
|
14
|
+
async def _execute(self, cursor, sql: str, params: tuple = None):
|
|
15
|
+
"""Execute SQL on the given cursor with optional parameters."""
|
|
16
|
+
if params:
|
|
17
|
+
await cursor.execute(sql, params)
|
|
18
|
+
return
|
|
19
|
+
await cursor.execute(sql)
|
|
20
|
+
|
|
21
|
+
@asynccontextmanager
|
|
22
|
+
async def query(self, sql: str, params: tuple = None):
|
|
23
|
+
"""Execute the given SQL as a statement with the given parameters. Provide the results as context.
|
|
24
|
+
|
|
25
|
+
Overrides the base class because the aiosqlite ``connection.cursor()`` is a coroutine.
|
|
26
|
+
|
|
27
|
+
:param sql: the SQL statement(s) to execute
|
|
28
|
+
:param params: the values to bind to the execution of the given SQL
|
|
29
|
+
:returns: an async result set representing the query's results
|
|
30
|
+
"""
|
|
31
|
+
cursor = await self._cnx.cursor()
|
|
32
|
+
await self._execute(cursor, sql, params)
|
|
33
|
+
try:
|
|
34
|
+
yield AsyncResultSet(cursor)
|
|
35
|
+
finally:
|
|
36
|
+
await cursor.close()
|
|
37
|
+
|
|
38
|
+
async def execute(self, sql: str, params: tuple = None, commit: bool = None) -> int:
|
|
39
|
+
"""Execute the given SQL and return the affected row count.
|
|
40
|
+
|
|
41
|
+
Overrides the base class because the aiosqlite ``connection.cursor()`` is a coroutine.
|
|
42
|
+
|
|
43
|
+
:param sql: the SQL statement(s) to execute
|
|
44
|
+
:param params: the values to bind to the execution of the given SQL
|
|
45
|
+
:param commit: commit the changes after execution, defaults to value given in constructor
|
|
46
|
+
"""
|
|
47
|
+
commit = commit if commit is not None else self._auto_commit
|
|
48
|
+
cursor = await self._cnx.cursor()
|
|
49
|
+
await self._execute(cursor, sql, params)
|
|
50
|
+
affected = cursor.rowcount
|
|
51
|
+
if commit:
|
|
52
|
+
await self.commit()
|
|
53
|
+
await cursor.close()
|
|
54
|
+
return affected
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class AsyncConnectionPoolAiosqlite(ConnectionPoolSQLiteMixin, AsyncConnectionPool):
|
|
58
|
+
"""Async ConnectionPool implementation for aiosqlite."""
|
|
59
|
+
|
|
60
|
+
_mung_symbol = StaticMungSymbolProvider("?")
|
|
61
|
+
|
|
62
|
+
def __init__(self, db_url: str):
|
|
63
|
+
"""Construct an async connection pool for the given connection URL.
|
|
64
|
+
|
|
65
|
+
The db_url is expected to be in the following format::
|
|
66
|
+
|
|
67
|
+
"sqlite3+aiosqlite://{filename}"
|
|
68
|
+
|
|
69
|
+
This is not a real pool; each call to ``lease()`` creates a new connection.
|
|
70
|
+
|
|
71
|
+
:param db_url: a url with the described format
|
|
72
|
+
:raises: ConfigurationError, BackendNotInstalledError
|
|
73
|
+
"""
|
|
74
|
+
super().__init__(db_url)
|
|
75
|
+
try:
|
|
76
|
+
import aiosqlite # noqa: F401
|
|
77
|
+
except ModuleNotFoundError: # pragma: no cover
|
|
78
|
+
raise BackendNotInstalledError("Module aiosqlite not installed, cannot create async connection pool")
|
|
79
|
+
self._cnx_kwargs = self._url_to_cnx_kwargs()
|
|
80
|
+
self._raise_for_unexpected_args()
|
|
81
|
+
|
|
82
|
+
async def lease(self): # noqa: D102
|
|
83
|
+
import aiosqlite
|
|
84
|
+
|
|
85
|
+
inner_cnx = await aiosqlite.connect(**self._cnx_kwargs)
|
|
86
|
+
return ConnectionAiosqlite(inner_cnx)
|
|
87
|
+
|
|
88
|
+
async def release(self, cnx): # noqa: D102
|
|
89
|
+
await cnx._cnx.close()
|
|
90
|
+
|
|
91
|
+
async def dispose(self): # noqa: D102
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def mung_symbol(self) -> StaticMungSymbolProvider: # noqa: D102
|
|
96
|
+
return self._mung_symbol
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Shared base classes for SQLite backends."""
|
|
2
|
+
|
|
3
|
+
import os.path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ConnectionPoolSQLiteMixin:
|
|
7
|
+
"""Mixin providing shared SQLite URL parsing and connection kwargs construction."""
|
|
8
|
+
|
|
9
|
+
def _url_to_cnx_kwargs(self):
|
|
10
|
+
"""Parse the URL and construct connection keyword arguments.
|
|
11
|
+
|
|
12
|
+
:returns: a dictionary of connection keyword arguments
|
|
13
|
+
"""
|
|
14
|
+
file_path = os.path.abspath(os.path.expanduser(self._db_url.path))
|
|
15
|
+
return {"database": file_path}
|
dinao-2.2.0.dev178/dinao/backend/sqlite.py → dinao-2.2.0.dev182/dinao/backend/sqlite/stdlib.py
RENAMED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
"""Implementation of SQLite
|
|
1
|
+
"""Implementation of SQLite backend using sqlite3."""
|
|
2
2
|
|
|
3
|
-
import os.path
|
|
4
3
|
import sqlite3
|
|
5
4
|
|
|
6
5
|
from dinao.backend.base import Connection, ConnectionPool
|
|
6
|
+
from dinao.backend.sqlite.base import ConnectionPoolSQLiteMixin
|
|
7
7
|
from dinao.mung import StaticMungSymbolProvider
|
|
8
8
|
|
|
9
9
|
|
|
@@ -11,13 +11,14 @@ class ConnectionSQLite3(Connection):
|
|
|
11
11
|
"""Implementation of Connection for Sqlite3."""
|
|
12
12
|
|
|
13
13
|
def _execute(self, cursor, sql: str, params: tuple = None):
|
|
14
|
+
"""Execute SQL on the given cursor with optional parameters."""
|
|
14
15
|
if params:
|
|
15
16
|
cursor.execute(sql, params)
|
|
16
17
|
return
|
|
17
18
|
cursor.execute(sql)
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
class ConnectionPoolSQLite3(ConnectionPool):
|
|
21
|
+
class ConnectionPoolSQLite3(ConnectionPoolSQLiteMixin, ConnectionPool):
|
|
21
22
|
"""Implementation of ConnectionPool for SQLite3."""
|
|
22
23
|
|
|
23
24
|
_mung_symbol = StaticMungSymbolProvider("?")
|
|
@@ -38,10 +39,6 @@ class ConnectionPoolSQLite3(ConnectionPool):
|
|
|
38
39
|
self._cnx_kwargs = self._url_to_cnx_kwargs()
|
|
39
40
|
self._raise_for_unexpected_args()
|
|
40
41
|
|
|
41
|
-
def _url_to_cnx_kwargs(self):
|
|
42
|
-
file_path = os.path.abspath(os.path.expanduser(self._db_url.path))
|
|
43
|
-
return {"database": file_path}
|
|
44
|
-
|
|
45
42
|
def lease(self) -> Connection: # noqa: D102
|
|
46
43
|
inner_cnx = sqlite3.connect(**self._cnx_kwargs)
|
|
47
44
|
return ConnectionSQLite3(inner_cnx)
|
|
@@ -69,10 +69,25 @@ class ClassRowMapper(DictRowMapper):
|
|
|
69
69
|
class SingleValueRowMapper(RowMapper):
|
|
70
70
|
"""Implements a row mapper for a primitive type."""
|
|
71
71
|
|
|
72
|
+
def __init__(self, mapped_type: typing.Type):
|
|
73
|
+
"""Construct a single value row mapper that casts the result to the target type.
|
|
74
|
+
|
|
75
|
+
:param mapped_type: the primitive type to cast the result to
|
|
76
|
+
"""
|
|
77
|
+
self._mapped_type = mapped_type
|
|
78
|
+
|
|
72
79
|
def __call__(self, row: typing.Tuple, description: typing.Tuple[ColumnDescriptor, ...]): # noqa: D102
|
|
73
80
|
if len(row) > 1:
|
|
74
81
|
raise TooManyValuesError(f"Too many values, expected 1, got {len(row)}")
|
|
75
|
-
|
|
82
|
+
value = row[0]
|
|
83
|
+
if value is None:
|
|
84
|
+
return None
|
|
85
|
+
if isinstance(value, self._mapped_type):
|
|
86
|
+
return value
|
|
87
|
+
# SQLite special case, we should consider better adapter handling
|
|
88
|
+
if self._mapped_type is datetime and isinstance(value, str):
|
|
89
|
+
return datetime.fromisoformat(value)
|
|
90
|
+
return self._mapped_type(value)
|
|
76
91
|
|
|
77
92
|
|
|
78
93
|
def get_row_mapper(row_type: typing.Type) -> typing.Optional[RowMapper]:
|
|
@@ -92,7 +107,7 @@ def get_row_mapper(row_type: typing.Type) -> typing.Optional[RowMapper]:
|
|
|
92
107
|
elif row_type in DICT_GENERICS:
|
|
93
108
|
return DictRowMapper()
|
|
94
109
|
elif row_type in NATIVE_SINGLE:
|
|
95
|
-
return SingleValueRowMapper()
|
|
110
|
+
return SingleValueRowMapper(row_type)
|
|
96
111
|
# Exclude typing special forms that look like classes in Python 3.14+
|
|
97
112
|
# Bare Union/Optional without type parameters cannot be mapped
|
|
98
113
|
elif row_type in TYPING_SPECIAL_FORMS:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dinao
|
|
3
|
-
Version: 2.2.0.
|
|
3
|
+
Version: 2.2.0.dev182
|
|
4
4
|
Summary: A simple database API
|
|
5
5
|
Author-email: Jim Carreer <jim.carreer+dinao@gmail.com>
|
|
6
6
|
License-Expression: ISC
|
|
@@ -24,6 +24,7 @@ Provides-Extra: tests
|
|
|
24
24
|
Requires-Dist: pytest; extra == "tests"
|
|
25
25
|
Requires-Dist: pytest-asyncio; extra == "tests"
|
|
26
26
|
Requires-Dist: pytest-cov; extra == "tests"
|
|
27
|
+
Requires-Dist: aiosqlite; extra == "tests"
|
|
27
28
|
Requires-Dist: asyncpg; extra == "tests"
|
|
28
29
|
Requires-Dist: psycopg2-binary; extra == "tests"
|
|
29
30
|
Requires-Dist: psycopg[binary]; extra == "tests"
|
|
@@ -15,12 +15,15 @@ dinao/backend/base.py
|
|
|
15
15
|
dinao/backend/errors.py
|
|
16
16
|
dinao/backend/mariadb.py
|
|
17
17
|
dinao/backend/mysql.py
|
|
18
|
-
dinao/backend/sqlite.py
|
|
19
18
|
dinao/backend/postgres/__init__.py
|
|
20
19
|
dinao/backend/postgres/asyncpg.py
|
|
21
20
|
dinao/backend/postgres/base.py
|
|
22
21
|
dinao/backend/postgres/psycopg2.py
|
|
23
22
|
dinao/backend/postgres/psycopg3.py
|
|
23
|
+
dinao/backend/sqlite/__init__.py
|
|
24
|
+
dinao/backend/sqlite/aiosqlite.py
|
|
25
|
+
dinao/backend/sqlite/base.py
|
|
26
|
+
dinao/backend/sqlite/stdlib.py
|
|
24
27
|
dinao/binding/__init__.py
|
|
25
28
|
dinao/binding/errors.py
|
|
26
29
|
dinao/binding/mappers.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|