pax_global_header00006660000000000000000000000064147500677010014521gustar00rootroot0000000000000052 comment=82eb2d2b05c257e247acba561b2548cca7fa365a aiosqlite-0.21.0/000077500000000000000000000000001475006770100135735ustar00rootroot00000000000000aiosqlite-0.21.0/.flake8000066400000000000000000000003121475006770100147420ustar00rootroot00000000000000[flake8] select = C,E,F,W,B,B9 ignore = # mccabe complexity C901 # covered by black/usort E1 E2 E3 E4 E501 max-line-length = 88 per-file-ignores = __init__.py: F401 aiosqlite-0.21.0/.github/000077500000000000000000000000001475006770100151335ustar00rootroot00000000000000aiosqlite-0.21.0/.github/dependabot.yml000066400000000000000000000003451475006770100177650ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" - package-ecosystem: "pip" directory: "/" schedule: interval: "monthly" day: "saturday" aiosqlite-0.21.0/.github/issue_template.md000066400000000000000000000002261475006770100205000ustar00rootroot00000000000000### Description ### Details * OS: * Python version: * aiosqlite version: * Can you repro on 'main' branch? * Can you repro in a clean virtualenv? aiosqlite-0.21.0/.github/pull_request_template.md000066400000000000000000000000751475006770100220760ustar00rootroot00000000000000### Description Fixes: # aiosqlite-0.21.0/.github/workflows/000077500000000000000000000000001475006770100171705ustar00rootroot00000000000000aiosqlite-0.21.0/.github/workflows/ci.yml000066400000000000000000000034021475006770100203050ustar00rootroot00000000000000name: CI on: push: branches: - main tags: - v* pull_request: permissions: contents: read env: UV_SYSTEM_PYTHON: 1 jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] os: [macOS-latest, ubuntu-latest, windows-latest] steps: - name: Checkout uses: actions/checkout@v4 - name: Set Up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} allow-prereleases: true - uses: astral-sh/setup-uv@v3 with: enable-cache: true cache-dependency-glob: pyproject.toml cache-suffix: ${{ matrix.python-version }} - name: Install run: make EXTRAS=dev install - name: Test run: make test - name: Lint run: make lint build: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.12' - uses: astral-sh/setup-uv@v3 with: enable-cache: true cache-dependency-glob: pyproject.toml - name: Install run: make EXTRAS=dev install - name: Build run: python -m build - name: Upload uses: actions/upload-artifact@v4 with: name: sdist path: dist publish: needs: build runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/v') permissions: id-token: write steps: - uses: actions/download-artifact@v4 with: name: sdist path: dist - uses: pypa/gh-action-pypi-publish@release/v1 aiosqlite-0.21.0/.gitignore000066400000000000000000000022601475006770100155630ustar00rootroot00000000000000README test.db* html/ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # dotenv .env # virtualenv .venv venv/ ENV/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ # Editors .vscode/ aiosqlite-0.21.0/.mailmap000066400000000000000000000000551475006770100152140ustar00rootroot00000000000000Amethyst Reese aiosqlite-0.21.0/.readthedocs.yml000066400000000000000000000003051475006770100166570ustar00rootroot00000000000000version: 2 sphinx: configuration: docs/conf.py python: install: - method: pip path: . extra_requirements: - docs build: os: "ubuntu-22.04" tools: python: "3.10" aiosqlite-0.21.0/CHANGELOG.md000066400000000000000000000164421475006770100154130ustar00rootroot00000000000000aiosqlite ========= [![Generated by attribution][attribution-badge]][attribution-url] v0.21.0 ------- Maintenance release - Fix: close connection correctly when BaseException raised in connection (#317) - Metadata improvements - Tested and supported on Python 3.13 - Drop support for Python 3.8 - Drop testing on PyPy ```text $ git shortlog -s v0.20.0...v0.21.0 6 Amethyst Reese 1 Gabriel 1 Stanley Kudrow 11 dependabot[bot] ``` v0.20.0 ------- Feature release - Connection `.close()` method is now idempotent (#238) - Performance improvements in connection thread and event loop (#213, #271) - Updated contributor guide (#255) - Tested on Python 3.12 - Dropped support for Python 3.7 (#230) ```text $ git shortlog -s v0.19.0...v0.20.0 6 Amethyst Reese 4 J. Nick Koston 1 Waket Zheng 18 dependabot[bot] 1 mike bayer 1 vdergachyov ``` v0.19.0 ------- Feature release - Add support for setting cursor `row_factory` (#229) - Dropped unused compatibility shims for 3.5 and 3.6 - Deprecated: Python 3.7 support will be dropped in v0.20.0 ```text $ git shortlog -s v0.18.0...v0.19.0 13 Amethyst Reese 1 Daniel Baulig 4 dependabot[bot] ``` v0.18.0 ------- Feature release - Added support for `paramstyle` (#197) - Better type hints for `isolation_level` (#172) and `text_factory` (#179) - Use stdlib typing module when possible (#114) - Replace aiounittest with stdlib on 3.8+ - Docmentation improvements (#108) - Dropped support for Python 3.7, added support for Python 3.10 and 3.11 (#208) ```text $ git shortlog -s v0.17.0...v0.18.0 31 Amethyst Reese 3 Nico0 Smart 3 Nicolas Martinez 37 dependabot[bot] 2 pandaninjas 6 pyup.io bot 1 vexelnet ``` v0.17.0 ------- Feature release * Connection objects now raise ValueError when closed and a command is executed (#79) * Fix documented examples in readme (#104) ```text $ git shortlog -s v0.16.1...v0.17.0 3 Amethyst Reese 5 Mariano Sorgente 1 Nuno André 1 pyup.io bot ``` v0.16.1 ------- Bug fix release - Reduce logging severity for exceptions (#93) - Stop logging result objects; they can be big (#102) ```text $ git shortlog -s v0.16.0...v0.16.1 1 Alexei Chetroi 3 Amethyst Reese 3 pyup.io bot ``` v0.16.0 ------- Feature release * Improved performance for async iteration on cursors (#34, #86) * Support for deterministic user functions in Python 3.8+ (#81, #83, #84) * Reduced logging severity for exceptions returned from children (#75, #76) * Fix InvalidStateError when setting future results (#80, #89) * Allow user to catch exceptions from `close()` (#68, #90) * Tested under Python 3.9 (#91) ```text $ git shortlog -s v0.15.0...v0.16.0 14 Amethyst Reese 3 Caleb Hattingh 1 Groosha 1 Lonami 4 Lonami Exo 4 ZsoltM 1 pyup.io bot ``` v0.15.0 ------- Feature release - Support for accessing connections from multiple event loops - Fixed type annotations for connection methods returning cursors - Move cursors into separate module from connections - Deprecated `loop` parameter to `connect()` and `Connection` ```text $ git shortlog -s v0.14.1...v0.15.0 7 Amethyst Reese ``` v0.14.1 ------- Bugfix release - Remove debugging print() calls. Oops! (#72) ```text $ git shortlog -s v0.14.0...v0.14.1 2 Amethyst Reese 1 Spyros Roum ``` v0.14.0 ------- Feature release - `Connection.backup()` now supported (#71) - PEP 561 support added to mark the package as type annotated (#69) - Better/fixed type annotations for context managers (#70) ```text $ git shortlog -s v0.13.0...v0.14.0 5 Amethyst Reese 3 montag451 ``` v0.13.0 ------- Feature release - `cursor.execute*()` now returns the cursor to match sqlite3 API (#62) - `Connection.set_trace_callback()` now supported (#62) - `Connection.iterdump()` is now supported (#66) - Fixed possible hung thread if connection failed (#55) - Dropped support for Python 3.5 ```text $ git shortlog -s v0.12.0...v0.13.0 32 Amethyst Reese 1 pyup.io bot 5 shipmints ``` v0.12.0 ------- Feature Release - Add support for custom functions (#58) - Official support for Python 3.8 ```text $ git shortlog -s v0.11.0...v0.12.0 4 Amethyst Reese 1 dmitrypolo 3 pyup.io bot ``` v0.11.0 ------- Feature release v0.11.0 - Added support for `set_progress_handler` (#49) - Improved and updated documentation ```text $ git shortlog -s v0.10.0...v0.11.0 11 Amethyst Reese 4 Stanislas 2 Vladislav Yarmak 1 pyup-bot 5 tat2grl85 ``` v0.10.0 ------- Feature release v0.10.0: - Support using connections without context managers (#29) - Include test suite in aiosqlite package ```text $ git shortlog -s v0.9.0...v0.10.0 16 Amethyst Reese 1 Simon Willison 1 dark0ghost ``` v0.9.0 ------ Feature release v0.9.0: - Support for sqlite extensions - Fixed support for type annotations on early Python 3.5 ```text $ git shortlog -s v0.8.1...v0.9.0 2 Alexander Lyon 3 Amethyst Reese ``` v0.8.1 ------ Bug fix release v0.8.1: - Fix connections to byte string db locations (#20) ```text $ git shortlog -s v0.8.0...v0.8.1 6 Amethyst Reese 1 DevilXD ``` v0.8.0 ------ Major release v0.8.0: - Use futures instead of polling for connections/cursors. This will significantly reduce time spent blocking the primary event loop, resulting in better performance of asyncio applications using aiosqlite. ```text $ git shortlog -s v0.7.0...v0.8.0 3 Amethyst Reese 2 Matthew Schubert ``` v0.7.0 ------ Feature release v0.7.0: - Added macros for combined insert/id and select/fetch - Better perf testing output ```text $ git shortlog -s v0.6.0...v0.7.0 4 Amethyst Reese 1 Grigi ``` v0.6.0 ------ Feature release v0.6.0: - Performance improvements for atomic or fast queries - Support passing Path-like objects to aiosqlite.connect - Unit tests now use aiounittest instead of a custom test harness - Limited set of performance tests now available ```text $ git shortlog -s v0.5.0...v0.6.0 8 Amethyst Reese 1 Grigi ``` v0.5.0 ------ Feature release v0.5.0: - More aliases from sqlite3, including Row, errors, and register_* - Additional connection properties for row/text factory, total changes - Better readme ```text $ git shortlog -s v0.4.0...v0.5.0 6 Amethyst Reese ``` v0.4.0 ------ Feature release v0.4.0: - Enable using a custom asyncio event loop - Increase performance by decreasing sleep time ```text $ git shortlog -s v0.3.0...v0.4.0 15 Amethyst Reese 1 Justin Kula 1 Richard Schwab ``` v0.3.0 ------ Feature release v0.3.0: - Cursors can be used as context managers ```text $ git shortlog -s v0.2.2...v0.3.0 6 Amethyst Reese 5 Linus Lewandowski ``` v0.2.2 ------ Minor release: - Correct aiosqlite.__version__ - Markdown readme, release via twine ```text $ git shortlog -s v0.2.1...v0.2.2 5 Amethyst Reese ``` v0.2.1 ------ Minor release v0.2.1: - Increase polling speed on event loop - Using black and pylint ```text $ git shortlog -s v0.2.0...v0.2.1 8 Amethyst Reese 2 Pavol Vargovcik ``` v0.2.0 ------ Beta version 0.2.0 ```text $ git shortlog -s v0.2.0 20 Amethyst Reese ``` [attribution-badge]: https://img.shields.io/badge/generated%20by-attribution-informational [attribution-url]: https://attribution.omnilib.dev aiosqlite-0.21.0/CODE_OF_CONDUCT.md000066400000000000000000000062201475006770100163720ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@omnilib.dev. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ aiosqlite-0.21.0/CONTRIBUTING.md000066400000000000000000000022531475006770100160260ustar00rootroot00000000000000# Contributing to aiosqlite ## Preparation You'll need to have at least Python 3.9 available for testing. You can do this with [pyenv][]: $ pyenv install $ pyenv local ## Setup Once cloned, create a clean virtual environment and install the appropriate tools and dependencies: $ cd $ make venv $ source .venv/bin/activate ## Formatting aiosqlite uses *[ufmt][]* for formatting code and imports. If your editor does not already support this workflow, you can manually format files: $ make format ## Testing Once you've made changes, you should run unit tests, validate your type annotations, and ensure your code meets the appropriate style and linting rules: $ make test lint ## Submitting Before submitting a pull request, please ensure that you have done the following: * Documented changes or features in README.md * Added appropriate license headers to new files * Written or modified tests for new functionality * Formatted code following project standards * Validated code and formatting with `make test lint` [pyenv]: https://github.com/pyenv/pyenv [µfmt]: https://ufmt.omnilib.dev aiosqlite-0.21.0/LICENSE000066400000000000000000000020571475006770100146040ustar00rootroot00000000000000MIT 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. aiosqlite-0.21.0/MANIFEST.in000066400000000000000000000000321475006770100153240ustar00rootroot00000000000000include README.md LICENSE aiosqlite-0.21.0/README.rst000066400000000000000000000060201475006770100152600ustar00rootroot00000000000000aiosqlite\: Sqlite for AsyncIO ============================== .. image:: https://readthedocs.org/projects/aiosqlite/badge/?version=latest :target: https://aiosqlite.omnilib.dev/en/latest/?badge=latest :alt: Documentation Status .. image:: https://img.shields.io/pypi/v/aiosqlite.svg :target: https://pypi.org/project/aiosqlite :alt: PyPI Release .. image:: https://img.shields.io/badge/change-log-blue :target: https://github.com/omnilib/aiosqlite/blob/master/CHANGELOG.md :alt: Changelog .. image:: https://img.shields.io/pypi/l/aiosqlite.svg :target: https://github.com/omnilib/aiosqlite/blob/master/LICENSE :alt: MIT Licensed aiosqlite provides a friendly, async interface to sqlite databases. It replicates the standard ``sqlite3`` module, but with async versions of all the standard connection and cursor methods, plus context managers for automatically closing connections and cursors: .. code-block:: python async with aiosqlite.connect(...) as db: await db.execute("INSERT INTO some_table ...") await db.commit() async with db.execute("SELECT * FROM some_table") as cursor: async for row in cursor: ... It can also be used in the traditional, procedural manner: .. code-block:: python db = await aiosqlite.connect(...) cursor = await db.execute('SELECT * FROM some_table') row = await cursor.fetchone() rows = await cursor.fetchall() await cursor.close() await db.close() aiosqlite also replicates most of the advanced features of ``sqlite3``: .. code-block:: python async with aiosqlite.connect(...) as db: db.row_factory = aiosqlite.Row async with db.execute('SELECT * FROM some_table') as cursor: async for row in cursor: value = row['column'] await db.execute('INSERT INTO foo some_table') assert db.total_changes > 0 Install ------- aiosqlite is compatible with Python 3.8 and newer. You can install it from PyPI: .. code-block:: console $ pip install aiosqlite Details ------- aiosqlite allows interaction with SQLite databases on the main AsyncIO event loop without blocking execution of other coroutines while waiting for queries or data fetches. It does this by using a single, shared thread per connection. This thread executes all actions within a shared request queue to prevent overlapping actions. Connection objects are proxies to the real connections, contain the shared execution thread, and provide context managers to handle automatically closing connections. Cursors are similarly proxies to the real cursors, and provide async iterators to query results. License ------- aiosqlite is copyright `Amethyst Reese `_, and licensed under the MIT license. I am providing code in this repository to you under an open source license. This is my personal repository; the license you receive to my code is from me and not from my employer. See the `LICENSE`_ file for details. .. _LICENSE: https://github.com/omnilib/aiosqlite/blob/master/LICENSE aiosqlite-0.21.0/aiosqlite/000077500000000000000000000000001475006770100155655ustar00rootroot00000000000000aiosqlite-0.21.0/aiosqlite/__init__.py000066400000000000000000000015701475006770100177010ustar00rootroot00000000000000# 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", ] aiosqlite-0.21.0/aiosqlite/__version__.py000066400000000000000000000002351475006770100204200ustar00rootroot00000000000000""" This file is automatically generated by attribution. Do not edit manually. Get more info at https://attribution.omnilib.dev """ __version__ = "0.21.0" aiosqlite-0.21.0/aiosqlite/context.py000066400000000000000000000026471475006770100176340ustar00rootroot00000000000000# 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 aiosqlite-0.21.0/aiosqlite/core.py000066400000000000000000000276421475006770100171020ustar00rootroot00000000000000# 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"] 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 BaseException: 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) aiosqlite-0.21.0/aiosqlite/cursor.py000066400000000000000000000066241475006770100174640ustar00rootroot00000000000000# 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() aiosqlite-0.21.0/aiosqlite/py.typed000066400000000000000000000000001475006770100172520ustar00rootroot00000000000000aiosqlite-0.21.0/aiosqlite/tests/000077500000000000000000000000001475006770100167275ustar00rootroot00000000000000aiosqlite-0.21.0/aiosqlite/tests/__init__.py000066400000000000000000000001321475006770100210340ustar00rootroot00000000000000# Copyright Amethyst Reese # Licensed under the MIT license from .smoke import SmokeTest aiosqlite-0.21.0/aiosqlite/tests/__main__.py000066400000000000000000000002421475006770100210170ustar00rootroot00000000000000# Copyright Amethyst Reese # Licensed under the MIT license import unittest if __name__ == "__main__": unittest.main(module="aiosqlite.tests", verbosity=2) aiosqlite-0.21.0/aiosqlite/tests/helpers.py000066400000000000000000000013221475006770100207410ustar00rootroot00000000000000# Copyright 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 aiosqlite-0.21.0/aiosqlite/tests/perf.py000066400000000000000000000146741475006770100202510ustar00rootroot00000000000000# Copyright 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) aiosqlite-0.21.0/aiosqlite/tests/smoke.py000066400000000000000000000414771475006770100204340ustar00rootroot00000000000000# Copyright Amethyst Reese # Licensed under the MIT license import asyncio import sqlite3 from pathlib import Path from sqlite3 import OperationalError from tempfile import TemporaryDirectory from threading import Thread from unittest import IsolatedAsyncioTestCase, SkipTest from unittest.mock import patch import aiosqlite from .helpers import setup_logger class SmokeTest(IsolatedAsyncioTestCase): @classmethod def setUpClass(cls): setup_logger() def setUp(self): td = TemporaryDirectory() self.addCleanup(td.cleanup) self.db = Path(td.name).resolve() / "test.db" async def test_connection_await(self): db = await aiosqlite.connect(self.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(self.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): TEST_DB = self.db.as_posix() class Fake: # pylint: disable=too-few-public-methods def __str__(self): return TEST_DB locs = (Path(TEST_DB), TEST_DB, TEST_DB.encode(), Fake()) async with aiosqlite.connect(locs[0]) 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(self.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(self.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(self.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(self.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(self.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(self.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(self.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(self.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(self.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(self.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(self.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, str) 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(self.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(self.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(self.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(self.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(self.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(self.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(self.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_connect_base_exception(self): # Check if connect task is cancelled, thread is properly closed. def _raise_cancelled_error(*_, **__): raise asyncio.CancelledError("I changed my mind") connection = aiosqlite.Connection(lambda: sqlite3.connect(":memory:"), 64) with ( patch.object(sqlite3, "connect", side_effect=_raise_cancelled_error), self.assertRaisesRegex(asyncio.CancelledError, "I changed my mind"), ): async with connection: ... # Terminate the thread here if the test fails to have a clear error. if connection._running: connection._stop_running() raise AssertionError("connection thread was not stopped") 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(self.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(self.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(self.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")]) aiosqlite-0.21.0/docs/000077500000000000000000000000001475006770100145235ustar00rootroot00000000000000aiosqlite-0.21.0/docs/_static/000077500000000000000000000000001475006770100161515ustar00rootroot00000000000000aiosqlite-0.21.0/docs/_static/custom.css000066400000000000000000000004341475006770100201760ustar00rootroot00000000000000div.omnilib { margin-top: 24px; } div.omnilib-badges { margin-top: 12px; margin-bottom: 10px; } div.omnilib-badges a { color: #bbb; text-decoration: none; font-size: 24px; border: none; } div.omnilib-badges a:hover { color: #777; border: none; }aiosqlite-0.21.0/docs/_templates/000077500000000000000000000000001475006770100166605ustar00rootroot00000000000000aiosqlite-0.21.0/docs/_templates/badges.html000066400000000000000000000005261475006770100207760ustar00rootroot00000000000000 aiosqlite-0.21.0/docs/_templates/omnilib.html000066400000000000000000000016461475006770100212060ustar00rootroot00000000000000

