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.
Files changed (37) hide show
  1. {dinao-2.2.0.dev178/dinao.egg-info → dinao-2.2.0.dev182}/PKG-INFO +2 -1
  2. dinao-2.2.0.dev182/dinao/__version__.py +2 -0
  3. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/__init__.py +6 -1
  4. dinao-2.2.0.dev182/dinao/backend/sqlite/__init__.py +9 -0
  5. dinao-2.2.0.dev182/dinao/backend/sqlite/aiosqlite.py +96 -0
  6. dinao-2.2.0.dev182/dinao/backend/sqlite/base.py +15 -0
  7. dinao-2.2.0.dev178/dinao/backend/sqlite.py → dinao-2.2.0.dev182/dinao/backend/sqlite/stdlib.py +4 -7
  8. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/binding/mappers.py +17 -2
  9. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182/dinao.egg-info}/PKG-INFO +2 -1
  10. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao.egg-info/SOURCES.txt +4 -1
  11. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao.egg-info/requires.txt +1 -0
  12. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/pyproject.toml +1 -0
  13. dinao-2.2.0.dev178/dinao/__version__.py +0 -2
  14. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/AUTHORS +0 -0
  15. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/LICENSE +0 -0
  16. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/README.md +0 -0
  17. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/__init__.py +0 -0
  18. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/base.py +0 -0
  19. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/errors.py +0 -0
  20. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/mariadb.py +0 -0
  21. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/mysql.py +0 -0
  22. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/postgres/__init__.py +0 -0
  23. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/postgres/asyncpg.py +0 -0
  24. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/postgres/base.py +0 -0
  25. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/postgres/psycopg2.py +0 -0
  26. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/backend/postgres/psycopg3.py +0 -0
  27. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/binding/__init__.py +0 -0
  28. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/binding/binders/__init__.py +0 -0
  29. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/binding/binders/async_.py +0 -0
  30. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/binding/binders/base.py +0 -0
  31. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/binding/binders/sync.py +0 -0
  32. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/binding/errors.py +0 -0
  33. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/binding/templating.py +0 -0
  34. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao/mung.py +0 -0
  35. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao.egg-info/dependency_links.txt +0 -0
  36. {dinao-2.2.0.dev178 → dinao-2.2.0.dev182}/dinao.egg-info/top_level.txt +0 -0
  37. {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.dev178
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"
@@ -0,0 +1,2 @@
1
+ # noqa: D100
2
+ __version__ = "2.2.0.dev182"
@@ -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,9 @@
1
+ """Implementation of SQLite backends."""
2
+
3
+ from dinao.backend.sqlite.aiosqlite import AsyncConnectionPoolAiosqlite
4
+ from dinao.backend.sqlite.stdlib import ConnectionPoolSQLite3
5
+
6
+ __all__ = [
7
+ "AsyncConnectionPoolAiosqlite",
8
+ "ConnectionPoolSQLite3",
9
+ ]
@@ -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}
@@ -1,9 +1,9 @@
1
- """Implementation of SQLite backends."""
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
- return row[0]
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.dev178
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
@@ -18,6 +18,7 @@ pyspelling>=2.12.1
18
18
  pytest
19
19
  pytest-asyncio
20
20
  pytest-cov
21
+ aiosqlite
21
22
  asyncpg
22
23
  psycopg2-binary
23
24
  psycopg[binary]
@@ -31,6 +31,7 @@ tests = [
31
31
  "pytest",
32
32
  "pytest-asyncio",
33
33
  "pytest-cov",
34
+ "aiosqlite",
34
35
  "asyncpg",
35
36
  "psycopg2-binary",
36
37
  "psycopg[binary]",
@@ -1,2 +0,0 @@
1
- # noqa: D100
2
- __version__ = "2.2.0.dev178"
File without changes
File without changes
File without changes
File without changes
File without changes