From 96da4ee1d44cf4d071a70b9eb88292e844bad12a Mon Sep 17 00:00:00 2001
From: James Graham <J.A.Graham@soton.ac.uk>
Date: Thu, 12 May 2016 11:23:47 +0100
Subject: [PATCH] Documentation and small changes to parsers/json

---
 doc/source/pycgtool.parsers.json.rst |  7 ++++++
 doc/source/pycgtool.parsers.rst      |  1 +
 pycgtool/parsers/json.py             | 36 +++++++++++++++++++---------
 test/test_parsers_json.py            |  4 ++++
 4 files changed, 37 insertions(+), 11 deletions(-)
 create mode 100644 doc/source/pycgtool.parsers.json.rst

diff --git a/doc/source/pycgtool.parsers.json.rst b/doc/source/pycgtool.parsers.json.rst
new file mode 100644
index 0000000..f761dae
--- /dev/null
+++ b/doc/source/pycgtool.parsers.json.rst
@@ -0,0 +1,7 @@
+pycgtool.parsers.json module
+============================
+
+.. automodule:: pycgtool.parsers.json
+    :members:
+    :undoc-members:
+    :show-inheritance:
diff --git a/doc/source/pycgtool.parsers.rst b/doc/source/pycgtool.parsers.rst
index e1fea8b..1bf99ce 100644
--- a/doc/source/pycgtool.parsers.rst
+++ b/doc/source/pycgtool.parsers.rst
@@ -7,6 +7,7 @@ Submodules
 .. toctree::
 
    pycgtool.parsers.cfg
+   pycgtool.parsers.json
 
 Module contents
 ---------------
diff --git a/pycgtool/parsers/json.py b/pycgtool/parsers/json.py
index bbfe9ae..9fc342f 100644
--- a/pycgtool/parsers/json.py
+++ b/pycgtool/parsers/json.py
@@ -2,23 +2,34 @@ import json
 import os
 
 
-class Record(dict):
-    def __setattr__(self, key, value):
-        self[key] = value
-
-    def __getattr__(self, item):
-        return self[item]
+class AttrDict(dict):
+    """
+    Class allowing dictionary entries to be accessed as attributes as well as keys.
+    """
+    def __init__(self, *args, **kwargs):
+        super(AttrDict, self).__init__(*args, **kwargs)
+        self.__dict__ = self
 
 
 class CFG:
+    """
+    Class to read data from JSON files.  Supports including other files and filtering a single section.
+    """
     def __init__(self, filename, from_section=None):
+        """
+        Create a new CFG JSON parser.
+
+        :param filename: JSON file to read
+        :param from_section: Optional section to select from file
+        """
         with open(filename) as f:
-            self._json = json.load(f, object_hook=Record)
+            self._json = json.load(f, object_hook=AttrDict)
 
+        # Recurse through include lists and add to self._json
         while self._json.include:
             include_file = os.path.join(os.path.dirname(filename), self._json.include.pop())
             with open(include_file) as include_file:
-                include_json = json.load(include_file, object_hook=Record)
+                include_json = json.load(include_file, object_hook=AttrDict)
 
             for curr, incl in zip(self._json.values(), include_json.values()):
                 try:
@@ -26,10 +37,13 @@ class CFG:
                 except TypeError:
                     curr.update(incl)
 
+        self._records = self._json
         if from_section is not None:
-            self._records = self._json[from_section]
-        else:
-            self._records = self._json
+            try:
+                self._records = self._json[from_section]
+            except KeyError as e:
+                e.args = ("Section '{0}' not in file '{1}'".format(from_section, filename),)
+                raise
 
     def __getitem__(self, item):
         return self._records[item]
diff --git a/test/test_parsers_json.py b/test/test_parsers_json.py
index c6f6c05..5878d96 100644
--- a/test/test_parsers_json.py
+++ b/test/test_parsers_json.py
@@ -49,6 +49,10 @@ class TestParsersJson(unittest.TestCase):
         self.assertTrue("DOPC" in cfg)
         self.assertTrue("GLY" in cfg)
 
+    def test_missing_section(self):
+        with self.assertRaises(KeyError):
+            cfg = CFG("test/data/water.json", "potato")
+
 
 if __name__ == '__main__':
     unittest.main()
-- 
GitLab