├── tests ├── __init__.py └── tests.py ├── setup.cfg ├── history.rst ├── .travis.yml ├── MANIFEST.in ├── jsondb ├── __init__.py ├── file_writer.py ├── compat.py └── db.py ├── .gitignore ├── LICENSE.md ├── setup.py └── readme.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [wheel] 2 | universal = 1 3 | 4 | [metadata] 5 | description-file = readme.md 6 | -------------------------------------------------------------------------------- /history.rst: -------------------------------------------------------------------------------- 1 | .. :changelog: 2 | 3 | History 4 | ------- 5 | 6 | `See release notes 7 | `_ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '2.7' 4 | - '3.6' 5 | 6 | install: 7 | - pip install pymongo 8 | 9 | script: nosetests 10 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include history.rst 2 | include readme.md 3 | 4 | recursive-include tests * 5 | 6 | recursive-exclude * *.pyc 7 | recursive-exclude * *.py~ 8 | -------------------------------------------------------------------------------- /jsondb/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | A flat file database for json objects. 3 | """ 4 | 5 | from .db import Database 6 | 7 | __version__ = '0.1.7' 8 | __author__ = 'Gunther Cox' 9 | __email__ = 'gunthercx@gmail.com' 10 | __url__ = 'https://github.com/gunthercox/jsondb' 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | 56 | # Automatically created test database 57 | test.db 58 | -------------------------------------------------------------------------------- /jsondb/file_writer.py: -------------------------------------------------------------------------------- 1 | from .compat import decode, encode, open_file_for_reading, open_file_for_writing 2 | 3 | 4 | def read_data(file_path): 5 | """ 6 | Reads a file and returns a json encoded representation of the file. 7 | """ 8 | 9 | if not is_valid(file_path): 10 | write_data(file_path, {}) 11 | 12 | db = open_file_for_reading(file_path) 13 | content = db.read() 14 | 15 | obj = decode(content) 16 | 17 | db.close() 18 | 19 | return obj 20 | 21 | def write_data(path, obj): 22 | """ 23 | Writes to a file and returns the updated file content. 24 | """ 25 | with open_file_for_writing(path) as db: 26 | db.write(encode(obj)) 27 | 28 | return obj 29 | 30 | def is_valid(file_path): 31 | """ 32 | Check to see if a file exists or is empty. 33 | """ 34 | from os import path, stat 35 | 36 | can_open = False 37 | 38 | try: 39 | with open(file_path) as fp: 40 | can_open = True 41 | except IOError: 42 | return False 43 | 44 | is_file = path.isfile(file_path) 45 | 46 | return path.exists(file_path) and is_file and stat(file_path).st_size > 0 47 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The BSD 3-Clause License 2 | 3 | Copyright (c) 2016, Gunther Cox 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | 12 | * Neither the name of chatterbot nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | try: 5 | from setuptools import setup 6 | except ImportError: 7 | from distutils.core import setup 8 | 9 | HISTORY = open('history.rst').read().replace('.. :changelog:', '') 10 | 11 | README = lambda f: open(f, 'r').read() 12 | 13 | # Dynamically import the package information 14 | JSONDB = __import__('jsondb') 15 | VERSION = JSONDB.__version__ 16 | AUTHOR = JSONDB.__author__ 17 | AUTHOR_EMAIL = JSONDB.__email__ 18 | URL = JSONDB.__url__ 19 | DESCRIPTION = JSONDB.__doc__ 20 | 21 | setup( 22 | name='jsondatabase', 23 | version=VERSION, 24 | description=DESCRIPTION, 25 | long_description=README('readme.md') + '\n\n' + HISTORY, 26 | author=AUTHOR, 27 | author_email=AUTHOR_EMAIL, 28 | url=URL, 29 | install_requires=['pymongo'], 30 | packages=['jsondb'], 31 | package_dir={'jsondb': 'jsondb'}, 32 | include_package_data=True, 33 | license='BSD', 34 | zip_safe=True, 35 | keywords=['jsondb'], 36 | classifiers=[ 37 | 'Development Status :: 2 - Pre-Alpha', 38 | 'Intended Audience :: Developers', 39 | 'License :: OSI Approved :: BSD License', 40 | 'Natural Language :: English', 41 | 'Programming Language :: Python :: 2', 42 | 'Programming Language :: Python :: 2.6', 43 | 'Programming Language :: Python :: 2.7', 44 | 'Programming Language :: Python :: 3', 45 | 'Programming Language :: Python :: 3.3', 46 | 'Programming Language :: Python :: 3.4', 47 | ], 48 | test_suite='tests', 49 | tests_require=[] 50 | ) 51 | -------------------------------------------------------------------------------- /jsondb/compat.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import sys 3 | import io 4 | 5 | 6 | try: 7 | # Use the faster cjson library if it is available 8 | import cjson as json 9 | 10 | json_encode = json.encode 11 | json_decode = json.decode 12 | 13 | except ImportError: 14 | import json 15 | 16 | json_encode = json.dumps 17 | json_decode = json.loads 18 | 19 | 20 | def encode(value): 21 | from bson import json_util 22 | 23 | value = json_encode(value, ensure_ascii=False, default=json_util.default) 24 | if sys.version < '3': 25 | return unicode(value) 26 | return value 27 | 28 | 29 | def decode(value): 30 | from bson import json_util 31 | 32 | return json_decode(value, encoding='utf-8', object_hook=json_util.object_hook) 33 | 34 | 35 | if sys.version < '3': 36 | 37 | # Python 2 and 3 unicode string compatability 38 | def u(x): 39 | return codecs.unicode_escape_decode(x)[0] 40 | 41 | # Dictionary iteration compatibility 42 | def iteritems(dictionary): 43 | return dictionary.iteritems() 44 | else: 45 | def u(x): 46 | return x 47 | 48 | # Dictionary iteration compatibility 49 | def iteritems(dictionary): 50 | return dictionary.items() 51 | 52 | 53 | def open_file_for_reading(*args, **kwargs): 54 | if sys.version < '3': 55 | kwargs['mode'] = 'rb+' 56 | else: 57 | kwargs['encoding'] = 'utf-8' 58 | kwargs['mode'] = 'r+' 59 | 60 | return io.open(*args, **kwargs) 61 | 62 | 63 | def open_file_for_writing(*args, **kwargs): 64 | if sys.version < '3': 65 | kwargs['mode'] = 'w+' 66 | else: 67 | kwargs['encoding'] = 'utf-8' 68 | kwargs['mode'] = 'w+' 69 | 70 | return io.open(*args, **kwargs) 71 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # jsondb 2 | 3 | [![Build Status][travis-image]][travis-build] 4 | [![PyPI version](https://badge.fury.io/py/jsondatabase.svg)](https://badge.fury.io/py/jsondatabase) 5 | 6 | This is a utility for managing content in a database which stores 7 | content in JSON format. 8 | 9 | ## Installation 10 | 11 | This package can be installed from [PyPi](https://pypi.python.org/pypi/jsondatabase) by running: 12 | 13 | ```bash 14 | pip install jsondatabase 15 | ``` 16 | 17 | Note, the package name and the import name are different. 18 | Import the package using `import jsondb`. 19 | 20 | ## Usage 21 | 22 | ```python 23 | from jsondb.db import Database 24 | db = Database("mydata.db") 25 | ``` 26 | 27 | The database has an attribute which works similar to 28 | [jQuery's `data`][jquery-data] attribute. 29 | 30 | ```python 31 | # Getting all data 32 | db = Database("mydata.db") 33 | print(db.data()) 34 | ``` 35 | 36 | ```python 37 | # Getting a stored value 38 | db = Database("mydata.db") 39 | print(db.data(key="user_count")) 40 | ``` 41 | 42 | **It is important to note that a key will be created regardless of whether it 43 | exists as long as a value is provided.** The database has the same functionality 44 | as a dictionary. 45 | 46 | ```python 47 | # Setting a value 48 | db = Database("mydata.db") 49 | db.data(key="user_count", value=241) 50 | ``` 51 | 52 | ```python 53 | # Passing in a dictionary value 54 | db = Database("mydata.db") 55 | data = { 56 | "user_id": 234565, 57 | "user_name": "AwesomeUserName", 58 | "is_moderator": True, 59 | } 60 | db.data(dictionary=data) 61 | ``` 62 | 63 | ```python 64 | # Deleting a value 65 | db = Database("mydata.db") 66 | db.delete("my_key") 67 | ``` 68 | 69 | The database also supports a dictionary-like syntax for retrieving, setting, and 70 | removing values. 71 | 72 | ```python 73 | db = Database("mydata.db") 74 | 75 | # Retrieving a value 76 | value = db["key"] 77 | 78 | # Setting a value 79 | db["key"] = value 80 | 81 | # Removing a key 82 | del db["key"] 83 | 84 | # Checking if a key exists 85 | "key" in db 86 | ``` 87 | 88 | ## Performance 89 | If performance is an issue with large databases then the `python-cjson` module 90 | can be installed. jsondb will automatically detect this and use cjson instead. 91 | 92 | [jquery-data]: http://api.jquery.com/data/ 93 | [travis-build]: https://travis-ci.org/gunthercox/jsondb 94 | [travis-image]: https://travis-ci.org/gunthercox/jsondb.svg 95 | -------------------------------------------------------------------------------- /jsondb/db.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .file_writer import is_valid 3 | from .compat import iteritems 4 | 5 | 6 | class Database(object): 7 | """ 8 | A class for a dictionary of data stored in a JSON file. 9 | """ 10 | 11 | def __init__(self, file_path): 12 | """ 13 | This class manages a json-formatted file database. 14 | Constructor takes the file path of the database as a parameter. 15 | """ 16 | from .file_writer import read_data, write_data 17 | 18 | self.read_data = read_data 19 | self.write_data = write_data 20 | 21 | self.path = None 22 | self.set_path(file_path) 23 | 24 | self._data = {} 25 | 26 | def memory_read(self, _): 27 | """ 28 | Return the in-memory data. 29 | """ 30 | return self._data 31 | 32 | def memory_write(self, _, data): 33 | """ 34 | Write to the in-memory data. 35 | """ 36 | self._data = data 37 | 38 | def set_path(self, file_path): 39 | """ 40 | Set the path of the database. 41 | Create the file if it does not exist. 42 | """ 43 | if not file_path: 44 | self.read_data = self.memory_read 45 | self.write_data = self.memory_write 46 | elif not is_valid(file_path): 47 | self.write_data(file_path, {}) 48 | 49 | self.path = file_path 50 | 51 | def _get_content(self, key=None): 52 | obj = self.read_data(self.path) 53 | 54 | if key or key == "": 55 | if key in obj.keys(): 56 | return obj[key] 57 | else: 58 | return None 59 | 60 | return obj 61 | 62 | def _set_content(self, key, value): 63 | obj = self._get_content() 64 | obj[key] = value 65 | 66 | self.write_data(self.path, obj) 67 | 68 | def delete(self, key): 69 | """ 70 | Removes the specified key from the database. 71 | """ 72 | obj = self._get_content() 73 | obj.pop(key, None) 74 | 75 | self.write_data(self.path, obj) 76 | 77 | def data(self, **kwargs): 78 | """ 79 | If a key is passed in, a corresponding value will be returned. 80 | If a key-value pair is passed in then the corresponding key in 81 | the database will be set to the specified value. 82 | A dictionary can be passed in as well. 83 | If a key does not exist and a value is provided then an entry 84 | will be created in the database. 85 | """ 86 | 87 | key = kwargs.pop('key', None) 88 | value = kwargs.pop('value', None) 89 | dictionary = kwargs.pop('dictionary', None) 90 | 91 | # Fail if a key and a dictionary or a value and a dictionary are given 92 | if (key is not None and dictionary is not None) or \ 93 | (value is not None and dictionary is not None): 94 | raise ValueError 95 | 96 | # If only a key was provided return the corresponding value 97 | if key is not None and value is None: 98 | return self._get_content(key) 99 | 100 | # if a key and a value are passed in 101 | if key is not None and value is not None: 102 | self._set_content(key, value) 103 | 104 | if dictionary is not None: 105 | for key in dictionary.keys(): 106 | value = dictionary[key] 107 | self._set_content(key, value) 108 | 109 | return self._get_content() 110 | 111 | def _contains_value(self, obj, keys, find_value): 112 | key = keys.pop(0) 113 | 114 | # If there are no keys left 115 | if not len(keys): 116 | if obj[key] == find_value: 117 | return True 118 | else: 119 | return False 120 | 121 | if isinstance(obj, dict): 122 | if key in obj: 123 | return self._contains_value(obj[key], keys, find_value) 124 | 125 | def filter(self, filter_arguments): 126 | """ 127 | Takes a dictionary of filter parameters. 128 | Return a list of objects based on a list of parameters. 129 | """ 130 | results = self._get_content() 131 | 132 | # Filter based on a dictionary of search parameters 133 | if isinstance(filter_arguments, dict): 134 | for item, content in iteritems(self._get_content()): 135 | for key, value in iteritems(filter_arguments): 136 | keys = key.split('.') 137 | value = filter_arguments[key] 138 | 139 | if not self._contains_value({item: content}, keys, value): 140 | del results[item] 141 | 142 | # Filter based on an input string that should match database key 143 | if isinstance(filter_arguments, str): 144 | if filter_arguments in results: 145 | return [{filter_arguments: results[filter_arguments]}] 146 | else: 147 | return [] 148 | 149 | return results 150 | 151 | def drop(self): 152 | """ 153 | Remove the database by deleting the JSON file. 154 | """ 155 | import os 156 | 157 | if self.path: 158 | if os.path.exists(self.path): 159 | os.remove(self.path) 160 | else: 161 | # Clear the in-memory data if there is no file path 162 | self._data = {} 163 | 164 | # Iterator methods 165 | 166 | def __len__(self): 167 | return len(self._get_content()) 168 | 169 | def __iter__(self): 170 | return iter(self._get_content().keys()) 171 | 172 | def __list__(self): 173 | return list(self._get_content().keys()) 174 | 175 | # Dictionary compatibility methods 176 | 177 | def __getitem__(self, key): 178 | return self.data(key=key) 179 | 180 | def __setitem__(self, key, value): 181 | return self.data(key=key, value=value) 182 | 183 | def __delitem__(self, key): 184 | return self.delete(key=key) 185 | 186 | def __contains__(self, key): 187 | return key in self._get_content() 188 | -------------------------------------------------------------------------------- /tests/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from unittest import TestCase 3 | from jsondb.db import Database 4 | from jsondb.compat import u 5 | 6 | 7 | class BaseTestCase(TestCase): 8 | 9 | def setUp(self): 10 | import json 11 | 12 | content = { 13 | "url": "http://sky.net", 14 | "ip": "http://10.0.1.337", 15 | } 16 | 17 | with open("test.db", "w+") as test: 18 | json.dump(content, test) 19 | 20 | self.database = Database("test.db") 21 | 22 | def tearDown(self): 23 | import os 24 | 25 | os.remove("test.db") 26 | 27 | 28 | class Tests(BaseTestCase): 29 | 30 | def test_create_none_exists(self): 31 | """ 32 | A file should be created if none exists 33 | and an empty {} is placed in the file. 34 | """ 35 | from os import path 36 | import os 37 | 38 | database = Database("new.db") 39 | 40 | data = "" 41 | with open ("new.db", "r") as db: 42 | data = db.read() 43 | 44 | self.assertTrue(path.exists("new.db")) 45 | self.assertTrue("{}" in data) 46 | 47 | os.remove("new.db") 48 | 49 | def test_empty_file_initialized(self): 50 | """ 51 | Test that when a file exists but is empty, 52 | a {} is added. 53 | """ 54 | import os 55 | 56 | # Create an new empty file 57 | open("empty.db", 'a').close() 58 | 59 | database = Database("empty.db") 60 | 61 | data = "" 62 | with open ("empty.db", "r") as db: 63 | data = db.read() 64 | 65 | self.assertTrue("{}" in data) 66 | 67 | os.remove("empty.db") 68 | 69 | def test_get_key(self): 70 | key = self.database.data(key="url") 71 | 72 | self.assertEqual(key, "http://sky.net") 73 | 74 | def test_get_key_not_in_database(self): 75 | key = self.database.data(key="bogus") 76 | 77 | self.assertEqual(key, None) 78 | 79 | def test_assign_key_value_pair(self): 80 | self.database.data(key="cool", value="robot") 81 | 82 | self.assertEqual(self.database.data(key="cool"), "robot") 83 | 84 | def test_assign_dictionary(self): 85 | self.database.data(dictionary={ 86 | "id": "123456", 87 | "arduino_ip": "xxxxxx" 88 | }) 89 | 90 | self.assertTrue("id" in self.database.data()) 91 | self.assertEqual(self.database.data(key="arduino_ip"), "xxxxxx") 92 | 93 | def test_delete_key(self): 94 | self.database.data(key="id", value="13") 95 | self.database.delete("id") 96 | 97 | self.assertFalse("id" in self.database.data()) 98 | 99 | def test_empty_key_string(self): 100 | data = self.database.data(key="") 101 | 102 | self.assertEqual(data, None) 103 | 104 | 105 | class FilterTests(BaseTestCase): 106 | 107 | def setUp(self): 108 | super(FilterTests, self).setUp() 109 | 110 | self.key_values = { 111 | "Eukarya": ["potatoes", "pine trees", "amoebae", "dogs"], 112 | "Prokaryote": ["Escherichia coli", "Streptomyces soil"] 113 | } 114 | 115 | self.domain_values = { 116 | "name": "Eukarya", 117 | "kingdom": { 118 | "name": "Animalia", 119 | "phylum": { 120 | "name": "Chordata", 121 | "class": { 122 | "name": "Mamalia", 123 | "order": { 124 | "name": "Carnivora", 125 | "family": { 126 | "name": "Canidae", 127 | "genus": { 128 | "name": "Vulpes", 129 | "species": { 130 | "name": "vulpes" 131 | } 132 | } 133 | } 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | def test_filter_nested_object_equals(self): 141 | self.database.data(key="domain", value=self.domain_values) 142 | 143 | results = self.database.filter({ 144 | "domain.kingdom.phylum.class.name": "Mamalia" 145 | }) 146 | 147 | self.assertEqual(len(results), 1) 148 | self.assertIn("domain", results) 149 | self.assertEqual(results["domain"], self.domain_values) 150 | 151 | def test_filter_nested_object_equals_false(self): 152 | self.database.data(key="domain", value=self.domain_values) 153 | 154 | results = self.database.filter({ 155 | "domain.kingdom.phylum.class.name": "Fake" 156 | }) 157 | 158 | self.assertEqual(len(results), 0) 159 | 160 | def test_filter_by_keys(self): 161 | self.database.data(dictionary=self.key_values) 162 | 163 | results = self.database.filter("Prokaryote") 164 | 165 | self.assertEqual(len(results), 1) 166 | self.assertIn("Prokaryote", results[0]) 167 | 168 | def test_filter_by_keys_false(self): 169 | self.database.data(dictionary=self.key_values) 170 | 171 | results = self.database.filter("James the Orangatang") 172 | 173 | self.assertEqual(len(results), 0) 174 | 175 | class UnicodeTests(BaseTestCase): 176 | 177 | def test_data_with_unicode_key(self): 178 | 179 | unicode_text = u("∰ ∱ ∲ ∳ ⨋ ⨌⨔ ⨕ ⨖ ⨗ ⨘ ⨙ ⨚ ∫ ∬ ∭ ∮ ∯ ⨍ ⨎ ⨏ ⨐ ⨑ ⨒ ⨓ ⨛ ⨜") 180 | 181 | self.database.data(key=unicode_text, value=13) 182 | 183 | self.assertTrue(unicode_text in self.database.data()) 184 | self.assertEqual(self.database.data(key=unicode_text), 13) 185 | 186 | def test_data_with_unicode_value(self): 187 | 188 | unicode_text = u("∠ ∡ ⦛ ⦞ ⦟ ⦢ ⦣ ⦤ ⦥ ⦦ ⦧ ⦨ ⦩ ⦪ ⦫ ⦬ ⦭ ⦮ ⦯ ⦓ ⦔ ⦕ ⦖ ⟀") 189 | 190 | self.database.data(key="unicode", value=unicode_text) 191 | 192 | self.assertEqual(unicode_text, self.database.data(key="unicode")) 193 | 194 | 195 | class DictCompatibleTests(BaseTestCase): 196 | 197 | def test_get_data(self): 198 | data = self.database["url"] 199 | 200 | self.assertEqual(data, "http://sky.net") 201 | 202 | def test_get_unset_data(self): 203 | data = self.database["invalid"] 204 | 205 | self.assertEqual(data, None) 206 | 207 | def test_set_data(self): 208 | self.database["new"] = "test" 209 | 210 | self.assertEqual(self.database["new"], "test") 211 | 212 | def test_key_exists(self): 213 | self.assertTrue("url" in self.database) 214 | self.assertFalse("invalid" in self.database) 215 | 216 | def test_delete_data(self): 217 | del self.database["url"] 218 | 219 | self.assertEqual(self.database["url"], None) 220 | 221 | 222 | class InMemoryTests(TestCase): 223 | 224 | def setUp(self): 225 | self.database = Database(None) 226 | 227 | def test_nonexistant_database_is_invalid(self): 228 | self.database.data(key="memory", value="yes") 229 | 230 | self.assertEqual(self.database.data(key="memory"), "yes") 231 | 232 | 233 | class UtilityTests(TestCase): 234 | 235 | def test_nonexistant_database_is_invalid(self): 236 | from jsondb.file_writer import is_valid 237 | 238 | self.assertFalse(is_valid("some_nonexistant_file.db")) 239 | 240 | --------------------------------------------------------------------------------