tinymongo 0.1.9__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.
Files changed (29) hide show
  1. tinymongo-1.0.0/LICENSE.txt +10 -0
  2. tinymongo-1.0.0/PKG-INFO +234 -0
  3. {tinymongo-0.1.9 → tinymongo-1.0.0}/README.md +16 -7
  4. tinymongo-1.0.0/pyproject.toml +54 -0
  5. {tinymongo-0.1.9 → tinymongo-1.0.0}/setup.cfg +1 -2
  6. tinymongo-1.0.0/tests/test_mongo_like.py +86 -0
  7. tinymongo-1.0.0/tests/test_multi_write.py +52 -0
  8. tinymongo-1.0.0/tests/test_query_more.py +108 -0
  9. tinymongo-1.0.0/tests/test_storage_backends.py +55 -0
  10. tinymongo-1.0.0/tests/test_tinymongo.py +562 -0
  11. tinymongo-1.0.0/tinymongo/parquet_storage.py +256 -0
  12. {tinymongo-0.1.9 → tinymongo-1.0.0}/tinymongo/results.py +8 -5
  13. tinymongo-1.0.0/tinymongo/storage_backends.py +174 -0
  14. tinymongo-1.0.0/tinymongo/tinymongo.py +1007 -0
  15. tinymongo-1.0.0/tinymongo.egg-info/PKG-INFO +234 -0
  16. {tinymongo-0.1.9 → tinymongo-1.0.0}/tinymongo.egg-info/SOURCES.txt +9 -2
  17. tinymongo-1.0.0/tinymongo.egg-info/requires.txt +8 -0
  18. tinymongo-0.1.9/PKG-INFO +0 -234
  19. tinymongo-0.1.9/requirements.txt +0 -5
  20. tinymongo-0.1.9/setup.py +0 -43
  21. tinymongo-0.1.9/tinymongo/tinymongo.py +0 -578
  22. tinymongo-0.1.9/tinymongo.egg-info/PKG-INFO +0 -234
  23. tinymongo-0.1.9/tinymongo.egg-info/requires.txt +0 -5
  24. {tinymongo-0.1.9 → tinymongo-1.0.0}/MANIFEST.in +0 -0
  25. {tinymongo-0.1.9 → tinymongo-1.0.0}/tinymongo/__init__.py +0 -0
  26. {tinymongo-0.1.9 → tinymongo-1.0.0}/tinymongo/errors.py +0 -0
  27. {tinymongo-0.1.9 → tinymongo-1.0.0}/tinymongo/serializers.py +0 -0
  28. {tinymongo-0.1.9 → tinymongo-1.0.0}/tinymongo.egg-info/dependency_links.txt +0 -0
  29. {tinymongo-0.1.9 → 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
+ ~
@@ -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
+ [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/schapman1974/tinymongo)
36
+
37
+
38
+ ![logo](artwork/tinymongo.png)
39
+
40
+ [![Build Status](https://travis-ci.org/schapman1974/tinymongo.svg?branch=master)](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
+ [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/schapman1974/tinymongo)
2
+
1
3
 
2
4
  ![logo](artwork/tinymongo.png)
3
5
 
4
- [![Build Status](https://travis-ci.org/jjonesAtMoog/tinymongo.svg?branch=master)](https://travis-ci.org/jjonesAtMoog/tinymongo)
6
+ [![Build Status](https://travis-ci.org/schapman1974/tinymongo.svg?branch=master)](https://travis-ci.org/schapman1974/tinymongo)
5
7
 
6
8
  # Purpose
7
9
 
@@ -23,14 +25,21 @@ The library is currently under rapid development and a more recent version
23
25
  may be desired.
24
26
 
25
27
  In this case, simply clone this repository, navigate
26
- to the root project directory, and `python setup.py install`
28
+ to the root project directory, and `pip install -e .`
27
29
 
28
- or use `pip install git+https://github.com/schapman1974/tinymongo.git#egg=tinymongo`
30
+ or use `pip install -e git+https://github.com/schapman1974/tinymongo.git#egg=tinymongo`
29
31
 
30
32
  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,
@@ -50,7 +59,7 @@ take a look at demo.py within the repository.
50
59
  collection = db.users
51
60
 
52
61
  # insert data adds a new record returns _id
53
- record_id = collection.insert_one({"username": "admin", "password": "admin", "module":"somemodule"})
62
+ record_id = collection.insert_one({"username": "admin", "password": "admin", "module":"somemodule"}).inserted_id
54
63
  user_info = collection.find_one({"_id": record_id}) # returns the record inserted
55
64
 
56
65
  # you can also use it directly
@@ -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={'username': -1})
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={'username': -1}, skip=0, limit=20)
80
+ db.users.find(sort=[('username', -1)], skip=0, limit=20)
72
81
  # Getting next 20 records
73
- db.users.find(sort={'username': -1}, skip=20, limit=20)
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
+
@@ -1,8 +1,7 @@
1
1
  [metadata]
2
- description-file = README.md
2
+ description_file = README.md
3
3
 
4
4
  [egg_info]
5
5
  tag_build =
6
6
  tag_date = 0
7
- tag_svn_revision = 0
8
7
 
@@ -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()