Compare commits

..

20 Commits

Author SHA1 Message Date
0551c0bb0b formatting 2023-10-02 16:55:33 +00:00
3ffd0adc21 fix get type 2023-08-28 09:56:59 +00:00
edbb207735 correct copyright 2023-08-27 01:19:58 +00:00
f8ee5d20f4 conf.py fix quotes 2023-06-18 10:57:10 +00:00
a103364f1a style fix 2023-06-16 12:23:43 +00:00
56e6160e6a -NightlyInstrumentation 2023-05-05 16:13:58 +00:00
360462287f .dockerignore compilation artifacts 2023-04-28 09:52:20 +00:00
e8a141a000 1.1.0 2023-03-02 01:24:37 +00:00
fffff4973e AbstractDbConnection 2023-02-27 11:38:36 +00:00
ba3d392328 new docs build 2023-02-21 14:24:08 +00:00
1ccd2009ee 1.1rc5: DbInterface 2023-02-09 19:32:29 +00:00
28f964a3e6 connection classes separation 2023-02-03 14:38:25 +00:00
3b622984bf global KVDELETE 2023-02-01 02:08:54 +00:00
1cd39ad061 1.1rc4: delete 2023-01-15 08:54:07 +00:00
dcc9d642aa 1.1rc3: more fsync 2023-01-13 15:33:04 +00:00
f3703c634e move scripts 2023-01-01 05:48:55 +00:00
c520ef646a 1.1rc2: instrumentation fix 2022-12-30 09:51:17 +00:00
369882c85c better docs 2022-12-29 09:45:59 +00:00
e760fca39e 1.1rc1 2022-12-28 07:23:21 +00:00
2aac64f65c more match + less asserts 2022-12-02 12:55:53 +00:00
21 changed files with 1312 additions and 917 deletions

View File

@ -1 +1,4 @@
.git* .git*
__pycache__
*.egg-info
build

4
.gitignore vendored
View File

