An asynchronous version of TinyDB.
Almost every method is asynchronous. And it's based on TinyDB 4.7.0+.
I will try to keep up with the latest version of TinyDB.
-
Asynchronous: Say goodbye to blocking IO. Don't forget to
awaitasync methods! -
Drop support: Only supports Python 3.10+. (for 3.8+ go to this branch)
-
ujson: Usingujsoninstead ofjson. Some arguments aren't compatible withjson1 -
Storage
closedproperty: OriginalTinyDBwon't raise exceptions when operating on a closed file. Now the propertyclosedofStorageclasses is required to be implemented. AnIOErrorshould be raised. -
Miscellaneous: Differences only matter in edge cases.
-
Event hooks: You can now use event hooks to do something before or after an operation. See Event Hooks for more details.
-
Redesigned ID & Doc class: You can replace and customise them more pleasingly.
-
DB-level caching: This significantly improves the performance of all operations. However, the responsibility of converting the data to the correct type is transferred to the Storage23.
-
Built-in
Modifier: UseModifierto easily encrypt, compress and extend types of your database. Sure you can do much more than these. (See Modifier) -
Isolation Level: Performance or ACID? It's up to you4.
-
Atomic Write: ACID!
- Minimum:
pip install async-tinydb - Encryption:
pip install async-tinydb[encryption] - Compression:
pip install async-tinydb[compression] - Full:
pip install async-tinydb[all]
from asynctinydb import TinyDB, whereSee the original TinyDB documents. Insert an await in front of async methods.
Notice that some codes are still blocking, for example, when calling len() on TinyDB or Table Objects.
That's it.
NOTICE: Mixing classes in one table may cause errors!
When a table exists in a file, Async-TinyDB won't determine classes by itself, it is your duty to make sure classes are matching.
IncreID: Default ID class, mimics the behaviours of the originalintID but requires much fewer IO operations.UUID: Usesuuid.UUID5.
Document: Default document class, usesdictunder the bonet.
from asynctinydb import TinyDB, UUID, IncreID, Document db = TinyDB("database.db") # Setting ID class to UUID, document class to Document tab = db.table("table1", document_id_class=UUID, document_class=Document)See Customisation for more details
Currently only supports AES-GCM encryption.
There are two ways to use encryption:
from asynctinydb import EncryptedJSONStorage, TinyDB async def main(): db = TinyDB("db.json", key="your key goes here", storage=EncryptedJSONStorage)See Encryption
To avoid blocking codes, Async-TinyDB puts CPU-bound tasks into another thread (Useful with interpreters without GIL)
Unfortunately, this introduces chances of data being modified when:
- Manipulating mutable objects within
Documentinstances in another coroutine - Performing updating/saving/searching operations (These operations are run in a different thread/process)
- The conditions above are satisfied in the same
Table - The conditions above are satisfied simultaneously.
Avoid these operations or set a higher isolation level to mitigate this problem.
db.isolevel = 2isolevel:
- No isolation
- Serialised CRUD operations (Also ensures thread safety) (default)
- Deepcopy documents on CRUD (Ensures
Index&Query Cacheconsistency)
DB-level caching improves performance dramatically.
However, this may cause data inconsistency between Storage and TinyDB if the file that Storage referred to is been shared.
To disable it:
db = TinyDB("./path", no_dbcache=True)import asyncio from asynctinydb import TinyDB, Query async def main(): db = TinyDB('test.json') await db.insert({"answer": 42}) print(await db.search(Query().answer == 42)) # >>> [{'answer': 42}] asyncio.run(main())async def main(): db = TinyDB('test.json') @db.storage.on.write.pre async def mul(ev: str, s: Storage, data: dict): data["_default"]["1"]['answer'] *= 2 # directly manipulate on data @db.storage.on.write.post async def _print(ev, s, anystr): print(anystr) # print json dumped string await db.insert({"answer": 21}) # insert() will trigger both write events await db.close() # Reload db = TinyDB('test.json') print(await db.search(Query().answer == 42)) # >>> [{'answer': 42}] Inherit from BaseID and implement the following methods, and then you are good to go.
from asynctinydb import BaseID class MyID(BaseID): def __init__(self, value: Any): """ You should be able to convert str into MyID instance if you want to use JSONStorage. """ def __str__(self) -> str: """ Optional. It should be implemented if you want to use JSONStorage. """ def __hash__(self) -> int: ... def __eq__(self, other: object) -> bool: ... @classmethod def next_id(cls, table: Table, keys) -> MyID: """ It should return a unique ID. """ @classmethod def mark_existed(cls, table: Table, new_id): """ Marks an ID as existing; the same ID shouldn't be generated by next_id. """ @classmethod def clear_cache(cls, table: Table): """ Clear cache of existing IDs, if such cache exists. """from asynctinydb import BaseDocument class MyDoc(BaseDocument): """ I am too lazy to write those necessary methods. """Anyways, a BaseDocument class looks like this:
class BaseDocument(Mapping[IDVar, Any]): @property @abstractmethod def doc_id(self) -> IDVar: raise NotImplementedError() @doc_id.setter def doc_id(self, value: IDVar): raise NotImplementedError()Make sure you have implemented all the methods required by BaseDocument class.
- Lazy-load: File loading & dirs creating are delayed to the first IO operation.
CachingMiddleWare:WRITE_CACHE_SIZEis now instance-specific.
Example:TinyDB("test.db", storage=CachingMiddleWare(JSONStorage, 1024))
