AbstractDbConnection

This commit is contained in:
AF 2023-02-27 11:38:36 +00:00
parent ba3d392328
commit fffff4973e
4 changed files with 69 additions and 49 deletions

View File

@ -286,6 +286,7 @@ async def main():
LogEE(__import__('ptvp35').FutureContext, '__init__').enter(es) LogEE(__import__('ptvp35').FutureContext, '__init__').enter(es)
ALogEE(__import__('ptvp35').FutureContext, '__aenter__').enter(es) ALogEE(__import__('ptvp35').FutureContext, '__aenter__').enter(es)
ALogEE(__import__('ptvp35').FutureContext, '__aexit__').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, '__init__').enter(es)
LogEE(__import__('ptvp35').TransactionView, 'future_context').enter(es) LogEE(__import__('ptvp35').TransactionView, 'future_context').enter(es)

View File

@ -5,9 +5,9 @@ Main-Memory DataBase
-------------------- --------------------
* Represented via Python dictionary (:code:`dict`). * 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. * 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. All KVPs added via submit-like methods are serialized before being added to the MMDB.
DataBase Stream File DataBase Stream File
@ -37,32 +37,34 @@ DataBase Stream File
Request Queue Request Queue
------------- -------------
Transaction View (:class:`ptvp35.TransactionView`) Transaction View (:class:`~ptvp35.TransactionView`)
-------------------------------------------------- --------------------------------------------------
Connection-like interface on top of another connection-like interface. Connection-like interface on top of another connection-like interface.
* Provides most of the same methods as :class:`ptvp35.DbConnection`. * Provides most of the same methods as :class:`~ptvp35.DbConnection`.
* From the common :class:`ptvp35.VirtualConnection` interface/base class: * From the common :class:`~ptvp35.VirtualConnection` interface/base class:
* :meth:`ptvp35.TransactionView.get` * :meth:`~ptvp35.TransactionView.get`
* :meth:`ptvp35.TransactionView.commit_transaction` * :meth:`~ptvp35.TransactionView.commit_transaction`
* :meth:`ptvp35.TransactionView.submit_transaction_request` * :meth:`~ptvp35.TransactionView.submit_transaction_request`
* :meth:`ptvp35.TransactionView.loop` * :meth:`~ptvp35.TransactionView.loop`
* :meth:`ptvp35.TransactionView.transaction` (default implementation) * :meth:`~ptvp35.VirtualConnection.transaction` (default implementation)
* Non-standard common methods: * Extra common methods (:class:`~ptvp35.ExtendedVirtualConnection`):
* :meth:`ptvp35.TransactionView.set_nowait` * :meth:`~ptvp35.TransactionView.set_nowait`
* :meth:`ptvp35.TransactionView.submit_transaction` * :meth:`~ptvp35.TransactionView.submit_transaction`
* :meth:`ptvp35.TransactionView.commit` * :meth:`~ptvp35.TransactionView.commit`
* Does not have the the analogue for the :meth:`ptvp35.DbConnection.set` method. * 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 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. * 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. * Transactions are meant for a more fine control.
* The equivalent would consist of the three method calls: * 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.set_nowait` to set the value in :code:`__delta`.
* :meth:`ptvp35.TransactionView.submit` to pass all the :code:`__delta` values. * :meth:`~ptvp35.TransactionView.submit` to pass all the :code:`__delta` values.
* :meth:`ptvp35.TransactionView.commit` wait until all changes are commited. * :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. Manages a Transaction View.

View File

@ -13,7 +13,7 @@ Default installation option is to use pip+git
Basic functionality 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**. 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. 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. 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) transaction.set_nowait('increment-5', value5 + 1)
await connection.commit() await connection.commit()
* :meth:`ptvp35.VirtualConnection.get` For common methods see: :class:`~ptvp35.AbstractDbConnection`
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`

View File

