├── tests ├── __init__.py ├── test_read_write.py └── test_password.py ├── requirements.txt ├── .coveragerc ├── .pypirc ├── requirements-building.txt ├── tinydb_encrypted_jsonstorage ├── __init__.py └── encrypted_json_storage.py ├── .travis.yml ├── docstr_badge.sh ├── .github └── FUNDING.yml ├── CHANGELOG.md ├── setup.py ├── badges └── doc_coverage.svg ├── LICENSE ├── .gitignore └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | tinydb==3.15.2 2 | pycryptodome==3.9.7 3 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source= 3 | ./ 4 | omit= 5 | .env/* 6 | -------------------------------------------------------------------------------- /.pypirc: -------------------------------------------------------------------------------- 1 | [distutils] 2 | index-servers=pypi 3 | [pypi] 4 | repository = https://upload.pypi.org/legacy/ 5 | username = bruthaler 6 | -------------------------------------------------------------------------------- /requirements-building.txt: -------------------------------------------------------------------------------- 1 | setuptools 2 | wheel 3 | tqdm 4 | twine 5 | python-coveralls 6 | pyyaml 7 | pytest-cov 8 | coverage==4.5.4 9 | docstr-coverage 10 | pybadges 11 | -------------------------------------------------------------------------------- /tinydb_encrypted_jsonstorage/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | EncryptedJSONStorage class 4 | 5 | Stores the data in an encrypted JSON file. 6 | 7 | """ 8 | 9 | from .encrypted_json_storage import EncryptedJSONStorage 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" # current default Python on Travis CI 4 | # - "3.7" 5 | # - "3.8" 6 | # - "3.8-dev" # 3.8 development branch 7 | # - "nightly" # nightly build 8 | # command to install dependencies 9 | install: 10 | - pip install -r requirements.txt 11 | - pip install -r requirements-building.txt 12 | # command to run tests 13 | script: 14 | - coverage run -m unittest 15 | - coverage report 16 | after_success: 17 | - coveralls 18 | -------------------------------------------------------------------------------- /docstr_badge.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | COV=$(docstr-coverage tinydb_encrypted_jsonstorage/ | grep "Total docstring coverage:" | cut -d":" -f 2 | cut -d";" -f1 | cut -d" " -f 2) 3 | CMP=$(echo $COV | cut -d"." -f 1) 4 | echo $CMP 5 | if [ $CMP -lt 50 ] 6 | then 7 | COLOR=#EF5350 8 | elif [ $CMP -lt 90 ] 9 | then 10 | COLOR=#FFCA28 11 | else 12 | COLOR=#66BB6A 13 | fi 14 | 15 | 16 | python -m pybadges --left-text=docstr-coverage --right-text=$COV --right-color=$COLOR > ./badges/doc_coverage.svg 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: stefanthaler 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | # [0.1.2] 8 | ## Added 9 | * docstr documentation 10 | * docstr documentation coverage 11 | 12 | # [0.1.1] 13 | ## Changes 14 | * fixes dependencies 15 | 16 | # [0.1.0] 2020-04-20 17 | ## Added 18 | * add code coverage 19 | * At travis CI 20 | 21 | ## Changed 22 | * bump number to 0.1.0 to mark first usable release 23 | * corrects requirements.txt 24 | 25 | # [0.0.2] 2020-04-19 26 | ## Added 27 | * Unit tests 28 | * Logging facilities 29 | 30 | ## Changed 31 | * Fixed stream closing issues 32 | 33 | # [0.0.1] 2020-04-14 34 | ## Added 35 | * read encrypted data 36 | * write encrypted data 37 | * change encryption key 38 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | with open("README.md", "r") as fh: 3 | long_description = fh.read() 4 | setuptools.setup( 5 | name='tinydb-encrypted-jsonstorage', 6 | version='0.1.2', 7 | author="Stefan Thaler", 8 | author_email="bruthaler@gmail.com", 9 | description="A TinyDB storage implementation that uses JSON and encryption.", 10 | long_description=long_description, 11 | long_description_content_type="text/markdown", 12 | url="https://github.com/stefanthaler/tinydb-encrypted-jsonstorage", 13 | packages=setuptools.find_packages(), 14 | install_requires=[ 15 | 'tinydb', 16 | 'pycryptodome', 17 | ], 18 | tests_require=[ 19 | 'nose' # python setup.py test / nosetests 20 | ], 21 | classifiers=[ 22 | "Programming Language :: Python :: 3", 23 | "License :: OSI Approved :: MIT License", 24 | "Operating System :: OS Independent", 25 | ], 26 | ) 27 | -------------------------------------------------------------------------------- /badges/doc_coverage.svg: -------------------------------------------------------------------------------- 1 | docstr-coveragedocstr-coverage100.0%100.0% -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Stefan Thaler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/test_read_write.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from unittest import TestCase 4 | from os.path import join as join_path 5 | from tinydb import TinyDB 6 | 7 | import tinydb_encrypted_jsonstorage as tae 8 | import os 9 | 10 | 11 | PATH =join_path(".",".test-reflect.db") 12 | KEY = "test" 13 | def a_function(): 14 | pass 15 | 16 | class TestReadWrite(unittest.TestCase): 17 | 18 | 19 | def setUp(self): 20 | self.db = TinyDB(encryption_key=KEY, path=PATH, storage=tae.EncryptedJSONStorage) 21 | 22 | def tearDown(self): 23 | self.db.close() 24 | os.remove(PATH) 25 | 26 | def test_storage_should_be_of_correct_type(self): 27 | self.assertIsInstance(self.db.storage, tae.EncryptedJSONStorage) 28 | 29 | # check what happens if file is wrong 30 | # test what happens when encrytpion key is wrong 31 | 32 | def test_data_can_be_written_and_read(self): 33 | inserted_values = {"a":"1","b":"2"} 34 | self.db.insert(inserted_values) 35 | 36 | read_data = self.db.all()[0] 37 | self.assertEqual(read_data,inserted_values, "Read data should be equal to inserted data") 38 | # data can be read 39 | 40 | def test_stored_data_is_untouched_if_write_error_occurs(self): 41 | inserted_values = {"a":"1","b":"2"} 42 | self.db.insert(inserted_values) 43 | 44 | self.db.insert({"a":a_function}) # Functions are illegal 45 | 46 | read_data = self.db.all()[0] 47 | self.assertEqual(read_data,inserted_values, "Writing illegal data shout not cause the database to be altered") 48 | -------------------------------------------------------------------------------- /tests/test_password.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from unittest import TestCase 4 | from os.path import join as join_path 5 | from tinydb import TinyDB 6 | 7 | import tinydb_encrypted_jsonstorage as tae 8 | import os 9 | 10 | 11 | PATH =join_path(".",".test-reflect.db") 12 | KEY = "test" 13 | def a_function(): 14 | pass 15 | 16 | class TestPassword(unittest.TestCase): 17 | 18 | def setUp(self): 19 | self.db = TinyDB(encryption_key=KEY, path=PATH, storage=tae.EncryptedJSONStorage) 20 | 21 | def tearDown(self): 22 | self.db.close() 23 | os.remove(PATH) 24 | 25 | def test_db_cannot_be_opened_with_wrong_key(self): 26 | self.db.close() 27 | with self.assertRaises(ValueError, msg="Wrong password should not work "): 28 | db = TinyDB(encryption_key="OTHERKEY", path=PATH, storage=tae.EncryptedJSONStorage) 29 | 30 | def test_password_can_be_changed(self): 31 | inserted_values = {"a":"1","b":"2"} 32 | self.db.insert(inserted_values) 33 | self.db.storage.change_encryption_key("NEW_KEY") 34 | 35 | with self.assertRaises(ValueError, msg="Wrong password should not work "): 36 | db = TinyDB(encryption_key="OTHERKEY", path=PATH, storage=tae.EncryptedJSONStorage) 37 | 38 | read_data = self.db.all()[0] 39 | self.assertEqual(read_data,inserted_values, "After password change DB should contain original data") 40 | 41 | self.db.close() 42 | re_opened_db = TinyDB(encryption_key="NEW_KEY", path=PATH, storage=tae.EncryptedJSONStorage) 43 | self.assertIsNotNone(re_opened_db,"you should be able to reopen the database with new password") 44 | read_data = re_opened_db.all()[0] 45 | self.assertEqual(read_data,inserted_values, "After reopening, the database should still contain original values") 46 | re_opened_db.close() 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.com/stefanthaler/tinydb-encrypted-jsonstorage.svg?branch=master)](https://travis-ci.com/stefanthaler/tinydb-encrypted-jsonstorage) 2 | [![Coverage Status](https://coveralls.io/repos/github/stefanthaler/tinydb-encrypted-jsonstorage/badge.svg?branch=master)](https://coveralls.io/github/stefanthaler/tinydb-encrypted-jsonstorage?branch=master) 3 | [![Documentation Status](./badges/doc_coverage.svg)](https://pypi.org/project/docstr-coverage/) 4 | 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 6 | 7 | # tinydb-encrypted-jsonstorage 8 | A TinyDB storage implementation that stores values as encrypted json. 9 | 10 | # Requirements 11 | ## Python 12 | * [TinyDB](https://tinydb.readthedocs.io/en/latest/getting-started.html) 13 | * [pycryptodome](https://pycryptodome.readthedocs.io/en/latest/) 14 | 15 | ## OS Dependencies: 16 | * python3, python3-venv, python3-pip 17 | 18 | # Get Sources 19 | * git clone git@github.com:stefanthaler/tinydb-encrypted-jsonstorage.git 20 | * cd tinydb-encrypted-jsonstorage 21 | * python3 -m venv .env 22 | * . .env/bin/activate.fish 23 | * pip install -r requirements.txt 24 | 25 | # Build 26 | * follow steps in "Get Sources" 27 | * pip3 install -r requirements-building.txt 28 | * python setup.py bdist_wheel 29 | 30 | # Test 31 | * follow steps in "Get Sources" 32 | * pip3 install -r requirements-building.txt 33 | * python setup.py test 34 | 35 | # Install 36 | 37 | ## Pip 38 | * pip install tinydb-encrypted-jsonstorage 39 | 40 | ## Pip + Git 41 | * pip install git+git://github.com/stefanthaler/tinydb-encrypted-jsonstorag.git#egg=tinydb-encrypted-jsonstorage 42 | 43 | ## Git + Local Pip 44 | * Follow steps in "Build" 45 | * pip install ./ 46 | 47 | # Use 48 | 49 | ## Create database 50 | You can use the encrypted storage by adding setting storage parameter of the TinyDB initializer to the EncryptedJSONStorage class. 51 | 52 | ``` python 53 | from tinydb import TinyDB 54 | import tinydb_encrypted_jsonstorage as tae 55 | KEY = "hello" 56 | PATH = ".encrypted_db" 57 | db = TinyDB(encryption_key=KEY, path=PATH, storage=tae.EncryptedJSONStorage) 58 | ``` 59 | 60 | ## Change encryption key 61 | You can change the encryption key of the storage by accessing the storage property of your [TinyDB](https://tinydb.readthedocs.io/en/latest/index.html) database. 62 | 63 | ``` python 64 | db = ... 65 | db.storage.change_encryption_key("NEW_KEY")) 66 | ``` 67 | 68 | ## Other operations 69 | 70 | For all the other operations, check the [TinyDB manual](https://tinydb.readthedocs.io/en/latest/index.html). 71 | 72 | ## TODO 73 | * documentation coverage + badge https://github.com/google/pybadges 74 | 75 | # Thanks 76 | * Shields.io, for providing the github paypal and the MIT license button: https://shields.io/ 77 | * [travis-ci.org](https://travis-ci.org) for providing free continuous integration for open source projects. 78 | * [coveralls.io](https://coveralls.io) for providing free continuous code coverage reporting for open source projects. 79 | -------------------------------------------------------------------------------- /tinydb_encrypted_jsonstorage/encrypted_json_storage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | EncryptedJSONStorage class 4 | 5 | Stores the data in an encrypted JSON file. 6 | 7 | """ 8 | 9 | from tinydb.storages import Storage, touch 10 | from typing import Dict, Any, Optional 11 | import json 12 | import os 13 | from os.path import join as join_path 14 | from Crypto.Cipher import AES 15 | from Crypto import Random 16 | from Crypto.Hash import SHA256 17 | import shutil 18 | import sys 19 | import traceback 20 | import shutil 21 | from tinydb import TinyDB 22 | import logging 23 | 24 | class EncryptedJSONStorage(Storage): 25 | """ 26 | Stores the data in an encrypted JSON file. 27 | """ 28 | 29 | def __init__(self, encryption_key, path: str, create_dirs=True, encoding=None, **kwargs): 30 | """ 31 | Create a new instance. 32 | Also creates the storage file, if it doesn't exist. 33 | :param path: Where to store the JSON data. 34 | :param encryption_key The encryption / decryption key 35 | :param create_dirs Whether or not to create the directories of the paths 36 | :param encoding encoding of the encrypted file. 37 | """ 38 | 39 | super().__init__() 40 | 41 | # create db if not exists 42 | touch(path, create_dirs=create_dirs) # Create file if not exists 43 | 44 | self.kwargs = kwargs 45 | self.raw_encryption_key=encryption_key 46 | self.encoding = encoding 47 | self.path=path 48 | self.chunk_size=64 49 | 50 | self.__reset_handle() 51 | 52 | def close(self) -> None: 53 | """ 54 | Closes the file handle to the storage 55 | """ 56 | self._handle.close() 57 | 58 | def read(self) -> Optional[Dict[str, Dict[str, Any]]]: 59 | """ 60 | Reads data from the file handle. This method is required from the TinyDB API. 61 | """ 62 | # Get the file size 63 | self._handle.seek(0, os.SEEK_END) 64 | size = self._handle.tell() 65 | 66 | if not size: 67 | # File is empty 68 | return None 69 | else: 70 | # load IV 71 | 72 | self._handle.seek(0) 73 | IV = self._handle.read(16) 74 | 75 | # load message_size 76 | db_size = self._handle.read(16) 77 | if (len(db_size)==0): 78 | return None 79 | db_size = int(db_size) 80 | # decrypt data 81 | crypt = AES.new(self.encryption_key, AES.MODE_CBC, IV) 82 | decrypted_chunks = [b""]*( int(db_size/self.chunk_size) + 1 ) 83 | for i in range(0,db_size,self.chunk_size): 84 | decrypted_chunks[int(i/self.chunk_size)] = crypt.decrypt(self._handle.read(self.chunk_size)) 85 | 86 | decrypted_db = b"".join(decrypted_chunks) 87 | 88 | if len(decrypted_db)==0: 89 | return None 90 | 91 | # parse json 92 | try: 93 | return json.loads(decrypted_db.decode("utf-8")) 94 | except: 95 | self.close() 96 | raise ValueError("Failed to read encrypted storage '%s', most likable cause is a corrupt database or a wrong encryption key."%self.path) 97 | 98 | 99 | def write(self, data: Dict[str, Dict[str, Any]]): 100 | """ 101 | Writes data tot he file. This method is required from the TinyDB API. 102 | :param data: A dictionary containing the JSON data to store. 103 | """ 104 | # backup old db 105 | self._handle.flush() 106 | os.fsync(self._handle.fileno()) 107 | self._handle.truncate() 108 | shutil.copyfile(self.path, self.path+"_backup") 109 | 110 | try: 111 | 112 | touch(self.path, False) # create database file 113 | 114 | # write IV 115 | self._handle.seek(0) 116 | #''.join(chr(random.randint(0, 0xFF)) 117 | IV = Random.new().read(16) 118 | self._handle.write(IV) 119 | 120 | # serialize and encrypt DB 121 | message = json.dumps(data, **self.kwargs) 122 | message_size = str(len(message)).zfill(16).encode("ascii") 123 | self._handle.write(message_size) 124 | 125 | crypt = AES.new(self.encryption_key, AES.MODE_CBC, IV) 126 | 127 | for i in range(0, len(message), self.chunk_size): 128 | 129 | chunk = message[i:i+self.chunk_size] 130 | if len(chunk) % self.chunk_size != 0: 131 | chunk += ' ' * (self.chunk_size - len(chunk) % self.chunk_size) 132 | self._handle.write(crypt.encrypt(str.encode(chunk))) 133 | 134 | # flush DB 135 | self._handle.flush() 136 | os.fsync(self._handle.fileno()) 137 | self._handle.truncate() 138 | except: 139 | logging.error("could not write database: ", sys.exc_info()[0]) 140 | #traceback.print_tb(sys.exc_info()[2]) 141 | self._handle.close() 142 | # Restore database 143 | shutil.copyfile(self.path+"_backup", self.path) 144 | self.__reset_handle() 145 | finally: 146 | os.remove(self.path+"_backup") 147 | 148 | def __reset_handle(self): 149 | """ 150 | Reopens the file handle with (potentially a new key) 151 | """ 152 | h = SHA256.new() 153 | h.update(str.encode(self.raw_encryption_key)) 154 | self.encryption_key = h.digest() 155 | self._handle = open(self.path, 'rb+', encoding=self.encoding) 156 | 157 | def change_encryption_key(self, new_encryption_key): 158 | """ 159 | Changes the encryption key of the storage to the new encryption key. Can be called via db.storage.change_encryption_key(...). 160 | :param new_encryption_key: A string that contains the new encryption key. 161 | """ 162 | new_db_path = self.path + "_clone" 163 | 164 | try: 165 | db_new_pw = TinyDB(encryption_key=new_encryption_key, path=new_db_path, storage=EncryptedJSONStorage) 166 | except: 167 | logger.error("Failed opening database with new password, aborting.", sys.exc_info()[0]) 168 | return False 169 | 170 | try: 171 | # copy from old to new 172 | self._handle.flush() 173 | db_new_pw.storage.write(self.read()) 174 | self.close() 175 | db_new_pw.close() 176 | 177 | # copy new over old 178 | shutil.copyfile(new_db_path, self.path) 179 | 180 | # reset encryption handle 181 | self.raw_encryption_key=new_encryption_key 182 | self.__reset_handle() 183 | 184 | success=True 185 | except: 186 | logger.error("could not write database: ", sys.exc_info()[0]) 187 | #print("Error: ", sys.exc_info()[1]) 188 | #traceback.print_tb(sys.exc_info()[2]) 189 | success=False 190 | finally: 191 | os.remove(new_db_path) 192 | return success 193 | --------------------------------------------------------------------------------