mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2026-08-24 10:05:29 -05:00
vendor aiosqlite
This commit is contained in:
@@ -7,11 +7,11 @@ from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from time import monotonic
|
||||
|
||||
import aiosqlite
|
||||
import click
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint8, uint32
|
||||
|
||||
from chia._vendored import aiosqlite
|
||||
from chia.consensus.block_height_map import BlockHeightMap
|
||||
from chia.consensus.blockchain import Blockchain
|
||||
from chia.consensus.default_constants import DEFAULT_CONSTANTS
|
||||
|
||||
@@ -4,7 +4,6 @@ import logging
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
import aiosqlite
|
||||
import pytest
|
||||
from chia_rs import CoinRecord, CoinState, FullBlock, additions_and_removals, get_flags_for_height_and_constants
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
@@ -14,6 +13,7 @@ from chia._tests.blockchain.blockchain_test_utils import _validate_and_add_block
|
||||
from chia._tests.util.coin_store import add_coin_records_to_db
|
||||
from chia._tests.util.db_connection import DBConnection
|
||||
from chia._tests.util.misc import Marks, datacases
|
||||
from chia._vendored import aiosqlite
|
||||
from chia.consensus.block_body_validation import ForkInfo
|
||||
from chia.consensus.block_generator_info import block_has_transactions_generator, get_transactions_generator_bytes
|
||||
from chia.consensus.block_height_map import BlockHeightMap
|
||||
|
||||
@@ -6,7 +6,6 @@ from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import aiosqlite
|
||||
import pytest
|
||||
|
||||
# TODO: update after resolution in https://github.com/pytest-dev/pytest/issues/7469
|
||||
@@ -14,6 +13,7 @@ from _pytest.fixtures import SubRequest
|
||||
|
||||
from chia._tests.util.db_connection import DBConnection, PathDBConnection
|
||||
from chia._tests.util.misc import Marks, boolean_datacases, datacases
|
||||
from chia._vendored import aiosqlite
|
||||
from chia.util.db_wrapper import DBWrapper2, ForeignKeyError, InternalError, NestedForeignKeyDelayedRequestError
|
||||
from chia.util.task_referencer import create_referenced_task
|
||||
|
||||
|
||||
@@ -5,8 +5,7 @@ from collections.abc import AsyncIterator
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from chia._vendored import aiosqlite
|
||||
from chia.util.db_wrapper import DBWrapper2, generate_in_memory_db_uri
|
||||
|
||||
|
||||
|
||||
@@ -13,13 +13,13 @@ from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
import aiosqlite
|
||||
import zstd
|
||||
from chia_rs import FullBlock
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint16
|
||||
|
||||
from chia._tests.util.constants import test_constants as TEST_CONSTANTS
|
||||
from chia._vendored import aiosqlite
|
||||
from chia.cmds.init_funcs import chia_init
|
||||
from chia.consensus.augmented_chain import AugmentedBlockchain
|
||||
from chia.consensus.block_body_validation import ForkInfo
|
||||
|
||||
@@ -11,7 +11,6 @@ from types import SimpleNamespace
|
||||
from typing import Any, cast
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import aiosqlite
|
||||
import pytest
|
||||
from chia_rs import CoinRecord, CoinSpend, G1Element, G2Element
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
@@ -49,6 +48,7 @@ from chia._tests.wallet.test_wallet_coin_store import (
|
||||
)
|
||||
from chia.cmds.cmd_classes import ChiaCliContext
|
||||
from chia.cmds.cmd_helpers import NeedsWalletRPC, WalletClientInfo
|
||||
from chia._vendored import aiosqlite
|
||||
from chia.cmds.coins import CombineCMD, SplitCMD
|
||||
from chia.cmds.param_types import CliAddress, CliAmount
|
||||
from chia.cmds.wallet import (
|
||||
|
||||
@@ -10,7 +10,6 @@ from dataclasses import dataclass
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from aiosqlite import Error as AIOSqliteError
|
||||
from chia_rs import (
|
||||
BlockRecord,
|
||||
Coin,
|
||||
@@ -40,6 +39,7 @@ from chia._tests.util.misc import patch_request_handler, wallet_height_at_least
|
||||
from chia._tests.util.setup_nodes import OldSimulatorsAndWallets
|
||||
from chia._tests.util.time_out_assert import time_out_assert, time_out_assert_not_none
|
||||
from chia._tests.weight_proof.test_weight_proof import load_blocks_dont_validate
|
||||
from chia._vendored.aiosqlite import Error as AIOSqliteError
|
||||
from chia.consensus.augmented_chain import AugmentedBlockchain
|
||||
from chia.consensus.block_body_validation import ForkInfo
|
||||
from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Vendored third-party packages
|
||||
|
||||
This directory contains third-party packages that are vendored (copied) into the
|
||||
`chia` package instead of being declared as external dependencies. Code here is
|
||||
imported as `chia._vendored.<package>`.
|
||||
|
||||
Do not edit the vendored sources by hand. To upgrade, replace the package
|
||||
directory wholesale with a new release and update the version recorded below.
|
||||
|
||||
## aiosqlite
|
||||
|
||||
- Upstream: https://github.com/omnilib/aiosqlite
|
||||
- Version: 0.22.1
|
||||
- Source: https://files.pythonhosted.org/packages/4e/8a/64761f4005f17809769d23e518d915db74e6310474e733e3593cfc854ef1/aiosqlite-0.22.1.tar.gz
|
||||
- sha256: 043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650
|
||||
- License: MIT (see `aiosqlite/LICENSE`)
|
||||
|
||||
Only the `aiosqlite/` package directory and its `LICENSE` are vendored; the
|
||||
upstream `tests/` directory is intentionally omitted. The package has no runtime
|
||||
dependencies beyond the standard library.
|
||||
|
||||
### Upgrade steps
|
||||
|
||||
1. Download the new sdist from PyPI and verify its hash.
|
||||
2. Replace `chia/_vendored/aiosqlite/` with the new `aiosqlite/` package dir plus
|
||||
its `LICENSE` (delete the bundled `tests/` dir).
|
||||
3. Update the version/source/hash recorded above.
|
||||
4. Run the DB tests and `mypy`/`ruff`.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Amethyst Reese
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Copyright 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",
|
||||
]
|
||||
@@ -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.22.1"
|
||||
@@ -0,0 +1,56 @@
|
||||
# Copyright Amethyst Reese
|
||||
# Licensed under the MIT license
|
||||
|
||||
|
||||
from collections.abc import Coroutine, Generator
|
||||
from contextlib import AbstractAsyncContextManager
|
||||
from functools import wraps
|
||||
from typing import Any, Callable, TypeVar
|
||||
|
||||
from .cursor import Cursor
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
class Result(AbstractAsyncContextManager[_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
|
||||
@@ -0,0 +1,468 @@
|
||||
# Copyright Amethyst Reese
|
||||
# Licensed under the MIT license
|
||||
|
||||
"""
|
||||
Core implementation of aiosqlite proxies
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import sqlite3
|
||||
from collections.abc import AsyncIterator, Generator, Iterable
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from queue import Empty, Queue, SimpleQueue
|
||||
from threading import Thread
|
||||
from typing import Any, Callable, Literal, Optional, Union
|
||||
from warnings import warn
|
||||
|
||||
from .context import contextmanager
|
||||
from .cursor import Cursor
|
||||
|
||||
__all__ = ["connect", "Connection", "Cursor"]
|
||||
|
||||
AuthorizerCallback = Callable[[int, str, str, str, str], int]
|
||||
|
||||
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()
|
||||
_TxQueue = SimpleQueue[tuple[Optional[asyncio.Future], Callable[[], Any]]]
|
||||
|
||||
|
||||
def _connection_worker_thread(tx: _TxQueue):
|
||||
"""
|
||||
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)
|
||||
|
||||
future, function = tx.get()
|
||||
|
||||
try:
|
||||
LOG.debug("executing %s", function)
|
||||
result = function()
|
||||
|
||||
if future:
|
||||
future.get_loop().call_soon_threadsafe(set_result, future, result)
|
||||
LOG.debug("operation %s completed", function)
|
||||
|
||||
if result is _STOP_RUNNING_SENTINEL:
|
||||
break
|
||||
|
||||
except BaseException as e: # noqa B036
|
||||
LOG.debug("returning exception %s", e)
|
||||
if future:
|
||||
future.get_loop().call_soon_threadsafe(set_exception, future, e)
|
||||
|
||||
|
||||
class Connection:
|
||||
def __init__(
|
||||
self,
|
||||
connector: Callable[[], sqlite3.Connection],
|
||||
iter_chunk_size: int,
|
||||
loop: Optional[asyncio.AbstractEventLoop] = None,
|
||||
) -> None:
|
||||
self._running = True
|
||||
self._connection: Optional[sqlite3.Connection] = None
|
||||
self._connector = connector
|
||||
self._tx: _TxQueue = SimpleQueue()
|
||||
self._iter_chunk_size = iter_chunk_size
|
||||
self._thread = Thread(target=_connection_worker_thread, args=(self._tx,))
|
||||
|
||||
if loop is not None:
|
||||
warn(
|
||||
"aiosqlite.Connection no longer uses the `loop` parameter",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
def __del__(self):
|
||||
if self._connection is None:
|
||||
return
|
||||
|
||||
warn(
|
||||
(
|
||||
f"{self!r} was deleted before being closed. "
|
||||
"Please use 'async with' or '.close()' to close the connection properly."
|
||||
),
|
||||
ResourceWarning,
|
||||
stacklevel=1,
|
||||
)
|
||||
|
||||
# Don't try to be creative here, the event loop may have already been closed.
|
||||
# Simply stop the worker thread, and let the underlying sqlite3 connection
|
||||
# be finalized by its own __del__.
|
||||
self.stop()
|
||||
|
||||
def stop(self) -> Optional[asyncio.Future]:
|
||||
"""Stop the background thread. Prefer `async with` or `await close()`"""
|
||||
self._running = False
|
||||
|
||||
def close_and_stop():
|
||||
if self._connection is not None:
|
||||
self._connection.close()
|
||||
self._connection = None
|
||||
return _STOP_RUNNING_SENTINEL
|
||||
|
||||
try:
|
||||
future = asyncio.get_event_loop().create_future()
|
||||
except Exception:
|
||||
future = None
|
||||
|
||||
self._tx.put_nowait((future, close_and_stop))
|
||||
return future
|
||||
|
||||
@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()
|
||||
|
||||
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 BaseException:
|
||||
self.stop()
|
||||
self._connection = None
|
||||
raise
|
||||
|
||||
return self
|
||||
|
||||
def __await__(self) -> Generator[Any, None, "Connection"]:
|
||||
self._thread.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._connection = None
|
||||
future = self.stop()
|
||||
if future:
|
||||
await future
|
||||
|
||||
@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 set_authorizer(
|
||||
self, authorizer_callback: Optional[AuthorizerCallback]
|
||||
) -> None:
|
||||
"""
|
||||
Set an authorizer callback to control database access.
|
||||
|
||||
The authorizer callback is invoked for each SQL statement that is prepared,
|
||||
and controls whether specific operations are permitted.
|
||||
|
||||
Example::
|
||||
|
||||
import sqlite3
|
||||
|
||||
def restrict_drops(action_code, arg1, arg2, db_name, trigger_name):
|
||||
# Deny all DROP operations
|
||||
if action_code == sqlite3.SQLITE_DROP_TABLE:
|
||||
return sqlite3.SQLITE_DENY
|
||||
# Allow everything else
|
||||
return sqlite3.SQLITE_OK
|
||||
|
||||
await conn.set_authorizer(restrict_drops)
|
||||
|
||||
See ``sqlite3`` documentation for details:
|
||||
https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.set_authorizer
|
||||
|
||||
:param authorizer_callback: An optional callable that receives five arguments:
|
||||
|
||||
- ``action_code`` (int): The action to be authorized (e.g., ``SQLITE_READ``)
|
||||
- ``arg1`` (str): First argument, meaning depends on ``action_code``
|
||||
- ``arg2`` (str): Second argument, meaning depends on ``action_code``
|
||||
- ``db_name`` (str): Database name (e.g., ``"main"``, ``"temp"``)
|
||||
- ``trigger_name`` (str): Name of trigger or view that is doing the access,
|
||||
or ``None``
|
||||
|
||||
The callback should return:
|
||||
|
||||
- ``SQLITE_OK`` (0): Allow the operation
|
||||
- ``SQLITE_DENY`` (1): Deny the operation, raise ``sqlite3.DatabaseError``
|
||||
- ``SQLITE_IGNORE`` (2): Treat operation as no-op
|
||||
|
||||
Pass ``None`` to remove the authorizer.
|
||||
"""
|
||||
await self._execute(self._conn.set_authorizer, authorizer_callback)
|
||||
|
||||
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)
|
||||
@@ -0,0 +1,110 @@
|
||||
# Copyright Amethyst Reese
|
||||
# Licensed under the MIT license
|
||||
|
||||
import sqlite3
|
||||
from collections.abc import AsyncIterator, Iterable
|
||||
from typing import Any, Callable, Optional, 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()
|
||||
@@ -7,12 +7,12 @@ from hashlib import sha256
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Union
|
||||
|
||||
import aiosqlite
|
||||
from chia_rs.datalayer import ProofOfInclusion, ProofOfInclusionLayer
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint8, uint64
|
||||
from typing_extensions import final
|
||||
|
||||
from chia._vendored import aiosqlite
|
||||
from chia.data_layer.data_layer_errors import ProofIntegrityError
|
||||
from chia.server.ws_connection import WSChiaConnection
|
||||
from chia.types.blockchain_format.program import Program
|
||||
|
||||
@@ -14,7 +14,6 @@ from hashlib import sha256
|
||||
from pathlib import Path
|
||||
from typing import Any, BinaryIO
|
||||
|
||||
import aiosqlite
|
||||
import anyio.to_thread
|
||||
import chia_rs.datalayer
|
||||
import zstd
|
||||
@@ -31,6 +30,7 @@ from chia_rs.datalayer import (
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import int64
|
||||
|
||||
from chia._vendored import aiosqlite
|
||||
from chia.data_layer.data_layer_errors import (
|
||||
KeyNotFoundError,
|
||||
MaxDeltaFileSizeExceededError,
|
||||
|
||||
@@ -2,11 +2,11 @@ from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
|
||||
from aiosqlite import Row
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint16, uint32, uint64
|
||||
from typing_extensions import Self
|
||||
|
||||
from chia._vendored.aiosqlite import Row
|
||||
from chia.data_layer.data_layer_wallet import Mirror
|
||||
from chia.data_layer.singleton_record import SingletonRecord
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
|
||||
@@ -8,11 +8,11 @@ from collections.abc import Collection
|
||||
from typing import Any, ClassVar
|
||||
|
||||
import typing_extensions
|
||||
from aiosqlite import Cursor
|
||||
from chia_rs import CoinRecord, CoinState
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint32, uint64
|
||||
|
||||
from chia._vendored.aiosqlite import Cursor
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.mempool_item import UnspentLineageInfo
|
||||
from chia.util.batches import to_batches
|
||||
|
||||
@@ -7,9 +7,9 @@ import time
|
||||
from dataclasses import dataclass, field, replace
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import aiosqlite
|
||||
from chia_rs.sized_ints import uint32, uint64
|
||||
|
||||
from chia._vendored import aiosqlite
|
||||
from chia.seeder.peer_record import PeerRecord, PeerReliability
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -12,10 +12,10 @@ from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, cast
|
||||
|
||||
import aiosqlite
|
||||
from chia_rs import ConsensusConstants
|
||||
from chia_rs.sized_ints import uint32, uint64
|
||||
|
||||
from chia._vendored import aiosqlite
|
||||
from chia.full_node.full_node_api import FullNodeAPI
|
||||
from chia.protocols import full_node_protocol
|
||||
from chia.protocols.full_node_protocol import RespondPeers
|
||||
|
||||
@@ -14,10 +14,10 @@ from pathlib import Path
|
||||
from types import FrameType
|
||||
from typing import Any
|
||||
|
||||
import aiosqlite
|
||||
import dns.asyncresolver
|
||||
from dnslib import AAAA, EDNS0, NS, QTYPE, RCODE, RD, RR, SOA, A, DNSError, DNSHeader, DNSQuestion, DNSRecord
|
||||
|
||||
from chia._vendored import aiosqlite
|
||||
from chia.seeder.crawl_store import CrawlStore
|
||||
from chia.server.signal_handlers import SignalHandlers
|
||||
from chia.util.chia_logging import initialize_service_logging
|
||||
|
||||
@@ -5,6 +5,8 @@ import copy
|
||||
from collections.abc import AsyncIterator, Awaitable, Callable
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Generic, TypeVar
|
||||
from chia._vendored import aiosqlite
|
||||
from chia.util.db_wrapper import DBWrapper2, execute_fetchone
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
|
||||
import aiosqlite
|
||||
from chia._vendored import aiosqlite
|
||||
|
||||
|
||||
async def lookup_db_version(db: aiosqlite.Connection) -> int:
|
||||
|
||||
@@ -14,10 +14,11 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, TextIO
|
||||
|
||||
import aiosqlite
|
||||
import anyio
|
||||
from typing_extensions import final
|
||||
|
||||
from chia._vendored import aiosqlite
|
||||
|
||||
if aiosqlite.sqlite_version_info < (3, 32, 0):
|
||||
SQLITE_MAX_VARIABLE_NUMBER = 900
|
||||
else:
|
||||
|
||||
@@ -3,10 +3,10 @@ from __future__ import annotations
|
||||
import logging
|
||||
from time import perf_counter
|
||||
|
||||
import aiosqlite
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint8, uint32
|
||||
|
||||
from chia._vendored import aiosqlite
|
||||
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
|
||||
from chia.util.db_wrapper import DBWrapper2
|
||||
from chia.util.errors import Err
|
||||
|
||||
@@ -3,11 +3,11 @@ from __future__ import annotations
|
||||
import dataclasses
|
||||
from functools import cmp_to_key
|
||||
|
||||
from aiosqlite import Row
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint32, uint64
|
||||
from typing_extensions import Self
|
||||
|
||||
from chia._vendored.aiosqlite import Row
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.util.db_wrapper import DBWrapper2
|
||||
|
||||
@@ -13,12 +13,12 @@ from collections.abc import AsyncIterator
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, Literal, cast, overload
|
||||
|
||||
import aiosqlite
|
||||
from chia_rs import AugSchemeMPL, CoinState, ConsensusConstants, G1Element, G2Element, HeaderBlock, PrivateKey
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint16, uint32, uint64, uint128
|
||||
from packaging.version import Version
|
||||
|
||||
from chia._vendored import aiosqlite
|
||||
from chia.consensus.blockchain import AddBlockResult
|
||||
from chia.daemon.keychain_proxy import KeychainProxy, connect_to_keychain_and_validate, wrap_local_keychain
|
||||
from chia.full_node.full_node_api import FullNodeAPI
|
||||
|
||||
@@ -15,11 +15,11 @@ from enum import IntEnum
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Literal, TypeVar, cast
|
||||
|
||||
import aiosqlite
|
||||
from chia_rs import CoinRecord, CoinSpend, CoinState, ConsensusConstants, G1Element, G2Element, PrivateKey
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint8, uint16, uint32, uint64, uint128
|
||||
|
||||
from chia._vendored import aiosqlite
|
||||
from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward
|
||||
from chia.consensus.coinbase import farmer_parent_id, pool_parent_id
|
||||
from chia.data_layer.data_layer_wallet import DataLayerWallet
|
||||
|
||||
@@ -4,11 +4,11 @@ import dataclasses
|
||||
import logging
|
||||
import time
|
||||
|
||||
import aiosqlite
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint8, uint32
|
||||
from typing_extensions import Any
|
||||
|
||||
from chia._vendored import aiosqlite
|
||||
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
|
||||
from chia.util.bech32m import encode_puzzle_hash
|
||||
from chia.util.db_wrapper import DBWrapper2
|
||||
|
||||
+1
-17
@@ -199,22 +199,6 @@ files = [
|
||||
frozenlist = ">=1.1.0"
|
||||
typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""}
|
||||
|
||||
[[package]]
|
||||
name = "aiosqlite"
|
||||
version = "0.22.1"
|
||||
description = "asyncio bridge to the standard sqlite3 module"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb"},
|
||||
{file = "aiosqlite-0.22.1.tar.gz", hash = "sha256:043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["attribution (==1.8.0)", "black (==25.11.0)", "build (>=1.2)", "coverage[toml] (==7.10.7)", "flake8 (==7.3.0)", "flake8-bugbear (==24.12.12)", "flit (==3.12.0)", "mypy (==1.19.0)", "ufmt (==2.8.0)", "usort (==1.0.8.post1)"]
|
||||
docs = ["sphinx (==8.1.3)", "sphinx-mdinclude (==0.6.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "altgraph"
|
||||
version = "0.17.5"
|
||||
@@ -5244,4 +5228,4 @@ upnp = ["miniupnpc"]
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.10, <4"
|
||||
content-hash = "0a699e35185bb71a490019eedf0b00326f181d8dbb28ee85f87a0a4824623c88"
|
||||
content-hash = "259a85430057f1187e4ac9d76ee3eb74a9d83a8604aceb8f1ec2aae51c14f3f4"
|
||||
|
||||
@@ -46,7 +46,6 @@ changelog = "https://github.com/Chia-Network/chia-blockchain/blob/main/CHANGELOG
|
||||
[tool.poetry.dependencies]
|
||||
aiofiles = ">=24.1.0" # Async IO for files
|
||||
aiohttp = ">=3.14.0" # HTTP server for full node rpc
|
||||
aiosqlite = ">=0.20.0" # asyncio wrapper for sqlite, to store blocks
|
||||
anyio = ">=4.6.2.post1"
|
||||
bitstring = ">=4.1.4" # Binary data management library
|
||||
boto3 = ">=1.43.8" # AWS S3 for Data Layer S3 plugin
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
line-length = 120
|
||||
|
||||
# Vendored third-party code is kept as-is and exempt from our lint rules.
|
||||
# force-exclude ensures these paths are skipped even when passed explicitly
|
||||
# (e.g. by pre-commit, which invokes ruff with a list of filenames).
|
||||
extend-exclude = ["chia/_vendored"]
|
||||
force-exclude = true
|
||||
|
||||
[lint]
|
||||
preview = true
|
||||
select = ["ALL"]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
interfaces = []
|
||||
exclude = ["**/*__pycache__", "**/*egg-info", "**/docs", "**/tests", "**/venv"]
|
||||
exclude = ["**/*__pycache__", "**/*egg-info", "**/docs", "**/tests", "**/venv", "chia/_vendored"]
|
||||
source_roots = ["."]
|
||||
|
||||
[[modules]]
|
||||
|
||||
@@ -6,12 +6,12 @@ import asyncio
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import aiosqlite
|
||||
import click
|
||||
import zstd
|
||||
from chia_rs import FullBlock
|
||||
|
||||
from chia._tests.util.full_sync import FakePeer, FakeServer, run_sync_test
|
||||
from chia._vendored import aiosqlite
|
||||
from chia.cmds.init_funcs import chia_init
|
||||
from chia.consensus.augmented_chain import AugmentedBlockchain
|
||||
from chia.consensus.block_body_validation import ForkInfo
|
||||
|
||||
Reference in New Issue
Block a user