📚 Documentação inicial do ALETHEIA

- MANUAL-PRODUTO.md: Manual do usuário final
- MANUAL-VENDAS.md: Estratégia comercial e vendas
- MANUAL-TECNICO.md: Infraestrutura e deploy
- README.md: Visão geral do projeto
This commit is contained in:
2026-02-10 15:08:15 -03:00
commit 20a26affaa
16617 changed files with 3202171 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
# Copyright 2022 Amethyst Reese
# Licensed under the MIT license
"""asyncio bridge to the standard sqlite3 module"""
from sqlite3 import ( # pylint: disable=redefined-builtin
DatabaseError,
Error,
IntegrityError,
NotSupportedError,
OperationalError,
paramstyle,
ProgrammingError,
register_adapter,
register_converter,
Row,
sqlite_version,
sqlite_version_info,
Warning,
)
__author__ = "Amethyst Reese"
from .__version__ import __version__
from .core import connect, Connection, Cursor
__all__ = [
"__version__",
"paramstyle",
"register_adapter",
"register_converter",
"sqlite_version",
"sqlite_version_info",
"connect",
"Connection",
"Cursor",
"Row",
"Warning",
"Error",
"DatabaseError",
"IntegrityError",
"ProgrammingError",
"OperationalError",
"NotSupportedError",
]

View File

@@ -0,0 +1,7 @@
"""
This file is automatically generated by attribution.
Do not edit manually. Get more info at https://attribution.omnilib.dev
"""
__version__ = "0.20.0"

View File

@@ -0,0 +1,54 @@
# Copyright 2018
# Licensed under the MIT license
from functools import wraps
from typing import Any, AsyncContextManager, Callable, Coroutine, Generator, TypeVar
from .cursor import Cursor
_T = TypeVar("_T")
class Result(AsyncContextManager[_T], Coroutine[Any, Any, _T]):
__slots__ = ("_coro", "_obj")
def __init__(self, coro: Coroutine[Any, Any, _T]):
self._coro = coro
self._obj: _T
def send(self, value) -> None:
return self._coro.send(value)
def throw(self, typ, val=None, tb=None) -> None:
if val is None:
return self._coro.throw(typ)
if tb is None:
return self._coro.throw(typ, val)
return self._coro.throw(typ, val, tb)
def close(self) -> None:
return self._coro.close()
def __await__(self) -> Generator[Any, None, _T]:
return self._coro.__await__()
async def __aenter__(self) -> _T:
self._obj = await self._coro
return self._obj
async def __aexit__(self, exc_type, exc, tb) -> None:
if isinstance(self._obj, Cursor):
await self._obj.close()
def contextmanager(
method: Callable[..., Coroutine[Any, Any, _T]]
) -> Callable[..., Result[_T]]:
@wraps(method)
def wrapper(self, *args, **kwargs) -> Result[_T]:
return Result(method(self, *args, **kwargs))
return wrapper

View File

