tinymongo 0.2.0__tar.gz → 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tinymongo-1.0.0/LICENSE.txt +10 -0
- tinymongo-1.0.0/PKG-INFO +234 -0
- {tinymongo-0.2.0 → tinymongo-1.0.0}/README.md +13 -4
- tinymongo-1.0.0/pyproject.toml +54 -0
- {tinymongo-0.2.0 → tinymongo-1.0.0}/setup.cfg +1 -2
- tinymongo-1.0.0/tests/test_mongo_like.py +86 -0
- tinymongo-1.0.0/tests/test_multi_write.py +52 -0
- tinymongo-1.0.0/tests/test_query_more.py +108 -0
- tinymongo-1.0.0/tests/test_storage_backends.py +55 -0
- tinymongo-1.0.0/tests/test_tinymongo.py +562 -0
- tinymongo-1.0.0/tinymongo/parquet_storage.py +256 -0
- {tinymongo-0.2.0 → tinymongo-1.0.0}/tinymongo/results.py +8 -5
- tinymongo-1.0.0/tinymongo/storage_backends.py +174 -0
- tinymongo-1.0.0/tinymongo/tinymongo.py +1007 -0
- tinymongo-1.0.0/tinymongo.egg-info/PKG-INFO +234 -0
- {tinymongo-0.2.0 → tinymongo-1.0.0}/tinymongo.egg-info/SOURCES.txt +9 -1
- tinymongo-1.0.0/tinymongo.egg-info/requires.txt +8 -0
- tinymongo-0.2.0/PKG-INFO +0 -239
- tinymongo-0.2.0/setup.py +0 -82
- tinymongo-0.2.0/tinymongo/tinymongo.py +0 -578
- tinymongo-0.2.0/tinymongo.egg-info/PKG-INFO +0 -239
- tinymongo-0.2.0/tinymongo.egg-info/requires.txt +0 -2
- {tinymongo-0.2.0 → tinymongo-1.0.0}/MANIFEST.in +0 -0
- {tinymongo-0.2.0 → tinymongo-1.0.0}/tinymongo/__init__.py +0 -0
- {tinymongo-0.2.0 → tinymongo-1.0.0}/tinymongo/errors.py +0 -0
- {tinymongo-0.2.0 → tinymongo-1.0.0}/tinymongo/serializers.py +0 -0
- {tinymongo-0.2.0 → tinymongo-1.0.0}/tinymongo.egg-info/dependency_links.txt +0 -0
- {tinymongo-0.2.0 → tinymongo-1.0.0}/tinymongo.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2016 Stephen Chapman
|
|
4
|
+
|
|
5
|
+
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:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
~
|
tinymongo-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tinymongo
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A flat file drop in replacement for mongodb. Requires TinyDB
|
|
5
|
+
Author-email: Stephen Chapman <schapman1974@gmail.com>
|
|
6
|
+
License: The MIT License (MIT)
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2016 Stephen Chapman
|
|
9
|
+
|
|
10
|
+
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:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
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.
|
|
15
|
+
~
|
|
16
|
+
|
|
17
|
+
Project-URL: Homepage, https://github.com/schapman1974/tinymongo
|
|
18
|
+
Project-URL: Source, https://github.com/schapman1974/tinymongo
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
22
|
+
Classifier: Topic :: Database
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE.txt
|
|
26
|
+
Requires-Dist: tinydb==3.2.1
|
|
27
|
+
Requires-Dist: tinydb-serialization==1.0.4
|
|
28
|
+
Requires-Dist: pymongo>=3.4.0
|
|
29
|
+
Requires-Dist: pyarrow>=10.0.0
|
|
30
|
+
Requires-Dist: portalocker>=2.7.0
|
|
31
|
+
Provides-Extra: uv
|
|
32
|
+
Requires-Dist: uvicorn>=0.23.0; extra == "uv"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
[](https://gitpod.io/#https://github.com/schapman1974/tinymongo)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
[](https://travis-ci.org/schapman1974/tinymongo)
|
|
41
|
+
|
|
42
|
+
# Purpose
|
|
43
|
+
|
|
44
|
+
A simple wrapper to make a drop in replacement for mongodb out of
|
|
45
|
+
[tinydb](http://tinydb.readthedocs.io/en/latest/). This module is an
|
|
46
|
+
attempt to add an interface familiar to those currently using pymongo.
|
|
47
|
+
|
|
48
|
+
# Status
|
|
49
|
+
|
|
50
|
+
Unit testing is currently being worked on and functionality is being
|
|
51
|
+
added to the library. Current coverage is 93%. Current builds tested
|
|
52
|
+
on Python versions 2.7 and 3.3+.
|
|
53
|
+
|
|
54
|
+
# Installation
|
|
55
|
+
|
|
56
|
+
The latest stable release can be installed via `pip install tinymongo`.
|
|
57
|
+
|
|
58
|
+
The library is currently under rapid development and a more recent version
|
|
59
|
+
may be desired.
|
|
60
|
+
|
|
61
|
+
In this case, simply clone this repository, navigate
|
|
62
|
+
to the root project directory, and `pip install -e .`
|
|
63
|
+
|
|
64
|
+
or use `pip install -e git+https://github.com/schapman1974/tinymongo.git#egg=tinymongo`
|
|
65
|
+
|
|
66
|
+
This
|
|
67
|
+
is a pure python distribution and - thus - should require no external
|
|
68
|
+
compilers or tools besides those contained within Python itself.
|
|
69
|
+
|
|
70
|
+
# Notes for this fork
|
|
71
|
+
|
|
72
|
+
- **Default storage:** this fork uses Parquet v2 files by default (via `pyarrow`) for improved I/O performance and reliability. If `pyarrow` is not available, it falls back to TinyDB's default JSON storage.
|
|
73
|
+
- **Concurrency:** writes use atomic temp-file replace and optional advisory locks (`portalocker`) to reduce corruption risk under concurrent writers.
|
|
74
|
+
- **Tests & CI:** a GitHub Actions workflow is included at `.github/workflows/ci.yml` to run unit tests and linters across Python versions. See `requirements-dev.txt` for dev dependencies.
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# Examples
|
|
78
|
+
|
|
79
|
+
The quick start is shown below. For a more detailed look at tinymongo,
|
|
80
|
+
take a look at demo.py within the repository.
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from tinymongo import TinyMongoClient
|
|
84
|
+
|
|
85
|
+
# you can include a folder name or absolute path
|
|
86
|
+
# as a parameter if not it will default to "tinydb"
|
|
87
|
+
connection = TinyMongoClient()
|
|
88
|
+
|
|
89
|
+
# either creates a new database file or accesses an existing one named `my_tiny_database`
|
|
90
|
+
db = connection.my_tiny_database
|
|
91
|
+
|
|
92
|
+
# either creates a new collection or accesses an existing one named `users`
|
|
93
|
+
collection = db.users
|
|
94
|
+
|
|
95
|
+
# insert data adds a new record returns _id
|
|
96
|
+
record_id = collection.insert_one({"username": "admin", "password": "admin", "module":"somemodule"}).inserted_id
|
|
97
|
+
user_info = collection.find_one({"_id": record_id}) # returns the record inserted
|
|
98
|
+
|
|
99
|
+
# you can also use it directly
|
|
100
|
+
db.users.insert_one({"username": "admin"})
|
|
101
|
+
|
|
102
|
+
# returns a list of all users of 'module'
|
|
103
|
+
users = db.users.find({'module': 'module'})
|
|
104
|
+
|
|
105
|
+
#update data returns True if successful and False if unsuccessful
|
|
106
|
+
upd = db.users.update_one({"username": "admin"}, {"$set": {"module":"someothermodule"}})
|
|
107
|
+
|
|
108
|
+
# Sorting users by its username DESC
|
|
109
|
+
# omitting `filter` returns all records
|
|
110
|
+
db.users.find(sort=[('username', -1)])
|
|
111
|
+
|
|
112
|
+
# Pagination of the results
|
|
113
|
+
# Getting the first 20 records
|
|
114
|
+
db.users.find(sort=[('username', -1)], skip=0, limit=20)
|
|
115
|
+
# Getting next 20 records
|
|
116
|
+
db.users.find(sort=[('username', -1)], skip=20, limit=20)
|
|
117
|
+
|
|
118
|
+
# Getting the total of records
|
|
119
|
+
db.users.count()
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
# Custom Storages and Serializers
|
|
124
|
+
|
|
125
|
+
> HINT: Learn more about TinyDB storages and Serializers in [documentation](https://tinydb.readthedocs.io/en/latest/usage.html#storages-middlewares)
|
|
126
|
+
|
|
127
|
+
## Custom Storages
|
|
128
|
+
|
|
129
|
+
You have to subclass `TinyMongoClient` and provide custom storages like
|
|
130
|
+
CachingMiddleware or other available TinyDB Extension.
|
|
131
|
+
|
|
132
|
+
### Caching Middleware
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from tinymongo import TinyMongoClient
|
|
136
|
+
from tinydb.storages import JSONStorage
|
|
137
|
+
from tinydb.middlewares import CachingMiddleware
|
|
138
|
+
|
|
139
|
+
class CachedClient(TinyMongoClient):
|
|
140
|
+
"""This client has cache"""
|
|
141
|
+
@property
|
|
142
|
+
def _storage(self):
|
|
143
|
+
return CachingMiddleware(JSONStorage)
|
|
144
|
+
|
|
145
|
+
connection = CachedClient('/path/to/folder')
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
> HINT: You can nest middlewares: `FirstMiddleware(SecondMiddleware(JSONStorage))`
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
## Serializers
|
|
152
|
+
|
|
153
|
+
To convert your data to a format that is writable to disk TinyDB uses the Python JSON module by default. It's great when only simple data types are involved but it cannot handle more complex data types like custom classes.
|
|
154
|
+
|
|
155
|
+
To support serialization of complex types you can write
|
|
156
|
+
your own serializers using the `tinydb-serialization` extension.
|
|
157
|
+
|
|
158
|
+
First you need to install it `pip install tinydb-serialization`
|
|
159
|
+
|
|
160
|
+
## Handling datetime objects
|
|
161
|
+
|
|
162
|
+
You can create a serializer for the python `datetime` using
|
|
163
|
+
the following snippet:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from datetime import datetime
|
|
167
|
+
from tinydb_serialization import Serializer
|
|
168
|
+
|
|
169
|
+
class DatetimeSerializer(Serializer):
|
|
170
|
+
OBJ_CLASS = datetime
|
|
171
|
+
|
|
172
|
+
def __init__(self, format='%Y-%m-%dT%H:%M:%S', *args, **kwargs):
|
|
173
|
+
super(DatetimeSerializer, self).__init__(*args, **kwargs)
|
|
174
|
+
self._format = format
|
|
175
|
+
|
|
176
|
+
def encode(self, obj):
|
|
177
|
+
return obj.strftime(self._format)
|
|
178
|
+
|
|
179
|
+
def decode(self, s):
|
|
180
|
+
return datetime.strptime(s, self._format)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
> NOTE: this serializer is available in `tinymongo.serializers.DateTimeSerializer`
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
Now you have to subclass `TinyMongoClient` and provide customs storage.
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
from tinymongo import TinyMongoClient
|
|
190
|
+
from tinymongo.serializers import DateTimeSerializer
|
|
191
|
+
from tinydb_serialization import SerializationMiddleware
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class CustomClient(TinyMongoClient):
|
|
195
|
+
@property
|
|
196
|
+
def _storage(self):
|
|
197
|
+
serialization = SerializationMiddleware()
|
|
198
|
+
serialization.register_serializer(DateTimeSerializer(), 'TinyDate')
|
|
199
|
+
# register other custom serializers
|
|
200
|
+
return serialization
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
connection = CustomClient('/path/to/folder')
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
# Flask-Admin
|
|
207
|
+
|
|
208
|
+
This extension can work with Flask-Admin which gives a web based administrative
|
|
209
|
+
panel to your TinyDB. Flask-Admin has features like filtering, search, web forms to
|
|
210
|
+
perform CRUD (Create, Read, Update, Delete) of the TinyDB records.
|
|
211
|
+
|
|
212
|
+
You can find the example of Flask-Admin with TinyMongo in [Flask-Admin Examples Repository](https://github.com/flask-admin/flask-admin/tree/master/examples/tinymongo)
|
|
213
|
+
|
|
214
|
+
> NOTE: To use Flask-Admin you need to register a DateTimeSerialization as showed in the previous topic.
|
|
215
|
+
|
|
216
|
+
# Contributions
|
|
217
|
+
|
|
218
|
+
Contributions are welcome! Currently, the most valuable contributions
|
|
219
|
+
would be:
|
|
220
|
+
|
|
221
|
+
* adding test cases
|
|
222
|
+
* adding functionality consistent with pymongo
|
|
223
|
+
* documentation
|
|
224
|
+
* identifying bugs and issues
|
|
225
|
+
|
|
226
|
+
# Future Development
|
|
227
|
+
|
|
228
|
+
I will also be adding support for gridFS by storing the files somehow and indexing them in a db like mongo currently does
|
|
229
|
+
|
|
230
|
+
More to come......
|
|
231
|
+
|
|
232
|
+
# License
|
|
233
|
+
|
|
234
|
+
MIT License
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
[](https://gitpod.io/#https://github.com/schapman1974/tinymongo)
|
|
2
|
+
|
|
1
3
|
|
|
2
4
|

|
|
3
5
|
|
|
4
|
-
[](https://travis-ci.org/schapman1974/tinymongo)
|
|
5
7
|
|
|
6
8
|
# Purpose
|
|
7
9
|
|
|
@@ -31,6 +33,13 @@ This
|
|
|
31
33
|
is a pure python distribution and - thus - should require no external
|
|
32
34
|
compilers or tools besides those contained within Python itself.
|
|
33
35
|
|
|
36
|
+
# Notes for this fork
|
|
37
|
+
|
|
38
|
+
- **Default storage:** this fork uses Parquet v2 files by default (via `pyarrow`) for improved I/O performance and reliability. If `pyarrow` is not available, it falls back to TinyDB's default JSON storage.
|
|
39
|
+
- **Concurrency:** writes use atomic temp-file replace and optional advisory locks (`portalocker`) to reduce corruption risk under concurrent writers.
|
|
40
|
+
- **Tests & CI:** a GitHub Actions workflow is included at `.github/workflows/ci.yml` to run unit tests and linters across Python versions. See `requirements-dev.txt` for dev dependencies.
|
|
41
|
+
|
|
42
|
+
|
|
34
43
|
# Examples
|
|
35
44
|
|
|
36
45
|
The quick start is shown below. For a more detailed look at tinymongo,
|
|
@@ -64,13 +73,13 @@ take a look at demo.py within the repository.
|
|
|
64
73
|
|
|
65
74
|
# Sorting users by its username DESC
|
|
66
75
|
# omitting `filter` returns all records
|
|
67
|
-
db.users.find(sort=
|
|
76
|
+
db.users.find(sort=[('username', -1)])
|
|
68
77
|
|
|
69
78
|
# Pagination of the results
|
|
70
79
|
# Getting the first 20 records
|
|
71
|
-
db.users.find(sort=
|
|
80
|
+
db.users.find(sort=[('username', -1)], skip=0, limit=20)
|
|
72
81
|
# Getting next 20 records
|
|
73
|
-
db.users.find(sort=
|
|
82
|
+
db.users.find(sort=[('username', -1)], skip=20, limit=20)
|
|
74
83
|
|
|
75
84
|
# Getting the total of records
|
|
76
85
|
db.users.count()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tinymongo"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "A flat file drop in replacement for mongodb. Requires TinyDB"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
authors = [ { name = "Stephen Chapman", email = "schapman1974@gmail.com" } ]
|
|
12
|
+
license = {file = "LICENSE.txt"}
|
|
13
|
+
dependencies = [
|
|
14
|
+
"tinydb==3.2.1",
|
|
15
|
+
"tinydb-serialization==1.0.4",
|
|
16
|
+
"pymongo>=3.4.0",
|
|
17
|
+
"pyarrow>=10.0.0",
|
|
18
|
+
"portalocker>=2.7.0"
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
24
|
+
"License :: OSI Approved :: MIT License",
|
|
25
|
+
"Topic :: Database",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/schapman1974/tinymongo"
|
|
30
|
+
Source = "https://github.com/schapman1974/tinymongo"
|
|
31
|
+
|
|
32
|
+
[tool.black]
|
|
33
|
+
line-length = 88
|
|
34
|
+
|
|
35
|
+
[tool.ruff]
|
|
36
|
+
line-length = 88
|
|
37
|
+
|
|
38
|
+
[tool.mypy]
|
|
39
|
+
python_version = 3.8
|
|
40
|
+
|
|
41
|
+
[tool.pytest.ini_options]
|
|
42
|
+
minversion = "6.0"
|
|
43
|
+
addopts = "-q"
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.packages.find]
|
|
46
|
+
where = ["."]
|
|
47
|
+
include = ["tinymongo*"]
|
|
48
|
+
|
|
49
|
+
[tool.setuptools]
|
|
50
|
+
include-package-data = true
|
|
51
|
+
|
|
52
|
+
[project.optional-dependencies]
|
|
53
|
+
uv = ["uvicorn>=0.23.0"]
|
|
54
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
|
|
4
|
+
import tinymongo as tm
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
DB_DIR = os.path.abspath("./test_db_mongo_like")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def setup_db():
|
|
11
|
+
if os.path.exists(DB_DIR):
|
|
12
|
+
shutil.rmtree(DB_DIR)
|
|
13
|
+
os.makedirs(DB_DIR, exist_ok=True)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_upsert_and_find():
|
|
17
|
+
setup_db()
|
|
18
|
+
client = tm.TinyMongoClient(DB_DIR)
|
|
19
|
+
c = client.db.collection
|
|
20
|
+
|
|
21
|
+
# insert doc
|
|
22
|
+
c.insert_one({"_id": "a", "count": 1})
|
|
23
|
+
|
|
24
|
+
# upsert behavior simulation: update if exists else insert
|
|
25
|
+
existing = c.find_one({"_id": "a"})
|
|
26
|
+
assert existing is not None
|
|
27
|
+
|
|
28
|
+
# update using $set
|
|
29
|
+
c.update_one({"_id": "a"}, {"$set": {"count": 5}})
|
|
30
|
+
doc = c.find_one({"_id": "a"})
|
|
31
|
+
assert doc["count"] == 5
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_update_operators():
|
|
35
|
+
setup_db()
|
|
36
|
+
client = tm.TinyMongoClient(DB_DIR)
|
|
37
|
+
c = client.db.collection
|
|
38
|
+
|
|
39
|
+
c.insert_many([{"_id": i, "v": i} for i in range(5)])
|
|
40
|
+
|
|
41
|
+
# Simulate $inc by reading, modifying, writing
|
|
42
|
+
for i in range(5):
|
|
43
|
+
c.update_one({"_id": i}, {"$set": {"v": i + 10}})
|
|
44
|
+
|
|
45
|
+
vals = sorted([d["v"] for d in c.find()])
|
|
46
|
+
assert vals == [10, 11, 12, 13, 14]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_projection_like():
|
|
50
|
+
setup_db()
|
|
51
|
+
client = tm.TinyMongoClient(DB_DIR)
|
|
52
|
+
c = client.db.collection
|
|
53
|
+
|
|
54
|
+
c.insert_one({"_id": "p", "a": 1, "b": 2, "c": 3})
|
|
55
|
+
doc = c.find_one({"_id": "p"})
|
|
56
|
+
# projection not implemented; ensure full doc available
|
|
57
|
+
assert set(doc.keys()) >= {"_id", "a", "b", "c"}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_find_and_modify_semantics():
|
|
61
|
+
setup_db()
|
|
62
|
+
client = tm.TinyMongoClient(DB_DIR)
|
|
63
|
+
c = client.db.collection
|
|
64
|
+
|
|
65
|
+
c.insert_one({"_id": "fm", "counter": 0})
|
|
66
|
+
|
|
67
|
+
# Emulate findOneAndUpdate: read, update atomically via TinyDB API
|
|
68
|
+
c.update_one({"_id": "fm"}, {"$set": {"counter": 1}})
|
|
69
|
+
d = c.find_one({"_id": "fm"})
|
|
70
|
+
assert d["counter"] == 1
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_unique_index_simulation():
|
|
74
|
+
setup_db()
|
|
75
|
+
client = tm.TinyMongoClient(DB_DIR)
|
|
76
|
+
c = client.db.collection
|
|
77
|
+
|
|
78
|
+
# TinyMongo insert_one enforces _id uniqueness
|
|
79
|
+
c.insert_one({"_id": "u1", "v": 1})
|
|
80
|
+
try:
|
|
81
|
+
c.insert_one({"_id": "u1", "v": 2})
|
|
82
|
+
inserted = True
|
|
83
|
+
except Exception:
|
|
84
|
+
inserted = False
|
|
85
|
+
|
|
86
|
+
assert not inserted
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import multiprocessing as mp
|
|
3
|
+
import shutil
|
|
4
|
+
|
|
5
|
+
import tinymongo as tm
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
DB_DIR = os.path.abspath("./test_db_multi")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def writer_process(proc_id, count, start_value=0):
|
|
12
|
+
client = tm.TinyMongoClient(DB_DIR)
|
|
13
|
+
db = client.multiDB
|
|
14
|
+
coll = db.multiCollection
|
|
15
|
+
|
|
16
|
+
items = []
|
|
17
|
+
for i in range(count):
|
|
18
|
+
items.append({"count": start_value + proc_id * count + i})
|
|
19
|
+
|
|
20
|
+
# insert many
|
|
21
|
+
coll.insert_many(items)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_multi_writer_stress():
|
|
25
|
+
# ensure clean dir
|
|
26
|
+
if os.path.exists(DB_DIR):
|
|
27
|
+
try:
|
|
28
|
+
shutil.rmtree(DB_DIR)
|
|
29
|
+
except Exception:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
num_procs = 6
|
|
33
|
+
per_proc = 50
|
|
34
|
+
|
|
35
|
+
procs = []
|
|
36
|
+
for pid in range(num_procs):
|
|
37
|
+
p = mp.Process(target=writer_process, args=(pid, per_proc, 0))
|
|
38
|
+
p.start()
|
|
39
|
+
procs.append(p)
|
|
40
|
+
|
|
41
|
+
for p in procs:
|
|
42
|
+
p.join()
|
|
43
|
+
|
|
44
|
+
# validate
|
|
45
|
+
client = tm.TinyMongoClient(DB_DIR)
|
|
46
|
+
coll = client.multiDB.multiCollection
|
|
47
|
+
all_docs = list(coll.find())
|
|
48
|
+
assert len(all_docs) == num_procs * per_proc
|
|
49
|
+
|
|
50
|
+
# check uniqueness
|
|
51
|
+
counts = sorted(d["count"] for d in all_docs)
|
|
52
|
+
assert counts == list(range(0, num_procs * per_proc))
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
|
|
4
|
+
import tinymongo as tm
|
|
5
|
+
|
|
6
|
+
DB_DIR = os.path.abspath("./test_db_query_more")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def setup_db():
|
|
10
|
+
if os.path.exists(DB_DIR):
|
|
11
|
+
shutil.rmtree(DB_DIR)
|
|
12
|
+
os.makedirs(DB_DIR, exist_ok=True)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_nin_query_operator():
|
|
16
|
+
setup_db()
|
|
17
|
+
client = tm.TinyMongoClient(DB_DIR)
|
|
18
|
+
c = client.db.collection
|
|
19
|
+
c.insert_many([
|
|
20
|
+
{"_id": 1, "tag": "alpha"},
|
|
21
|
+
{"_id": 2, "tag": "beta"},
|
|
22
|
+
{"_id": 3, "tag": "gamma"},
|
|
23
|
+
])
|
|
24
|
+
|
|
25
|
+
results = c.find({"tag": {"$nin": ["alpha", "beta"]}})
|
|
26
|
+
assert results.count() == 1
|
|
27
|
+
assert results[0]["tag"] == "gamma"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_update_many_applies_to_all_matches():
|
|
31
|
+
setup_db()
|
|
32
|
+
client = tm.TinyMongoClient(DB_DIR)
|
|
33
|
+
c = client.db.collection
|
|
34
|
+
c.insert_many([
|
|
35
|
+
{"_id": 1, "group": "a", "value": 1},
|
|
36
|
+
{"_id": 2, "group": "a", "value": 2},
|
|
37
|
+
{"_id": 3, "group": "b", "value": 3},
|
|
38
|
+
])
|
|
39
|
+
|
|
40
|
+
result = c.update_many({"group": "a"}, {"$set": {"active": True}})
|
|
41
|
+
assert result.matched_count == 2
|
|
42
|
+
assert result.modified_count == 2
|
|
43
|
+
assert c.find({"active": True}).count() == 2
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_replace_one_replaces_single_document():
|
|
47
|
+
setup_db()
|
|
48
|
+
client = tm.TinyMongoClient(DB_DIR)
|
|
49
|
+
c = client.db.collection
|
|
50
|
+
c.insert_one({"_id": "one", "count": 1, "tag": "keep"})
|
|
51
|
+
c.insert_one({"_id": "two", "count": 2, "tag": "keep"})
|
|
52
|
+
|
|
53
|
+
result = c.replace_one({"_id": "one"}, {"count": 42})
|
|
54
|
+
assert result.matched_count == 1
|
|
55
|
+
doc = c.find_one({"_id": "one"})
|
|
56
|
+
assert doc["count"] == 42
|
|
57
|
+
assert doc["_id"] == "one"
|
|
58
|
+
assert "tag" not in doc
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_find_one_and_update_returns_old_document():
|
|
62
|
+
setup_db()
|
|
63
|
+
client = tm.TinyMongoClient(DB_DIR)
|
|
64
|
+
c = client.db.collection
|
|
65
|
+
c.insert_one({"_id": "item", "score": 10})
|
|
66
|
+
|
|
67
|
+
old_doc = c.find_one_and_update({"_id": "item"}, {"$set": {"score": 20}})
|
|
68
|
+
assert old_doc["score"] == 10
|
|
69
|
+
assert c.find_one({"_id": "item"})["score"] == 20
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_skip_limit_pagination_with_find():
|
|
73
|
+
setup_db()
|
|
74
|
+
client = tm.TinyMongoClient(DB_DIR)
|
|
75
|
+
c = client.db.collection
|
|
76
|
+
c.insert_many([{"_id": i, "value": i} for i in range(20)])
|
|
77
|
+
|
|
78
|
+
page = c.find(sort=[("value", 1)], skip=5, limit=5)
|
|
79
|
+
assert page.count() == 5
|
|
80
|
+
assert page[0]["value"] == 5
|
|
81
|
+
assert page[-1]["value"] == 9
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_all_operator_matches_arrays():
|
|
85
|
+
setup_db()
|
|
86
|
+
client = tm.TinyMongoClient(DB_DIR)
|
|
87
|
+
c = client.db.collection
|
|
88
|
+
c.insert_many([
|
|
89
|
+
{"_id": 1, "tags": ["a", "b", "c"]},
|
|
90
|
+
{"_id": 2, "tags": ["a", "c"]},
|
|
91
|
+
{"_id": 3, "tags": ["b", "a", "c"]},
|
|
92
|
+
])
|
|
93
|
+
|
|
94
|
+
matches = c.find({"tags": {"$all": ["a", "c"]}})
|
|
95
|
+
assert matches.count() == 3
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_count_documents_uses_filter():
|
|
99
|
+
setup_db()
|
|
100
|
+
client = tm.TinyMongoClient(DB_DIR)
|
|
101
|
+
c = client.db.collection
|
|
102
|
+
c.insert_many([
|
|
103
|
+
{"_id": 1, "even": True},
|
|
104
|
+
{"_id": 2, "even": False},
|
|
105
|
+
{"_id": 3, "even": True},
|
|
106
|
+
])
|
|
107
|
+
|
|
108
|
+
assert c.count_documents({"even": True}) == 2
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import tinymongo as tm
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_tinydb_backend_default(tmp_path):
|
|
6
|
+
client = tm.TinyMongoClient(str(tmp_path / "db"), backend="tinydb")
|
|
7
|
+
db = client.testdb
|
|
8
|
+
coll = db.testcollection
|
|
9
|
+
coll.insert_one({"_id": 1, "value": "tinydb"})
|
|
10
|
+
|
|
11
|
+
assert coll.count() == 1
|
|
12
|
+
assert (tmp_path / "db" / "testdb.json").exists()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_sqlite_backend(tmp_path):
|
|
16
|
+
client = tm.TinyMongoClient(str(tmp_path / "db"), backend="sqlite")
|
|
17
|
+
db = client.testdb
|
|
18
|
+
coll = db.testcollection
|
|
19
|
+
coll.insert_one({"_id": 1, "value": "sqlite"})
|
|
20
|
+
|
|
21
|
+
assert coll.count() == 1
|
|
22
|
+
assert (tmp_path / "db" / "testdb.sqlite").exists()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_parquet_backend(tmp_path):
|
|
26
|
+
pytest.importorskip("pyarrow")
|
|
27
|
+
client = tm.TinyMongoClient(str(tmp_path / "db"), backend="parquet")
|
|
28
|
+
db = client.testdb
|
|
29
|
+
coll = db.testcollection
|
|
30
|
+
coll.insert_one({"_id": 1, "value": "parquet"})
|
|
31
|
+
|
|
32
|
+
assert coll.count() == 1
|
|
33
|
+
assert (tmp_path / "db" / "testdb.parquet").exists()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_duckdb_backend(tmp_path):
|
|
37
|
+
pytest.importorskip("duckdb")
|
|
38
|
+
client = tm.TinyMongoClient(str(tmp_path / "db"), backend="duckdb")
|
|
39
|
+
db = client.testdb
|
|
40
|
+
coll = db.testcollection
|
|
41
|
+
coll.insert_one({"_id": 1, "value": "duckdb"})
|
|
42
|
+
|
|
43
|
+
assert coll.count() == 1
|
|
44
|
+
assert (tmp_path / "db" / "testdb.duckdb").exists()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_internal_client_attrs_are_preserved(tmp_path):
|
|
48
|
+
client = tm.TinyMongoClient(str(tmp_path / "db"), backend="tinydb")
|
|
49
|
+
|
|
50
|
+
assert client._backend == "tinydb"
|
|
51
|
+
assert client._storage is not None
|
|
52
|
+
|
|
53
|
+
db = client.testdb
|
|
54
|
+
assert db._foldername == str(tmp_path / "db")
|
|
55
|
+
assert not (tmp_path / "db" / "_storage.json").exists()
|