@ -10,6 +10,7 @@ __all__ = (
'VirtualConnection', 'VirtualConnection',
'ExtendedVirtualConnection', 'ExtendedVirtualConnection',
'DbInterface', 'DbInterface',
'AbstractDbConnection',
'DbConnection', 'DbConnection',
'DbManager', 'DbManager',
'DbFactory', 'DbFactory',
@ -28,7 +29,7 @@ import pathlib
import threading import threading
from collections.abc import Hashable from collections.abc import Hashable
from io import StringIO, UnsupportedOperation from io import StringIO, UnsupportedOperation
from typing import IO, Any, TypeAlias from typing import IO, Any, Protocol, TypeAlias
class Request: class Request:
@ -50,7 +51,7 @@ class Request:
if self.__future is not None: if self.__future is not None:
self.__future.set_exception(exception) self.__future.set_exception(exception)
async def wait(self, /): async def wait(self, /) -> None:
if self.__future is not None: if self.__future is not None:
await self.__future await self.__future
@ -164,7 +165,7 @@ class KVRequest(LineRequest):
'value', '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) super().__init__(factory.line(key, value), future=future)
self.__factory = factory self.__factory = factory
self.key = key self.key = key
@ -213,7 +214,7 @@ class TransactionRequest(LineRequest):
'buffer', '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) super().__init__(buffer.getvalue(), future=future)
self.buffer = buffer self.buffer = buffer
@ -246,7 +247,7 @@ class VirtualConnection(
__slots__ = () __slots__ = ()
@abc.abstractmethod @abc.abstractmethod
def get(self, key: Any, default: Any, /): def get(self, key: Any, default: Any, /) -> Any:
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod
@ -270,7 +271,7 @@ class VirtualConnection(
class ExtendedVirtualConnection( class ExtendedVirtualConnection(
VirtualConnection VirtualConnection, abc.ABC
): ):
"""maximal intersection of DbConnection and TransactionView functionality""" """maximal intersection of DbConnection and TransactionView functionality"""
@ -288,13 +289,38 @@ class ExtendedVirtualConnection(
class DbInterface( class DbInterface(
ExtendedVirtualConnection ExtendedVirtualConnection, abc.ABC
): ):
@abc.abstractmethod @abc.abstractmethod
async def set(self, key: Any, value: Any, /) -> None: async def set(self, key: Any, value: Any, /) -> None:
raise NotImplementedError 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: class _Loop:
__slots__ = ( __slots__ = (
'__loop', '__loop',
@ -663,7 +689,7 @@ class _WriteableBuffer:
case _: case _:
raise UnknownRequestType raise UnknownRequestType
async def _close(self, /): async def _close(self, /) -> None:
await self._commit() await self._commit()
if not self.__buffer_future.done(): if not self.__buffer_future.done():
self.__buffer_future.set_exception(RequestToClosedConnection()) self.__buffer_future.set_exception(RequestToClosedConnection())
@ -920,7 +946,7 @@ class DbManager:
self.__db = await _DbConnection.create(self.__parameters) self.__db = await _DbConnection.create(self.__parameters)
return self.__db 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() await self.__db.aclose()
@ -932,7 +958,7 @@ class Db(_DbConnection):
__slots__ = () __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__( _DbConnection.__init__(
self, self,
DbParameters( DbParameters(
@ -944,7 +970,7 @@ class Db(_DbConnection):
await self._initialize() await self._initialize()
return self 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() await self.aclose()
@ -955,7 +981,10 @@ class FutureContext:
async def __aenter__(self) -> None: async def __aenter__(self) -> None:
pass 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: if self.__future is not None:
await self.__future await self.__future
@ -1170,7 +1199,7 @@ class Transaction:
async def __aenter__(self) -> TransactionView: async def __aenter__(self) -> TransactionView:
return self.__enter__() 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: if exc_type is None:
await self.__view.commit() await self.__view.commit()
else: else:
@ -1188,7 +1217,7 @@ class Transaction:
self.__view = TransactionView({}, self.__connection) self.__view = TransactionView({}, self.__connection)
return self.__view 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: if exc_type is None:
self.__view.submit() self.__view.submit()
else: else: