Compare commits

...

68 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
0e55f88adb 1.1rc0: rollback + new docs 2022-11-29 17:17:43 +00:00
ee7659bda2 abstractmethod 2022-11-29 14:28:09 +00:00
e92d860b25 .dockerignore 2022-11-29 12:09:26 +00:00
d54a1e5744 wrap.__non_nightly__ + instrumentation 2022-11-29 10:49:52 +00:00
c3d1876a7e 1.0 2022-11-25 17:57:31 +00:00
7a8b7a82af VirtualConnection + nightly 2022-11-23 15:53:48 +00:00
07ea5a7a1a one more usecase 2022-11-22 13:54:20 +00:00
8dbb1af421 projects 2022-11-22 13:04:37 +00:00
b34aae1051 factory docs upd 2022-11-22 12:26:30 +00:00
b9c83d13f2 better close 2022-11-21 17:30:54 +00:00
0e7efbb1d5 1.0rc6: rename to commit 2022-11-21 16:09:22 +00:00
e1a2cb59b1 1.0rc5: traced_example + minor fixes 2022-11-21 15:39:54 +00:00
ef3bf09cdc add new usecase to docs 2022-11-21 13:03:04 +00:00
20b7c31f0a 1.0rc4: submit implementation + dbparametres
+ move io2db/db2io to kvfactory
2022-11-21 12:22:42 +00:00
09bb45e867 usage example fix 2022-11-21 02:18:02 +00:00
3c67459bf1 replace optional with union 2022-11-21 01:11:40 +00:00
87ba808c2a more on usage 2022-11-20 22:54:57 +00:00
af90b9c9c6 1.0rc3: LineRequest 2022-11-20 22:23:52 +00:00
04e8ba559e docs 2022-11-20 16:00:08 +00:00
f52bad680c 1.0rc1: call restrictions + kvfactory + slots 2022-11-19 19:20:27 +00:00
85a6bc0301 1.0rc0: early commit + path truncate flag 2022-11-19 11:52:01 +00:00
6262dd28e9 prevent impossible deadlock 2022-11-17 15:01:50 +00:00
f7c167f120 unused usecase fixed 2022-11-17 14:32:36 +00:00
b2b326fc55 truncation 2022-11-11 17:36:29 +00:00
8be67bf834 flush + fsync 2022-11-08 15:01:45 +00:00
bc72d96ddb remove unused import 2022-11-05 03:35:50 +00:00
c9cdbf86a6 run_in_executor -> _run_in_thread 2022-11-04 08:05:55 +00:00
90e7cd39c6 reduce run_in_executor calls 2022-11-04 07:53:11 +00:00
89cbb24386 io2db2io _copy_sync 2022-11-04 06:53:12 +00:00
243741346e _copy_sync refactor 2022-11-04 06:45:53 +00:00
1bba6902d0 refactor copy 2022-11-04 06:40:25 +00:00
da8db42dae pool isolation 2022-11-04 06:32:50 +00:00
46d585c762 pool 2 2022-11-04 06:22:42 +00:00
80aa527e52 pool 2022-11-04 06:12:35 +00:00
d1564637f2 transactions 2022-11-03 10:26:34 +00:00
632569a135 now Db works like it did 2022-11-01 03:36:43 +00:00
4a67e2421a style 2022-11-01 02:35:38 +00:00
c201370f48 fix __all__ 2022-11-01 02:03:43 +00:00
4ffae0a1ab DbFactory + DbConnection 2022-11-01 02:03:31 +00:00
d29d1b1395 _build_file 2022-10-27 17:26:23 +03:00
f3429eb1eb fixes 2022-04-23 12:30:08 +03:00
fbca105490 parsing error handling 2022-02-02 21:17:04 +03:00
d99d106f84 async io2db/db2io 2022-02-02 20:43:50 +03:00
6aea9d7044 async copy 2022-02-02 20:39:31 +03:00
25727aabd7 KVJson._load_key + aexit backup 2021-12-08 16:32:51 +03:00
b2a3503e0f backup and recover 2021-12-08 14:40:56 +03:00
4dcca8067f Merge remote-tracking branch 'origin/master'
# Conflicts:
#	ptvp35/__init__.py
2021-12-08 14:39:05 +03:00
5e20f57816 backup and recover 2021-12-08 14:37:54 +03:00
28 changed files with 2011 additions and 289 deletions

4
.dockerignore Normal file
View File

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

3
.gitignore vendored
View File

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

8
.idea/.gitignore vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

View File