@@ -0,0 +1,394 @@
# Copyright 2022 Amethyst Reese
# Licensed under the MIT license
"""
Core implementation of aiosqlite proxies
"""
import asyncio
import logging
import sqlite3
from functools import partial
from pathlib import Path
from queue import Empty, Queue, SimpleQueue
from threading import Thread
from typing import (
Any,
AsyncIterator,
Callable,
Generator,
Iterable,
Literal,
Optional,
Tuple,
Type,
Union,
)
from warnings import warn
from .context import contextmanager
from .cursor import Cursor
__all__ = ["connect", "Connection", "Cursor"]
LOG = logging.getLogger("aiosqlite")
IsolationLevel = Optional[Literal["DEFERRED", "IMMEDIATE", "EXCLUSIVE"]]
def set_result(fut: asyncio.Future, result: Any) -> None:
"""Set the result of a future if it hasn't been set already."""
if not fut.done():
fut.set_result(result)
def set_exception(fut: asyncio.Future, e: BaseException) -> None:
"""Set the exception of a future if it hasn't been set already."""
if not fut.done():
fut.set_exception(e)
_STOP_RUNNING_SENTINEL = object()
class Connection(Thread):
def __init__(
self,
connector: Callable[[], sqlite3.Connection],
iter_chunk_size: int,
loop: Optional[asyncio.AbstractEventLoop] = None,
) -> None:
super().__init__()
self._running = True
self._connection: Optional[sqlite3.Connection] = None
self._connector = connector
self._tx: SimpleQueue[Tuple[asyncio.Future, Callable[[], Any]]] = SimpleQueue()
self._iter_chunk_size = iter_chunk_size
if loop is not None:
warn(
"aiosqlite.Connection no longer uses the `loop` parameter",
DeprecationWarning,
)
def _stop_running(self):
self._running = False
# PEP 661 is not accepted yet, so we cannot type a sentinel
self._tx.put_nowait(_STOP_RUNNING_SENTINEL) # type: ignore[arg-type]
@property
def _conn(self) -> sqlite3.Connection:
if self._connection is None:
raise ValueError("no active connection")
return self._connection
def _execute_insert(self, sql: str, parameters: Any) -> Optional[sqlite3.Row]:
cursor = self._conn.execute(sql, parameters)
cursor.execute("SELECT last_insert_rowid()")
return cursor.fetchone()
def _execute_fetchall(self, sql: str, parameters: Any) -> Iterable[sqlite3.Row]:
cursor = self._conn.execute(sql, parameters)
return cursor.fetchall()
def run(self) -> None:
"""
Execute function calls on a separate thread.
:meta private:
"""
while True:
# Continues running until all queue items are processed,
# even after connection is closed (so we can finalize all
# futures)
tx_item = self._tx.get()
if tx_item is _STOP_RUNNING_SENTINEL:
break
future, function = tx_item
try:
LOG.debug("executing %s", function)
result = function()
LOG.debug("operation %s completed", function)
future.get_loop().call_soon_threadsafe(set_result, future, result)
except BaseException as e: # noqa B036
LOG.debug("returning exception %s", e)
future.get_loop().call_soon_threadsafe(set_exception, future, e)
async def _execute(self, fn, *args, **kwargs):
"""Queue a function with the given arguments for execution."""
if not self._running or not self._connection:
raise ValueError("Connection closed")
function = partial(fn, *args, **kwargs)
future = asyncio.get_event_loop().create_future()
self._tx.put_nowait((future, function))
return await future
async def _connect(self) -> "Connection":
"""Connect to the actual sqlite database."""
if self._connection is None:
try:
future = asyncio.get_event_loop().create_future()
self._tx.put_nowait((future, self._connector))
self._connection = await future
except Exception:
self._stop_running()
self._connection = None
raise
return self
def __await__(self) -> Generator[Any, None, "Connection"]:
self.start()
return self._connect().__await__()
async def __aenter__(self) -> "Connection":
return await self
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
await self.close()
@contextmanager
async def cursor(self) -> Cursor:
"""Create an aiosqlite cursor wrapping a sqlite3 cursor object."""
return Cursor(self, await self._execute(self._conn.cursor))
async def commit(self) -> None:
"""Commit the current transaction."""
await self._execute(self._conn.commit)
async def rollback(self) -> None:
"""Roll back the current transaction."""
await self._execute(self._conn.rollback)
async def close(self) -> None:
"""Complete queued queries/cursors and close the connection."""
if self._connection is None:
return
try:
await self._execute(self._conn.close)
except Exception:
LOG.info("exception occurred while closing connection")
raise
finally:
self._stop_running()
self._connection = None
@contextmanager
async def execute(
self, sql: str, parameters: Optional[Iterable[Any]] = None
) -> Cursor:
"""Helper to create a cursor and execute the given query."""
if parameters is None:
parameters = []
cursor = await self._execute(self._conn.execute, sql, parameters)
return Cursor(self, cursor)
@contextmanager
async def execute_insert(
self, sql: str, parameters: Optional[Iterable[Any]] = None
) -> Optional[sqlite3.Row]:
"""Helper to insert and get the last_insert_rowid."""
if parameters is None:
parameters = []
return await self._execute(self._execute_insert, sql, parameters)
@contextmanager
async def execute_fetchall(
self, sql: str, parameters: Optional[Iterable[Any]] = None
) -> Iterable[sqlite3.Row]:
"""Helper to execute a query and return all the data."""
if parameters is None:
parameters = []
return await self._execute(self._execute_fetchall, sql, parameters)
@contextmanager
async def executemany(
self, sql: str, parameters: Iterable[Iterable[Any]]
) -> Cursor:
"""Helper to create a cursor and execute the given multiquery."""
cursor = await self._execute(self._conn.executemany, sql, parameters)
return Cursor(self, cursor)
@contextmanager
async def executescript(self, sql_script: str) -> Cursor:
"""Helper to create a cursor and execute a user script."""
cursor = await self._execute(self._conn.executescript, sql_script)
return Cursor(self, cursor)
async def interrupt(self) -> None:
"""Interrupt pending queries."""
return self._conn.interrupt()
async def create_function(
self, name: str, num_params: int, func: Callable, deterministic: bool = False
) -> None:
"""
Create user-defined function that can be later used
within SQL statements. Must be run within the same thread
that query executions take place so instead of executing directly
against the connection, we defer this to `run` function.
If ``deterministic`` is true, the created function is marked as deterministic,
which allows SQLite to perform additional optimizations. This flag is supported
by SQLite 3.8.3 or higher, ``NotSupportedError`` will be raised if used with
older versions.
"""
await self._execute(
self._conn.create_function,
name,
num_params,
func,
deterministic=deterministic,
)
@property
def in_transaction(self) -> bool:
return self._conn.in_transaction
@property
def isolation_level(self) -> Optional[str]:
return self._conn.isolation_level
@isolation_level.setter
def isolation_level(self, value: IsolationLevel) -> None:
self._conn.isolation_level = value
@property
def row_factory(self) -> Optional[Type]:
return self._conn.row_factory
@row_factory.setter
def row_factory(self, factory: Optional[Type]) -> None:
self._conn.row_factory = factory
@property
def text_factory(self) -> Callable[[bytes], Any]:
return self._conn.text_factory
@text_factory.setter
def text_factory(self, factory: Callable[[bytes], Any]) -> None:
self._conn.text_factory = factory
@property
def total_changes(self) -> int:
return self._conn.total_changes
async def enable_load_extension(self, value: bool) -> None:
await self._execute(self._conn.enable_load_extension, value) # type: ignore
async def load_extension(self, path: str):
await self._execute(self._conn.load_extension, path) # type: ignore
async def set_progress_handler(
self, handler: Callable[[], Optional[int]], n: int
) -> None:
await self._execute(self._conn.set_progress_handler, handler, n)
async def set_trace_callback(self, handler: Callable) -> None:
await self._execute(self._conn.set_trace_callback, handler)
async def iterdump(self) -> AsyncIterator[str]:
"""
Return an async iterator to dump the database in SQL text format.
Example::
async for line in db.iterdump():
...
"""
dump_queue: Queue = Queue()
def dumper():
try:
for line in self._conn.iterdump():
dump_queue.put_nowait(line)
dump_queue.put_nowait(None)
except Exception:
LOG.exception("exception while dumping db")
dump_queue.put_nowait(None)
raise
fut = self._execute(dumper)
task = asyncio.ensure_future(fut)
while True:
try:
line: Optional[str] = dump_queue.get_nowait()
if line is None:
break
yield line
except Empty:
if task.done():
LOG.warning("iterdump completed unexpectedly")
break
await asyncio.sleep(0.01)
await task
async def backup(
self,
target: Union["Connection", sqlite3.Connection],
*,
pages: int = 0,
progress: Optional[Callable[[int, int, int], None]] = None,
name: str = "main",
sleep: float = 0.250,
) -> None:
"""
Make a backup of the current database to the target database.
Takes either a standard sqlite3 or aiosqlite Connection object as the target.
"""
if isinstance(target, Connection):
target = target._conn
await self._execute(
self._conn.backup,
target,
pages=pages,
progress=progress,
name=name,
sleep=sleep,
)
def connect(
database: Union[str, Path],
*,
iter_chunk_size=64,
loop: Optional[asyncio.AbstractEventLoop] = None,
**kwargs: Any,
) -> Connection:
"""Create and return a connection proxy to the sqlite database."""
if loop is not None:
warn(
"aiosqlite.connect() no longer uses the `loop` parameter",
DeprecationWarning,
)
def connector() -> sqlite3.Connection:
if isinstance(database, str):
loc = database
elif isinstance(database, bytes):
loc = database.decode("utf-8")
else:
loc = str(database)
return sqlite3.connect(loc, **kwargs)
return Connection(connector, iter_chunk_size)

