From bf6297aaf2cd09dad6bc3e292d6b30b2d994cfb6 Mon Sep 17 00:00:00 2001 From: Michael H Date: Sun, 22 Dec 2019 13:18:31 -0500 Subject: [PATCH] Add a small wrapper for APSW use (#3202) * Add a small wrapper for APSW use * changelog --- .github/CODEOWNERS | 1 + changelog.d/3202.misc.rst | 1 + redbot/core/utils/dbtools.py | 61 ++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 changelog.d/3202.misc.rst create mode 100644 redbot/core/utils/dbtools.py diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7dac8bd23..440ff654c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -25,6 +25,7 @@ redbot/core/utils/antispam.py @mikeshardmind redbot/core/utils/tunnel.py @mikeshardmind redbot/core/utils/caching.py @mikeshardmind redbot/core/utils/common_filters.py @mikeshardmind +redbot/core/utils/dbtools.py @mikeshardmind # Cogs redbot/cogs/admin/* @tekulvw diff --git a/changelog.d/3202.misc.rst b/changelog.d/3202.misc.rst new file mode 100644 index 000000000..8732460fe --- /dev/null +++ b/changelog.d/3202.misc.rst @@ -0,0 +1 @@ +Adds a not fully documented class for APSW interaction diff --git a/redbot/core/utils/dbtools.py b/redbot/core/utils/dbtools.py new file mode 100644 index 000000000..6f8d370cf --- /dev/null +++ b/redbot/core/utils/dbtools.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +from contextlib import contextmanager +from pathlib import Path +from typing import Generator, Union + +import apsw + +__all__ = ["APSWConnectionWrapper"] + + +# TODO (mikeshardmind): make this inherit typing_extensions.Protocol +# long term: mypy; short term: removing the pylint disables below +class ProvidesCursor: + def cursor(self) -> apsw.Cursor: + ... + + +class ContextManagerMixin(ProvidesCursor): + @contextmanager + def with_cursor(self) -> Generator[apsw.Cursor, None, None]: + """ + apsw cursors are relatively cheap, and are gc safe + In most cases, it's fine not to use this. + """ + c = self.cursor() # pylint: disable=assignment-from-no-return + try: + yield c + finally: + c.close() + + @contextmanager + def transaction(self) -> Generator[apsw.Cursor, None, None]: + """ + Wraps a cursor as a context manager for a transaction + which is rolled back on unhandled exception, + or commited on non-exception exit + """ + c = self.cursor() # pylint: disable=assignment-from-no-return + try: + c.execute("BEGIN TRANSACTION") + yield c + except Exception: + c.execute("ROLLBACK TRANSACTION") + raise + else: + c.execute("COMMIT TRANSACTION") + finally: + c.close() + + +class APSWConnectionWrapper(apsw.Connection, ContextManagerMixin): + """ + Provides a few convenience methods, and allows a path object for construction + """ + + def __init__(self, filename: Union[Path, str], *args, **kwargs): + super().__init__(str(filename), *args, **kwargs) + + +# TODO (mikeshardmind): asyncio friendly ThreadedAPSWConnection class