The Omnilib Project

Omnilib is a group of MIT licensed software libraries developed under a common, inclusive Code of Conduct. We are committed to providing a welcoming and open space for all contributors who adhere to these rules.
   
aiosqlite-0.21.0/docs/api.rst000066400000000000000000000013551475006770100160320ustar00rootroot00000000000000 API Reference ============= .. module:: aiosqlite Connection ---------- .. autofunction:: connect .. autoclass:: Connection :special-members: __aenter__, __aexit__, __await__ Cursors ------- .. autoclass:: aiosqlite.cursor.Cursor :special-members: __aiter__, __anext__, __aenter__, __aexit__ Errors ------ .. autoexception:: Warning :members: .. autoexception:: Error :members: .. autoexception:: DatabaseError :members: .. autoexception:: IntegrityError :members: .. autoexception:: ProgrammingError :members: .. autoexception:: OperationalError :members: .. autoexception:: NotSupportedError :members: Advanced -------- .. autofunction:: register_adapter .. autofunction:: register_converter aiosqlite-0.21.0/docs/changelog.rst000066400000000000000000000001061475006770100172010ustar00rootroot00000000000000Changelog ========= .. mdinclude:: ../CHANGELOG.md :start-line: 2aiosqlite-0.21.0/docs/conf.py000066400000000000000000000056011475006770100160240ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- Project information ----------------------------------------------------- import datetime import os project = "aiosqlite" copyright = f"{datetime.date.today().year}, Amethyst Reese" author = "Amethyst Reese" # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinx_mdinclude", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] autodoc_default_options = { "show-inheritance": True, "members": True, "undoc-members": True, } autodoc_member_order = "groupwise" autodoc_typehints = "description" highlight_language = "text" intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} master_doc = "index" # Set canonical URL from the Read the Docs Domain html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "") # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "alabaster" html_theme_options = { "description": "Sqlite for Python AsyncIO", "fixed_sidebar": True, "badge_branch": "master", "github_button": False, "github_user": "omnilib", "github_repo": "aiosqlite", "show_powered_by": False, "sidebar_collapse": False, "extra_nav_links": { "Report Issues": "https://github.com/omnilib/aiosqlite/issues", }, } html_sidebars = { "**": [ "about.html", "badges.html", "navigation.html", "relations.html", "searchbox.html", "omnilib.html", ], } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] aiosqlite-0.21.0/docs/contributing.rst000066400000000000000000000001171475006770100177630ustar00rootroot00000000000000Contributing ============ .. mdinclude:: ../CONTRIBUTING.md :start-line: 2aiosqlite-0.21.0/docs/index.rst000066400000000000000000000005731475006770100163710ustar00rootroot00000000000000.. aiosqlite documentation master file, created by sphinx-quickstart on Sun Apr 26 19:57:46 2020. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. .. include:: ../README.rst .. toctree:: :hidden: :maxdepth: 2 api .. toctree:: :hidden: :maxdepth: 1 changelog contributing aiosqlite-0.21.0/makefile000066400000000000000000000014541475006770100152770ustar00rootroot00000000000000PKG:=aiosqlite EXTRAS:=dev,docs UV:=$(shell uv --version) ifdef UV VENV:=uv venv PIP:=uv pip else VENV:=python -m venv PIP:=python -m pip endif install: $(PIP) install -Ue .[$(EXTRAS)] .venv: $(VENV) .venv venv: .venv source .venv/bin/activate && make install echo 'run `source .venv/bin/activate` to activate virtualenv' test: python -m coverage run -m $(PKG).tests python -m coverage report python -m mypy -p $(PKG) lint: python -m flake8 $(PKG) python -m ufmt check $(PKG) format: python -m ufmt format $(PKG) perf: python -m unittest -v $(PKG).tests.perf .PHONY: html html: .venv README.rst docs/*.rst docs/conf.py .venv/bin/sphinx-build -an -b html docs html .PHONY: clean clean: rm -rf build dist html README MANIFEST *.egg-info .PHONY: distclean distclean: clean rm -rf .venv aiosqlite-0.21.0/pyproject.toml000066400000000000000000000030101475006770100165010ustar00rootroot00000000000000[build-system] requires = ["flit_core >=3.8,<4"] build-backend = "flit_core.buildapi" [project] name = "aiosqlite" readme = "README.rst" license = {file="LICENSE"} dynamic = ["version", "description"] authors = [ {name="Amethyst Reese", email="amethyst@n7.gg"}, ] classifiers = [ "Development Status :: 5 - Production/Stable", "Framework :: AsyncIO", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Topic :: Software Development :: Libraries", ] requires-python = ">=3.9" dependencies = [ "typing_extensions >= 4.0", ] [project.optional-dependencies] dev = [ "attribution==1.7.1", "black==24.3.0", "build>=1.2", "coverage[toml]==7.6.10", "flake8==7.0.0", "flake8-bugbear==24.12.12", "flit==3.10.1", "mypy==1.14.1", "ufmt==2.5.1", "usort==1.0.8.post1", ] docs = [ "sphinx==8.1.3", "sphinx-mdinclude==0.6.1", ] [project.urls] Documentation = "https://aiosqlite.omnilib.dev" Github = "https://github.com/omnilib/aiosqlite" [tool.flit.sdist] exclude = [ ".github/", ] [tool.attribution] name = "aiosqlite" package = "aiosqlite" ignored_authors = ["dependabot"] signed_tags = true version_file = true [tool.coverage.run] branch = true include = ["aiosqlite/*"] omit = ["aiosqlite/tests/*"] [tool.coverage.report] fail_under = 75 precision = 1 show_missing = true skip_covered = true [tool.mypy] ignore_missing_imports = true python_version = "3.9" [[tool.mypy.overrides]] module = "aiosqlite.tests.perf" follow_imports = "silent"