View File

@@ -0,0 +1,118 @@
# Copyright 2022 Amethyst Reese
# Licensed under the MIT license
import sqlite3
from typing import (
Any,
AsyncIterator,
Callable,
Iterable,
Optional,
Tuple,
Type,
TYPE_CHECKING,
)
if TYPE_CHECKING:
from .core import Connection
class Cursor:
def __init__(self, conn: "Connection", cursor: sqlite3.Cursor) -> None:
self.iter_chunk_size = conn._iter_chunk_size
self._conn = conn
self._cursor = cursor
def __aiter__(self) -> AsyncIterator[sqlite3.Row]:
"""The cursor proxy is also an async iterator."""
return self._fetch_chunked()
async def _fetch_chunked(self):
while True:
rows = await self.fetchmany(self.iter_chunk_size)
if not rows:
return
for row in rows:
yield row
async def _execute(self, fn, *args, **kwargs):
"""Execute the given function on the shared connection's thread."""
return await self._conn._execute(fn, *args, **kwargs)
async def execute(
self, sql: str, parameters: Optional[Iterable[Any]] = None
) -> "Cursor":
"""Execute the given query."""
if parameters is None:
parameters = []
await self._execute(self._cursor.execute, sql, parameters)
return self
async def executemany(
self, sql: str, parameters: Iterable[Iterable[Any]]
) -> "Cursor":
"""Execute the given multiquery."""
await self._execute(self._cursor.executemany, sql, parameters)
return self
async def executescript(self, sql_script: str) -> "Cursor":
"""Execute a user script."""
await self._execute(self._cursor.executescript, sql_script)
return self
async def fetchone(self) -> Optional[sqlite3.Row]:
"""Fetch a single row."""
return await self._execute(self._cursor.fetchone)
async def fetchmany(self, size: Optional[int] = None) -> Iterable[sqlite3.Row]:
"""Fetch up to `cursor.arraysize` number of rows."""
args: Tuple[int, ...] = ()
if size is not None:
args = (size,)
return await self._execute(self._cursor.fetchmany, *args)
async def fetchall(self) -> Iterable[sqlite3.Row]:
"""Fetch all remaining rows."""
return await self._execute(self._cursor.fetchall)
async def close(self) -> None:
"""Close the cursor."""
await self._execute(self._cursor.close)
@property
def rowcount(self) -> int:
return self._cursor.rowcount
@property
def lastrowid(self) -> Optional[int]:
return self._cursor.lastrowid
@property
def arraysize(self) -> int:
return self._cursor.arraysize
@arraysize.setter
def arraysize(self, value: int) -> None:
self._cursor.arraysize = value
@property
def description(self) -> Tuple[Tuple[str, None, None, None, None, None, None], ...]:
return self._cursor.description
@property
def row_factory(self) -> Optional[Callable[[sqlite3.Cursor, sqlite3.Row], object]]:
return self._cursor.row_factory
@row_factory.setter
def row_factory(self, factory: Optional[Type]) -> None:
self._cursor.row_factory = factory
@property
def connection(self) -> sqlite3.Connection:
return self._cursor.connection
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
await self.close()

