changeset 122:21d92ff8cf31

Begin the handling of JSON-style configuration files
author Franz Glasner <hg@dom66.de>
date Wed, 04 Apr 2018 09:45:29 +0200
parents 0d378dcc018b
children 4218c66b9281
files CHANGES.txt README.txt configmix/__init__.py configmix/json.py doc/apidoc.rst doc/introduction.rst tests/data/conf1.json tests/test.py
diffstat 8 files changed, 81 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGES.txt	Fri Mar 30 08:48:25 2018 +0200
+++ b/CHANGES.txt	Wed Apr 04 09:45:29 2018 +0200
@@ -46,6 +46,11 @@
 
       Build a tree of configuration settings from INI files
 
+   .. change::
+      :tags: feature
+
+      Allow JSON formatted files as configuration files also (suffix ".json").
+
 
 .. changelog::
    :version: 0.5
--- a/README.txt	Fri Mar 30 08:48:25 2018 +0200
+++ b/README.txt	Wed Apr 04 09:45:29 2018 +0200
@@ -18,6 +18,7 @@
 It reads configuration files in the following styles:
 
 - YAML files
+- JSON files  
 - INI files
 - executable Python scripts
 
--- a/configmix/__init__.py	Fri Mar 30 08:48:25 2018 +0200
+++ b/configmix/__init__.py	Wed Apr 04 09:45:29 2018 +0200
@@ -64,6 +64,9 @@
         from . import yaml
         with open(filename, "rb") as yf:
             return yaml.safe_load(yf)
+    elif fnl.endswith(".json"):
+        from . import json
+        return json.load(filename)
     elif fnl.endswith(".py"):
         from . import py
         return py.load(filename)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/configmix/json.py	Wed Apr 04 09:45:29 2018 +0200
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+#-
+# :Copyright: (c) 2018, Franz Glasner. All rights reserved.
+# :License:   3-clause BSD. See LICENSE.txt for details.
+#-
+"""Read JSON-style configuration files.
+
+"""
+
+from __future__ import division, absolute_import, print_function
+
+import io
+import json.decoder
+
+
+__all__ = ["load"]
+
+
+def load(filename, encoding="utf-8"):
+    """Load a single JSON file with name `filename` and encoding `encoding`.
+
+    .. todo:: Allow comments in JSON files
+
+    .. todo:: Allow all Python string literals
+
+    .. todo:: Use OrderedDict as default mapping implementation (Python 2.7+)
+
+    """
+    with io.open(filename, "rt", encoding=encoding) as jsfp:
+        decoder = json.decoder.JSONDecoder(
+            encoding=encoding,
+            parse_int=lambda n: int(n, 0),
+            strict=False)
+        return decoder.decode(jsfp.read())
--- a/doc/apidoc.rst	Fri Mar 30 08:48:25 2018 +0200
+++ b/doc/apidoc.rst	Wed Apr 04 09:45:29 2018 +0200
@@ -60,3 +60,11 @@
 .. automodule:: configmix.yaml
    :members:
    :ignore-module-all:
+
+
+Module :mod:`configmix.json`
+----------------------------
+
+.. automodule:: configmix.json
+   :members:
+   :ignore-module-all:
--- a/doc/introduction.rst	Fri Mar 30 08:48:25 2018 +0200
+++ b/doc/introduction.rst	Wed Apr 04 09:45:29 2018 +0200
@@ -8,6 +8,7 @@
 The configurations can be read from different types of files:
 
 - :ref:`YAML files <yaml-files>`
+- :ref:`JSON files <json-files>`  
 - :ref:`INI files <ini-files>`
 - :ref:`executable Python scripts <executable-python-scripts>`
 
@@ -28,6 +29,14 @@
    :language: yaml
 
 
+.. _json-files:
+
+JSON files
+----------
+
+.. todo:: Document basic JSON usage
+
+
 .. _ini-files:
 
 INI Files
@@ -138,6 +147,9 @@
   ``.yml`` or ``.yaml``
     for YAML configuration files
 
+  ``.json``
+    for JSON configuration files
+
   ``.ini``
     for INI configuration files
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/data/conf1.json	Wed Apr 04 09:45:29 2018 +0200
@@ -0,0 +1,8 @@
+{"key1": "the value",
+ "key2": 2,
+ "key3": 5.7,
+ "key4": true,
+ "key5": false,
+ "key6": 255,
+ "key7": "Umlaute: ÄÖÜäöüß"
+}
--- a/tests/test.py	Fri Mar 30 08:48:25 2018 +0200
+++ b/tests/test.py	Wed Apr 04 09:45:29 2018 +0200
@@ -14,6 +14,7 @@
 import configmix
 import configmix.ini
 import configmix.yaml
+import configmix.json
 import configmix.py
 from configmix.compat import u
 
@@ -67,29 +68,33 @@
             cfg = configmix.yaml.safe_load(f)
             self.__check_types(cfg)
 
-    def test04_py_export_all(self):
+    def test04_json_types(self):
+        cfg = configmix.json.load(os.path.join(TESTDATADIR, "conf1.json"))
+        self.__check_types(cfg)
+
+    def test05_py_export_all(self):
         # When __all__ is given only it's keys are exported
         cfg = configmix.py.load(os.path.join(TESTDATADIR, "conf2.py"))
         self.assertEqual(u("the next value"), cfg.get("key1"))
         self.assertTrue(isinstance(cfg.get("key1"), type(u(''))))
         self.assertTrue(cfg.get("key2") is None)
 
-    def test05_py_hide_private(self):
+    def test06_py_hide_private(self):
         # When no __all__ is given all symbols with leading "_" are hidden
         cfg = configmix.py.load(os.path.join(TESTDATADIR, "conf3.py"))
         self.assertEqual(u("the next value "), cfg.get("key1"))
         self.assertTrue(isinstance(cfg.get("key1"), type(u(''))))
         self.assertTrue(cfg.get("_key2") is None)
 
-    def test06_ini_tree(self):
+    def test07_ini_tree(self):
         cfg = configmix.ini.load(os.path.join(TESTDATADIR, "conf10.ini"))
         self.__check_tree(cfg)
 
-    def test07_py_tree(self):
+    def test08_py_tree(self):
         cfg = configmix.py.load(os.path.join(TESTDATADIR, "conf10.py"))
         self.__check_tree(cfg)
 
-    def test08_yaml_tree(self):
+    def test09_yaml_tree(self):
         with io.open(os.path.join(TESTDATADIR, "conf10.yml"), "rt",
                      encoding="utf-8") as f:
             cfg = configmix.yaml.safe_load(f)