From 95767ae68455c053a84cd1672954c4cbf75554a2 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sun, 28 Jan 2018 04:44:03 -0600 Subject: [PATCH] initial --- .gitignore | 118 +++++++++++++++++++++ Dockerfile | 34 ++++++ Makefile | 4 + Pipfile | 28 +++++ Pipfile.lock | 243 +++++++++++++++++++++++++++++++++++++++++++ README.markdown | 40 +++++++ README.md | 2 - circle.yml | 11 ++ jsondict/__init__.py | 63 +++++++++++ setup.cfg | 18 ++++ setup.py | 36 +++++++ tests/__init__.py | 3 + tests/test_import.py | 11 ++ tests/test_pdd.py | 40 +++++++ 14 files changed, 649 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 README.markdown delete mode 100644 README.md create mode 100644 circle.yml create mode 100644 jsondict/__init__.py create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 tests/__init__.py create mode 100644 tests/test_import.py create mode 100644 tests/test_pdd.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b990971 --- /dev/null +++ b/.gitignore @@ -0,0 +1,118 @@ +README.rst + +.vagrant + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit tests / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# IPython Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env +envfile + +# virtualenv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject + +# Vim +Session.vim + +# Pycharm +.idea + +# sqlite db for testing +local.db +/tests/fast-test.sh +/tests/fastest-test.sh +/tests/sbds-install.sh +/tests/test.sh +/tests/sync.sh +/tests/failed_blocks/ +tests/failed_blocks/ +/envdir/ +/sbds.egg-info/ +/envd/ +/tests/envdir-to-envfile.sh +/deploy/ +/test.py + +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1bd59dd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +FROM phusion/baseimage:0.9.19 + +# Standard stuff +ENV LANG en_US.UTF-8 +ENV LC_ALL en_US.UTF-8 + +# Stuff for building steem-python +ARG BUILD_ROOT=/build + +# Now we install the essentials +RUN \ + apt-get update && \ + apt-get install -y python3-pip + +# This updates the distro-provided pip +RUN pip3 install --upgrade pip + +RUN mkdir ${BUILD_ROOT} + +COPY Makefile ${BUILD_ROOT}/ +COPY Pipfile ${BUILD_ROOT}/ +COPY Pipfile.lock ${BUILD_ROOT}/ + +WORKDIR ${BUILD_ROOT} + +RUN pip3 install --upgrade pip && \ + pip3 install --upgrade pipenv && \ + pipenv install --three --dev && \ + pipenv install . + +COPY . ${BUILD_ROOT} + +# run tests +RUN pipenv run py.test diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..097f2b3 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +default: test + +test: + pipenv run pytest diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..e5dd0cf --- /dev/null +++ b/Pipfile @@ -0,0 +1,28 @@ +[[source]] + +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + + +[packages] + +attrdict = "*" +appdirs = "*" + + +[dev-packages] + +"pep8" = "*" +pytest = "*" +"pytest-pep8" = "*" +pytest-pylint = "*" +yapf = "*" +"autopep8" = "*" +"flake8" = "*" +"pytest-flake8" = "*" + + +[requires] + +python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..489e148 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,243 @@ +{ + "_meta": { + "hash": { + "sha256": "0cff184f2ce33ae5643e32fe505945779e8c9ae2e91b46c1c38f5938bf42050e" + }, + "host-environment-markers": { + "implementation_name": "cpython", + "implementation_version": "3.6.4", + "os_name": "posix", + "platform_machine": "x86_64", + "platform_python_implementation": "CPython", + "platform_release": "17.3.0", + "platform_system": "Darwin", + "platform_version": "Darwin Kernel Version 17.3.0: Thu Nov 9 18:09:22 PST 2017; root:xnu-4570.31.3~1/RELEASE_X86_64", + "python_full_version": "3.6.4", + "python_version": "3.6", + "sys_platform": "darwin" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.6" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "appdirs": { + "hashes": [ + "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e", + "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92" + ], + "version": "==1.4.3" + }, + "attrdict": { + "hashes": [ + "sha256:86aeb6d3809e0344409f8148d7cac9eabce5f0b577c160b5e90d10df3f8d2ad3" + ], + "version": "==2.0.0" + }, + "six": { + "hashes": [ + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb", + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9" + ], + "version": "==1.11.0" + } + }, + "develop": { + "apipkg": { + "hashes": [ + "sha256:65d2aa68b28e7d31233bb2ba8eb31cda40e4671f8ac2d6b241e358c9652a74b9", + "sha256:2e38399dbe842891fe85392601aab8f40a8f4cc5a9053c326de35a1cc0297ac6" + ], + "version": "==1.4" + }, + "astroid": { + "hashes": [ + "sha256:db5cfc9af6e0b60cd07c19478fb54021fc20d2d189882fbcbc94fc69a8aecc58", + "sha256:f0a0e386dbca9f93ea9f3ea6f32b37a24720502b7baa9cb17c3976a680d43a06" + ], + "version": "==1.6.1" + }, + "attrs": { + "hashes": [ + "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450", + "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9" + ], + "version": "==17.4.0" + }, + "autopep8": { + "hashes": [ + "sha256:c7be71ab0cb2f50c9c22c82f0c9acaafc6f57492c3fbfee9790c415005c2b9a5" + ], + "version": "==1.3.4" + }, + "execnet": { + "hashes": [ + "sha256:fc155a6b553c66c838d1a22dba1dc9f5f505c43285a878c6f74a79c024750b83", + "sha256:a7a84d5fa07a089186a329528f127c9d73b9de57f1a1131b82bb5320ee651f6a" + ], + "version": "==1.5.0" + }, + "flake8": { + "hashes": [ + "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37", + "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0" + ], + "version": "==3.5.0" + }, + "isort": { + "hashes": [ + "sha256:cd5d3fc2c16006b567a17193edf4ed9830d9454cbeb5a42ac80b36ea00c23db4", + "sha256:79f46172d3a4e2e53e7016e663cc7a8b538bec525c36675fcfd2767df30b3983" + ], + "version": "==4.2.15" + }, + "lazy-object-proxy": { + "hashes": [ + "sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019", + "sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39", + "sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c", + "sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e", + "sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b", + "sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6", + "sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b", + "sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d", + "sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff", + "sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd", + "sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f", + "sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514", + "sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92", + "sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35", + "sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff", + "sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252", + "sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7", + "sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b", + "sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f", + "sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4", + "sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577", + "sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d", + "sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109", + "sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2", + "sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33", + "sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d", + "sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5", + "sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088", + "sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a" + ], + "version": "==1.3.1" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "pep8": { + "hashes": [ + "sha256:b22cfae5db09833bb9bd7c8463b53e1a9c9b39f12e304a8d0bba729c501827ee", + "sha256:fe249b52e20498e59e0b5c5256aa52ee99fc295b26ec9eaa85776ffdb9fe6374" + ], + "version": "==1.7.1" + }, + "pluggy": { + "hashes": [ + "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff" + ], + "version": "==0.6.0" + }, + "py": { + "hashes": [ + "sha256:8cca5c229d225f8c1e3085be4fcf306090b00850fefad892f9d96c7b6e2f310f", + "sha256:ca18943e28235417756316bfada6cd96b23ce60dd532642690dcfdaba988a76d" + ], + "version": "==1.5.2" + }, + "pycodestyle": { + "hashes": [ + "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9", + "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766" + ], + "version": "==2.3.1" + }, + "pyflakes": { + "hashes": [ + "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f", + "sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805" + ], + "version": "==1.6.0" + }, + "pylint": { + "hashes": [ + "sha256:156839bedaa798febee72893beef00c650c2e7abafb5586fc7a6a56be7f80412", + "sha256:4fe3b99da7e789545327b75548cee6b511e4faa98afe268130fea1af4b5ec022" + ], + "version": "==1.8.2" + }, + "pytest": { + "hashes": [ + "sha256:b84878865558194630c6147f44bdaef27222a9f153bbd4a08908b16bf285e0b1", + "sha256:53548280ede7818f4dc2ad96608b9f08ae2cc2ca3874f2ceb6f97e3583f25bc4" + ], + "version": "==3.3.2" + }, + "pytest-cache": { + "hashes": [ + "sha256:be7468edd4d3d83f1e844959fd6e3fd28e77a481440a7118d430130ea31b07a9" + ], + "version": "==1.0" + }, + "pytest-flake8": { + "hashes": [ + "sha256:e67686645860009cf1f9a5016e110234b4a2c4584711d62ba753ed651ab9ab28", + "sha256:e716072d07a557defdd5c4141984569731e292961370a5663c1697283aa16200" + ], + "version": "==0.9.1" + }, + "pytest-pep8": { + "hashes": [ + "sha256:032ef7e5fa3ac30f4458c73e05bb67b0f036a8a5cb418a534b3170f89f120318" + ], + "version": "==1.0.6" + }, + "pytest-pylint": { + "hashes": [ + "sha256:9b8ca25823b2f39e89d8170453f5282e57b973395060e838ced5f8c09271ca65", + "sha256:2efaf761472637df9a8f4a3f4fac37f8ce433d70957c5f5767c4be322a42a3d2", + "sha256:9f38725b22967a56724115c9df0a93dda37fea71dd5495fb1354b82e3d938d0d", + "sha256:85da6403c69eb715b9703df640818f337603f2cac947f932b033588851aaaf16", + "sha256:b85763dc36757bfb736b07fecb4f67a0892dcb00868e01f150c7424f608bd62e", + "sha256:ec63f7c4c05331654ab54fda8e68b8a11512009d506a8e35ee9b6d40a359356d", + "sha256:2bb26948f0355d14b274742153a6b4daa51e6d60481143bfd7f025699a27210d" + ], + "version": "==0.7.1" + }, + "six": { + "hashes": [ + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb", + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9" + ], + "version": "==1.11.0" + }, + "wrapt": { + "hashes": [ + "sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6" + ], + "version": "==1.10.11" + }, + "yapf": { + "hashes": [ + "sha256:a0bbc8ed274609f9c7575a5d69056fa393e26a778b3e070a72f4998b8e90c3cd", + "sha256:bd19f246be7193ad2acdc04702b92315f1ae28d49c82f6671afdeefe9d32f468" + ], + "version": "==0.20.1" + } + } +} diff --git a/README.markdown b/README.markdown new file mode 100644 index 0000000..98f2e05 --- /dev/null +++ b/README.markdown @@ -0,0 +1,40 @@ +# jsondict + +This is a python package called jsondict that wraps a dict to +provide dumb json file backed persistence. + +It also returns a default of `None` for missing keys instead of KeyError, +because an exception for an undefined key is annoying, so it's sort of like +a defaultdict. + +It's not general purpose, and it's not for large amounts of data. It's +just sort of like a standard dictionary that you don't need to worry about +reading/saving to disk. It's naive and inefficient but perfect for +application configs and whatnot. Don't be silly and put more than a few +kilobytes in this, the whole file is written out every update and the whole +file is re-read and re-parsed every read. + +# Installation + +``` +pip3 install --upgrade pipenv +git clone https://github.com/sneak/jsondict.git +cd jsondict +pipenv install --three . +``` + +# Other Info + +Right now it's only tested on python3. Python2 support is desired. + +# Author + +Jeffrey Paul + +# See Also + +* [attrdict](https://github.com/bcj/AttrDict) + +# License + +MIT diff --git a/README.md b/README.md deleted file mode 100644 index a0672b2..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# jsondict -dumb json persistence for a python dictionary diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..63ca215 --- /dev/null +++ b/circle.yml @@ -0,0 +1,11 @@ +machine: + services: + - docker + +dependencies: + override: + - echo "Ignore CircleCI detected dependencies" + +test: + override: + - docker build -t sneak/persistentattrdict . diff --git a/jsondict/__init__.py b/jsondict/__init__.py new file mode 100644 index 0000000..8de3975 --- /dev/null +++ b/jsondict/__init__.py @@ -0,0 +1,63 @@ +from attrdict import AttrDict +import json +import os.path +import os +import tempfile + + +class JsonDict(object): + def __init__(self,persistence=None): + # TODO: either filename or file-like object + # for now, just filename + self._persistence = persistence + self._wrapped = dict() + + def __len__(self): + self._reread() + return len(self._wrapped) + + def keys(self): + self._reread() + return self._wrapped.keys() + + def values(self): + self._reread() + return self._wrapped.values() + + def __setitem__(self, key, item): + self._wrapped[key] = item + self._commit() + return item + + def __delitem__(self, key): + del self._wrapped[key] + self._commit() + + def __getitem__(self, key): + self._reread() + if key in self._wrapped: + return self._wrapped[key] + else: + return None + + def _commit(self): + serialized = json.dumps(self._wrapped) + atomic_write(self._persistence, serialized) + + def _reread(self): + with open(self._persistence, 'rb') as fd: + self._wrapped = json.load(fd) + +#def PersistentAppAttrDict(appname): + #configfilename = appdirs.user_data_dir(appname,appname) + "appconfig.json" + #open + +def atomic_write(fn,content): + filedir = os.path.dirname(os.path.abspath(fn)) + if not os.path.exists(filedir): + os.makedirs(filedir) + (tempfd, tempfn) = tempfile.mkstemp(dir=filedir) + tempfh = os.fdopen(tempfd, 'w') + tempfh.write(content) + tempfh.close() + os.rename(tempfn,fn) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..26b0aa0 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,18 @@ +[metadata] +description-file=README.markdown +[aliases] +test=pytest + +[tool:pytest] +norecursedirs=dist docs build .tox deploy +addopts = --pep8 --flake8 +testpaths = tests + +[yapf] +indent_width = 4 +column_limit = 77 +based_on_style = pep8 +spaces_before_comment = 2 +split_before_logical_operator = true +dedent_closing_brackets = true +i18n_comment = NOQA diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..c92b4f2 --- /dev/null +++ b/setup.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +from pipenv.project import Project +from pipenv.utils import convert_deps_to_pip +from setuptools import setup, find_packages +import sys + +pfile = Project(chdir=False).parsed_pipfile +requirements = convert_deps_to_pip(pfile['packages'], r=False) +test_requirements = convert_deps_to_pip(pfile['dev-packages'], r=False) + +setup( + name='jsondict', + version='0.0.1', + description='dumb json persistence for a dict with defaults', + long_description=open('README.markdown','r').read(), + keywords=['persistence', 'json', 'datastructure'], + license='MIT', + url='https://github.com/sneak/jsondict', + maintainer='Jeffrey Paul', + maintainer_email='sneak@sneak.berlin', + packages=find_packages(), + setup_requires=[ + 'pytest-runner', + 'pipenv', + ], + tests_require=test_requirements, + install_requires=requirements, + classifiers=[ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Natural Language :: English', + 'Programming Language :: Python :: 3', + 'Topic :: Software Development :: Libraries', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Development Status :: 4 - Beta' + ]) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..be0b585 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +from logging import getLogger + +getLogger('flake8').propagate = False diff --git a/tests/test_import.py b/tests/test_import.py new file mode 100644 index 0000000..122a027 --- /dev/null +++ b/tests/test_import.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +import os +import sys +sys.path.insert(0, os.path.abspath('..')) + +from jsondict import JsonDict # noqa + + +# pylint: disable=unused-import,unused-variable +def test_import(): + _ = JsonDict() # noqa diff --git a/tests/test_pdd.py b/tests/test_pdd.py new file mode 100644 index 0000000..51ca355 --- /dev/null +++ b/tests/test_pdd.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +from jsondict import JsonDict, atomic_write +import json +import unittest + + +class Testcases(unittest.TestCase): + + def test_atomic_write(self): + ts = "this is my test string" + tfn = '/tmp/pad_test.txt' + atomic_write(tfn, ts) + with open(tfn, 'r') as fh: + self.assertEqual(fh.read(), ts) + + def test_persistence(self): + tfn = '/tmp/pad_test.json' + mypdd = JsonDict(persistence=tfn) + mypdd['testattr'] = 'loldongs' + + with open(tfn, 'r') as fh: + x = json.load(fh) + + self.assertEqual(x['testattr'], 'loldongs') + + del mypdd['testattr'] + + self.assertEqual(mypdd['testattr'], None) + + def test_read(self): + tfn = '/tmp/pad_test.json' + mypdd = JsonDict(persistence=tfn) + mypdd['testattr'] = 'loldongs' + + with open(tfn, 'w') as fh: + json.dump({'testattr': 'notloldongs'}, fh) + + self.assertNotEqual(mypdd['testattr'], 'loldongs') + + self.assertEqual(mypdd['nonexist'], None)