View File

@@ -0,0 +1,4 @@
# Copyright 2022 Amethyst Reese
# Licensed under the MIT license
from .smoke import SmokeTest

View File

@@ -0,0 +1,7 @@
# Copyright 2022 Amethyst Reese
# Licensed under the MIT license
import unittest
if __name__ == "__main__":
unittest.main(module="aiosqlite.tests", verbosity=2)

View File

@@ -0,0 +1,29 @@
# Copyright 2022 Amethyst Reese
# Licensed under the MIT license
import logging
import sys
def setup_logger():
log = logging.getLogger("")
log.setLevel(logging.INFO)
logging.addLevelName(logging.ERROR, "E")
logging.addLevelName(logging.WARNING, "W")
logging.addLevelName(logging.INFO, "I")
logging.addLevelName(logging.DEBUG, "V")
date_fmt = r"%H:%M:%S"
verbose_fmt = (
"%(asctime)s,%(msecs)d %(levelname)s "
"%(module)s:%(funcName)s():%(lineno)d "
"%(message)s"
)
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.INFO)
handler.setFormatter(logging.Formatter(verbose_fmt, date_fmt))
log.addHandler(handler)
return log

View File

@@ -0,0 +1,203 @@
# Copyright 2022 Amethyst Reese
# Licensed under the MIT license
"""
Simple perf tests for aiosqlite and the asyncio run loop.
"""
import string
import tempfile
import time
from unittest import IsolatedAsyncioTestCase as TestCase
import aiosqlite
from .smoke import setup_logger
TEST_DB = ":memory:"
TARGET = 2.0
RESULTS = {}
def timed(fn, name=None):
"""
Decorator for perf testing a block of async code.
Expects the wrapped function to return an async generator.
The generator should do setup, then yield when ready to start perf testing.
The decorator will then pump the generator repeatedly until the target
time has been reached, then close the generator and print perf results.
"""
name = name or fn.__name__
async def wrapper(*args, **kwargs):
gen = fn(*args, **kwargs)
await gen.asend(None)
count = 0
before = time.time()
while True:
count += 1
value = time.time() - before < TARGET
try:
if value:
await gen.asend(value)
else:
await gen.aclose()
break
except StopAsyncIteration:
break
except Exception as e:
print(f"exception occurred: {e}")
return
duration = time.time() - before
RESULTS[name] = (count, duration)
return wrapper
class PerfTest(TestCase):
@classmethod
def setUpClass(cls):
print(f"Running perf tests for at least {TARGET:.1f}s each...")
setup_logger()
@classmethod
def tearDownClass(cls):
print(f"\n{'Perf Test':<25} Iterations Duration {'Rate':>11}")
for name in sorted(RESULTS):
count, duration = RESULTS[name]
rate = count / duration
name = name.replace("test_", "")
print(f"{name:<25} {count:>10} {duration:>7.1f}s {rate:>9.1f}/s")
@timed
async def test_connection_memory(self):
while True:
yield
async with aiosqlite.connect(TEST_DB):
pass
@timed
async def test_connection_file(self):
with tempfile.NamedTemporaryFile(delete=False) as tf:
path = tf.name
tf.close()
async with aiosqlite.connect(path) as db:
await db.execute(
"create table perf (i integer primary key asc, k integer)"
)
await db.execute("insert into perf (k) values (2), (3)")
await db.commit()
while True:
yield
async with aiosqlite.connect(path):
pass
@timed
async def test_atomics(self):
async with aiosqlite.connect(TEST_DB) as db:
await db.execute("create table perf (i integer primary key asc, k integer)")
await db.execute("insert into perf (k) values (2), (3)")
await db.commit()
while True:
yield
async with db.execute("select last_insert_rowid()") as cursor:
await cursor.fetchone()
@timed
async def test_inserts(self):
async with aiosqlite.connect(TEST_DB) as db:
await db.execute("create table perf (i integer primary key asc, k integer)")
await db.commit()
while True:
yield
await db.execute("insert into perf (k) values (1), (2), (3)")
await db.commit()
@timed
async def test_insert_ids(self):
async with aiosqlite.connect(TEST_DB) as db:
await db.execute("create table perf (i integer primary key asc, k integer)")
await db.commit()
while True:
yield
cursor = await db.execute("insert into perf (k) values (1)")
await cursor.execute("select last_insert_rowid()")
await cursor.fetchone()
await db.commit()
@timed
async def test_insert_macro_ids(self):
async with aiosqlite.connect(TEST_DB) as db:
await db.execute("create table perf (i integer primary key asc, k integer)")
await db.commit()
while True:
yield
await db.execute_insert("insert into perf (k) values (1)")
await db.commit()
@timed
async def test_select(self):
async with aiosqlite.connect(TEST_DB) as db:
await db.execute("create table perf (i integer primary key asc, k integer)")
for i in range(100):
await db.execute("insert into perf (k) values (%d)" % (i,))
await db.commit()
while True:
yield
cursor = await db.execute("select i, k from perf")
assert len(await cursor.fetchall()) == 100
@timed
async def test_select_macro(self):
async with aiosqlite.connect(TEST_DB) as db:
await db.execute("create table perf (i integer primary key asc, k integer)")
for i in range(100):
await db.execute("insert into perf (k) values (%d)" % (i,))
await db.commit()
while True:
yield
assert len(await db.execute_fetchall("select i, k from perf")) == 100
async def test_iterable_cursor_perf(self):
async with aiosqlite.connect(TEST_DB) as db:
await db.execute(
"create table ic_perf ("
"i integer primary key asc, k integer, a integer, b integer, c char(16))"
)
for batch in range(128): # add 128k rows
r_start = batch * 1024
await db.executemany(
"insert into ic_perf (k, a, b, c) values(?, 1, 2, ?)",
[
*[
(i, string.ascii_lowercase)
for i in range(r_start, r_start + 1024)
]
],
)
await db.commit()
async def test_perf(chunk_size: int):
while True:
async with db.execute("SELECT * FROM ic_perf") as cursor:
cursor.iter_chunk_size = chunk_size
async for _ in cursor:
yield
for chunk_size in [2**i for i in range(4, 11)]:
await timed(test_perf, f"iterable_cursor @ {chunk_size}")(chunk_size)