@ -1,51 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues">
<value>
<list size="11">
<item index="0" class="java.lang.String" itemvalue="nobr" />
<item index="1" class="java.lang.String" itemvalue="noembed" />
<item index="2" class="java.lang.String" itemvalue="comment" />
<item index="3" class="java.lang.String" itemvalue="noscript" />
<item index="4" class="java.lang.String" itemvalue="embed" />
<item index="5" class="java.lang.String" itemvalue="script" />
<item index="6" class="java.lang.String" itemvalue="markdown" />
<item index="7" class="java.lang.String" itemvalue="sv3i" />
<item index="8" class="java.lang.String" itemvalue="sv3o" />
<item index="9" class="java.lang.String" itemvalue="sv3a" />
<item index="10" class="java.lang.String" itemvalue="sv3c" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="nacl" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="PySide2.QtWidgets.clicked.connect" />
<option value="PySide2.QtWidgets.valueChanged.connect" />
<option value="PySide2.QtWidgets.textChanged.connect" />
<option value="PySide2.QtCore.Signal.emit" />
<option value="PySide2.QtCore.Signal.connect" />
</list>
</option>
</inspection_tool>
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

View File

@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (ptvp35)" project-jdk-type="Python SDK" />
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ptvp35.iml" filepath="$PROJECT_DIR$/.idea/ptvp35.iml" />
</modules>
</component>
</project>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

71
Dockerfile Normal file
View File

@ -0,0 +1,71 @@
# syntax=docker/dockerfile:1
FROM python:3.10 as compile-sphinx5.3.0-base
RUN apt-get update
RUN apt-get install -y python3-sphinx
RUN pip install Sphinx==5.3.0 pydata-sphinx-theme==0.12.0
WORKDIR /app/
RUN pip install git+https://gitea.parrrate.ru/PTV/rainbowadn.git@e9fba7b064902ceedee0dd5578cb47030665a6aa
RUN mkdir /app/docs/
FROM compile-sphinx5.3.0-base as compile-legacy
WORKDIR /app/legacy/
RUN git clone --branch 1.0 https://gitea.parrrate.ru/PTV/ptvp35.git
WORKDIR /app/legacy/ptvp35/
FROM compile-legacy as compile-1.0
RUN python traced_example.py > traced_example.txt
RUN python traced_example.py all > traced_example_all.txt
RUN cp -r docs/source/ ./source/
RUN cp docs/Makefile ./Makefile
RUN make html
FROM compile-legacy as compile-1.1rc0
RUN git fetch && git checkout 1.1rc0
WORKDIR /app/legacy/ptvp35/docs/
RUN make html
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 setup.py setup.py
COPY docs/scripts docs/scripts
COPY ptvp35 ptvp35
COPY docs/source docs/source
WORKDIR /app/docs/
RUN make html
FROM node:19
RUN npm install -g http-server
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" ]

22
docs/Makefile Normal file
View File

@ -0,0 +1,22 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
python scripts/traced_example.py > scripts/traced_example.txt
python scripts/traced_example.py all > scripts/traced_example_all.txt
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

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())

42
docs/source/conf.py Normal file
View File

@ -0,0 +1,42 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
import os.path
import sys
project = "ptvp35"
copyright = "2022, PARRRATE TNV"
author = "PARRRATE TNV"
with open("../../setup.py") as f:
_src = f.read()
_src = _src[_src.index('version="') + 9 :]
_src = _src[: _src.index('"')]
release = _src
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
"sphinx.ext.autodoc",
]
templates_path = ["_templates"]
exclude_patterns = []
# -- 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_options = {
"navbar_center": [],
}
html_static_path = ["_static"]
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.

View File

