📚 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:
@@ -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",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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"
|
||||
@@ -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
|
||||
394
backend/venv/lib/python3.12/site-packages/aiosqlite/core.py
Normal file
394
backend/venv/lib/python3.12/site-packages/aiosqlite/core.py
Normal 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)
|
||||
118
backend/venv/lib/python3.12/site-packages/aiosqlite/cursor.py
Normal file
118
backend/venv/lib/python3.12/site-packages/aiosqlite/cursor.py
Normal 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()
|
||||
@@ -0,0 +1,4 @@
|
||||
# Copyright 2022 Amethyst Reese
|
||||
# Licensed under the MIT license
|
||||
|
||||
from .smoke import SmokeTest
|
||||
@@ -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)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -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)
|
||||
@@ -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")])
|
||||
Reference in New Issue
Block a user