Repository: ManiacalLabs/BiblioPixel (python)

Issue:
`"animation"` section can have a default: `"animation"`.
Just a tiny enhancement.

We already have [two projects](https://github.com/ManiacalLabs/BiblioPixel/blob/master/test/bibliopixel/control/artnet_test.py#L74-L119) in our unit tests that have no animation, because the control is doing all the work and now Adam2 has one in the wild.

I'm all in favor of "you should be able to leave it out if you don't need it".  Luckily, `animation.Animation` does the job of a do-nothing animation really well.

Pull request description:
Give "animation" section a default (fix #786)
* Make sure that the "datatype" field always overrides the "typename" field in any type dictionary

Patch:
diff --git a/bibliopixel/drivers/network_receiver.py b/bibliopixel/drivers/network_receiver.py
index 7505644..dd5b6be 100644
--- a/bibliopixel/drivers/network_receiver.py
+++ b/bibliopixel/drivers/network_receiver.py
@@ -1,14 +1,8 @@
-import threading
-import os
+import os, threading
 
-try:
-    import SocketServer
-except:
-    import socketserver as SocketServer
+import socketserver as SocketServer
 from .. drivers.return_codes import RETURN_CODES
 from . network import CMDTYPE
-
-os.sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 from .. util import log
 
 
diff --git a/bibliopixel/project/construct.py b/bibliopixel/project/construct.py
index 0a4e32f..a084488 100644
--- a/bibliopixel/project/construct.py
+++ b/bibliopixel/project/construct.py
@@ -17,7 +17,15 @@ def to_type(d):
     fn = load.load_if_filename(d)
     if fn:
         return fn
-    return {'typename': d} if isinstance(d, str) else copy.deepcopy(d)
+    if isinstance(d, str):
+        return {'typename': d}
+
+    result = copy.deepcopy(d)
+
+    # 'datatype' always overrides 'typename'
+    if 'datatype' in result:
+        result.pop('typename', None)
+    return result
 
 
 def to_type_constructor(value, python_path=None):
@@ -46,6 +54,7 @@ def to_type_constructor(value, python_path=None):
         try:
             value['datatype'] = importer.import_symbol(
                 r, python_path=python_path)
+            del value['typename']
         except Exception as e:
             value['_exception'] = e
 
diff --git a/bibliopixel/project/merge.py b/bibliopixel/project/merge.py
index dab9034..4bf8754 100644
--- a/bibliopixel/project/merge.py
+++ b/bibliopixel/project/merge.py
@@ -16,7 +16,7 @@ DEFAULT_PROJECT = collections.OrderedDict((
     ('shape', ()),
     ('layout', {}),
     ('run', {}),
-    ('animation', {}),
+    ('animation', {'typename': 'animation'}),
     ('controls', []),
 ))
 PROJECT_SECTIONS = tuple(DEFAULT_PROJECT.keys())
@@ -52,5 +52,7 @@ def merge(*projects):
                     section['run'] = adesc.get('run', {})
 
             result.setdefault(name, {}).update(construct.to_type(section))
+            if 'datatype' in result[name]:
+                result[name].pop('typename', None)
 
     return result


Test patch:
diff --git a/test/bibliopixel/project/bad_project_test.py b/test/bibliopixel/project/bad_project_test.py
index b70e6a4..ae3a1dc 100644
--- a/test/bibliopixel/project/bad_project_test.py
+++ b/test/bibliopixel/project/bad_project_test.py
@@ -6,7 +6,6 @@ from . make import make
 
 PYTHON_FILE = 'driver = {"a": "b"}'
 MISSING_LAYOUT = '{"animation": "off", "driver": "dummy"}'
-MISSING_ANIMATION = '{"layout": "matrix", "driver": "dummy"}'
 
 
 # This one works, but prints a warning.
@@ -117,16 +116,9 @@ class BadProjectTest(unittest.TestCase):
             e.exception.args,
             ('Missing "layout" section',))
 
-    def test_missing_animation(self):
-        with self.assertRaises(ValueError) as e:
-            make(MISSING_ANIMATION)
-        self.assertEquals(
-            e.exception.args,
-            ('Missing "animation" section',))
-
     def test_missing_everything(self):
         with self.assertRaises(ValueError) as e:
             make('{}')
         self.assertEquals(
             e.exception.args,
-            ('Missing "animation" section',))
+            ('Missing "layout" section',))
diff --git a/test/bibliopixel/project/cleanup_test.py b/test/bibliopixel/project/cleanup_test.py
index d529d10..9b3cdab 100644
--- a/test/bibliopixel/project/cleanup_test.py
+++ b/test/bibliopixel/project/cleanup_test.py
@@ -10,7 +10,6 @@ BASE = {
         'run': {},
         'datatype': OffAnim,
         'name': 'Off',
-        'typename': 'off',
     },
     'controls': [],
     'drivers': [{'typename': 'simpixel'}],
diff --git a/test/bibliopixel/project/recurse_test.py b/test/bibliopixel/project/recurse_test.py
index 8d0d992..cda5031 100644
--- a/test/bibliopixel/project/recurse_test.py
+++ b/test/bibliopixel/project/recurse_test.py
@@ -35,7 +35,6 @@ class RecurseTest(unittest.TestCase):
             'bing': 'bang',
         }
         expected = {
-            'typename': '%s.%s' % (Tester.__module__, Tester.__name__),
             'datatype': Tester,
             'foo': {'datatype': RecurseTest, 'foo': 'bar', 'color': colors.Red},
             'bing': 'post-bang',