@ -0,0 +1,36 @@
Guarantees
==========
MMDB-level guarantees
---------------
* All 0L writes change MMDB instantly.
* Transaction write works as one atomic write.
Queue-level guarantees
----------------------
This level is the mediator between MMDB and filesystem levels. This level includes the pre-write compression buffer.
* Queue requests occur in the same orders as they were acted upon at MMDB level and in the same order as they will be acted upon at filesystem level.
* DB can't close until all requests are done.
* No request can crash the task.
* Every request eventually succeedes or fails (except for the case of runtime termination).
Buffer-specific guarantees:
* If buffer dump was ever requested, then this request (queued or indirect) will eventually be satisfied.
Filesystem-level guarantees
---------------------------
* If main file exists without .recover or .truncate_flag, then it's valid.
* If .recover file is present, then .backup is valid.
* If .truncate_flag is present, then .truncate is valid and first :code:`.truncate_target()` (contents of .truncate) characters of main file are valid.
* Every write is final and irreversible (can't be reversed unintentionally due to termination), otherwise it's not considered done. That's achieved using :code:`os.fsync`.
Performance guarantees
----------------------
* No normal sync methods (except for :code:`io2db`/:code:`db2io` if they're supplied with blocking :code:`IO`) block on IO. Other methods are explicitly marked with postfix :code:`_sync`.
* All requests are resolved as soon as their conditions are met.

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.

27
docs/source/index.rst Normal file
View File

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

7
docs/source/modules.rst Normal file
View File

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

View File

@ -0,0 +1,30 @@
What is Persistence 5 for?
==========================
This page describes reasons for certain design decisions.
General structure
-----------------
* A key-value database.
* 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.
* DB should be able to survive powercut at any point (D in ACID).
AP/LP get
------
The central idea for this database is the zero-latency (as far as python goes) reads. Therefore, the DB has all its data stored as a :code:`dict`.
CAP/CLP set
-------
The database allows both availability-(zero)latency and consistency (note: it's not ACID consistency, it's CAP consistency and ACID durability) variants for its writes.
All zero-latency writes are also consistent with respect to (zero-latency) reads.
Consistent get
------
Later versions of the database will include remote connections, and thus also consistent reads.
These will probably be implemented as requests (same as writes).
Some connection variants may not even include availability reads/writes.

20
docs/source/ordering.rst Normal file
View File

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

27
docs/source/projects.rst Normal file
View File

@ -0,0 +1,27 @@
Projects that use persistence5
==============================
parrrate-music
--------------
Music bot by PARRRATE TNV.
Uses `cache.db`, `effects.db`, `queue.db`, `session.db`, `volume.db`.
https://gitea.parrrate.ru/PTV/v6d3music
ADaaS -- Audio Download as a Service
------------------------------------
Standalone variant of parrrate-music's url resolution.
Uses `cache.db`.
https://gitea.parrrate.ru/PTV/adaas.git
bad apple on a database file
----------------------------
Demonstrates how stable and predictable the inner structure of persistence5 DB file is.
https://youtu.be/csLmTD8UrDg

10
docs/source/ptvp35.rst Normal file
View File

@ -0,0 +1,10 @@
ptvp35 (API Reference)
======================
Module contents
---------------
.. automodule:: ptvp35
:members:
:undoc-members:
:show-inheritance:

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__`.

63
docs/source/usage.rst Normal file
View File

@ -0,0 +1,63 @@
Usage
=====
Installation
------------
Default installation option is to use pip+git
.. code-block:: console
(venv) $ pip install git+https://gitea.parrrate.ru/PTV/ptvp35.git
Basic functionality
-------------------
: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.
.. code-block:: python3
import pathlib
from ptvp35 import DbFactory, KVJson
async def main():
async with DbFactory(pathlib.Path('example.db'), kvfactory=KVJson()) as connection:
await _main(connection)
Different ways to get/set a value:
.. code-block:: python3
from ptvp35 import DbConnection
async def _main(connection: DbConnection):
value0 = connection.get('increment-0', 0)
await connection.set('increment-0', value0 + 1)
value1 = connection.get('increment-1', 0)
connection.set_nowait('increment-1', value1 + 1)
await connection.commit()
async with connection.transaction() as transaction:
value2 = transaction.get('increment-2', 0)
transaction.set_nowait('increment-2', value2 + 1)
async with connection.transaction() as transaction:
value3 = transaction.get('increment-3', 0)
transaction.set_nowait('increment-3', value3 + 1)
await transaction.commit()
with connection.transaction() as transaction:
value4 = transaction.get('increment-4', 0)
transaction.set_nowait('increment-4', value4 + 1)
await transaction.commit()
with connection.transaction() as transaction:
value5 = transaction.get('increment-5', 0)
transaction.set_nowait('increment-5', value5 + 1)
await connection.commit()
For common methods see: :class:`~ptvp35.AbstractDbConnection`

File diff suppressed because it is too large Load Diff

16
ptvp35/instrumentation.py Normal file
View File

@ -0,0 +1,16 @@
import ptvp35
from rainbowadn.instrument import Instrumentation
__all__ = ("InstrumentDiskWrites",)
class InstrumentDiskWrites(Instrumentation):
def __init__(self, /):
super().__init__(ptvp35._File, "write_to_disk_sync")
def on_write(self, line: str, /) -> None:
pass
def instrument(self, method, db, line, /):
self.on_write(line)
return method(db, line)

View File

@ -1,12 +1,12 @@
from setuptools import setup
setup(
name='ptvp35',
version='',
packages=['ptvp35'],
url='https://gitea.ongoteam.net/PTV/ptvp35',
license='',
author='PARRRATE T&V',
author_email='',
description=''
name="ptvp35",
version="1.1.0",
packages=["ptvp35"],
url="https://gitea.ongoteam.net/PTV/ptvp35",
license="MIT",
author="PARRRATE TNV",
author_email="",
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())