json-database 0.7.0__tar.gz → 0.8.1__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.
- json_database-0.8.1/PKG-INFO +202 -0
- {json_database-0.7.0 → json_database-0.8.1}/json_database/__init__.py +73 -0
- json_database-0.8.1/json_database/crypto.py +80 -0
- {json_database-0.7.0 → json_database-0.8.1}/json_database/exceptions.py +8 -0
- json_database-0.8.1/json_database/version.py +6 -0
- json_database-0.8.1/json_database.egg-info/PKG-INFO +202 -0
- {json_database-0.7.0 → json_database-0.8.1}/json_database.egg-info/SOURCES.txt +4 -1
- json_database-0.8.1/json_database.egg-info/requires.txt +1 -0
- json_database-0.8.1/setup.py +73 -0
- json_database-0.8.1/test/test_crypto.py +53 -0
- json_database-0.7.0/PKG-INFO +0 -13
- json_database-0.7.0/json_database.egg-info/PKG-INFO +0 -13
- json_database-0.7.0/json_database.egg-info/requires.txt +0 -1
- json_database-0.7.0/setup.py +0 -13
- {json_database-0.7.0 → json_database-0.8.1}/LICENSE +0 -0
- {json_database-0.7.0 → json_database-0.8.1}/README.md +0 -0
- {json_database-0.7.0 → json_database-0.8.1}/json_database/search.py +0 -0
- {json_database-0.7.0 → json_database-0.8.1}/json_database/utils.py +0 -0
- {json_database-0.7.0 → json_database-0.8.1}/json_database/xdg_utils.py +0 -0
- {json_database-0.7.0 → json_database-0.8.1}/json_database.egg-info/dependency_links.txt +0 -0
- {json_database-0.7.0 → json_database-0.8.1}/json_database.egg-info/top_level.txt +0 -0
- {json_database-0.7.0 → json_database-0.8.1}/setup.cfg +0 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: json_database
|
|
3
|
+
Version: 0.8.1
|
|
4
|
+
Summary: searchable json database with persistence
|
|
5
|
+
Home-page: https://github.com/TigreGotico/json_database
|
|
6
|
+
Author: jarbasAI
|
|
7
|
+
Author-email: jarbasai@mailfence.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Description: # Json Database
|
|
10
|
+
|
|
11
|
+
Python dict based database with persistence and search capabilities
|
|
12
|
+
|
|
13
|
+
For those times when you need something simple and sql is overkill
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- pure python
|
|
19
|
+
- save and load from file
|
|
20
|
+
- search recursively by key and key/value pairs
|
|
21
|
+
- fuzzy search
|
|
22
|
+
- supports arbitrary objects
|
|
23
|
+
- supports comments in saved files
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install json_database
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### JsonStorage
|
|
35
|
+
|
|
36
|
+
Sometimes you need persistent dicts that you can save and load from file
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from json_database import JsonStorage
|
|
40
|
+
from os.path import exists
|
|
41
|
+
|
|
42
|
+
save_path = "my_dict.conf"
|
|
43
|
+
|
|
44
|
+
my_config = JsonStorage(save_path)
|
|
45
|
+
|
|
46
|
+
my_config["lang"] = "pt"
|
|
47
|
+
my_config["secondary_lang"] = "en"
|
|
48
|
+
my_config["email"] = "jarbasai@mailfence.com"
|
|
49
|
+
|
|
50
|
+
# my_config is a python dict
|
|
51
|
+
assert isinstance(my_config, dict)
|
|
52
|
+
|
|
53
|
+
# save to file
|
|
54
|
+
my_config.store()
|
|
55
|
+
|
|
56
|
+
my_config["lang"] = "pt-pt"
|
|
57
|
+
|
|
58
|
+
# revert to previous saved file
|
|
59
|
+
my_config.reload()
|
|
60
|
+
assert my_config["lang"] == "pt"
|
|
61
|
+
|
|
62
|
+
# clear all fields
|
|
63
|
+
my_config.clear()
|
|
64
|
+
assert my_config == {}
|
|
65
|
+
|
|
66
|
+
# load from a specific path
|
|
67
|
+
my_config.load_local(save_path)
|
|
68
|
+
assert my_config == JsonStorage(save_path)
|
|
69
|
+
|
|
70
|
+
# delete stored file
|
|
71
|
+
my_config.remove()
|
|
72
|
+
assert not exists(save_path)
|
|
73
|
+
|
|
74
|
+
# keep working with dict in memory
|
|
75
|
+
print(my_config)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### JsonDatabase
|
|
79
|
+
|
|
80
|
+
Ever wanted to search a dict?
|
|
81
|
+
|
|
82
|
+
Let's create a dummy database with users
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from json_database import JsonDatabase
|
|
86
|
+
|
|
87
|
+
db_path = "users.db"
|
|
88
|
+
|
|
89
|
+
with JsonDatabase("users", db_path) as db:
|
|
90
|
+
# add some users to the database
|
|
91
|
+
|
|
92
|
+
for user in [
|
|
93
|
+
{"name": "bob", "age": 12},
|
|
94
|
+
{"name": "bobby"},
|
|
95
|
+
{"name": ["joe", "jony"]},
|
|
96
|
+
{"name": "john"},
|
|
97
|
+
{"name": "jones", "age": 35},
|
|
98
|
+
{"name": "joey", "birthday": "may 12"}]:
|
|
99
|
+
db.add_item(user)
|
|
100
|
+
|
|
101
|
+
# pretty print database contents
|
|
102
|
+
db.print()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# auto saved when used with context manager
|
|
106
|
+
# db.commit()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
search entries by key
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from json_database import JsonDatabase
|
|
115
|
+
|
|
116
|
+
db_path = "users.db"
|
|
117
|
+
|
|
118
|
+
db = JsonDatabase("users", db_path) # load db created in previous example
|
|
119
|
+
|
|
120
|
+
# search by exact key match
|
|
121
|
+
users_with_defined_age = db.search_by_key("age")
|
|
122
|
+
|
|
123
|
+
for user in users_with_defined_age:
|
|
124
|
+
print(user["name"], user["age"])
|
|
125
|
+
|
|
126
|
+
# fuzzy search
|
|
127
|
+
users = db.search_by_key("birth", fuzzy=True)
|
|
128
|
+
for user, conf in users:
|
|
129
|
+
print("matched with confidence", conf)
|
|
130
|
+
print(user["name"], user["birthday"])
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
search by key value pair
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
# search by key/value pair
|
|
137
|
+
users_12years_old = db.search_by_value("age", 12)
|
|
138
|
+
|
|
139
|
+
for user in users_12years_old:
|
|
140
|
+
assert user["age"] == 12
|
|
141
|
+
|
|
142
|
+
# fuzzy search
|
|
143
|
+
jon_users = db.search_by_value("name", "jon", fuzzy=True)
|
|
144
|
+
for user, conf in jon_users:
|
|
145
|
+
print(user["name"])
|
|
146
|
+
print("matched with confidence", conf)
|
|
147
|
+
# NOTE that one of the users has a list instead of a string in the name, it also matches
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
updating an existing entry
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
# get database item
|
|
154
|
+
item = {"name": "bobby"}
|
|
155
|
+
|
|
156
|
+
item_id = db.get_item_id(item)
|
|
157
|
+
|
|
158
|
+
if item_id >= 0:
|
|
159
|
+
new_item = {"name": "don't call me bobby"}
|
|
160
|
+
db.update_item(item_id, new_item)
|
|
161
|
+
else:
|
|
162
|
+
print("item not found in database")
|
|
163
|
+
|
|
164
|
+
# clear changes since last commit
|
|
165
|
+
db.reset()
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
You can save arbitrary objects to the database
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from json_database import JsonDatabase
|
|
172
|
+
|
|
173
|
+
db = JsonDatabase("users", "~/databases/users.json")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class User:
|
|
177
|
+
def __init__(self, email, key=None, data=None):
|
|
178
|
+
self.email = email
|
|
179
|
+
self.secret_key = key
|
|
180
|
+
self.data = data
|
|
181
|
+
|
|
182
|
+
user1 = User("first@mail.net", data={"name": "jonas", "birthday": "12 May"})
|
|
183
|
+
user2 = User("second@mail.net", "secret", data={"name": ["joe", "jony"], "age": 12})
|
|
184
|
+
|
|
185
|
+
# objects will be "jsonified" here, they will no longer be User objects
|
|
186
|
+
# if you need them to be a specific class use some ORM lib instead (SQLAlchemy is great)
|
|
187
|
+
db.add_item(user1)
|
|
188
|
+
db.add_item(user2)
|
|
189
|
+
|
|
190
|
+
# search entries with non empty key
|
|
191
|
+
print(db.search_by_key("secret_key"))
|
|
192
|
+
|
|
193
|
+
# search in user provided data
|
|
194
|
+
print(db.search_by_key("birth", fuzzy=True))
|
|
195
|
+
|
|
196
|
+
# search entries with a certain value
|
|
197
|
+
print(db.search_by_value("age", 12))
|
|
198
|
+
print(db.search_by_value("name", "jon", fuzzy=True))
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
Platform: UNKNOWN
|
|
202
|
+
Description-Content-Type: text/markdown
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
+
import os
|
|
3
4
|
from os import makedirs, remove
|
|
4
5
|
from os.path import expanduser, isdir, dirname, exists, isfile, join
|
|
5
6
|
from pprint import pprint
|
|
@@ -7,6 +8,7 @@ from tempfile import gettempdir
|
|
|
7
8
|
|
|
8
9
|
from combo_lock import ComboLock
|
|
9
10
|
|
|
11
|
+
from json_database.crypto import decrypt_from_json, encrypt_as_json
|
|
10
12
|
from json_database.exceptions import InvalidItemID, DatabaseNotCommitted, \
|
|
11
13
|
SessionError, MatchError
|
|
12
14
|
from json_database.utils import DummyLock, load_commented_json, merge_dict, \
|
|
@@ -104,6 +106,41 @@ class JsonStorage(dict):
|
|
|
104
106
|
raise SessionError
|
|
105
107
|
|
|
106
108
|
|
|
109
|
+
class EncryptedJsonStorage(JsonStorage):
|
|
110
|
+
"""persistent python dict, stored AES encrypted to file"""
|
|
111
|
+
|
|
112
|
+
def __init__(self, encrypt_key: str, path: str, disable_lock=False):
|
|
113
|
+
assert len(encrypt_key) == 16
|
|
114
|
+
self.encrypt_key = encrypt_key
|
|
115
|
+
super().__init__(path, disable_lock)
|
|
116
|
+
|
|
117
|
+
def load_local(self, path):
|
|
118
|
+
"""
|
|
119
|
+
Load local json file into self.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
path (str): file to load
|
|
123
|
+
"""
|
|
124
|
+
super().load_local(path)
|
|
125
|
+
# decrypt after load
|
|
126
|
+
if self:
|
|
127
|
+
decrypted = json.loads(decrypt_from_json(self.encrypt_key, dict(self)))
|
|
128
|
+
self.clear()
|
|
129
|
+
self.update(decrypted)
|
|
130
|
+
|
|
131
|
+
def store(self, path=None):
|
|
132
|
+
"""
|
|
133
|
+
store the json db locally.
|
|
134
|
+
"""
|
|
135
|
+
decrypted = dict(self)
|
|
136
|
+
encrypted = json.loads(encrypt_as_json(self.encrypt_key, decrypted))
|
|
137
|
+
self.clear()
|
|
138
|
+
self.merge(encrypted) # encrypt before storage
|
|
139
|
+
super().store()
|
|
140
|
+
self.clear()
|
|
141
|
+
self.update(decrypted) # keep it decrypted in memory
|
|
142
|
+
|
|
143
|
+
|
|
107
144
|
class JsonDatabase(dict):
|
|
108
145
|
""" searchable persistent dict """
|
|
109
146
|
|
|
@@ -295,6 +332,22 @@ class JsonStorageXDG(JsonStorage):
|
|
|
295
332
|
super().__init__(path, disable_lock=disable_lock)
|
|
296
333
|
|
|
297
334
|
|
|
335
|
+
class EncryptedJsonStorageXDG(EncryptedJsonStorage):
|
|
336
|
+
""" xdg respectful persistent dicts """
|
|
337
|
+
|
|
338
|
+
def __init__(self,
|
|
339
|
+
encrypt_key: str,
|
|
340
|
+
name: str,
|
|
341
|
+
xdg_folder=xdg_data_home(),
|
|
342
|
+
disable_lock=False,
|
|
343
|
+
subfolder="json_database",
|
|
344
|
+
extension="ejson"):
|
|
345
|
+
self.name = name
|
|
346
|
+
path = join(xdg_folder, subfolder, f"{name}.{extension}")
|
|
347
|
+
super().__init__(encrypt_key=encrypt_key, path=path,
|
|
348
|
+
disable_lock=disable_lock)
|
|
349
|
+
|
|
350
|
+
|
|
298
351
|
class JsonDatabaseXDG(JsonDatabase):
|
|
299
352
|
""" xdg respectful json database """
|
|
300
353
|
|
|
@@ -312,3 +365,23 @@ class JsonConfigXDG(JsonStorageXDG):
|
|
|
312
365
|
disable_lock=False, subfolder="json_database",
|
|
313
366
|
extension="json"):
|
|
314
367
|
super().__init__(name, xdg_folder, disable_lock, subfolder, extension)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
if __name__ == "__main__":
|
|
371
|
+
# quick test
|
|
372
|
+
os.remove("/tmp/test.json")
|
|
373
|
+
db = EncryptedJsonStorage("S" * 16, "/tmp/test.json")
|
|
374
|
+
db["A"] = "42"
|
|
375
|
+
print(db) # {'A': '42'} - not encrypted in memory
|
|
376
|
+
db.store()
|
|
377
|
+
print(db) # {'A': '42'} - still decrypted
|
|
378
|
+
db.reload()
|
|
379
|
+
print(db) # {'A': '42'} - still decrypted
|
|
380
|
+
db = EncryptedJsonStorage("S" * 16, "/tmp/test.json")
|
|
381
|
+
print(db) # {'A': '42'} - still not encrypted
|
|
382
|
+
|
|
383
|
+
db = JsonStorage("/tmp/test.json")
|
|
384
|
+
print(db) # encrypted
|
|
385
|
+
# {'ciphertext': 'ad0da72dc412d6b1240e478560354893d62caf',
|
|
386
|
+
# 'tag': '3bc39dbfad7b0d7e50f3e652ee341819',
|
|
387
|
+
# 'nonce': '3020ddafc9853e7686ee0368f9be6e25'}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import zlib
|
|
3
|
+
from binascii import hexlify
|
|
4
|
+
from binascii import unhexlify
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
# pycryptodomex
|
|
8
|
+
from Cryptodome.Cipher import AES
|
|
9
|
+
except ImportError:
|
|
10
|
+
# pycrypto + pycryptodome
|
|
11
|
+
try:
|
|
12
|
+
from Crypto.Cipher import AES
|
|
13
|
+
except:
|
|
14
|
+
AES = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def encrypt(key, text, nonce=None):
|
|
18
|
+
if AES is None:
|
|
19
|
+
raise ImportError("run pip install pycryptodomex")
|
|
20
|
+
if not isinstance(text, bytes):
|
|
21
|
+
text = bytes(text, encoding="utf-8")
|
|
22
|
+
if not isinstance(key, bytes):
|
|
23
|
+
key = bytes(key, encoding="utf-8")
|
|
24
|
+
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
|
|
25
|
+
text = compress_payload(text)
|
|
26
|
+
ciphertext, tag = cipher.encrypt_and_digest(text)
|
|
27
|
+
return ciphertext, tag, cipher.nonce
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def decrypt(key, ciphertext, tag, nonce) -> str:
|
|
31
|
+
if AES is None:
|
|
32
|
+
raise ImportError("run pip install pycryptodomex")
|
|
33
|
+
if not isinstance(key, bytes):
|
|
34
|
+
key = bytes(key, encoding="utf-8")
|
|
35
|
+
cipher = AES.new(key, AES.MODE_GCM, nonce)
|
|
36
|
+
data = cipher.decrypt_and_verify(ciphertext, tag)
|
|
37
|
+
text = decompress_payload(data).decode(encoding="utf-8")
|
|
38
|
+
return text
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def encrypt_as_json(key, data):
|
|
42
|
+
if isinstance(data, dict):
|
|
43
|
+
data = json.dumps(data)
|
|
44
|
+
if len(key) > 16:
|
|
45
|
+
key = key[0:16]
|
|
46
|
+
ciphertext, tag, nonce = encrypt(key, data)
|
|
47
|
+
return json.dumps({"ciphertext": hexlify(ciphertext).decode('utf-8'),
|
|
48
|
+
"tag": hexlify(tag).decode('utf-8'),
|
|
49
|
+
"nonce": hexlify(nonce).decode('utf-8')})
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def decrypt_from_json(key, data):
|
|
53
|
+
if isinstance(data, str):
|
|
54
|
+
data = json.loads(data)
|
|
55
|
+
if len(key) > 16:
|
|
56
|
+
key = key[0:16]
|
|
57
|
+
ciphertext = unhexlify(data["ciphertext"])
|
|
58
|
+
if data.get("tag") is None: # web crypto
|
|
59
|
+
ciphertext, tag = ciphertext[:-16], ciphertext[-16:]
|
|
60
|
+
else:
|
|
61
|
+
tag = unhexlify(data["tag"])
|
|
62
|
+
nonce = unhexlify(data["nonce"])
|
|
63
|
+
return decrypt(key, ciphertext, tag, nonce)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def compress_payload(text):
|
|
67
|
+
# Compressing text
|
|
68
|
+
if isinstance(text, str):
|
|
69
|
+
decompressed = text.encode("utf-8")
|
|
70
|
+
else:
|
|
71
|
+
decompressed = text
|
|
72
|
+
return zlib.compress(decompressed)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def decompress_payload(compressed):
|
|
76
|
+
# Decompressing text
|
|
77
|
+
if isinstance(compressed, str):
|
|
78
|
+
# assume hex
|
|
79
|
+
compressed = unhexlify(compressed)
|
|
80
|
+
return zlib.decompress(compressed)
|
|
@@ -12,3 +12,11 @@ class SessionError(RuntimeError):
|
|
|
12
12
|
|
|
13
13
|
class MatchError(ValueError):
|
|
14
14
|
""" could not match an item in db """
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DecryptionKeyError(KeyError):
|
|
18
|
+
""" Could not decrypt payload """
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EncryptionKeyError(KeyError):
|
|
22
|
+
""" Could not encrypt payload """
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: json-database
|
|
3
|
+
Version: 0.8.1
|
|
4
|
+
Summary: searchable json database with persistence
|
|
5
|
+
Home-page: https://github.com/TigreGotico/json_database
|
|
6
|
+
Author: jarbasAI
|
|
7
|
+
Author-email: jarbasai@mailfence.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Description: # Json Database
|
|
10
|
+
|
|
11
|
+
Python dict based database with persistence and search capabilities
|
|
12
|
+
|
|
13
|
+
For those times when you need something simple and sql is overkill
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- pure python
|
|
19
|
+
- save and load from file
|
|
20
|
+
- search recursively by key and key/value pairs
|
|
21
|
+
- fuzzy search
|
|
22
|
+
- supports arbitrary objects
|
|
23
|
+
- supports comments in saved files
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install json_database
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### JsonStorage
|
|
35
|
+
|
|
36
|
+
Sometimes you need persistent dicts that you can save and load from file
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from json_database import JsonStorage
|
|
40
|
+
from os.path import exists
|
|
41
|
+
|
|
42
|
+
save_path = "my_dict.conf"
|
|
43
|
+
|
|
44
|
+
my_config = JsonStorage(save_path)
|
|
45
|
+
|
|
46
|
+
my_config["lang"] = "pt"
|
|
47
|
+
my_config["secondary_lang"] = "en"
|
|
48
|
+
my_config["email"] = "jarbasai@mailfence.com"
|
|
49
|
+
|
|
50
|
+
# my_config is a python dict
|
|
51
|
+
assert isinstance(my_config, dict)
|
|
52
|
+
|
|
53
|
+
# save to file
|
|
54
|
+
my_config.store()
|
|
55
|
+
|
|
56
|
+
my_config["lang"] = "pt-pt"
|
|
57
|
+
|
|
58
|
+
# revert to previous saved file
|
|
59
|
+
my_config.reload()
|
|
60
|
+
assert my_config["lang"] == "pt"
|
|
61
|
+
|
|
62
|
+
# clear all fields
|
|
63
|
+
my_config.clear()
|
|
64
|
+
assert my_config == {}
|
|
65
|
+
|
|
66
|
+
# load from a specific path
|
|
67
|
+
my_config.load_local(save_path)
|
|
68
|
+
assert my_config == JsonStorage(save_path)
|
|
69
|
+
|
|
70
|
+
# delete stored file
|
|
71
|
+
my_config.remove()
|
|
72
|
+
assert not exists(save_path)
|
|
73
|
+
|
|
74
|
+
# keep working with dict in memory
|
|
75
|
+
print(my_config)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### JsonDatabase
|
|
79
|
+
|
|
80
|
+
Ever wanted to search a dict?
|
|
81
|
+
|
|
82
|
+
Let's create a dummy database with users
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from json_database import JsonDatabase
|
|
86
|
+
|
|
87
|
+
db_path = "users.db"
|
|
88
|
+
|
|
89
|
+
with JsonDatabase("users", db_path) as db:
|
|
90
|
+
# add some users to the database
|
|
91
|
+
|
|
92
|
+
for user in [
|
|
93
|
+
{"name": "bob", "age": 12},
|
|
94
|
+
{"name": "bobby"},
|
|
95
|
+
{"name": ["joe", "jony"]},
|
|
96
|
+
{"name": "john"},
|
|
97
|
+
{"name": "jones", "age": 35},
|
|
98
|
+
{"name": "joey", "birthday": "may 12"}]:
|
|
99
|
+
db.add_item(user)
|
|
100
|
+
|
|
101
|
+
# pretty print database contents
|
|
102
|
+
db.print()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# auto saved when used with context manager
|
|
106
|
+
# db.commit()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
search entries by key
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from json_database import JsonDatabase
|
|
115
|
+
|
|
116
|
+
db_path = "users.db"
|
|
117
|
+
|
|
118
|
+
db = JsonDatabase("users", db_path) # load db created in previous example
|
|
119
|
+
|
|
120
|
+
# search by exact key match
|
|
121
|
+
users_with_defined_age = db.search_by_key("age")
|
|
122
|
+
|
|
123
|
+
for user in users_with_defined_age:
|
|
124
|
+
print(user["name"], user["age"])
|
|
125
|
+
|
|
126
|
+
# fuzzy search
|
|
127
|
+
users = db.search_by_key("birth", fuzzy=True)
|
|
128
|
+
for user, conf in users:
|
|
129
|
+
print("matched with confidence", conf)
|
|
130
|
+
print(user["name"], user["birthday"])
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
search by key value pair
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
# search by key/value pair
|
|
137
|
+
users_12years_old = db.search_by_value("age", 12)
|
|
138
|
+
|
|
139
|
+
for user in users_12years_old:
|
|
140
|
+
assert user["age"] == 12
|
|
141
|
+
|
|
142
|
+
# fuzzy search
|
|
143
|
+
jon_users = db.search_by_value("name", "jon", fuzzy=True)
|
|
144
|
+
for user, conf in jon_users:
|
|
145
|
+
print(user["name"])
|
|
146
|
+
print("matched with confidence", conf)
|
|
147
|
+
# NOTE that one of the users has a list instead of a string in the name, it also matches
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
updating an existing entry
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
# get database item
|
|
154
|
+
item = {"name": "bobby"}
|
|
155
|
+
|
|
156
|
+
item_id = db.get_item_id(item)
|
|
157
|
+
|
|
158
|
+
if item_id >= 0:
|
|
159
|
+
new_item = {"name": "don't call me bobby"}
|
|
160
|
+
db.update_item(item_id, new_item)
|
|
161
|
+
else:
|
|
162
|
+
print("item not found in database")
|
|
163
|
+
|
|
164
|
+
# clear changes since last commit
|
|
165
|
+
db.reset()
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
You can save arbitrary objects to the database
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from json_database import JsonDatabase
|
|
172
|
+
|
|
173
|
+
db = JsonDatabase("users", "~/databases/users.json")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class User:
|
|
177
|
+
def __init__(self, email, key=None, data=None):
|
|
178
|
+
self.email = email
|
|
179
|
+
self.secret_key = key
|
|
180
|
+
self.data = data
|
|
181
|
+
|
|
182
|
+
user1 = User("first@mail.net", data={"name": "jonas", "birthday": "12 May"})
|
|
183
|
+
user2 = User("second@mail.net", "secret", data={"name": ["joe", "jony"], "age": 12})
|
|
184
|
+
|
|
185
|
+
# objects will be "jsonified" here, they will no longer be User objects
|
|
186
|
+
# if you need them to be a specific class use some ORM lib instead (SQLAlchemy is great)
|
|
187
|
+
db.add_item(user1)
|
|
188
|
+
db.add_item(user2)
|
|
189
|
+
|
|
190
|
+
# search entries with non empty key
|
|
191
|
+
print(db.search_by_key("secret_key"))
|
|
192
|
+
|
|
193
|
+
# search in user provided data
|
|
194
|
+
print(db.search_by_key("birth", fuzzy=True))
|
|
195
|
+
|
|
196
|
+
# search entries with a certain value
|
|
197
|
+
print(db.search_by_value("age", 12))
|
|
198
|
+
print(db.search_by_value("name", "jon", fuzzy=True))
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
Platform: UNKNOWN
|
|
202
|
+
Description-Content-Type: text/markdown
|
|
@@ -2,12 +2,15 @@ LICENSE
|
|
|
2
2
|
README.md
|
|
3
3
|
setup.py
|
|
4
4
|
json_database/__init__.py
|
|
5
|
+
json_database/crypto.py
|
|
5
6
|
json_database/exceptions.py
|
|
6
7
|
json_database/search.py
|
|
7
8
|
json_database/utils.py
|
|
9
|
+
json_database/version.py
|
|
8
10
|
json_database/xdg_utils.py
|
|
9
11
|
json_database.egg-info/PKG-INFO
|
|
10
12
|
json_database.egg-info/SOURCES.txt
|
|
11
13
|
json_database.egg-info/dependency_links.txt
|
|
12
14
|
json_database.egg-info/requires.txt
|
|
13
|
-
json_database.egg-info/top_level.txt
|
|
15
|
+
json_database.egg-info/top_level.txt
|
|
16
|
+
test/test_crypto.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
combo_lock<1.0.0,>=0.2.1
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import os.path
|
|
3
|
+
|
|
4
|
+
from setuptools import setup
|
|
5
|
+
|
|
6
|
+
BASEDIR = os.path.abspath(os.path.dirname(__file__))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def package_files(directory):
|
|
10
|
+
paths = []
|
|
11
|
+
for (path, _, filenames) in os.walk(directory):
|
|
12
|
+
for filename in filenames:
|
|
13
|
+
paths.append(os.path.join('..', path, filename))
|
|
14
|
+
return paths
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def required(requirements_file):
|
|
18
|
+
""" Read requirements file and remove comments and empty lines. """
|
|
19
|
+
with open(os.path.join(BASEDIR, requirements_file), 'r') as f:
|
|
20
|
+
requirements = f.read().splitlines()
|
|
21
|
+
if 'MYCROFT_LOOSE_REQUIREMENTS' in os.environ:
|
|
22
|
+
print('USING LOOSE REQUIREMENTS!')
|
|
23
|
+
requirements = [r.replace('==', '>=').replace('~=', '>=') for r in requirements]
|
|
24
|
+
return [pkg for pkg in requirements
|
|
25
|
+
if pkg.strip() and not pkg.startswith("#")]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_version():
|
|
29
|
+
""" Find the version of ovos-core"""
|
|
30
|
+
version = None
|
|
31
|
+
version_file = os.path.join(BASEDIR, 'json_database', 'version.py')
|
|
32
|
+
major, minor, build, alpha = (None, None, None, None)
|
|
33
|
+
with open(version_file) as f:
|
|
34
|
+
for line in f:
|
|
35
|
+
if 'VERSION_MAJOR' in line:
|
|
36
|
+
major = line.split('=')[1].strip()
|
|
37
|
+
elif 'VERSION_MINOR' in line:
|
|
38
|
+
minor = line.split('=')[1].strip()
|
|
39
|
+
elif 'VERSION_BUILD' in line:
|
|
40
|
+
build = line.split('=')[1].strip()
|
|
41
|
+
elif 'VERSION_ALPHA' in line:
|
|
42
|
+
alpha = line.split('=')[1].strip()
|
|
43
|
+
|
|
44
|
+
if ((major and minor and build and alpha) or
|
|
45
|
+
'# END_VERSION_BLOCK' in line):
|
|
46
|
+
break
|
|
47
|
+
version = f"{major}.{minor}.{build}"
|
|
48
|
+
if int(alpha):
|
|
49
|
+
version += f"a{alpha}"
|
|
50
|
+
return version
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_description():
|
|
54
|
+
with open(os.path.join(BASEDIR, "README.md"), "r") as f:
|
|
55
|
+
long_description = f.read()
|
|
56
|
+
return long_description
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
setup(
|
|
60
|
+
name='json_database',
|
|
61
|
+
version=get_version(),
|
|
62
|
+
packages=['json_database'],
|
|
63
|
+
package_data={'': package_files('json_database')},
|
|
64
|
+
include_package_data=True,
|
|
65
|
+
url='https://github.com/TigreGotico/json_database',
|
|
66
|
+
license='MIT',
|
|
67
|
+
author='jarbasAI',
|
|
68
|
+
author_email='jarbasai@mailfence.com',
|
|
69
|
+
install_requires=required('requirements.txt'),
|
|
70
|
+
description='searchable json database with persistence',
|
|
71
|
+
long_description=get_description(),
|
|
72
|
+
long_description_content_type="text/markdown",
|
|
73
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
from json_database import EncryptedJsonStorage, JsonStorage # Replace with actual import
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestEncryptedJsonStorage(unittest.TestCase):
|
|
8
|
+
def setUp(self):
|
|
9
|
+
self.key = "S" * 16 # Replace with actual key generation if needed
|
|
10
|
+
self.file_path = "/tmp/test.json"
|
|
11
|
+
# Ensure the test file doesn't exist at the start of each test
|
|
12
|
+
if os.path.exists(self.file_path):
|
|
13
|
+
os.remove(self.file_path)
|
|
14
|
+
|
|
15
|
+
def tearDown(self):
|
|
16
|
+
# Clean up the test file after each test
|
|
17
|
+
if os.path.exists(self.file_path):
|
|
18
|
+
os.remove(self.file_path)
|
|
19
|
+
|
|
20
|
+
def test_add_and_store_data(self):
|
|
21
|
+
db = EncryptedJsonStorage(self.key, self.file_path)
|
|
22
|
+
db["A"] = "42"
|
|
23
|
+
self.assertEqual(db["A"], "42") # Check in-memory data
|
|
24
|
+
db.store()
|
|
25
|
+
self.assertTrue(os.path.exists(self.file_path)) # File should be created
|
|
26
|
+
|
|
27
|
+
def test_encryption_in_file(self):
|
|
28
|
+
db = EncryptedJsonStorage(self.key, self.file_path)
|
|
29
|
+
db["A"] = "42"
|
|
30
|
+
db.store()
|
|
31
|
+
with open(self.file_path, "r") as file:
|
|
32
|
+
file_data = file.read()
|
|
33
|
+
self.assertNotIn("42", file_data) # Data should be encrypted
|
|
34
|
+
|
|
35
|
+
def test_decryption_after_reload(self):
|
|
36
|
+
db = EncryptedJsonStorage(self.key, self.file_path)
|
|
37
|
+
db["A"] = "42"
|
|
38
|
+
db.store()
|
|
39
|
+
db.reload()
|
|
40
|
+
self.assertEqual(db["A"], "42") # Data should be decrypted correctly
|
|
41
|
+
|
|
42
|
+
def test_jsonstorage_read_encrypted_data(self):
|
|
43
|
+
encrypted_db = EncryptedJsonStorage(self.key, self.file_path)
|
|
44
|
+
encrypted_db["A"] = "42"
|
|
45
|
+
encrypted_db.store()
|
|
46
|
+
|
|
47
|
+
db = JsonStorage(self.file_path)
|
|
48
|
+
self.assertIn("ciphertext", db) # Check that it's encrypted
|
|
49
|
+
self.assertNotIn("A", db)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
unittest.main()
|
json_database-0.7.0/PKG-INFO
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: json_database
|
|
3
|
-
Version: 0.7.0
|
|
4
|
-
Summary: searchable json database with persistence
|
|
5
|
-
Home-page: https://github.com/OpenJarbas/json_database
|
|
6
|
-
Author: jarbasAI
|
|
7
|
-
Author-email: jarbasai@mailfence.com
|
|
8
|
-
License: MIT
|
|
9
|
-
Platform: UNKNOWN
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
|
|
12
|
-
UNKNOWN
|
|
13
|
-
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: json-database
|
|
3
|
-
Version: 0.7.0
|
|
4
|
-
Summary: searchable json database with persistence
|
|
5
|
-
Home-page: https://github.com/OpenJarbas/json_database
|
|
6
|
-
Author: jarbasAI
|
|
7
|
-
Author-email: jarbasai@mailfence.com
|
|
8
|
-
License: MIT
|
|
9
|
-
Platform: UNKNOWN
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
|
|
12
|
-
UNKNOWN
|
|
13
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
combo_lock~=0.2.1
|
json_database-0.7.0/setup.py
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from setuptools import setup
|
|
2
|
-
|
|
3
|
-
setup(
|
|
4
|
-
name='json_database',
|
|
5
|
-
version='0.7.0',
|
|
6
|
-
packages=['json_database'],
|
|
7
|
-
url='https://github.com/OpenJarbas/json_database',
|
|
8
|
-
license='MIT',
|
|
9
|
-
author='jarbasAI',
|
|
10
|
-
author_email='jarbasai@mailfence.com',
|
|
11
|
-
install_requires=["combo_lock~=0.2.1"],
|
|
12
|
-
description='searchable json database with persistence'
|
|
13
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|