Repository: ManiacalLabs/BiblioPixel
Pull request #697 (2018-03-26 15:09:08)

v3.4.8
Adam2 needs a resolution to #689, which is why this tiny release.

This would happen after the other pull request is resolved.


Patch:
diff --git a/CHANGELIST.md b/CHANGELIST.md
index 2a92ff6..5218000 100644
--- a/CHANGELIST.md
+++ b/CHANGELIST.md
@@ -1,3 +1,8 @@
+## v3.4.8 - 2018-03-26
+- Fix #686 and #689
+- Allow animation.Remote to open the URL for its control page
+- --v4 flag
+
 ## v3.4.7 - 2018-03-19
 - Adding preliminary ArtNet support
 - Better error reporting
diff --git a/bibliopixel/VERSION b/bibliopixel/VERSION
index 2aa5131..7921bd0 100644
--- a/bibliopixel/VERSION
+++ b/bibliopixel/VERSION
@@ -1,1 +1,1 @@
-3.4.7
+3.4.8
diff --git a/bibliopixel/animation/collection.py b/bibliopixel/animation/collection.py
index c4993ba..dc54e7d 100644
--- a/bibliopixel/animation/collection.py
+++ b/bibliopixel/animation/collection.py
@@ -14,32 +14,35 @@ class Collection(animation.Animation):
         animations = []
 
         for a in desc['animations']:
-            ld = load.load_if_filename(a)
-            if ld:
-                a = {k: v for k, v in ld.items() if k in ('animation', 'run')}
+            loaded = load.load_if_filename(a)
+            if loaded:
+                animation = loaded.get('animation', {})
+                run = loaded.get('run', {})
 
-            if callable(a) or isinstance(a, str) or 'animation' not in a:
+            elif callable(a) or isinstance(a, str) or 'animation' not in a:
                 animation = a
-                a = {}
+                run = {}
+
             else:
-                a = dict(a)
-                animation = a.pop('animation')
+                animation = a.pop('animation', {})
+                run = a.pop('run', {})
+                if a:
+                    raise ValueError(
+                        'Extra fields in animation: ' + ', '.join(a))
+
             animation = construct.to_type_constructor(
                 animation, 'bibliopixel.animation')
 
-            arun = a.pop('run', {})
-            arun = animation['run'] = dict(arun, **animation.get('run', {}))
-            drun = desc['run']
+            run.update(animation.get('run', {}))
+            animation['run'] = run
 
             # Children without fps or sleep_time get it from their parents.
-            if not ('fps' in arun or 'sleep_time' in arun):
-                if 'fps' in drun:
-                    arun.update(fps=drun['fps'])
-                elif 'sleep_time' in drun:
-                    arun.update(sleep_time=drun['sleep_time'])
-
-            if a:
-                raise ValueError('Extra fields in animation: ' + ', '.join(a))
+            if not ('fps' in run or 'sleep_time' in run):
+                if 'fps' in desc['run']:
+                    run.update(fps=desc['run']['fps'])
+                elif 'sleep_time' in desc['run']:
+                    run.update(sleep_time=desc['run']['sleep_time'])
+
             animations.append(animation)
 
         desc['animations'] = animations
diff --git a/bibliopixel/project/construct.py b/bibliopixel/project/construct.py
index 64fa883..3f9b370 100644
--- a/bibliopixel/project/construct.py
+++ b/bibliopixel/project/construct.py
@@ -1,5 +1,5 @@
 import copy
-from . import aliases, importer
+from . import aliases, importer, load
 
 
 def construct(*args, datatype, typename=None, **kwds):
@@ -14,6 +14,9 @@ def construct(*args, datatype, typename=None, **kwds):
 
 
 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)
 
 
diff --git a/bibliopixel/project/merge.py b/bibliopixel/project/merge.py
index fcdf779..dab9034 100644
--- a/bibliopixel/project/merge.py
+++ b/bibliopixel/project/merge.py
@@ -1,5 +1,5 @@
 import collections
-from . import construct
+from . import construct, load
 
 # Elements in the DEFAULT_PROJECT are listed in order of dependency: sections
 # can only be dependent on sections that are above them in this list.
@@ -38,10 +38,19 @@ def merge(*projects):
         for name, section in (project or {}).items():
             if name in NOT_MERGEABLE:
                 result[name] = section
-            elif section and not isinstance(section, (dict, str)):
+                continue
+
+            if section and not isinstance(section, (dict, str)):
                 cname = section.__class__.__name__
                 raise ValueError(SECTION_ISNT_DICT_ERROR % (name, cname))
-            else:
-                result.setdefault(name, {}).update(construct.to_type(section))
+
+            if name == 'animation':
+                # Useful hack to allow you to load projects as animations.
+                adesc = load.load_if_filename(section)
+                if adesc:
+                    section = adesc.get('animation', {})
+                    section['run'] = adesc.get('run', {})
+
+            result.setdefault(name, {}).update(construct.to_type(section))
 
     return result
diff --git a/bibliopixel/project/project.py b/bibliopixel/project/project.py
index 768e4a2..8389553 100644
--- a/bibliopixel/project/project.py
+++ b/bibliopixel/project/project.py
@@ -85,12 +85,14 @@ class Project:
 
 def project(*descs, root_file=None):
     """
-    Make a project with recursion and alias resolution.  Use this
-    instead of calling Project() directly.
+    Make a new project, using recursion and alias resolution.
+
+    Use this function in preference to calling Project() directly.
     """
+    load.ROOT_FILE = root_file
+
     desc = defaults.merge(*descs)
 
-    load.ROOT_FILE = root_file
     with load.extender(desc.get('path', '')):
         desc = recurse.recurse(desc)
 


Test patch:
diff --git a/test/bibliopixel/project/layout.json b/test/bibliopixel/project/layout.json
new file mode 100644
index 0000000..013c0f6
--- /dev/null
+++ b/test/bibliopixel/project/layout.json
@@ -0,0 +1,4 @@
+{
+    "typename": "bibliopixel.layout.strip.Strip",
+    "pixelWidth": 2
+}
diff --git a/test/bibliopixel/project/make.py b/test/bibliopixel/project/make.py
index 925a723..954d4dd 100644
--- a/test/bibliopixel/project/make.py
+++ b/test/bibliopixel/project/make.py
@@ -7,6 +7,7 @@ from .. mark_tests import SKIP_LONG_TESTS
 def make_project(data):
     if isinstance(data, dict):
         desc = data
+        name = None
 
     elif not isinstance(data, str):
         raise ValueError('Cannot understand data %s' % data)
@@ -16,11 +17,13 @@ def make_project(data):
             fp = tempfile.NamedTemporaryFile(mode='w')
             fp.write(data)
             fp.seek(0)
-            data = fp.name
+            name = fp.name
+        else:
+            name = data
 
-        desc = json.load(data)
+        desc = json.load(name)
 
-    return project.project(desc)
+    return project.project(desc, root_file=name)
 
 
 def make(data, run_start=not SKIP_LONG_TESTS):
diff --git a/test/bibliopixel/project/project_test.py b/test/bibliopixel/project/project_test.py
index acd3936..a6f78b3 100644
--- a/test/bibliopixel/project/project_test.py
+++ b/test/bibliopixel/project/project_test.py
@@ -30,6 +30,11 @@ class ProjectTest(unittest.TestCase):
     def test_yaml_file(self):
         make('test/bibliopixel/project/project.yml', False)
 
+    def test_super(self):
+        animation = make('test/bibliopixel/project/super_project.json', False)
+        self.assertEquals(animation.__class__.__name__, 'StripChannelTest')
+        self.assertEquals(animation.layout.pixelWidth, 2)
+
     def test_multi(self):
         animation = make(PROJECT_MULTI)
         k = [d._kwds for d in animation.layout.drivers]
diff --git a/test/bibliopixel/project/super_project.json b/test/bibliopixel/project/super_project.json
new file mode 100644
index 0000000..edbede9
--- /dev/null
+++ b/test/bibliopixel/project/super_project.json
@@ -0,0 +1,4 @@
+{
+    "layout": "layout.json",
+    "animation": "project.json"
+}