@ -222,5 +222,5 @@ cython_debug/
# Other # Other
/dev.py /dev.py
/*.db *.db
/*.db.* *.db.*

View File

@ -1,42 +1,71 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
FROM python:3.10
WORKDIR /app/ FROM python:3.10 as compile-sphinx5.3.0-base
RUN apt-get update RUN apt-get update
RUN apt-get install -y python3-sphinx node.js RUN apt-get install -y python3-sphinx
RUN apt-get install -y npm RUN pip install Sphinx==5.3.0 pydata-sphinx-theme==0.12.0
RUN npm install -g http-server WORKDIR /app/
RUN pip install pydata-sphinx-theme
RUN pip install git+https://gitea.parrrate.ru/PTV/rainbowadn.git@e9fba7b064902ceedee0dd5578cb47030665a6aa RUN pip install git+https://gitea.parrrate.ru/PTV/rainbowadn.git@e9fba7b064902ceedee0dd5578cb47030665a6aa
RUN mkdir /app/docs/ RUN mkdir /app/docs/
RUN mkdir /app/docs/build/
RUN mkdir /app/docs/build/html/
FROM compile-sphinx5.3.0-base as compile-legacy
WORKDIR /app/legacy/ WORKDIR /app/legacy/
RUN git clone --branch 1.0 https://gitea.parrrate.ru/PTV/ptvp35.git RUN git clone --branch 1.0 https://gitea.parrrate.ru/PTV/ptvp35.git
WORKDIR /app/legacy/ptvp35/ WORKDIR /app/legacy/ptvp35/
FROM compile-legacy as compile-1.0
RUN python traced_example.py > traced_example.txt RUN python traced_example.py > traced_example.txt
RUN python traced_example.py all > traced_example_all.txt RUN python traced_example.py all > traced_example_all.txt
RUN cp -r docs/source/ ./source/ RUN cp -r docs/source/ ./source/
RUN cp docs/Makefile ./Makefile RUN cp docs/Makefile ./Makefile
RUN make html RUN make html
RUN cp -r /app/legacy/ptvp35/build/html/ /app/docs/build/html/1.0/
RUN git reset --hard
FROM compile-legacy as compile-1.1rc0
RUN git fetch && git checkout 1.1rc0 RUN git fetch && git checkout 1.1rc0
WORKDIR /app/legacy/ptvp35/docs/ WORKDIR /app/legacy/ptvp35/docs/
RUN make html RUN make html
RUN cp -r /app/legacy/ptvp35/docs/build/html/ /app/docs/build/html/1.1rc0/
RUN rm -r /app/legacy/ptvp35/docs/build/
WORKDIR /app/legacy/ptvp35/
WORKDIR /app/
FROM compile-legacy as compile-1.1rc2
RUN git fetch && git checkout 1.1rc2
WORKDIR /app/legacy/ptvp35/docs/
RUN make html
FROM compile-legacy as compile-1.1.0
RUN git fetch && git checkout 1.1.0
WORKDIR /app/legacy/ptvp35/docs/
RUN make html
FROM compile-sphinx5.3.0-base as compile-latest
COPY docs/Makefile docs/Makefile COPY docs/Makefile docs/Makefile
COPY setup.py setup.py COPY setup.py setup.py
COPY traced_example.py traced_example.py COPY docs/scripts docs/scripts
COPY ptvp35 ptvp35 COPY ptvp35 ptvp35
COPY docs/source docs/source COPY docs/source docs/source
WORKDIR /app/docs/ WORKDIR /app/docs/
RUN make html RUN make html
FROM node:19
RUN npm install -g http-server
WORKDIR /app/docs/build/html/ WORKDIR /app/docs/build/html/
COPY --from=compile-1.0 /app/legacy/ptvp35/build/html/ /app/docs/build/html/1.0/
COPY --from=compile-1.1rc0 /app/legacy/ptvp35/docs/build/html/ /app/docs/build/html/1.1rc0/
COPY --from=compile-1.1rc2 /app/legacy/ptvp35/docs/build/html/ /app/docs/build/html/1.1rc2/
COPY --from=compile-1.1.0 /app/legacy/ptvp35/docs/build/html/ /app/docs/build/html/1.1.0/
COPY --from=compile-latest /app/docs/build/html/ /app/docs/build/html/
CMD [ "http-server", "-p", "80" ] CMD [ "http-server", "-p", "80" ]

View File

@ -17,6 +17,6 @@ help:
# Catch-all target: route all unknown targets to Sphinx using the new # Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile %: Makefile
python ../traced_example.py > ../traced_example.txt python scripts/traced_example.py > scripts/traced_example.txt
python ../traced_example.py all > ../traced_example_all.txt python scripts/traced_example.py all > scripts/traced_example_all.txt
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View File

@ -1,35 +0,0 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)
if "%1" == "" goto help
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

View File

@ -0,0 +1,320 @@
import asyncio
import pathlib
import sys
import threading
from contextlib import ExitStack
from rainbowadn.instrument import Instrumentation
try:
sys.path.append(str((pathlib.Path(__file__).parent / "../..").absolute()))
from ptvp35 import DbConnection, DbFactory, KVJson
from ptvp35.instrumentation import InstrumentDiskWrites
except:
raise
async def aprint(*args, **kwargs):
print(*args, **kwargs)
class LogWrites(InstrumentDiskWrites):
def __init__(self, /):
super().__init__()
self.loop = asyncio.get_running_loop()
def on_write(self, line: str, /) -> None:
asyncio.run_coroutine_threadsafe(aprint(f"{self.methodname}[{line}]"), self.loop).result()
class LogEE(Instrumentation):
def __init__(self, target, methodname: str):
super().__init__(target, methodname)
self.loop = asyncio.get_running_loop()
def _target_id(self) -> str:
name = self.target.__name__ if hasattr(self.target, "__name__") else self.target.__class__.__name__
return f"{name}.{self.methodname}"
def _print(self, thread, *args) -> None:
print(thread, self._target_id(), *args, sep="\t")
async def aprint(self, thread, *args) -> None:
self._print(thread, *args)
def print(self, *args) -> None:
if (ct := threading.current_thread()) is threading.main_thread():
self._print("main", *args)
else:
asyncio.run_coroutine_threadsafe(self.aprint("aux", *args), self.loop).result()
def instrument(self, method, *args, **kwargs):
self.print("enter")
try:
result = method(*args, **kwargs)
except:
self.print("error")
raise
else:
self.print("exit")
return result
class ALogEE(LogEE):
async def instrument(self, method, *args, **kwargs):
self._print("aio", "enter")
try:
result = await method(*args, **kwargs)
except:
self._print("aio", "error")
raise
else:
self._print("aio", "exit")
return result
async def transaction_test(db: DbConnection):
def logdb(*args):
if args:
args = (
" ",
" ",
"@",
) + args
print(db.get("test", "0"), *args, sep="\t")
def logstate(*args):
if args:
args = ("@",) + args
print(db.get("test", "0"), "|", state.get("test", "0"), *args, sep="\t")
logdb("empty db")
db.set_nowait("test", "1")
logdb("after set_nowait")
await db.set("test", "2")
logdb("after set")
try:
async with db.transaction() as state:
logstate("empty transaction")
state.set_nowait("test", "3")
logstate("after transaction.set_nowait")
state.submit()
logstate("after transaction.submit")
await state.commit()
logstate("after transaction.commit")
state.set_nowait("test", print) # will throw TypeError later
logstate()
except TypeError:
print("type error")
logdb("after transaction")
async with db.transaction() as state:
logstate()
state.set_nowait("test", "4")
logstate("before implicit transaction.commit")
logdb("after transaction with implicit commit")
with db.transaction() as state:
logstate()
state.set_nowait("test", "5")
logstate("before implicit transaction.submit")
logdb("after transaction with implicit submit")
def print_private_db_attrs(db: DbConnection):
if run_all:
for attr in dir(db):
if attr.startswith("_DbConnection") and hasattr(db, attr):
print(attr)
run_all = "all" in sys.argv
async def main():
(path := pathlib.Path(__file__).parent / "trace_example.db").unlink(missing_ok=True)
with ExitStack() as es:
LogWrites().enter(es)
if run_all:
LogEE(__import__("ptvp35").Request, "__init__").enter(es)
LogEE(__import__("ptvp35").Request, "waiting").enter(es)
LogEE(__import__("ptvp35").Request, "set_result").enter(es)
LogEE(__import__("ptvp35").Request, "set_exception").enter(es)
ALogEE(__import__("ptvp35").Request, "wait").enter(es)
LogEE(__import__("ptvp35").LineRequest, "__init__").enter(es)
LogEE(__import__("ptvp35").KVFactory, "run").enter(es)
LogEE(__import__("ptvp35").KVFactory, "_dbset").enter(es)
LogEE(__import__("ptvp35").KVFactory, "dbset").enter(es)
LogEE(__import__("ptvp35").KVFactory, "dbget").enter(es)
LogEE(__import__("ptvp35").KVFactory, "filter_value").enter(es)
LogEE(__import__("ptvp35").KVFactory, "request").enter(es)
LogEE(__import__("ptvp35").KVFactory, "free").enter(es)
LogEE(__import__("ptvp35").KVFactory, "io2db").enter(es)
LogEE(__import__("ptvp35").KVFactory, "db2io").enter(es)
LogEE(__import__("ptvp35").KVFactory, "path2db_sync").enter(es)
LogEE(__import__("ptvp35").KVFactory, "db2path_sync").enter(es)
LogEE(__import__("ptvp35").KVRequest, "__init__").enter(es)
LogEE(__import__("ptvp35").KVJson, "line").enter(es)
LogEE(__import__("ptvp35").KVJson, "_load_key").enter(es)
LogEE(__import__("ptvp35").KVJson, "fromline").enter(es)
LogEE(__import__("ptvp35").TransactionRequest, "__init__").enter(es)
LogEE(__import__("ptvp35").DbParameters, "__init__").enter(es)
LogEE(__import__("ptvp35").VirtualConnection, "transaction").enter(es)
LogEE(__import__("ptvp35")._Loop, "__init__").enter(es)
LogEE(__import__("ptvp35")._Loop, "create_future").enter(es)
LogEE(__import__("ptvp35")._Loop, "loop").enter(es)
LogEE(__import__("ptvp35")._Loop, "run_in_thread").enter(es)
LogEE(__import__("ptvp35")._Errors, "__init__").enter(es)
LogEE(__import__("ptvp35")._Errors, "_save_sync").enter(es)
ALogEE(__import__("ptvp35")._Errors, "_save").enter(es)
LogEE(__import__("ptvp35")._Errors, "save_from_thread").enter(es)
LogEE(__import__("ptvp35")._File, "__init__").enter(es)
LogEE(__import__("ptvp35")._File, "path").enter(es)
LogEE(__import__("ptvp35")._File, "tell").enter(es)
LogEE(__import__("ptvp35")._File, "write_to_disk_sync").enter(es)
LogEE(__import__("ptvp35")._File, "open_sync").enter(es)
LogEE(__import__("ptvp35")._File, "close_sync").enter(es)
LogEE(__import__("ptvp35")._Backup, "__init__").enter(es)
LogEE(__import__("ptvp35")._Backup, "file").enter(es)
LogEE(__import__("ptvp35")._Backup, "kvfactory").enter(es)
LogEE(__import__("ptvp35")._Backup, "_copy_sync").enter(es)
LogEE(__import__("ptvp35")._Backup, "_recovery_unset_sync").enter(es)
LogEE(__import__("ptvp35")._Backup, "_finish_recovery_sync").enter(es)
LogEE(__import__("ptvp35")._Backup, "_recovery_set_sync").enter(es)
LogEE(__import__("ptvp35")._Backup, "build_file_sync").enter(es)
LogEE(__import__("ptvp35")._Backup, "_rebuild_file_sync").enter(es)
LogEE(__import__("ptvp35")._Backup, "_reload_sync").enter(es)
LogEE(__import__("ptvp35")._Backup, "run_in_thread").enter(es)
ALogEE(__import__("ptvp35")._Backup, "_reload").enter(es)
ALogEE(__import__("ptvp35")._Backup, "reload_if_oversized").enter(es)
LogEE(__import__("ptvp35")._Backup, "load_mmdb_sync").enter(es)
LogEE(__import__("ptvp35")._Backup, "uninitialize").enter(es)
LogEE(__import__("ptvp35")._Guard, "__init__").enter(es)
LogEE(__import__("ptvp35")._Guard, "backup").enter(es)
LogEE(__import__("ptvp35")._Guard, "_write_bytes_sync").enter(es)
LogEE(__import__("ptvp35")._Guard, "_write_value_sync").enter(es)
LogEE(__import__("ptvp35")._Guard, "_set_sync").enter(es)
LogEE(__import__("ptvp35")._Guard, "_unset_sync").enter(es)
LogEE(__import__("ptvp35")._Guard, "_read_bytes_sync").enter(es)
LogEE(__import__("ptvp35")._Guard, "_read_value_sync").enter(es)
LogEE(__import__("ptvp35")._Guard, "_truncate_sync").enter(es)
LogEE(__import__("ptvp35")._Guard, "assure_sync").enter(es)
LogEE(__import__("ptvp35")._Guard, "_file_truncate_sync").enter(es)
LogEE(__import__("ptvp35")._Guard, "file_write_sync").enter(es)
LogEE(__import__("ptvp35")._ReceivingQueue, "__init__").enter(es)
LogEE(__import__("ptvp35")._ReceivingQueue, "submit").enter(es)
LogEE(__import__("ptvp35")._WriteableBuffer, "__init__").enter(es)
LogEE(__import__("ptvp35")._WriteableBuffer, "writeable").enter(es)
LogEE(__import__("ptvp35")._WriteableBuffer, "loop").enter(es)
LogEE(__import__("ptvp35")._WriteableBuffer, "_compressed").enter(es)
LogEE(__import__("ptvp35")._WriteableBuffer, "_commit_compressed_sync").enter(es)
ALogEE(__import__("ptvp35")._WriteableBuffer, "_commit_compressed").enter(es)
LogEE(__import__("ptvp35")._WriteableBuffer, "_clear").enter(es)
LogEE(__import__("ptvp35")._WriteableBuffer, "_satisfy_future").enter(es)
LogEE(__import__("ptvp35")._WriteableBuffer, "_fail_future").enter(es)
ALogEE(__import__("ptvp35")._WriteableBuffer, "_do_commit_buffer").enter(es)
LogEE(__import__("ptvp35")._WriteableBuffer, "_request_buffer").enter(es)
ALogEE(__import__("ptvp35")._WriteableBuffer, "_commit").enter(es)
ALogEE(__import__("ptvp35")._WriteableBuffer, "_commit_or_request_so").enter(es)
ALogEE(__import__("ptvp35")._WriteableBuffer, "_write").enter(es)
ALogEE(__import__("ptvp35")._WriteableBuffer, "_handle_request").enter(es)
ALogEE(__import__("ptvp35")._WriteableBuffer, "_close").enter(es)
LogEE(__import__("ptvp35")._Memory, "__init__").enter(es)
LogEE(__import__("ptvp35")._Memory, "_initialize_sync").enter(es)
LogEE(__import__("ptvp35")._Memory, "_load_from_file_sync").enter(es)
ALogEE(__import__("ptvp35")._Memory, "_load_from_file").enter(es)
LogEE(__import__("ptvp35")._Memory, "_close_sync").enter(es)
ALogEE(__import__("ptvp35")._Memory, "_close").enter(es)
LogEE(__import__("ptvp35")._Memory, "_transaction_buffer").enter(es)
LogEE(__import__("ptvp35")._Memory, "get").enter(es)
LogEE(__import__("ptvp35")._Memory, "set").enter(es)
LogEE(__import__("ptvp35")._QueueTask, "__init__").enter(es)
ALogEE(__import__("ptvp35")._QueueTask, "_background_cycle").enter(es)
ALogEE(__import__("ptvp35")._QueueTask, "_background_task").enter(es)
ALogEE(__import__("ptvp35")._QueueTask, "close").enter(es)
LogEE(__import__("ptvp35")._QueueTask, "start").enter(es)
LogEE(__import__("ptvp35")._DbConnection, "__init__").enter(es)
LogEE(__import__("ptvp35")._DbConnection, "kvprotocol").enter(es)
LogEE(__import__("ptvp35")._DbConnection, "get").enter(es)
ALogEE(__import__("ptvp35")._DbConnection, "set").enter(es)
LogEE(__import__("ptvp35")._DbConnection, "set_nowait").enter(es)
ALogEE(__import__("ptvp35")._DbConnection, "_initialize_running").enter(es)
ALogEE(__import__("ptvp35")._DbConnection, "_initialize").enter(es)
ALogEE(__import__("ptvp35")._DbConnection, "create").enter(es)
ALogEE(__import__("ptvp35")._DbConnection, "_close_running").enter(es)
ALogEE(__import__("ptvp35")._DbConnection, "aclose").enter(es)
ALogEE(__import__("ptvp35")._DbConnection, "commit_transaction").enter(es)
LogEE(__import__("ptvp35")._DbConnection, "submit_transaction").enter(es)
LogEE(__import__("ptvp35")._DbConnection, "submit_transaction_request").enter(es)
ALogEE(__import__("ptvp35")._DbConnection, "commit").enter(es)
LogEE(__import__("ptvp35")._DbConnection, "loop").enter(es)
LogEE(__import__("ptvp35")._DbConnection, "transaction").enter(es)
LogEE(__import__("ptvp35").DbManager, "__init__").enter(es)
ALogEE(__import__("ptvp35").DbManager, "__aenter__").enter(es)
ALogEE(__import__("ptvp35").DbManager, "__aexit__").enter(es)
LogEE(__import__("ptvp35").Db, "__init__").enter(es)
ALogEE(__import__("ptvp35").Db, "__aenter__").enter(es)
ALogEE(__import__("ptvp35").Db, "__aexit__").enter(es)
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)
LogEE(__import__("ptvp35").TransactionView, "rollback").enter(es)
LogEE(__import__("ptvp35").TransactionView, "illuminate").enter(es)
ALogEE(__import__("ptvp35").TransactionView, "ailluminate").enter(es)
LogEE(__import__("ptvp35").TransactionView, "fork").enter(es)
ALogEE(__import__("ptvp35").TransactionView, "afork").enter(es)
LogEE(__import__("ptvp35").TransactionView, "clear").enter(es)
ALogEE(__import__("ptvp35").TransactionView, "aclear").enter(es)
LogEE(__import__("ptvp35").TransactionView, "reset").enter(es)
ALogEE(__import__("ptvp35").TransactionView, "areset").enter(es)
LogEE(__import__("ptvp35").TransactionView, "get").enter(es)
LogEE(__import__("ptvp35").TransactionView, "set_nowait").enter(es)
LogEE(__import__("ptvp35").TransactionView, "_delta").enter(es)
ALogEE(__import__("ptvp35").TransactionView, "commit").enter(es)
LogEE(__import__("ptvp35").TransactionView, "submit").enter(es)
LogEE(__import__("ptvp35").TransactionView, "_do_gather").enter(es)
LogEE(__import__("ptvp35").TransactionView, "_reduce_future").enter(es)
LogEE(__import__("ptvp35").TransactionView, "_gather").enter(es)
ALogEE(__import__("ptvp35").TransactionView, "commit_transaction").enter(es)
LogEE(__import__("ptvp35").TransactionView, "submit_transaction").enter(es)
LogEE(__import__("ptvp35").TransactionView, "submit_transaction_request").enter(es)
LogEE(__import__("ptvp35").TransactionView, "loop").enter(es)
LogEE(__import__("ptvp35").TransactionView, "transaction").enter(es)
LogEE(__import__("ptvp35").Transaction, "__init__").enter(es)
ALogEE(__import__("ptvp35").Transaction, "__aenter__").enter(es)
ALogEE(__import__("ptvp35").Transaction, "__aexit__").enter(es)
LogEE(__import__("ptvp35").Transaction, "_clean").enter(es)
LogEE(__import__("ptvp35").Transaction, "__enter__").enter(es)
LogEE(__import__("ptvp35").Transaction, "__exit__").enter(es)
async with DbFactory(path, kvfactory=KVJson()) as db:
await transaction_test(db)
print_private_db_attrs(db)
print_private_db_attrs(db)
if __name__ == "__main__":
asyncio.run(main())

View File

@ -6,35 +6,37 @@
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'ptvp35' import os.path
copyright = '2022, PARRRATE TNV' import sys
author = 'PARRRATE TNV'
with open('../../setup.py') as f: project = "ptvp35"
copyright = "2022, PARRRATE TNV"
author = "PARRRATE TNV"
with open("../../setup.py") as f:
_src = f.read() _src = f.read()
_src = _src[_src.index('version=\'') + 9:] _src = _src[_src.index('version="') + 9 :]
_src = _src[:_src.index('\'')] _src = _src[: _src.index('"')]
release = _src release = _src
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [ extensions = [
'sphinx.ext.autodoc', "sphinx.ext.autodoc",
] ]
templates_path = ['_templates'] templates_path = ["_templates"]
exclude_patterns = [] exclude_patterns = []
# -- Options for HTML output ------------------------------------------------- # -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = 'pydata_sphinx_theme' html_theme = "pydata_sphinx_theme"
html_static_path = ['_static'] html_theme_options = {
"navbar_center": [],
}
html_static_path = ["_static"]
import sys sys.path.insert(0, os.path.abspath("../.."))
import os.path
sys.path.insert(0, os.path.abspath('../..'))

View File

@ -0,0 +1,22 @@
Current core development values
===============================
* Target :code:`async` only.
* Store the database file as a single human-editable file.
* Keep the core module a single minimalistic file.
* Provide for dynamic instrumentation.
Guidelines for developers of Persistence 5
==========================================
These apply both to the internal PARRRATE TNV team behind Persistence 5 and to external contributors.
* Before contributing a feature to Persistence 5, the following should be considered:
* Addition of a simple extensible/flexible support for the further integration of external features is preferred over adding features themselves.
* Addition of features as extensions is preferred over adding features to core codebase.
* Maintaining extensions as separate projects is preferred over adding them to the core repository.
* Forking of the repository is encouraged.
* Usage of the repository as a reference for custom data storage solutions is encouraged.
* Instrumentation code base is more open to direct code contributions.
* Main way of instrumentation is code injection.
* Instrumentation on its own shouldn't require changes to the core code, even though instrumentation-allowing core code is preferred.

55
docs/source/history.rst Normal file
View File

@ -0,0 +1,55 @@
Historical notes
================
Persistence 1/2. CmbPrst.
--------------
* Internal storage of CMB.
Persistence 3. ptvp3.
---------------------
* Cluster of backed-up-on-disk dictionaries.
* Used by CBMB.
Persistence 4. ptvp3.4. ShelveSQLite.
-------------------------------------
* SQLite-based analogue of :code:`shelve` module.
* Used by v6x12 implementation of CBMB.
Persistence 5. ptvp3.5.
-----------------------
* First :code:`async` database in the line.
* File stream storage.
Persistence 5 early release candidates.
--------------------------------------
* Prevents database corruption.
Persistence 5 1.0 (5.1.0).
-------------------------
* ACID compliant.
* Reduced or none blocking code.
Persistence 5 1.1 (5.1.1).
-------------------------
* Non-nightly support for transactions.
* Instrumentation support.
* Structural preparations for generic DBs.
* :code:`VDELETE`
Proposed future versions
========================
Persistence 5 1.2.
------------------
* More abstract concepts (expansion of :code:`VirtualConnection` and :code:`LineRequest`).
Persistence 5 2.0.
------------------
* Any future breaking changes are going to be listed here.

View File

@ -1,26 +1,27 @@
Welcome to ptvp35's documentation! Welcome to Persistence 5
================================== ========================
Memory-Resident DataBase for simple single-process asynchronous Python applications.
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
:caption: Contents: :caption: Contents:
motivation motivation
usage usage
modules structure
guarantees guarantees
ordering ordering
projects projects
history
development
modules
Indices and tables Indices and tables
================== ==================
* :doc:`motivation`
* :doc:`usage`
* :doc:`guarantees`
* :doc:`ordering`
* :doc:`projects`
* :ref:`genindex` * :ref:`genindex`
* :ref:`modindex` * :ref:`modindex`
* :ref:`search` * :ref:`search`

View File

@ -1,5 +1,5 @@
ptvp35 Modules
====== =======
.. toctree:: .. toctree::
:maxdepth: 4 :maxdepth: 4

View File

@ -1,12 +1,12 @@
Motivation What is Persistence 5 for?
========== ==========================
This page describes reasons for certain design decisions. This page describes reasons for certain design decisions.
General structure General structure
----------------- -----------------
* We're making a key-value database. * A key-value database.
* Keys and values are python objects. * Keys and values are python objects.
* While the DB is offline (after a correct shutdown), all its KVPs are stored as lines in one editable file. * While the DB is offline (after a correct shutdown), all its KVPs are stored as lines in one editable file.
* DB should be able to survive powercut at any point (D in ACID). * DB should be able to survive powercut at any point (D in ACID).

View File

@ -4,17 +4,17 @@ Traced example of how ordering works in persistence5
Source Source
------ ------
.. literalinclude :: ../../traced_example.py .. literalinclude :: ../scripts/traced_example.py
:language: python3 :language: python3
Writes/reads log Writes/reads log
---------------- ----------------
.. literalinclude :: ../../traced_example.txt .. literalinclude :: ../scripts/traced_example.txt
:language: plain :language: plain
Everything log Everything log
-------------- --------------
.. literalinclude :: ../../traced_example_all.txt .. literalinclude :: ../scripts/traced_example_all.txt
:language: plain :language: plain

View File

@ -1,5 +1,5 @@
ptvp35 package ptvp35 (API Reference)
============== ======================
Module contents Module contents
--------------- ---------------

75
docs/source/structure.rst Normal file
View File

@ -0,0 +1,75 @@
Inner structure
===============
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
* Keys and values are almost guaranteed to be serializable.
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
--------------------
* In current implementation, all database storage storage files are Newline-Delimited JSON streams (https://en.wikipedia.org/wiki/JSON_streaming#Newline-Delimited_JSON).
.. code-block:: json
{"key": ["tuple", "example"], "value": {"dict": "example"}}
{"key": 123, "value": null}
* During the runtime, the database uses 6 different files:
* `.db` Main file.
Should be the only non-error file after correct shutdown.
* `.db.backup` Backup file.
Generated when the main file is being rebuilt.
* `.db.recover` Flag file.
Indicates that backup file is valid and that main file's validity is undefined.
* `.db.truncate` Auxiliary file created on each write to main file.
Contains 16 bytes little-endian representation of up to how many characters the main file is valid.
* `.db.truncate_flag` Flag file.
Indicates that truncate file is valid and that main file's validity after the specified character count is undefined.
* `.db.error` Error log file.
In current implementation, it contains only the main file contents that got truncated on recovery.
* All storage file writes are :code:`fsync`'ed.
* :code:`pathlib.Path.write_bytes` usecase relies on synchronisation/file-creation ordering. That may get replaced in future versions.
Request Queue
-------------
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.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.
* 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`)
-----------------------------------------
Manages a Transaction View.
* Creates and returns a Transaction View on :code:`__aenter__`/:code:`__enter__`.
* Submits changes on successful :code:`__exit__`.
* Commits changes on successful :code:`__aexit__`.
* Rolls back changes on unsuccessful :code:`__exit__`/:code:`__aexit__`.

View File

@ -13,12 +13,10 @@ Default installation option is to use pip+git
Basic functionality Basic functionality
------------------- -------------------
.. autoclass:: ptvp35.DbFactory :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**.
:code:`DbFactory` class provides context management for database connections (:code:`DbConnection`) via :code:`async with` statement. Also, that means that each connection start/shutdown is quite time expensive.
The connection isn't just a "connection", it's also the MMDB itself, so **using two connections to one database is an undefined behaviour**. 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.
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.
.. code-block:: python3 .. code-block:: python3
@ -62,26 +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()
.. autoclass:: ptvp35.DbConnection For common methods see: :class:`~ptvp35.AbstractDbConnection`
.. automethod:: get
this method is instant.
.. automethod:: set
this method may take time to run.
ordering may not be guaranteed (depends on event loop implementation).
.. automethod:: set_nowait
this method is instant.
ordering is guaranteed.
.. automethod:: commit
this method may take time to run.
respects the ordering of previously called :code:`set_nowait` methods.
will, under most circumstances, also execute later changes.
.. automethod:: transaction

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,12 @@
import ptvp35
from rainbowadn.instrument import Instrumentation from rainbowadn.instrument import Instrumentation
from . import DbConnection
__all__ = ("InstrumentDiskWrites",)
class InstrumentDiskWrites(Instrumentation): class InstrumentDiskWrites(Instrumentation):
def __init__(self, /): def __init__(self, /):
super().__init__(DbConnection, '_write_to_disk_sync') super().__init__(ptvp35._File, "write_to_disk_sync")
def on_write(self, line: str, /) -> None: def on_write(self, line: str, /) -> None:
pass pass
@ -12,12 +14,3 @@ class InstrumentDiskWrites(Instrumentation):
def instrument(self, method, db, line, /): def instrument(self, method, db, line, /):
self.on_write(line) self.on_write(line)
return method(db, line) return method(db, line)
class NightlyInstrumentation(Instrumentation):
def __init__(self, target, methodname: str):
method = getattr(target, methodname)
if hasattr(method, '__non_nightly__'):
target = method
methodname = '__non_nightly__'
super().__init__(target, methodname)

View File

@ -1,12 +1,12 @@
from setuptools import setup from setuptools import setup
setup( setup(
name='ptvp35', name="ptvp35",
version='1.1rc0', version="1.1.0",
packages=['ptvp35'], packages=["ptvp35"],
url='https://gitea.ongoteam.net/PTV/ptvp35', url="https://gitea.ongoteam.net/PTV/ptvp35",
license='MIT', license="MIT",
author='PARRRATE TNV', author="PARRRATE TNV",
author_email='', author_email="",
description='' description="",
) )

23
test_delete.py Normal file
View File

@ -0,0 +1,23 @@
import asyncio
import pathlib
from ptvp35 import VDELETE, DbFactory, KVJson
async def main():
path = pathlib.Path("test_delete.db")
path.unlink(missing_ok=True)
async with DbFactory(path, kvfactory=KVJson()) as connection:
connection.set_nowait(0, 0)
print(connection.get(0, 1))
await connection.commit()
async with connection.transaction() as transaction:
print(transaction.get(0, 1))
transaction.set_nowait(0, VDELETE)
print(transaction.get(0, 1))
input()
print(connection.get(0, 1))
# path.unlink(missing_ok=True)
asyncio.run(main())

View File

@ -1,286 +0,0 @@
import asyncio
import pathlib
import sys
import threading
from contextlib import ExitStack
from ptvp35 import DbConnection, DbFactory, KVJson
from ptvp35.instrumentation import InstrumentDiskWrites, NightlyInstrumentation
async def aprint(*args, **kwargs):
print(*args, **kwargs)
class LogWrites(InstrumentDiskWrites):
def __init__(self, /):
super().__init__()
self.loop = asyncio.get_running_loop()
def on_write(self, line: str, /) -> None:
asyncio.run_coroutine_threadsafe(
aprint(f'{self.methodname}[{line}]'), self.loop
).result()
class LogEE(NightlyInstrumentation):
def __init__(self, target, methodname: str):
super().__init__(target, methodname)
self.loop = asyncio.get_running_loop()
def _target_id(self) -> str:
name = (
self.target.__name__
if
hasattr(self.target, '__name__')
else
self.target.__class__.__name__
)
return f'{name}.{self.methodname}'
def _print(self, thread, *args) -> None:
print(
thread,
self._target_id(),
*args,
sep='\t'
)
async def aprint(self, thread, *args) -> None:
self._print(thread, *args)
def print(self, *args) -> None:
if (ct := threading.current_thread()) is threading.main_thread():
self._print('main', *args)
else:
asyncio.run_coroutine_threadsafe(
self.aprint('aux', *args), self.loop
).result()
def instrument(self, method, *args, **kwargs):
self.print('enter')
try:
result = method(*args, **kwargs)
except:
self.print('error')
raise
else:
self.print('exit')
return result
class ALogEE(LogEE):
async def instrument(self, method, *args, **kwargs):
self._print('aio', 'enter')
try:
result = await method(*args, **kwargs)
except:
self._print('aio', 'error')
raise
else:
self._print('aio', 'exit')
return result
async def transaction_test(db: DbConnection):
def logdb(*args):
if args:
args = (' ', ' ', '@',) + args
print(db.get('test', '0'), *args, sep='\t')
def logstate(*args):
if args:
args = ('@',) + args
print(db.get('test', '0'), '|', state.get('test', '0'), *args, sep='\t')
logdb('empty db')
db.set_nowait('test', '1')
logdb('after set_nowait')
await db.set('test', '2')
logdb('after set')
try:
async with db.transaction() as state:
logstate('empty transaction')
state.set_nowait('test', '3')
logstate('after transaction.set_nowait')
state.submit()
logstate('after transaction.submit')
await state.commit()
logstate('after transaction.commit')
state.set_nowait('test', print) # will throw TypeError later
logstate()
except TypeError:
print('type error')
logdb('after transaction')
async with db.transaction() as state:
logstate()
state.set_nowait('test', '4')
logstate('before implicit transaction.commit')
logdb('after transaction with implicit commit')
with db.transaction() as state:
logstate()
state.set_nowait('test', '5')
logstate('before implicit transaction.submit')
logdb('after transaction with implicit submit')
def print_private_db_attrs(db: DbConnection):
if run_all:
for attr in dir(db):
if attr.startswith('_DbConnection') and hasattr(db, attr):
print(attr)
run_all = 'all' in sys.argv
async def main():
(path := pathlib.Path('dev.db')).unlink(missing_ok=True)
with ExitStack() as es:
LogWrites().enter(es)
if run_all:
LogEE(__import__('ptvp35').Request, '__init__').enter(es)
LogEE(__import__('ptvp35').Request, 'waiting').enter(es)
LogEE(__import__('ptvp35').Request, 'set_result').enter(es)
LogEE(__import__('ptvp35').Request, 'set_exception').enter(es)
ALogEE(__import__('ptvp35').Request, 'wait').enter(es)
LogEE(__import__('ptvp35').LineRequest, '__init__').enter(es)
LogEE(__import__('ptvp35').KVFactory, 'run').enter(es)
LogEE(__import__('ptvp35').KVFactory, 'request').enter(es)
LogEE(__import__('ptvp35').KVFactory, 'free').enter(es)
LogEE(__import__('ptvp35').KVFactory, 'io2db').enter(es)
LogEE(__import__('ptvp35').KVFactory, 'db2io').enter(es)
LogEE(__import__('ptvp35').KVRequest, '__init__').enter(es)
LogEE(__import__('ptvp35').KVJson, 'line').enter(es)
LogEE(__import__('ptvp35').KVJson, '_load_key').enter(es)
LogEE(__import__('ptvp35').KVJson, 'fromline').enter(es)
LogEE(__import__('ptvp35').TransactionRequest, '__init__').enter(es)
LogEE(__import__('ptvp35').DbParametres, '__init__').enter(es)
LogEE(__import__('ptvp35').VirtualConnection, 'transaction').enter(es)
LogEE(__import__('ptvp35').DbConnection, '__init__').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_create_future').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_save_error_sync').enter(es)
ALogEE(__import__('ptvp35').DbConnection, '_save_error').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_queue_error').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_save_error_from_thread').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_path2db_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_db2path_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, 'get').enter(es)
ALogEE(__import__('ptvp35').DbConnection, 'set').enter(es)
LogEE(__import__('ptvp35').DbConnection, 'set_nowait').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_clear_buffer').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_compress_buffer').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_commit_compressed_buffer_sync').enter(es)
ALogEE(__import__('ptvp35').DbConnection, '_commit_compressed_buffer').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_satisfy_buffer_future').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_fail_buffer_future').enter(es)
ALogEE(__import__('ptvp35').DbConnection, '_do_commit_buffer').enter(es)
ALogEE(__import__('ptvp35').DbConnection, '_commit_buffer').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_request_buffer').enter(es)
ALogEE(__import__('ptvp35').DbConnection, '_commit_buffer_or_request_so').enter(es)
ALogEE(__import__('ptvp35').DbConnection, '_write').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_truncation_set_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_truncation_unset_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_file_truncate_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_truncation_target_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_truncate_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_assure_truncation_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_write_to_disk_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_file_write_sync').enter(es)
ALogEE(__import__('ptvp35').DbConnection, '_reload_if_oversized').enter(es)
ALogEE(__import__('ptvp35').DbConnection, '_handle_request').enter(es)
ALogEE(__import__('ptvp35').DbConnection, '_background_cycle').enter(es)
ALogEE(__import__('ptvp35').DbConnection, '_background_task').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_start_task').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_recovery_set_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_recovery_unset_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_copy_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_finish_recovery_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_build_file_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_run_in_thread').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_rebuild_file_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_file_open_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_file_close_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_reload_sync').enter(es)
ALogEE(__import__('ptvp35').DbConnection, '_reload').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_load_mmdb_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_initialize_mmdb_sync').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_load_from_file_sync').enter(es)
ALogEE(__import__('ptvp35').DbConnection, '_load_from_file').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_initialize_queue').enter(es)
ALogEE(__import__('ptvp35').DbConnection, '_initialize_running').enter(es)
ALogEE(__import__('ptvp35').DbConnection, '_initialize').enter(es)
ALogEE(__import__('ptvp35').DbConnection, '_close_buffer').enter(es)
ALogEE(__import__('ptvp35').DbConnection, '_close_queue').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_close_mmdb_sync').enter(es)
ALogEE(__import__('ptvp35').DbConnection, '_close_mmdb').enter(es)
ALogEE(__import__('ptvp35').DbConnection, '_close_running').enter(es)
ALogEE(__import__('ptvp35').DbConnection, 'aclose').enter(es)
LogEE(__import__('ptvp35').DbConnection, '_transaction_buffer').enter(es)
ALogEE(__import__('ptvp35').DbConnection, 'commit_transaction').enter(es)
LogEE(__import__('ptvp35').DbConnection, 'submit_transaction').enter(es)
LogEE(__import__('ptvp35').DbConnection, 'submit_transaction_request').enter(es)
ALogEE(__import__('ptvp35').DbConnection, 'commit').enter(es)
LogEE(__import__('ptvp35').DbConnection, 'loop').enter(es)
LogEE(__import__('ptvp35').DbConnection, 'transaction').enter(es)
LogEE(__import__('ptvp35').DbFactory, '__init__').enter(es)
ALogEE(__import__('ptvp35').DbFactory, '__aenter__').enter(es)
ALogEE(__import__('ptvp35').DbFactory, '__aexit__').enter(es)
LogEE(__import__('ptvp35').Db, '__init__').enter(es)
ALogEE(__import__('ptvp35').Db, '__aenter__').enter(es)
ALogEE(__import__('ptvp35').Db, '__aexit__').enter(es)
LogEE(__import__('ptvp35').FutureContext, '__init__').enter(es)
ALogEE(__import__('ptvp35').FutureContext, '__aenter__').enter(es)
ALogEE(__import__('ptvp35').FutureContext, '__aexit__').enter(es)
LogEE(__import__('ptvp35').TransactionView, '__init__').enter(es)
LogEE(__import__('ptvp35').TransactionView, 'future_context').enter(es)
LogEE(__import__('ptvp35').TransactionView, 'rollback').enter(es)
LogEE(__import__('ptvp35').TransactionView, 'illuminate').enter(es)
ALogEE(__import__('ptvp35').TransactionView, 'ailluminate').enter(es)
LogEE(__import__('ptvp35').TransactionView, 'fork').enter(es)
ALogEE(__import__('ptvp35').TransactionView, 'afork').enter(es)
LogEE(__import__('ptvp35').TransactionView, 'clear').enter(es)
ALogEE(__import__('ptvp35').TransactionView, 'aclear').enter(es)
LogEE(__import__('ptvp35').TransactionView, 'reset').enter(es)
ALogEE(__import__('ptvp35').TransactionView, 'areset').enter(es)
LogEE(__import__('ptvp35').TransactionView, 'get').enter(es)
LogEE(__import__('ptvp35').TransactionView, 'set_nowait').enter(es)
LogEE(__import__('ptvp35').TransactionView, '_delta').enter(es)
ALogEE(__import__('ptvp35').TransactionView, 'commit').enter(es)
LogEE(__import__('ptvp35').TransactionView, 'submit').enter(es)
LogEE(__import__('ptvp35').TransactionView, '_do_gather').enter(es)
LogEE(__import__('ptvp35').TransactionView, '_reduce_future').enter(es)
LogEE(__import__('ptvp35').TransactionView, '_gather').enter(es)
ALogEE(__import__('ptvp35').TransactionView, 'commit_transaction').enter(es)
LogEE(__import__('ptvp35').TransactionView, 'submit_transaction').enter(es)
LogEE(__import__('ptvp35').TransactionView, 'submit_transaction_request').enter(es)
LogEE(__import__('ptvp35').TransactionView, 'loop').enter(es)
LogEE(__import__('ptvp35').TransactionView, 'transaction').enter(es)
LogEE(__import__('ptvp35').Transaction, '__init__').enter(es)
ALogEE(__import__('ptvp35').Transaction, '__aenter__').enter(es)
ALogEE(__import__('ptvp35').Transaction, '__aexit__').enter(es)
LogEE(__import__('ptvp35').Transaction, '_clean').enter(es)
LogEE(__import__('ptvp35').Transaction, '__enter__').enter(es)
LogEE(__import__('ptvp35').Transaction, '__exit__').enter(es)
async with DbFactory(path, kvfactory=KVJson()) as db:
await transaction_test(db)
print_private_db_attrs(db)
print_private_db_attrs(db)
if __name__ == '__main__':
asyncio.run(main())