Skip to content

Commit

Permalink
Merge branch 'release/1.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
manuphatak committed May 19, 2015
2 parents 338f164 + eac7655 commit 2cc26bf
Show file tree
Hide file tree
Showing 12 changed files with 345 additions and 149 deletions.
11 changes: 5 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
# Config file for automatic testing at travis-ci.org

language: python

python:
- "2.7"
python: 2.7

install:
- pip install tox --use-mirrors
- pip install coveralls

env:
- TOXENV=py27
- TOXENV=py32
- TOXENV=py33
- TOXENV=py34
- TOXENV=pypy
- TOXENV=docs
script:
- tox
- tox -e $TOXENV
- coverage run --source=json_config setup.py test
after_success:
coveralls
21 changes: 17 additions & 4 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,31 @@
History
-------

0.1.0 (2015-04-11)
Next Release
++++++++++++
* Stay Tuned.

1.2.0 (2015-05-18)
++++++++++++++++++
* Feature: Improved compatibility to PY27, PY32, PY33, PY34, and PYPY
* Feature: Supports multiple config files.
* Feature: Writes less, smarter logic on deciding if a write is necessary.
* Feature: Delegates writes to a background process.
* Testing: Renamed tests to be more descriptive of expectations.
* Testing: Added a bunch of tests describing different scenarios.
* Massive Refactoring

* First release on PyPI.
1.1.0 (2015-04-15)
++++++++++++++++++

* Massive improvement to documentation and presentation.

1.0.0 (2015-04-13)
++++++++++++++++++

* First working version.

1.1.0 (2015-04-15)
0.1.0 (2015-04-11)
++++++++++++++++++

* Massive improvement to documentation and presentation.
* First release on PyPI.
12 changes: 10 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
===========
json_config
===========
.. image:: https://img.shields.io/badge/Status-Beta-yellow.svg
:target: https://pypi.python.org/pypi/json_config/
:alt: Development Status

.. image:: https://travis-ci.org/bionikspoon/json_config.svg?branch=develop
:target: https://travis-ci.org/bionikspoon/json_config

.. image:: https://pypip.in/version/json_config/badge.svg?branch=develop
.. image:: https://img.shields.io/pypi/v/json_config.svg
:target: https://pypi.python.org/pypi/json_config?branch=develop

.. image:: https://coveralls.io/repos/bionikspoon/json_config/badge.svg?branch=develop
Expand All @@ -20,10 +24,14 @@ A convenience utility for working with JSON configuration files.
Features
--------

* Automatically syncs file on changes
* Automatically syncs file on changes.
* Automatically handles complicated nested data structures.
* Lightweight (<5KB) and Fast.
* Takes advantage of Python's native dictionary syntax.
* Tested against python 2.7, 3.2, 3.3, 3.4, and PYPY.
* Saves silently in the background.
* Unit Tested with high coverage.
* Fully documented at https://json-config.readthedocs.org

.. code-block:: python
Expand Down
25 changes: 1 addition & 24 deletions docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,31 +48,8 @@ help:
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"

all:
make html
make dirhtml
make singlehtml
make pickle
make json
make htmlhelp
make qthelp
make devhelp
make epub
make latex
make text
make man
make texinfo
make info
make gettext
make changes
make xml
make pseudoxml
make linkcheck
make doctest
make coverage