View File

@@ -0,0 +1,452 @@
# Copyright 2022 Amethyst Reese
# Licensed under the MIT license
import asyncio
import sqlite3
from pathlib import Path
from sqlite3 import OperationalError
from threading import Thread
from unittest import IsolatedAsyncioTestCase as TestCase, SkipTest
import aiosqlite
from .helpers import setup_logger
TEST_DB = Path("test.db")
# pypy uses non-standard text factory for low-level sqlite implementation
try:
from _sqlite3 import _unicode_text_factory as default_text_factory
except ImportError:
default_text_factory = str
class SmokeTest(TestCase):
@classmethod
def setUpClass(cls):
setup_logger()
def setUp(self):
if TEST_DB.exists():
TEST_DB.unlink()
def tearDown(self):
if TEST_DB.exists():
TEST_DB.unlink()
async def test_connection_await(self):
db = await aiosqlite.connect(TEST_DB)
self.assertIsInstance(db, aiosqlite.Connection)
async with db.execute("select 1, 2") as cursor:
rows = await cursor.fetchall()
self.assertEqual(rows, [(1, 2)])
await db.close()
async def test_connection_context(self):
async with aiosqlite.connect(TEST_DB) as db:
self.assertIsInstance(db, aiosqlite.Connection)
async with db.execute("select 1, 2") as cursor:
rows = await cursor.fetchall()
self.assertEqual(rows, [(1, 2)])
async def test_connection_locations(self):
class Fake: # pylint: disable=too-few-public-methods
def __str__(self):
return str(TEST_DB)
locs = ("test.db", b"test.db", Path("test.db"), Fake())
async with aiosqlite.connect(TEST_DB) as db:
await db.execute("create table foo (i integer, k integer)")
await db.execute("insert into foo (i, k) values (1, 5)")
await db.commit()
cursor = await db.execute("select * from foo")
rows = await cursor.fetchall()
for loc in locs:
async with aiosqlite.connect(loc) as db:
cursor = await db.execute("select * from foo")
self.assertEqual(await cursor.fetchall(), rows)
async def test_multiple_connections(self):
async with aiosqlite.connect(TEST_DB) as db:
await db.execute(
"create table multiple_connections "
"(i integer primary key asc, k integer)"
)
async def do_one_conn(i):
async with aiosqlite.connect(TEST_DB) as db:
await db.execute("insert into multiple_connections (k) values (?)", [i])
await db.commit()
await asyncio.gather(*[do_one_conn(i) for i in range(10)])
async with aiosqlite.connect(TEST_DB) as db:
cursor = await db.execute("select * from multiple_connections")
rows = await cursor.fetchall()
assert len(rows) == 10
async def test_multiple_queries(self):
async with aiosqlite.connect(TEST_DB) as db:
await db.execute(
"create table multiple_queries "
"(i integer primary key asc, k integer)"
)
await asyncio.gather(
*[
db.execute("insert into multiple_queries (k) values (?)", [i])
for i in range(10)
]
)
await db.commit()
async with aiosqlite.connect(TEST_DB) as db:
cursor = await db.execute("select * from multiple_queries")
rows = await cursor.fetchall()
assert len(rows) == 10
async def test_iterable_cursor(self):
async with aiosqlite.connect(TEST_DB) as db:
cursor = await db.cursor()
await cursor.execute(
"create table iterable_cursor " "(i integer primary key asc, k integer)"
)
await cursor.executemany(
"insert into iterable_cursor (k) values (?)", [[i] for i in range(10)]
)
await db.commit()
async with aiosqlite.connect(TEST_DB) as db:
cursor = await db.execute("select * from iterable_cursor")
rows = []
async for row in cursor:
rows.append(row)
assert len(rows) == 10
async def test_multi_loop_usage(self):
results = {}
def runner(k, conn):
async def query():
async with conn.execute("select * from foo") as cursor:
rows = await cursor.fetchall()
self.assertEqual(len(rows), 2)
return rows
with self.subTest(k):
loop = asyncio.new_event_loop()
rows = loop.run_until_complete(query())
loop.close()
results[k] = rows
async with aiosqlite.connect(":memory:") as db:
await db.execute("create table foo (id int, name varchar)")
await db.execute(
"insert into foo values (?, ?), (?, ?)", (1, "Sally", 2, "Janet")
)
await db.commit()
threads = [Thread(target=runner, args=(k, db)) for k in range(4)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
self.assertEqual(len(results), 4)
for rows in results.values():
self.assertEqual(len(rows), 2)
async def test_context_cursor(self):
async with aiosqlite.connect(TEST_DB) as db:
async with db.cursor() as cursor:
await cursor.execute(
"create table context_cursor "
"(i integer primary key asc, k integer)"
)
await cursor.executemany(
"insert into context_cursor (k) values (?)",
[[i] for i in range(10)],
)
await db.commit()
async with aiosqlite.connect(TEST_DB) as db:
async with db.execute("select * from context_cursor") as cursor:
rows = []
async for row in cursor:
rows.append(row)
assert len(rows) == 10
async def test_cursor_return_self(self):
async with aiosqlite.connect(TEST_DB) as db:
cursor = await db.cursor()
result = await cursor.execute(
"create table test_cursor_return_self (i integer, k integer)"
)
self.assertEqual(result, cursor, "cursor execute returns itself")
result = await cursor.executemany(
"insert into test_cursor_return_self values (?, ?)", [(1, 1), (2, 2)]
)
self.assertEqual(result, cursor)
result = await cursor.executescript(
"insert into test_cursor_return_self values (3, 3);"
"insert into test_cursor_return_self values (4, 4);"
"insert into test_cursor_return_self values (5, 5);"
)
self.assertEqual(result, cursor)
async def test_connection_properties(self):
async with aiosqlite.connect(TEST_DB) as db:
self.assertEqual(db.total_changes, 0)
async with db.cursor() as cursor:
self.assertFalse(db.in_transaction)
await cursor.execute(
"create table test_properties "
"(i integer primary key asc, k integer, d text)"
)
await cursor.execute(
"insert into test_properties (k, d) values (1, 'hi')"
)
self.assertTrue(db.in_transaction)
await db.commit()
self.assertFalse(db.in_transaction)
self.assertEqual(db.total_changes, 1)
self.assertIsNone(db.row_factory)
self.assertEqual(db.text_factory, default_text_factory)
async with db.cursor() as cursor:
await cursor.execute("select * from test_properties")
row = await cursor.fetchone()
self.assertIsInstance(row, tuple)
self.assertEqual(row, (1, 1, "hi"))
with self.assertRaises(TypeError):
_ = row["k"]
async with db.cursor() as cursor:
cursor.row_factory = aiosqlite.Row
self.assertEqual(cursor.row_factory, aiosqlite.Row)
await cursor.execute("select * from test_properties")
row = await cursor.fetchone()
self.assertIsInstance(row, aiosqlite.Row)
self.assertEqual(row[1], 1)
self.assertEqual(row[2], "hi")
self.assertEqual(row["k"], 1)
self.assertEqual(row["d"], "hi")
db.row_factory = aiosqlite.Row
db.text_factory = bytes
self.assertEqual(db.row_factory, aiosqlite.Row)
self.assertEqual(db.text_factory, bytes)
async with db.cursor() as cursor:
await cursor.execute("select * from test_properties")
row = await cursor.fetchone()
self.assertIsInstance(row, aiosqlite.Row)
self.assertEqual(row[1], 1)
self.assertEqual(row[2], b"hi")
self.assertEqual(row["k"], 1)
self.assertEqual(row["d"], b"hi")
async def test_fetch_all(self):
async with aiosqlite.connect(TEST_DB) as db:
await db.execute(
"create table test_fetch_all (i integer primary key asc, k integer)"
)
await db.execute(
"insert into test_fetch_all (k) values (10), (24), (16), (32)"
)
await db.commit()
async with aiosqlite.connect(TEST_DB) as db:
cursor = await db.execute("select k from test_fetch_all where k < 30")
rows = await cursor.fetchall()
self.assertEqual(rows, [(10,), (24,), (16,)])
async def test_enable_load_extension(self):
"""Assert that after enabling extension loading, they can be loaded"""
async with aiosqlite.connect(TEST_DB) as db:
try:
await db.enable_load_extension(True)
await db.load_extension("test")
except OperationalError as e:
assert "not authorized" not in e.args
except AttributeError as e:
raise SkipTest(
"python was not compiled with sqlite3 "
"extension support, so we can't test it"
) from e
async def test_set_progress_handler(self):
"""
Assert that after setting a progress handler returning 1, DB operations are aborted
"""
async with aiosqlite.connect(TEST_DB) as db:
await db.set_progress_handler(lambda: 1, 1)
with self.assertRaises(OperationalError):
await db.execute(
"create table test_progress_handler (i integer primary key asc, k integer)"
)
async def test_create_function(self):
"""Assert that after creating a custom function, it can be used"""
def no_arg():
return "no arg"
def one_arg(num):
return num * 2
async with aiosqlite.connect(TEST_DB) as db:
await db.create_function("no_arg", 0, no_arg)
await db.create_function("one_arg", 1, one_arg)
async with db.execute("SELECT no_arg();") as res:
row = await res.fetchone()
self.assertEqual(row[0], "no arg")
async with db.execute("SELECT one_arg(10);") as res:
row = await res.fetchone()
self.assertEqual(row[0], 20)
async def test_create_function_deterministic(self):
"""Assert that after creating a deterministic custom function, it can be used.
https://sqlite.org/deterministic.html
"""
def one_arg(num):
return num * 2
async with aiosqlite.connect(TEST_DB) as db:
await db.create_function("one_arg", 1, one_arg, deterministic=True)
await db.execute("create table foo (id int, bar int)")
# Non-deterministic functions cannot be used in indexes
await db.execute("create index t on foo(one_arg(bar))")
async def test_set_trace_callback(self):
statements = []
def callback(statement: str):
statements.append(statement)
async with aiosqlite.connect(TEST_DB) as db:
await db.set_trace_callback(callback)
await db.execute("select 10")
self.assertIn("select 10", statements)
async def test_connect_error(self):
bad_db = Path("/something/that/shouldnt/exist.db")
with self.assertRaisesRegex(OperationalError, "unable to open database"):
async with aiosqlite.connect(bad_db) as db:
self.assertIsNone(db) # should never be reached
with self.assertRaisesRegex(OperationalError, "unable to open database"):
await aiosqlite.connect(bad_db)
async def test_iterdump(self):
async with aiosqlite.connect(":memory:") as db:
await db.execute("create table foo (i integer, k charvar(250))")
await db.executemany(
"insert into foo values (?, ?)", [(1, "hello"), (2, "world")]
)
lines = [line async for line in db.iterdump()]
self.assertEqual(
lines,
[
"BEGIN TRANSACTION;",
"CREATE TABLE foo (i integer, k charvar(250));",
"INSERT INTO \"foo\" VALUES(1,'hello');",
"INSERT INTO \"foo\" VALUES(2,'world');",
"COMMIT;",
],
)
async def test_cursor_on_closed_connection(self):
db = await aiosqlite.connect(TEST_DB)
cursor = await db.execute("select 1, 2")
await db.close()
with self.assertRaisesRegex(ValueError, "Connection closed"):
await cursor.fetchall()
with self.assertRaisesRegex(ValueError, "Connection closed"):
await cursor.fetchall()
async def test_cursor_on_closed_connection_loop(self):
db = await aiosqlite.connect(TEST_DB)
cursor = await db.execute("select 1, 2")
tasks = []
for i in range(100):
if i == 50:
tasks.append(asyncio.ensure_future(db.close()))
tasks.append(asyncio.ensure_future(cursor.fetchall()))
for task in tasks:
try:
await task
except sqlite3.ProgrammingError:
pass
async def test_close_twice(self):
db = await aiosqlite.connect(TEST_DB)
await db.close()
# no error
await db.close()
async def test_backup_aiosqlite(self):
def progress(a, b, c):
print(a, b, c)
async with aiosqlite.connect(":memory:") as db1, aiosqlite.connect(
":memory:"
) as db2:
await db1.execute("create table foo (i integer, k charvar(250))")
await db1.executemany(
"insert into foo values (?, ?)", [(1, "hello"), (2, "world")]
)
await db1.commit()
with self.assertRaisesRegex(OperationalError, "no such table: foo"):
await db2.execute("select * from foo")
await db1.backup(db2, progress=progress)
async with db2.execute("select * from foo") as cursor:
rows = await cursor.fetchall()
self.assertEqual(rows, [(1, "hello"), (2, "world")])
async def test_backup_sqlite(self):
async with aiosqlite.connect(":memory:") as db1:
with sqlite3.connect(":memory:") as db2:
await db1.execute("create table foo (i integer, k charvar(250))")
await db1.executemany(
"insert into foo values (?, ?)", [(1, "hello"), (2, "world")]
)
await db1.commit()
with self.assertRaisesRegex(OperationalError, "no such table: foo"):
db2.execute("select * from foo")
await db1.backup(db2)
cursor = db2.execute("select * from foo")
rows = cursor.fetchall()
self.assertEqual(rows, [(1, "hello"), (2, "world")])