json-database 0.6.0__tar.gz → 0.8.0a1__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.0a1/PKG-INFO +202 -0
- {json_database-0.6.0 → json_database-0.8.0a1}/json_database/__init__.py +92 -15
- json_database-0.8.0a1/json_database/crypto.py +80 -0
- {json_database-0.6.0 → json_database-0.8.0a1}/json_database/exceptions.py +8 -0
- {json_database-0.6.0 → json_database-0.8.0a1}/json_database/search.py +1 -1
- json_database-0.6.0/json_database/utils/__init__.py → json_database-0.8.0a1/json_database/utils.py +33 -0
- json_database-0.8.0a1/json_database/version.py +6 -0
- json_database-0.8.0a1/json_database/xdg_utils.py +174 -0
- json_database-0.8.0a1/json_database.egg-info/PKG-INFO +202 -0
- {json_database-0.6.0 → json_database-0.8.0a1}/json_database.egg-info/SOURCES.txt +5 -2
- json_database-0.8.0a1/json_database.egg-info/requires.txt +1 -0
- json_database-0.8.0a1/setup.py +73 -0
- json_database-0.8.0a1/test/test_crypto.py +53 -0
- json_database-0.6.0/PKG-INFO +0 -13
- json_database-0.6.0/json_database/utils/combo_lock.py +0 -108
- json_database-0.6.0/json_database.egg-info/PKG-INFO +0 -13
- json_database-0.6.0/json_database.egg-info/requires.txt +0 -2
- json_database-0.6.0/setup.py +0 -13
- {json_database-0.6.0 → json_database-0.8.0a1}/LICENSE +0 -0
- {json_database-0.6.0 → json_database-0.8.0a1}/README.md +0 -0
- {json_database-0.6.0 → json_database-0.8.0a1}/json_database.egg-info/dependency_links.txt +0 -0
- {json_database-0.6.0 → json_database-0.8.0a1}/json_database.egg-info/top_level.txt +0 -0
- {json_database-0.6.0 → json_database-0.8.0a1}/setup.cfg +0 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: json_database
|
|
3
|
+
Version: 0.8.0a1
|
|
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,16 +1,21 @@
|
|
|
1
|
-
from json_database.utils import *
|
|
2
|
-
from json_database.exceptions import InvalidItemID, DatabaseNotCommitted, \
|
|
3
|
-
SessionError, MatchError
|
|
4
|
-
from os.path import expanduser, isdir, dirname, exists, isfile, join
|
|
5
|
-
from os import makedirs, remove
|
|
6
1
|
import json
|
|
7
2
|
import logging
|
|
3
|
+
import os
|
|
4
|
+
from os import makedirs, remove
|
|
5
|
+
from os.path import expanduser, isdir, dirname, exists, isfile, join
|
|
8
6
|
from pprint import pprint
|
|
9
|
-
from xdg import BaseDirectory
|
|
10
|
-
from json_database.utils.combo_lock import ComboLock, DummyLock
|
|
11
|
-
|
|
12
7
|
from tempfile import gettempdir
|
|
13
8
|
|
|
9
|
+
from combo_lock import ComboLock
|
|
10
|
+
|
|
11
|
+
from json_database.crypto import decrypt_from_json, encrypt_as_json
|
|
12
|
+
from json_database.exceptions import InvalidItemID, DatabaseNotCommitted, \
|
|
13
|
+
SessionError, MatchError
|
|
14
|
+
from json_database.utils import DummyLock, load_commented_json, merge_dict, \
|
|
15
|
+
jsonify_recursively, get_key_recursively, get_key_recursively_fuzzy, \
|
|
16
|
+
get_value_recursively_fuzzy, get_value_recursively
|
|
17
|
+
from json_database.xdg_utils import xdg_cache_home, xdg_data_home, xdg_config_home
|
|
18
|
+
|
|
14
19
|
LOG = logging.getLogger("JsonDatabase")
|
|
15
20
|
LOG.setLevel("INFO")
|
|
16
21
|
|
|
@@ -101,13 +106,49 @@ class JsonStorage(dict):
|
|
|
101
106
|
raise SessionError
|
|
102
107
|
|
|
103
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
|
+
|
|
104
144
|
class JsonDatabase(dict):
|
|
105
145
|
""" searchable persistent dict """
|
|
146
|
+
|
|
106
147
|
def __init__(self,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
148
|
+
name,
|
|
149
|
+
path=None,
|
|
150
|
+
disable_lock=False,
|
|
151
|
+
extension="json"):
|
|
111
152
|
super().__init__()
|
|
112
153
|
self.name = name
|
|
113
154
|
self.path = path or f"{name}.{extension}"
|
|
@@ -283,7 +324,7 @@ class JsonStorageXDG(JsonStorage):
|
|
|
283
324
|
|
|
284
325
|
def __init__(self,
|
|
285
326
|
name,
|
|
286
|
-
xdg_folder=
|
|
327
|
+
xdg_folder=xdg_cache_home(),
|
|
287
328
|
disable_lock=False, subfolder="json_database",
|
|
288
329
|
extension="json"):
|
|
289
330
|
self.name = name
|
|
@@ -291,10 +332,26 @@ class JsonStorageXDG(JsonStorage):
|
|
|
291
332
|
super().__init__(path, disable_lock=disable_lock)
|
|
292
333
|
|
|
293
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
|
+
|
|
294
351
|
class JsonDatabaseXDG(JsonDatabase):
|
|
295
352
|
""" xdg respectful json database """
|
|
296
353
|
|
|
297
|
-
def __init__(self, name, xdg_folder=
|
|
354
|
+
def __init__(self, name, xdg_folder=xdg_data_home(),
|
|
298
355
|
disable_lock=False, subfolder="json_database",
|
|
299
356
|
extension="jsondb"):
|
|
300
357
|
path = join(xdg_folder, subfolder, f"{name}.{extension}")
|
|
@@ -304,7 +361,27 @@ class JsonDatabaseXDG(JsonDatabase):
|
|
|
304
361
|
class JsonConfigXDG(JsonStorageXDG):
|
|
305
362
|
""" xdg respectful config files, using json_storage.JsonStorageXDG """
|
|
306
363
|
|
|
307
|
-
def __init__(self, name, xdg_folder=
|
|
364
|
+
def __init__(self, name, xdg_folder=xdg_config_home(),
|
|
308
365
|
disable_lock=False, subfolder="json_database",
|
|
309
366
|
extension="json"):
|
|
310
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 """
|
json_database-0.6.0/json_database/utils/__init__.py → json_database-0.8.0a1/json_database/utils.py
RENAMED
|
@@ -2,6 +2,39 @@ import json
|
|
|
2
2
|
from difflib import SequenceMatcher
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
class DummyLock:
|
|
6
|
+
""" A fake lock.
|
|
7
|
+
|
|
8
|
+
Arguments:
|
|
9
|
+
path (str): path to the lockfile for the lock
|
|
10
|
+
"""
|
|
11
|
+
def __init__(self, path=""):
|
|
12
|
+
self.path = path
|
|
13
|
+
|
|
14
|
+
def acquire(self, blocking=True):
|
|
15
|
+
""" Acquire lock, locks thread and process lock.
|
|
16
|
+
|
|
17
|
+
Arguments:
|
|
18
|
+
blocking(bool): Set's blocking mode of acquire operation.
|
|
19
|
+
Default True.
|
|
20
|
+
|
|
21
|
+
Returns: True if lock succeeded otherwise False
|
|
22
|
+
"""
|
|
23
|
+
return True
|
|
24
|
+
|
|
25
|
+
def release(self):
|
|
26
|
+
""" Release acquired lock. """
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
def __enter__(self):
|
|
30
|
+
""" Context handler, acquires lock in blocking mode. """
|
|
31
|
+
return self
|
|
32
|
+
|
|
33
|
+
def __exit__(self, _type, value, traceback):
|
|
34
|
+
""" Releases the lock. """
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
5
38
|
def fuzzy_match(x, against):
|
|
6
39
|
"""Perform a 'fuzzy' comparison between two strings.
|
|
7
40
|
Returns:
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Copyright © 2016-2021 Scott Stevenson <scott@stevenson.io>
|
|
2
|
+
#
|
|
3
|
+
# Permission to use, copy, modify, and/or distribute this software for
|
|
4
|
+
# any purpose with or without fee is hereby granted, provided that the
|
|
5
|
+
# above copyright notice and this permission notice appear in all
|
|
6
|
+
# copies.
|
|
7
|
+
#
|
|
8
|
+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
|
9
|
+
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
|
10
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
|
11
|
+
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
|
12
|
+
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
|
|
13
|
+
# PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
14
|
+
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
# PERFORMANCE OF THIS SOFTWARE.
|
|
16
|
+
|
|
17
|
+
"""XDG Base Directory Specification variables.
|
|
18
|
+
|
|
19
|
+
xdg_cache_home(), xdg_config_home(), xdg_data_home(), and xdg_state_home()
|
|
20
|
+
return pathlib.Path objects containing the value of the environment variable
|
|
21
|
+
named XDG_CACHE_HOME, XDG_CONFIG_HOME, XDG_DATA_HOME, and XDG_STATE_HOME
|
|
22
|
+
respectively, or the default defined in the specification if the environment
|
|
23
|
+
variable is unset, empty, or contains a relative path rather than absolute
|
|
24
|
+
path.
|
|
25
|
+
|
|
26
|
+
xdg_config_dirs() and xdg_data_dirs() return a list of pathlib.Path
|
|
27
|
+
objects containing the value, split on colons, of the environment
|
|
28
|
+
variable named XDG_CONFIG_DIRS and XDG_DATA_DIRS respectively, or the
|
|
29
|
+
default defined in the specification if the environment variable is
|
|
30
|
+
unset or empty. Relative paths are ignored, as per the specification.
|
|
31
|
+
|
|
32
|
+
xdg_runtime_dir() returns a pathlib.Path object containing the value of
|
|
33
|
+
the XDG_RUNTIME_DIR environment variable, or None if the environment
|
|
34
|
+
variable is not set, or contains a relative path rather than absolute path.
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
# pylint: disable=fixme
|
|
39
|
+
|
|
40
|
+
import os
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
from typing import List, Optional
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
"xdg_cache_home",
|
|
46
|
+
"xdg_config_dirs",
|
|
47
|
+
"xdg_config_home",
|
|
48
|
+
"xdg_data_dirs",
|
|
49
|
+
"xdg_data_home",
|
|
50
|
+
"xdg_runtime_dir",
|
|
51
|
+
"xdg_state_home",
|
|
52
|
+
"XDG_CACHE_HOME",
|
|
53
|
+
"XDG_CONFIG_DIRS",
|
|
54
|
+
"XDG_CONFIG_HOME",
|
|
55
|
+
"XDG_DATA_DIRS",
|
|
56
|
+
"XDG_DATA_HOME",
|
|
57
|
+
"XDG_RUNTIME_DIR",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _path_from_env(variable: str, default: Path) -> Path:
|
|
62
|
+
"""Read an environment variable as a path.
|
|
63
|
+
|
|
64
|
+
The environment variable with the specified name is read, and its
|
|
65
|
+
value returned as a path. If the environment variable is not set, is
|
|
66
|
+
set to the empty string, or is set to a relative rather than
|
|
67
|
+
absolute path, the default value is returned.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
variable : str
|
|
72
|
+
Name of the environment variable.
|
|
73
|
+
default : Path
|
|
74
|
+
Default value.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
Path
|
|
79
|
+
Value from environment or default.
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
# TODO(srstevenson): Use assignment expression in Python 3.8.
|
|
83
|
+
value = os.environ.get(variable)
|
|
84
|
+
if value and os.path.isabs(value):
|
|
85
|
+
return Path(value)
|
|
86
|
+
return default
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _paths_from_env(variable: str, default: List[Path]) -> List[Path]:
|
|
90
|
+
"""Read an environment variable as a list of paths.
|
|
91
|
+
|
|
92
|
+
The environment variable with the specified name is read, and its
|
|
93
|
+
value split on colons and returned as a list of paths. If the
|
|
94
|
+
environment variable is not set, or set to the empty string, the
|
|
95
|
+
default value is returned. Relative paths are ignored, as per the
|
|
96
|
+
specification.
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
variable : str
|
|
101
|
+
Name of the environment variable.
|
|
102
|
+
default : List[Path]
|
|
103
|
+
Default value.
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
List[Path]
|
|
108
|
+
Value from environment or default.
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
# TODO(srstevenson): Use assignment expression in Python 3.8.
|
|
112
|
+
value = os.environ.get(variable)
|
|
113
|
+
if value:
|
|
114
|
+
paths = [
|
|
115
|
+
Path(path) for path in value.split(":") if os.path.isabs(path)
|
|
116
|
+
]
|
|
117
|
+
if paths:
|
|
118
|
+
return paths
|
|
119
|
+
return default
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def xdg_cache_home() -> Path:
|
|
123
|
+
"""Return a Path corresponding to XDG_CACHE_HOME."""
|
|
124
|
+
return _path_from_env("XDG_CACHE_HOME", Path.home() / ".cache")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def xdg_config_dirs() -> List[Path]:
|
|
128
|
+
"""Return a list of Paths corresponding to XDG_CONFIG_DIRS."""
|
|
129
|
+
return _paths_from_env("XDG_CONFIG_DIRS", [Path("/etc/xdg")])
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def xdg_config_home() -> Path:
|
|
133
|
+
"""Return a Path corresponding to XDG_CONFIG_HOME."""
|
|
134
|
+
return _path_from_env("XDG_CONFIG_HOME", Path.home() / ".config")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def xdg_data_dirs() -> List[Path]:
|
|
138
|
+
"""Return a list of Paths corresponding to XDG_DATA_DIRS."""
|
|
139
|
+
return _paths_from_env(
|
|
140
|
+
"XDG_DATA_DIRS",
|
|
141
|
+
[Path(path) for path in "/usr/local/share/:/usr/share/".split(":")],
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def xdg_data_home() -> Path:
|
|
146
|
+
"""Return a Path corresponding to XDG_DATA_HOME."""
|
|
147
|
+
return _path_from_env("XDG_DATA_HOME", Path.home() / ".local" / "share")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def xdg_runtime_dir() -> Optional[Path]:
|
|
151
|
+
"""Return a Path corresponding to XDG_RUNTIME_DIR.
|
|
152
|
+
|
|
153
|
+
If the XDG_RUNTIME_DIR environment variable is not set, None will be
|
|
154
|
+
returned as per the specification.
|
|
155
|
+
|
|
156
|
+
"""
|
|
157
|
+
value = os.getenv("XDG_RUNTIME_DIR")
|
|
158
|
+
if value and os.path.isabs(value):
|
|
159
|
+
return Path(value)
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def xdg_state_home() -> Path:
|
|
164
|
+
"""Return a Path corresponding to XDG_STATE_HOME."""
|
|
165
|
+
return _path_from_env("XDG_STATE_HOME", Path.home() / ".local" / "state")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# The following variables are deprecated, but remain for backward compatibility.
|
|
169
|
+
XDG_CACHE_HOME = xdg_cache_home()
|
|
170
|
+
XDG_CONFIG_DIRS = xdg_config_dirs()
|
|
171
|
+
XDG_CONFIG_HOME = xdg_config_home()
|
|
172
|
+
XDG_DATA_DIRS = xdg_data_dirs()
|
|
173
|
+
XDG_DATA_HOME = xdg_data_home()
|
|
174
|
+
XDG_RUNTIME_DIR = xdg_runtime_dir()
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: json-database
|
|
3
|
+
Version: 0.8.0a1
|
|
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
|
|
8
|
+
json_database/utils.py
|
|
9
|
+
json_database/version.py
|
|
10
|
+
json_database/xdg_utils.py
|
|
7
11
|
json_database.egg-info/PKG-INFO
|
|
8
12
|
json_database.egg-info/SOURCES.txt
|
|
9
13
|
json_database.egg-info/dependency_links.txt
|
|
10
14
|
json_database.egg-info/requires.txt
|
|
11
15
|
json_database.egg-info/top_level.txt
|
|
12
|
-
|
|
13
|
-
json_database/utils/combo_lock.py
|
|
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.6.0/PKG-INFO
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: json_database
|
|
3
|
-
Version: 0.6.0
|
|
4
|
-
Summary: searchable json database with persistence
|
|
5
|
-
Home-page: https://github.com/HelloChatterbox/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,108 +0,0 @@
|
|
|
1
|
-
# Copyright 2018 Mycroft AI Inc.
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
#
|
|
15
|
-
from threading import Lock
|
|
16
|
-
from fasteners.process_lock import InterProcessLock
|
|
17
|
-
from os.path import exists
|
|
18
|
-
from os import chmod
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class ComboLock:
|
|
22
|
-
""" A combined process and thread lock.
|
|
23
|
-
|
|
24
|
-
Arguments:
|
|
25
|
-
path (str): path to the lockfile for the lock
|
|
26
|
-
"""
|
|
27
|
-
def __init__(self, path):
|
|
28
|
-
self.path = path
|
|
29
|
-
self.plock = InterProcessLock(self.path)
|
|
30
|
-
self.tlock = Lock()
|
|
31
|
-
|
|
32
|
-
def acquire(self, blocking=True):
|
|
33
|
-
""" Acquire lock, locks thread and process lock.
|
|
34
|
-
|
|
35
|
-
Arguments:
|
|
36
|
-
blocking(bool): Set's blocking mode of acquire operation.
|
|
37
|
-
Default True.
|
|
38
|
-
|
|
39
|
-
Returns: True if lock succeeded otherwise False
|
|
40
|
-
"""
|
|
41
|
-
# Create lock file if it doesn't exist and set permissions for
|
|
42
|
-
# all users to lock/unlock
|
|
43
|
-
if not exists(self.path):
|
|
44
|
-
f = open(self.path, 'w+')
|
|
45
|
-
f.close()
|
|
46
|
-
chmod(self.path, 0o777)
|
|
47
|
-
if not blocking:
|
|
48
|
-
# Lock thread
|
|
49
|
-
tlocked = self.tlock.acquire(blocking=False)
|
|
50
|
-
if not tlocked:
|
|
51
|
-
return False
|
|
52
|
-
# Lock process
|
|
53
|
-
plocked = self.plock.acquire(blocking=False)
|
|
54
|
-
if not plocked:
|
|
55
|
-
# Release thread lock if process couldn't be locked
|
|
56
|
-
self.tlock.release()
|
|
57
|
-
return False
|
|
58
|
-
else: # blocking, just wait and acquire ALL THE LOCKS!!!
|
|
59
|
-
self.tlock.acquire()
|
|
60
|
-
self.plock.acquire()
|
|
61
|
-
return True
|
|
62
|
-
|
|
63
|
-
def release(self):
|
|
64
|
-
""" Release acquired lock. """
|
|
65
|
-
self.plock.release()
|
|
66
|
-
self.tlock.release()
|
|
67
|
-
|
|
68
|
-
def __enter__(self):
|
|
69
|
-
""" Context handler, acquires lock in blocking mode. """
|
|
70
|
-
self.acquire()
|
|
71
|
-
return self
|
|
72
|
-
|
|
73
|
-
def __exit__(self, _type, value, traceback):
|
|
74
|
-
""" Releases the lock. """
|
|
75
|
-
self.release()
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
class DummyLock:
|
|
79
|
-
""" A combined process and thread lock.
|
|
80
|
-
|
|
81
|
-
Arguments:
|
|
82
|
-
path (str): path to the lockfile for the lock
|
|
83
|
-
"""
|
|
84
|
-
def __init__(self, path):
|
|
85
|
-
self.path = path
|
|
86
|
-
|
|
87
|
-
def acquire(self, blocking=True):
|
|
88
|
-
""" Acquire lock, locks thread and process lock.
|
|
89
|
-
|
|
90
|
-
Arguments:
|
|
91
|
-
blocking(bool): Set's blocking mode of acquire operation.
|
|
92
|
-
Default True.
|
|
93
|
-
|
|
94
|
-
Returns: True if lock succeeded otherwise False
|
|
95
|
-
"""
|
|
96
|
-
return True
|
|
97
|
-
|
|
98
|
-
def release(self):
|
|
99
|
-
""" Release acquired lock. """
|
|
100
|
-
pass
|
|
101
|
-
|
|
102
|
-
def __enter__(self):
|
|
103
|
-
""" Context handler, acquires lock in blocking mode. """
|
|
104
|
-
return self
|
|
105
|
-
|
|
106
|
-
def __exit__(self, _type, value, traceback):
|
|
107
|
-
""" Releases the lock. """
|
|
108
|
-
pass
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: json-database
|
|
3
|
-
Version: 0.6.0
|
|
4
|
-
Summary: searchable json database with persistence
|
|
5
|
-
Home-page: https://github.com/HelloChatterbox/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
|
-
|
json_database-0.6.0/setup.py
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from setuptools import setup
|
|
2
|
-
|
|
3
|
-
setup(
|
|
4
|
-
name='json_database',
|
|
5
|
-
version='0.6.0',
|
|
6
|
-
packages=['json_database', 'json_database.utils'],
|
|
7
|
-
url='https://github.com/HelloChatterbox/json_database',
|
|
8
|
-
license='MIT',
|
|
9
|
-
author='jarbasAI',
|
|
10
|
-
author_email='jarbasai@mailfence.com',
|
|
11
|
-
install_requires=["pyxdg", "fasteners"],
|
|
12
|
-
description='searchable json database with persistence'
|
|
13
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|