Skip to content

Commit 5db3539

Browse files
committed
v1.4.0 Atomic Write & Serialised CRUD
1 parent 514399f commit 5db3539

31 files changed

+2377
-746
lines changed

.coveragerc

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,15 @@ branch = True
55
exclude_lines =
66
pragma: no cover
77
raise NotImplementedError.*
8+
return NotImplemented
89
warnings\.warn.*
9-
def __repr__
10-
def __str__
1110
def main()
1211
if __name__ == .__main__.:
12+
if TYPE_CHECKING:
13+
except ImportError:
14+
@overload
15+
@abstractmethod
16+
17+
include = asynctinydb/*
18+
19+
omit = asynctinydb/mypy_plugin.py

.deepsource.toml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
version = 1
22

3-
[[analyzers]]
4-
name = "python"
5-
enabled = true
63
test_patterns = [
74
"tests/**",
85
"test_*.py"
96
]
107

8+
exclude_patterns = [
9+
"htmlcov/**",
10+
"benchmark/**"
11+
]
12+
13+
[[analyzers]]
14+
name = "python"
15+
enabled = true
16+
1117
[analyzers.meta]
1218
runtime_version = "3.x.x"

.github/workflows/ci-workflow.yml

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,8 @@ jobs:
1111
runs-on: ${{ matrix.os }}
1212
strategy:
1313
matrix:
14-
python-version: ["3.8", "3.9", "3.10", "pypy3"]
14+
python-version: ["3.10", "3.11.0-rc.2"]
1515
os: [ubuntu-latest, macos-latest, windows-latest]
16-
exclude:
17-
- os: windows-latest
18-
python-version: "pypy3"
19-
- os: macos-latest
20-
python-version: "pypy3"
2116

2217
steps:
2318
- uses: actions/checkout@v2
@@ -31,13 +26,14 @@ jobs:
3126
pip install poetry
3227
poetry config experimental.new-installer false
3328
poetry install
29+
poetry install -E all
3430
- name: Run test suite
3531
run: |
36-
poetry run py.test -v --cov=tinydb
32+
poetry run py.test -v --cov=asynctinydb
3733
- name: Perform type check
3834
run: |
3935
poetry run pip install pytest-mypy
40-
poetry run pytest --mypy -m mypy tinydb tests
36+
poetry run pytest --mypy -m mypy asynctinydb tests
4137
if: matrix.python-version != 'pypy3'
4238
- name: Verify dist package format
4339
run: |
@@ -51,3 +47,4 @@ jobs:
5147
if [ -n "${COVERALLS_REPO_TOKEN}" ]; then
5248
poetry run coveralls
5349
fi
50+
if: ${{ matrix.os != 'windows-latest' }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
.vscode
33
.empty
44
benchmark
5+
htmlcov
56

67
# C extensions
78
*.so

.readthedocs.yml

Lines changed: 0 additions & 11 deletions
This file was deleted.

LICENSE

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
1-
Copyright (C) 2013 Markus Siemens <markus@m-siemens.de>
1+
MIT License
22

3-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
3+
Copyright (c) VermiIIi0n
44

5-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
611

7-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 66 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## What's This?
44

5-
An asynchronous version of `TinyDB` based on `aiofiles`.
5+
An asynchronous version of `TinyDB`.
66

77
Almost every method is asynchronous. And it's based on `TinyDB 4.7.0+`.
88

@@ -12,42 +12,38 @@ I will try to keep up with the latest version of `TinyDB`.
1212

1313
## Incompatible Changes
1414

15-
Major Changes:
16-
1715
* **Asynchronous**: Say goodbye to blocking IO. **Don't forget to `await` async methods**!
18-
* **Drop support**: Only supports Python 3.8+.
19-
20-
Minor Changes:
2116

22-
* **Lazy-load:** When `access_mode` is set to `'r'`, `FileNotExistsError` is not raised until the first read operation.
17+
* **Drop support**: Only supports Python 3.10+. (for 3.8+ go to this [branch](https://github.com/VermiIIi0n/async-tinydb/tree/legacy))
2318

24-
* **`ujson`:** Using `ujson` instead of `json`. Some arguments aren't compatible with `json`
25-
Why not `orjson`? Because `ujson` is fast enough and has more features.
19+
* **`ujson`:** Using `ujson` instead of `json`. Some arguments aren't compatible with `json`[^1]
2620

2721
* **Storage `closed` property**: Original `TinyDB` won't raise exceptions when operating on a closed file. Now the property `closed` of `Storage` classes is required to be implemented. An `IOError` should be raised.
2822

29-
* **`CachingMiddleWare`**: `WRITE_CACHE_SIZE` is now instance-specific.
30-
Example: `TinyDB("test.db", storage=CachingMiddleWare(JSONStorage, 1024))`
23+
* **[Miscellaneous](#misc)**: Differences only matter in edge cases.
3124

3225
## New Features
3326

3427
* **Event hooks**: You can now use event hooks to do something before or after an operation. See [Event Hooks](#event-hooks) for more details.
3528

36-
* **Redesigned ID & Doc class**: You can [customise them](#customise-id-class) more pleasingly.
37-
The default ID class is `IncreID`, which mimics the behaviours of the original `int` ID but requires much fewer IO operations.
38-
The default Doc class remains almost the same.
29+
* **Redesigned ID & Doc class**: You can [replace](#replacing-id-&-document-class) and [customise them](#customise-id-class) more pleasingly.
30+
31+
* **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 Storage[^2][^disable-db-level].
32+
33+
* **Built-in `Modifier`**: Use `Modifier` to easily [encrypt](#encryption), [compress](./docs/Modifier.md#Compression) and [extend types](./docs/Modifier.md#Conversion) of your database. Sure you can do much more than these. _(See [Modifier](./docs/Modifier.md))_
3934

40-
* **DB level caching**: This significantly improves the performance of all operations. But it requires more memory, and the responsibility of converting the data to the correct type is transmitted to the Storage. e.g. `JSONStorage` needs to convert the keys to `str` by itself.
35+
* **Isolation Level**: Performance or ACID? It's up to you[^isolevel].
4136

42-
* **Built-in `Modifier`**: Use `Modifier` to easily [encrypt](#encryption) and [compress](#compression) your database. Sure you can do much more than these. _(See [Modifier](./docs/Modifier.md))_
37+
* **Atomic Write**: **A**CID!
4338

4439
## How to use it?
4540

4641
#### Installation
4742

48-
```Bash
49-
pip install async-tinydb
50-
```
43+
* Minimum: `pip install async-tinydb`
44+
* Encryption: `pip install async-tinydb[encryption]`
45+
* Compression: `pip install async-tinydb[compression]`
46+
* Full: `pip install async-tinydb[all]`
5147

5248
#### Importing
5349

@@ -72,15 +68,25 @@ That's it.
7268

7369
#### Replacing ID & Document Class
7470

75-
**Mixing classes in one table may cause errors!**
71+
**NOTICE: Mixing classes in one table may cause errors!**
72+
73+
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.
74+
75+
##### ID Classes
76+
77+
* `IncreID`: Default ID class, mimics the behaviours of the original `int` ID but requires much fewer IO operations.
78+
* `UUID`: Uses `uuid.UUID`[^uuid-version].
79+
80+
##### Document Class
81+
82+
* `Document`: Default document class, uses `dict`under the bonet.
7683

7784
```Python
7885
from asynctinydb import TinyDB, UUID, IncreID, Document
7986

8087
db = TinyDB("database.db")
8188

82-
# Replacing ID class to UUID
83-
# By default, ID class is IncreID, document_class is Document
89+
# Setting ID class to UUID, document class to Document
8490
tab = db.table("table1", document_id_class=UUID, document_class=Document)
8591
```
8692

@@ -104,49 +110,44 @@ async def main():
104110

105111
##### 2. Use `Modifier` class
106112

107-
The modifier class contains some methods to modify the behaviour of `TinyDB` and `Storage` classes.
108-
109-
It relies on `event hooks`.
110-
111-
`Encryption` is a subclass of the `Modifier` class. It contains methods to add encryption to the storage that fulfils the following conditions:
112-
113-
1. The storage has `write.post` and `read.pre` events.
114-
2. The storage stores data in `bytes`.
115-
3. The argument passed to the methods is `str` or `bytes`. See the implementation of `JSONStorage` for more details.
116-
117-
```Python
118-
from asynctinydb import TinyDB, Modifier
119-
120-
async def main():
121-
db = TinyDB("db.json", access_mode="rb+") # Binary mode is required
122-
Modifier.Encryption.AES_GCM(db, "your key goes here")
123-
# Or, you can pass a Storage instance
124-
# Modifier.Encryption.AES_GCM(db.storage, "your key goes here")
125-
126-
```
113+
_See [Encryption](./docs/Modifier.md#Encryption)_
127114

128115
#### Isolation Level
129116

130-
To avoid blocking codes, Async-TinyDB puts CPU-bound tasks to another thread/process (On Linux, if forking a process is possible, `ProcessPoolExecutor` will be used instead of the `ThreadPoolExecutor`).
117+
To avoid blocking codes, Async-TinyDB puts CPU-bound tasks into another thread (Useful with interpreters without GIL)
131118

132119
Unfortunately, this introduces chances of data being modified when:
133120

134121
* Manipulating mutable objects within `Document` instances in another coroutine
135122
* Performing updating/saving/searching operations (These operations are run in a different thread/process)
136123
* The conditions above are satisfied in the same `Table`
137-
* The conditions above are satisfied simultaneously
124+
* The conditions above are satisfied simultaneously.
138125

139-
You can either avoid these operations or set a higher isolation level to mitigate this problem.
126+
Avoid these operations or set a higher isolation level to mitigate this problem.
140127

141128
```Python
142-
db.isolevel = 1 # By default isolevel is 0
129+
db.isolevel = 2
143130
```
144131

145132
`isolevel`:
146133

147134
0. No isolation
148-
1. Don't run operations in another thread/process, or run in a blocking way.
149-
2. Disable
135+
1. Serialised CRUD operations (Also ensures thread safety) (default)
136+
2. Deepcopy documents on CRUD (Ensures `Index` consistency)
137+
138+
139+
140+
#### DB-level caching
141+
142+
DB-level caching improves performance dramatically.
143+
144+
However, this may cause data inconsistency between `Storage` and `TinyDB` if the file that `Storage` referred to is been shared.
145+
146+
To disable it:
147+
148+
```Python
149+
db = TinyDB("./path", no_dbcache=True)
150+
```
150151

151152
## Example Codes:
152153

@@ -210,16 +211,15 @@ class MyID(BaseID):
210211
...
211212

212213
@classmethod
213-
def next_id(cls, table: Table) -> IncreID:
214+
def next_id(cls, table: Table, keys) -> MyID:
214215
"""
215-
Recommended to define it as an async function, but a sync def will do.
216216
It should return a unique ID.
217217
"""
218218

219219
@classmethod
220-
def mark_existed(cls, table: Table, new_id: IncreID):
220+
def mark_existed(cls, table: Table, new_id):
221221
"""
222-
Marks an ID as existing; the same ID shouldn't be generated by next_id again.
222+
Marks an ID as existing; the same ID shouldn't be generated by next_id.
223223
"""
224224

225225
@classmethod
@@ -255,3 +255,17 @@ class BaseDocument(Mapping[IDVar, Any]):
255255
```
256256

257257
Make sure you have implemented all the methods required by `BaseDocument` class.
258+
259+
## Misc
260+
261+
* **Lazy-load:** File loading & dirs creating are delayed to the first IO operation.
262+
* **`CachingMiddleWare`**: `WRITE_CACHE_SIZE` is now instance-specific.
263+
Example: `TinyDB("test.db", storage=CachingMiddleWare(JSONStorage, 1024))`
264+
265+
266+
267+
[^1]: Why not `orjson`? Because `ujson` is fast enough and has more features.
268+
[^2]: e.g. `JSONStorage` needs to convert the keys to `str` by itself.
269+
[^UUID-version]:Currently using UUID4
270+
[^disable-db-level]: See [DB-level caching](#db-level-caching) to learn how to disable this feature if it causes dirty reads.
271+
[^isolevel]: See [isolevel](#isolation-level)

0 commit comments

Comments
 (0)