clean:
rm -rf $(BUILDDIR)/*
rm -rf $(BUILDDIR)

html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
Expand Down
Empty file added docs/test.json
Empty file.
5 changes: 3 additions & 2 deletions json_config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
===========
JSON Config
===========
A convenience utility for working with json config files.
>>> import json_config
Expand All @@ -17,6 +18,6 @@
"""
__author__ = 'Manu Phatak'
__email__ = 'bionikspoon@gmail.com'
__version__ = '1.1.0'
__version__ = '1.2.0'

from .configuration import connect
from .configuration import connect
169 changes: 118 additions & 51 deletions json_config/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,93 +3,160 @@
"""
A convenience utility for working with JSON config files.
"""
from functools import wraps

import json
from collections import MutableMapping, defaultdict
import threading
from functools import wraps, partial
from collections import defaultdict

pprint = True

config_file = None
config = None

class ConfigObject(defaultdict):
_container = None
"""Reference to the parent node"""

def connect(config_file_):
"""
Connect to a json config file.
def __init__(self, parent=None, config_file=None):
self.config_file = config_file
super(ConfigObject, self).__init__(self.factory)
self._container = parent

Returns a python dict-like object. Automatically syncs with the file.
Automatically handles nested data.
@classmethod
def connect(cls, config_file):
"""
Connect to a json config file.
:param str config_file_: String path pointing to json file.
:return: Dictionary like python object.
"""
global config_file
global config
Returns a python dict-like object. Automatically syncs with the file.
Automatically handles nested data.
config_file = config_file_
config = Configuration()
:param str config_file: String path pointing to json file.
:return: Dictionary like python object.
"""
self_ = cls.parent_node(config_file=config_file)
json_hook = partial(cls.child_node, self_)

try:
config.update(json.load(open(config_file), object_hook=json_hook))
except IOError:
with open(config_file, 'w') as f:
f.close()
try:
self_.block()
with open(config_file) as f:
self_.update(json.load(f, object_hook=json_hook))
except IOError:
with open(config_file, 'w') as f:
f.close() # open + close required for pypy

return config
return self_

@classmethod
def parent_node(cls, config_file):
"""
Create a parent node, with special parent properties.
def json_hook(obj):
config_ = Configuration()
config_.update(**obj)
return config_
:param string config_file: Path to JSON file.
:return: self.
:rtype : ConfigObject
"""
self = cls(config_file=config_file)
self._container = self
self.timer = None
return self

@classmethod
def child_node(cls, parent, *obj):
"""
Create a child node with a reference to the parent.
class Configuration(defaultdict, MutableMapping):
"""
Internal class. Handles nested dictionary recursion.
"""
:param parent: Reference to the parent node.
:param obj: Values to be stored.
:return: self
:rtype : ConfigObject
"""
self = cls(parent=parent)
self.update(*obj)
return self

def save_config(*func_args):
def save_config(*method_arguments):
"""
Decorator. Write the config file after execution.
Decorator. Start the delayed worker to save the file after a series
of calls.
:param func_args: a hack for method decorators.
:param method_arguments: a hack for method decorators.
:return: decorated method
"""
func = func_args[-1]
function = method_arguments[-1]
"""A hack for method decorators"""

@wraps(func)
@wraps(function)
def _wrapper(self, *args, **kwargs):

"""Cancel save attempts until series of requests is complete."""
try:
return func(self, *args, **kwargs)
if self._container.timer:
self._container.timer.cancel()
return function(self, *args, **kwargs)

finally:
with open(config_file, 'w') as f:
json.dump(config, f, indent=2, sort_keys=True, separators=(',', ': '))
save = self._container.write_file
self._container.timer = threading.Timer(0.001, save)
self._container.timer.name = self._container.config_file
self._container.timer.start()

return _wrapper

def __init__(self, **kwargs):
super(Configuration, self).__init__(self.factory, **kwargs)
def write_file(self):
"""
Write the JSON config file to disk.
"""
if not self.config_file:
raise RuntimeError('Missing Config File')

with open(self.config_file, 'w') as f:
f.write(repr(self._container))

@save_config
def __setitem__(self, key, value):
super(Configuration, self).__setitem__(key, value)

def __getitem__(self, key):
return super(Configuration, self).__getitem__(key)
super(ConfigObject, self).__setitem__(key, value)
return self

@save_config
def __delitem__(self, key):
super(Configuration, self).__delitem__(key)
super(ConfigObject, self).__delitem__(key)
return self

def __repr__(self):
return json.dumps(self, indent=2, sort_keys=True, separators=(',', ': '))
def factory(self):
"""
Lazy load child instance of self.
:return:
"""
return ConfigObject.child_node(parent=self._container)

@staticmethod
def factory():
def pformat(obj):
"""
Lazy load child instance of self.
Pretty Print JSON output.
:param obj:
:return:
"""
return Configuration()
return json.dumps(obj, indent=2, sort_keys=True, separators=(',', ': '))

def __repr__(self):
return json.dumps(self) if not pprint else self.pformat(self)

def __hash__(self):
return hash(str(self))

def block(self):
"""
Block until writing threads are completed.
This is mostly for internal use and testing. If you plan to connect
to a file being managed to this tool, use this to make sure the file
safe to read and write on.
"""
save_config_threads = [thread for thread in threading.enumerate() if
thread.name == self._container.config_file]

for save_config_thread in save_config_threads:
save_config_thread.join()

# export
connect = ConfigObject.connect
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
mock
pytest
wheel
wheel
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,21 @@ def run_tests(self):
cmdclass={'test': PyTest},
tests_require=['pytest', 'mock'],
classifiers=[
'Development Status :: 3 - Alpha',
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: Implementation :: PyPy',
'Programming Language :: Python :: Implementation :: CPython',
'Topic :: Database',
'Topic :: Software Development',
'Topic :: Utilities'


],
)
# @:on
Loading

0 comments on commit 2cc26bf

Please sign in to comment.