From fffff4973e0fbc7fdf3425e6486ce6378dccf821 Mon Sep 17 00:00:00 2001 From: timofey Date: Mon, 27 Feb 2023 11:38:36 +0000 Subject: [PATCH] AbstractDbConnection --- docs/scripts/traced_example.py | 1 + docs/source/structure.rst | 44 +++++++++++++------------- docs/source/usage.rst | 16 ++-------- ptvp35/__init__.py | 57 +++++++++++++++++++++++++--------- 4 files changed, 69 insertions(+), 49 deletions(-) diff --git a/docs/scripts/traced_example.py b/docs/scripts/traced_example.py index 750ea49..586dcd3 100644 --- a/docs/scripts/traced_example.py +++ b/docs/scripts/traced_example.py @@ -286,6 +286,7 @@ async def main(): LogEE(__import__('ptvp35').FutureContext, '__init__').enter(es) ALogEE(__import__('ptvp35').FutureContext, '__aenter__').enter(es) ALogEE(__import__('ptvp35').FutureContext, '__aexit__').enter(es) + ALogEE(__import__('ptvp35').FutureContext, 'wait').enter(es) LogEE(__import__('ptvp35').TransactionView, '__init__').enter(es) LogEE(__import__('ptvp35').TransactionView, 'future_context').enter(es) diff --git a/docs/source/structure.rst b/docs/source/structure.rst index 2ce753d..6f76cae 100644 --- a/docs/source/structure.rst +++ b/docs/source/structure.rst @@ -5,9 +5,9 @@ Main-Memory DataBase -------------------- * Represented via Python dictionary (:code:`dict`). - Future version may include more abstract options, i.e. :class:`ptvp35.DbConnection` would be generic over :attr:`ptvp35.DbConnection.__mmdb` field + Future version may include more abstract options, i.e. :class:`~ptvp35.DbConnection` would be generic over :attr:`~ptvp35._DbConnection.__mmdb` field * Keys and values are almost guaranteed to be serializable. - All KVPs loaded on :code:`__aenter__` are re-serialized during file reload. + All KVPs loaded on :meth:`~ptvp35.DbManager.__aenter__` are re-serialized during file reload. All KVPs added via submit-like methods are serialized before being added to the MMDB. DataBase Stream File @@ -37,32 +37,34 @@ DataBase Stream File Request Queue ------------- -Transaction View (:class:`ptvp35.TransactionView`) +Transaction View (:class:`~ptvp35.TransactionView`) -------------------------------------------------- Connection-like interface on top of another connection-like interface. -* Provides most of the same methods as :class:`ptvp35.DbConnection`. - * From the common :class:`ptvp35.VirtualConnection` interface/base class: - * :meth:`ptvp35.TransactionView.get` - * :meth:`ptvp35.TransactionView.commit_transaction` - * :meth:`ptvp35.TransactionView.submit_transaction_request` - * :meth:`ptvp35.TransactionView.loop` - * :meth:`ptvp35.TransactionView.transaction` (default implementation) - * Non-standard common methods: - * :meth:`ptvp35.TransactionView.set_nowait` - * :meth:`ptvp35.TransactionView.submit_transaction` - * :meth:`ptvp35.TransactionView.commit` -* Does not have the the analogue for the :meth:`ptvp35.DbConnection.set` method. +* Provides most of the same methods as :class:`~ptvp35.DbConnection`. + * From the common :class:`~ptvp35.VirtualConnection` interface/base class: + * :meth:`~ptvp35.TransactionView.get` + * :meth:`~ptvp35.TransactionView.commit_transaction` + * :meth:`~ptvp35.TransactionView.submit_transaction_request` + * :meth:`~ptvp35.TransactionView.loop` + * :meth:`~ptvp35.VirtualConnection.transaction` (default implementation) + * Extra common methods (:class:`~ptvp35.ExtendedVirtualConnection`): + * :meth:`~ptvp35.TransactionView.set_nowait` + * :meth:`~ptvp35.TransactionView.submit_transaction` + * :meth:`~ptvp35.TransactionView.commit` +* Does not have the the analogue for the :meth:`~ptvp35.DbInterface.set` method. * The reason for that is :code:`set` method having semantics contradictory to transactions. * The :code:`set` provides a way to set a *single* value and wait until it's committed. - * Transaction are meant for a more fine control. - * The equivalent would consist of the three method calls: - * :meth:`ptvp35.TransactionView.set_nowait` to set the value in :code:`__delta`. - * :meth:`ptvp35.TransactionView.submit` to pass all the :code:`__delta` values. - * :meth:`ptvp35.TransactionView.commit` wait until all changes are commited. + * Transactions are meant for a more fine control. + * The equivalent would consist of using a subtransaction or of the following method calls: + * :meth:`~ptvp35.TransactionView.set_nowait` to set the value in :code:`__delta`. + * :meth:`~ptvp35.TransactionView.submit` to pass all the :code:`__delta` values. + * :meth:`~ptvp35.TransactionView.future_context` to get the context for that specific key. + * :meth:`~ptvp35.FutureContext.wait` to wait until that key is committed. + * :meth:`~ptvp35.TransactionView.illuminate` to clear :code:`__shadow` thus resetting the view to DB state. -Transaction (:class:`ptvp35.Transaction`) +Transaction (:class:`~ptvp35.Transaction`) ----------------------------------------- Manages a Transaction View. diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 3a7154b..7b3e83c 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -13,7 +13,7 @@ Default installation option is to use pip+git Basic functionality ------------------- -:class:`ptvp35.DbFactory` class provides context management for database connections (:class:`ptvp35.DbConnection`) via :code:`async with` statement. +:class:`~ptvp35.DbFactory` class provides context management for database connections (:class:`~ptvp35.DbConnection`) via :code:`async with` statement. The connection isn't just a "connection", it's also the MMDB itself, so **using two connections to one database is an undefined behaviour**. Also, that means that each connection start/shutdown is quite time expensive. These two facts together tell that, if you intend on using the connection, you should probably wrap the main program in an :code:`async with` block. @@ -60,16 +60,4 @@ Different ways to get/set a value: transaction.set_nowait('increment-5', value5 + 1) await connection.commit() -* :meth:`ptvp35.VirtualConnection.get` - this method is instant. -* :meth:`ptvp35.DbInterface.set` - this method may take time to run. - ordering may not be guaranteed (depends on event loop implementation). -* :meth:`ptvp35.ExtendedVirtualConnection.set_nowait` - this method is instant. - ordering is guaranteed. -* :meth:`ptvp35.ExtendedVirtualConnection.commit` - this method may take time to run. - respects the ordering of previously called :code:`set_nowait` methods. - will, depending on event loop implementation, also execute later changes. -* :meth:`ptvp35.VirtualConnection.transaction` +For common methods see: :class:`~ptvp35.AbstractDbConnection` diff --git a/ptvp35/__init__.py b/ptvp35/__init__.py index 6dabad3..46a328f 100644 --- a/ptvp35/__init__.py +++ b/ptvp35/__init__.py @@ -10,6 +10,7 @@ __all__ = ( 'VirtualConnection', 'ExtendedVirtualConnection', 'DbInterface', + 'AbstractDbConnection', 'DbConnection', 'DbManager', 'DbFactory', @@ -28,7 +29,7 @@ import pathlib import threading from collections.abc import Hashable from io import StringIO, UnsupportedOperation -from typing import IO, Any, TypeAlias +from typing import IO, Any, Protocol, TypeAlias class Request: @@ -50,7 +51,7 @@ class Request: if self.__future is not None: self.__future.set_exception(exception) - async def wait(self, /): + async def wait(self, /) -> None: if self.__future is not None: await self.__future @@ -164,7 +165,7 @@ class KVRequest(LineRequest): 'value', ) - def __init__(self, key: Any, value: Any, /, *, future: asyncio.Future | None, factory: KVFactory): + def __init__(self, key: Any, value: Any, /, *, future: asyncio.Future | None, factory: KVFactory) -> None: super().__init__(factory.line(key, value), future=future) self.__factory = factory self.key = key @@ -213,7 +214,7 @@ class TransactionRequest(LineRequest): 'buffer', ) - def __init__(self, buffer: StringIO, /, *, future: asyncio.Future | None): + def __init__(self, buffer: StringIO, /, *, future: asyncio.Future | None) -> None: super().__init__(buffer.getvalue(), future=future) self.buffer = buffer @@ -246,7 +247,7 @@ class VirtualConnection( __slots__ = () @abc.abstractmethod - def get(self, key: Any, default: Any, /): + def get(self, key: Any, default: Any, /) -> Any: raise NotImplementedError @abc.abstractmethod @@ -270,7 +271,7 @@ class VirtualConnection( class ExtendedVirtualConnection( - VirtualConnection + VirtualConnection, abc.ABC ): """maximal intersection of DbConnection and TransactionView functionality""" @@ -288,13 +289,38 @@ class ExtendedVirtualConnection( class DbInterface( - ExtendedVirtualConnection + ExtendedVirtualConnection, abc.ABC ): @abc.abstractmethod async def set(self, key: Any, value: Any, /) -> None: raise NotImplementedError +class AbstractDbConnection(Protocol): + def get(self, key: Any, default: Any, /) -> Any: + """this method is instant.""" + raise NotImplementedError + + async def set(self, key: Any, value: Any, /) -> None: + """this method may take time to run. +ordering may not be guaranteed (depends on event loop implementation).""" + raise NotImplementedError + + def set_nowait(self, key: Any, value: Any, /) -> None: + """this method is instant. +ordering is guaranteed.""" + raise NotImplementedError + + async def commit(self, /) -> None: + """this method may take time to run. +respects the ordering of previously called :meth:`~ptvp35.AbstractDbConnection.set_nowait` methods. +will, depending on event loop implementation, also execute later changes.""" + raise NotImplementedError + + def transaction(self, /) -> Transaction: + raise NotImplementedError + + class _Loop: __slots__ = ( '__loop', @@ -663,7 +689,7 @@ class _WriteableBuffer: case _: raise UnknownRequestType - async def _close(self, /): + async def _close(self, /) -> None: await self._commit() if not self.__buffer_future.done(): self.__buffer_future.set_exception(RequestToClosedConnection()) @@ -920,7 +946,7 @@ class DbManager: self.__db = await _DbConnection.create(self.__parameters) return self.__db - async def __aexit__(self, exc_type, exc_val, exc_tb): + async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: await self.__db.aclose() @@ -932,7 +958,7 @@ class Db(_DbConnection): __slots__ = () - def __init__(self, path: str | pathlib.Path, /, *, kvfactory: KVFactory, buffersize=1048576): + def __init__(self, path: str | pathlib.Path, /, *, kvfactory: KVFactory, buffersize=1048576) -> None: _DbConnection.__init__( self, DbParameters( @@ -944,7 +970,7 @@ class Db(_DbConnection): await self._initialize() return self - async def __aexit__(self, exc_type, exc_val, exc_tb): + async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: await self.aclose() @@ -955,7 +981,10 @@ class FutureContext: async def __aenter__(self) -> None: pass - async def __aexit__(self, exc_type, exc_val, exc_tb): + async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: + await self.wait() + + async def wait(self, /) -> None: if self.__future is not None: await self.__future @@ -1170,7 +1199,7 @@ class Transaction: async def __aenter__(self) -> TransactionView: return self.__enter__() - async def __aexit__(self, exc_type, exc_val, exc_tb): + async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: if exc_type is None: await self.__view.commit() else: @@ -1188,7 +1217,7 @@ class Transaction: self.__view = TransactionView({}, self.__connection) return self.__view - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__(self, exc_type, exc_val, exc_tb) -> None: if exc_type is None: self.__view.submit() else: