Skip to content

Commit

Permalink
Make any Project class loadable from a JSON file (fix #689)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tom Ritchford committed Mar 26, 2018
1 parent 61268d9 commit 299578c
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 29 deletions.
39 changes: 21 additions & 18 deletions bibliopixel/animation/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,35 @@ def pre_recursion(desc):
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
Expand Down
5 changes: 4 additions & 1 deletion bibliopixel/project/construct.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import copy
from . import aliases, importer
from . import aliases, importer, load


def construct(*args, datatype, typename=None, **kwds):
Expand All @@ -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)


Expand Down
17 changes: 13 additions & 4 deletions bibliopixel/project/merge.py
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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
8 changes: 5 additions & 3 deletions bibliopixel/project/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,14 @@ def run(self):

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)

Expand Down
4 changes: 4 additions & 0 deletions test/bibliopixel/project/layout.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"typename": "bibliopixel.layout.strip.Strip",
"pixelWidth": 2
}
9 changes: 6 additions & 3 deletions test/bibliopixel/project/make.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
def make_project(data):
if isinstance(data, dict):
desc = data
name = None

elif not isinstance(data, str):
raise ValueError('Cannot understand data %s' % data)
Expand All @@ -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):
Expand Down
5 changes: 5 additions & 0 deletions test/bibliopixel/project/project_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ def test_file(self):
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]
Expand Down
4 changes: 4 additions & 0 deletions test/bibliopixel/project/super_project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"layout": "layout.json",
"animation": "project.json"
}

0 comments on commit 299578c

Please sign in to comment.