Repository: ManiacalLabs/BiblioPixel
Pull request #654 (2018-03-09 14:18:14)

Give Address an empty constructor and state
Spun off from the tatters of the general controllers.  :-D

I needed it for a while but I don't really need this at this time, but it's quite elegant to have an empty Address which does nothing and no doubt will be convenient in the future.

rec: Ping!  This isn't so important but want to get it out of client.

Patch:
diff --git a/bibliopixel/animation/animation.py b/bibliopixel/animation/animation.py
index e7d13f5..031b2a5 100644
--- a/bibliopixel/animation/animation.py
+++ b/bibliopixel/animation/animation.py
@@ -1,6 +1,6 @@
 import contextlib, threading, time
 from . runner import Runner, STATE
-from .. util import log
+from .. util import deprecate, log
 from .. util.threads.animation_threading import AnimationThreading
 from .. project import attributes, fields
 
@@ -97,7 +97,7 @@ class BaseAnimation(object):
                 yield
 
     def run(self, **kwds):
-        # DEPRECATED
+        deprecate.deprecate('BaseAnimation.run')
         self._set_runner(kwds)
         self.start()
 
diff --git a/bibliopixel/animation/collection.py b/bibliopixel/animation/collection.py
index 611625d..4de89a8 100644
--- a/bibliopixel/animation/collection.py
+++ b/bibliopixel/animation/collection.py
@@ -1,7 +1,7 @@
 import os, traceback
 from .. project import aliases, construct, load, project
 from . import animation
-from .. util import json, log
+from .. util import deprecate, json, log
 
 
 class Collection(animation.BaseAnimation):
@@ -61,7 +61,7 @@ class Collection(animation.BaseAnimation):
         super().cleanup(clean_layout)
 
     def add_animation(self, anim, **kwds):
-        # DEPRECATED.
+        deprecate.deprecate('Collection.add_animation')
         anim.set_runner(kwds)
         self.animations.append(anim)
 
diff --git a/bibliopixel/control/address.py b/bibliopixel/control/address.py
index dfe5edc..fd30239 100644
--- a/bibliopixel/control/address.py
+++ b/bibliopixel/control/address.py
@@ -63,7 +63,11 @@ class Address:
         def set(self, target, *value):
             target(*value)
 
-    def __init__(self, name):
+    def __init__(self, name=None):
+        if not name:
+            self.address = self.assignment = ()
+            return
+
         self.name, *assignment = name.split('=', 1)
         self.name = self.name.strip()
 
@@ -76,6 +80,8 @@ class Address:
             raise ValueError('Empty Addresses are not allowed')
 
         if assignment:
+            if not self.address:
+                raise ValueError('Cannot assign to an empty address')
             if isinstance(self.address[-1], Address.Call):
                 raise ValueError('Cannot assign to a call operation')
             assignment = [s.strip() for s in assignment[0].split(',')]
@@ -84,6 +90,9 @@ class Address:
         else:
             self.assignment = ()
 
+    def __bool__(self):
+        return bool(self.address)
+
     def __str__(self):
         return self.name
 
diff --git a/bibliopixel/control/serial_gamepad.py b/bibliopixel/control/serial_gamepad.py
index 6143884..23a5532 100644
--- a/bibliopixel/control/serial_gamepad.py
+++ b/bibliopixel/control/serial_gamepad.py
@@ -1,5 +1,5 @@
 from distutils.version import LooseVersion
-from .. util import AttributeDict, generate_header, log
+from .. util import AttributeDict, deprecate, generate_header, log
 from .. drivers.return_codes import RETURN_CODES, print_error
 from . gamepad import BaseGamePad
 
@@ -49,7 +49,7 @@ class SerialGamePad(BaseGamePad):
             self._com.close()
 
     def close(self):
-        # DEPRECATED.
+        deprecate.deprecate('SerialGamePad.close')
         self.cleanup()
 
     @staticmethod
diff --git a/bibliopixel/drivers/SPI/base.py b/bibliopixel/drivers/SPI/base.py
index c4d9e42..77633cf 100644
--- a/bibliopixel/drivers/SPI/base.py
+++ b/bibliopixel/drivers/SPI/base.py
@@ -1,6 +1,7 @@
 from .. channel_order import ChannelOrder
 from .. driver_base import DriverBase
 from ... util.colors import gamma as _gamma
+from ... util import deprecate
 from . import interfaces
 
 
@@ -20,11 +21,14 @@ class SPIBase(DriverBase):
     """
     def __init__(self, num, dev='/dev/spidev0.0',
                  spi_interface=None, spi_speed=1,
-                 interface='FILE',  # DEPRECATED
-                 **kwargs):
+                 interface=None, **kwargs):
         super().__init__(num, **kwargs)
         from ...project.types.spi_interface import make
         # See https://github.com/ManiacalLabs/BiblioPixel/issues/419
+        if interface:
+            deprecate.deprecate('SPIBase.interface')
+        else:
+            interface = 'FILE'
 
         maker = interfaces._SPI_INTERFACES[make(spi_interface or interface)]
         self._interface = maker(dev=dev, spi_speed=spi_speed)
diff --git a/bibliopixel/layout/circle.py b/bibliopixel/layout/circle.py
index 34c8842..20c5bba 100644
--- a/bibliopixel/layout/circle.py
+++ b/bibliopixel/layout/circle.py
@@ -2,6 +2,7 @@ from . layout import Layout
 from . geometry.circle import (
     make_circle_coord_map, calc_ring_steps, calc_ring_pixel_count,
     make_circle_coord_map_positions)
+from .. util import deprecate
 
 
 class Circle(Layout):
@@ -23,9 +24,6 @@ class Circle(Layout):
             self.ringSteps = calc_ring_steps(self.rings)
 
         self.ringCount = len(self.rings)
-        # DEPRECATED
-        self.lastRing = self.ringCount - 1
-
         self.set_pixel_positions(make_circle_coord_map_positions(self.rings))
 
         num = calc_ring_pixel_count(self.rings)
@@ -38,7 +36,12 @@ class Circle(Layout):
                 "Total ring LED count does not equal driver LED count!")
 
     @property
-    def dimensions(self):
+    def lastRing(self):
+        deprecate.deprecate('Circle.lastRing')
+        return self.ringCount - 1
+
+    @property
+    def shape(self):
         """Returns ``ringCount, ringSteps``."""
         return self.ringCount, self.ringSteps
 
diff --git a/bibliopixel/layout/cube.py b/bibliopixel/layout/cube.py
index 824e93e..62fc87c 100644
--- a/bibliopixel/layout/cube.py
+++ b/bibliopixel/layout/cube.py
@@ -33,7 +33,7 @@ class Cube(Layout):
         return make_cube_coord_map_positions(self.coord_map)
 
     @property
-    def dimensions(self):
+    def shape(self):
         """Returns ``x, y, z``"""
         return self.x, self.y, self.z
 
diff --git a/bibliopixel/layout/layout.py b/bibliopixel/layout/layout.py
index 39130a8..fff16bf 100644
--- a/bibliopixel/layout/layout.py
+++ b/bibliopixel/layout/layout.py
@@ -63,7 +63,8 @@ class Layout(object):
             d.set_pixel_positions(pixel_positions)
 
     def update(self):
-        """DEPRECATED - use :py:func:`push_to_driver`"""
+        """DEPRECATED: Use :py:func:`push_to_driver` instead"""
+        util.deprecate.deprecate('Layout.update')
         return self.push_to_driver()
 
     def start(self):
@@ -80,7 +81,7 @@ class Layout(object):
         self.threading.wait_for_update()
 
     @property
-    def dimensions(self):
+    def shape(self):
         """
         Return a tuple indicating the dimensions of the layout - (x,) for a
         strip, (x, y) for an array, (x, y, z) for a cube, and
@@ -88,6 +89,11 @@ class Layout(object):
         """
         raise NotImplementedError
 
+    @property
+    def dimensions(self):
+        util.deprecate.deprecate('Layout.dimensions')
+        return self.shape
+
     @property
     def color_list(self):
         return self._colors
@@ -133,7 +139,8 @@ class Layout(object):
         self._colors[:] = buf
 
     def setBuffer(self, buf):
-        """DEPRECATED!"""
+        util.deprecate.deprecate('layout.setBuffer')
+
         # https://stackoverflow.com/questions/1624883
         self.set_colors(buf=list(zip(*(iter(buf),) * 3)))
 
diff --git a/bibliopixel/layout/matrix.py b/bibliopixel/layout/matrix.py
index 8e8d529..7e421b5 100644
--- a/bibliopixel/layout/matrix.py
+++ b/bibliopixel/layout/matrix.py
@@ -113,7 +113,7 @@ class Matrix(MultiLayout):
         self.fonts = font.fonts
 
     @property
-    def dimensions(self):
+    def shape(self):
         """Returns ``width, height``"""
         return self.width, self.height
 
diff --git a/bibliopixel/layout/strip.py b/bibliopixel/layout/strip.py
index 5c6c539..e635f77 100644
--- a/bibliopixel/layout/strip.py
+++ b/bibliopixel/layout/strip.py
@@ -34,7 +34,7 @@ class Strip(MultiLayout):
         self.set_pixel_positions(make_strip_coord_map_positions(self.numLEDs))
 
     @property
-    def dimensions(self):
+    def shape(self):
         """Returns a 1-tuple with the length of the strip"""
         return self.numLEDs,
 
diff --git a/bibliopixel/main/commands.py b/bibliopixel/main/commands.py
index 9848079..d163273 100644
--- a/bibliopixel/main/commands.py
+++ b/bibliopixel/main/commands.py
@@ -50,8 +50,8 @@ Examples of flags are ``-v``, ``--verbose``, ``--defaults``, ``-d``,
 ``--simpixel``.  Flags either start with ``-``, for one letter flags, or ``--``
 for multi-letter flags.
 
-Sometimes two flags have the same meaning, like ``--simpixel`` and ``-s``,
-``--verbose`` and ``-v``, or ``--dimensions`` and ``--dim``.
+Sometimes two flags have the same meaning, like ``--simpixel`` and ``-s``
+or ``--verbose`` and ``-v``.
 
 Some flags take an argument, like ``--dim=160`` or ``--loglevel=frame``.
 Other flags do not, like ``--verbose`` or ``-v``.
diff --git a/bibliopixel/main/common_flags.py b/bibliopixel/main/common_flags.py
index 373b970..6d1cb03 100644
--- a/bibliopixel/main/common_flags.py
+++ b/bibliopixel/main/common_flags.py
@@ -1,6 +1,6 @@
 import os
 from .. project import data_maker, defaults, project
-from .. util import json, log
+from .. util import deprecate, json, log
 
 """Common command line arguments for run and demo."""
 
@@ -20,6 +20,9 @@ NUMBER_TYPES = ('python',) + data_maker.NUMPY_TYPES
 
 
 def add_common_flags(parser):
+    parser.add_argument(
+        '--deprecate', choices=deprecate.CHOICES, default=deprecate.DEFAULT,
+        help=deprecate.HELP)
     parser.add_argument(
         '--loglevel', choices=log.SORTED_NAMES, default='info',
         help=LOGLEVEL_HELP)
@@ -29,6 +32,15 @@ def add_common_flags(parser):
         '--version', action='store_true', help=VERSION_HELP)
 
 
+def execute_args(args):
+    if args.verbose and args.loglevel != 'frame':
+        log.set_log_level('debug')
+    else:
+        log.set_log_level(args.loglevel)
+
+    deprecate.ACTION = args.deprecate
+
+
 def add_project_flags(parser):
     parser.add_argument(
         '-a', '--animation', default=None,
@@ -44,6 +56,10 @@ def add_project_flags(parser):
 
     parser.add_argument(
         '--dimensions', '--dim', default=None,
+        help='DEPRECATED: x, (x, y) or (x, y, z) dimensions for project')
+
+    parser.add_argument(
+        '--shape', default=None,
         help='x, (x, y) or (x, y, z) dimensions for project')
 
     parser.add_argument(
@@ -134,11 +150,15 @@ def _make_project_flags(args):
         animation['length'] = length
 
     if args.dimensions is not None:
-        dimensions = args.dimensions.split(',')
+        deprecate.deprecate('Use --shape: --dimensions')
+
+    shape = args.shape or args.dimensions
+    if shape is not None:
+        shape = shape.split(',')
         try:
-            project_flags['dimensions'] = [int(i) for i in dimensions]
+            project_flags['shape'] = [int(i) for i in shape]
         except:
-            raise ValueError('--dimensions must be one to three numbers '
+            raise ValueError('--shape must be one to three numbers '
                              'separated by commas.')
 
     return project_flags
diff --git a/bibliopixel/main/main.py b/bibliopixel/main/main.py
index b024b0e..61e22f9 100644
--- a/bibliopixel/main/main.py
+++ b/bibliopixel/main/main.py
@@ -52,6 +52,7 @@ def get_args(argv=sys.argv):
 
     if argv == ['-h']:
         log.printer(commands.HELP)
+
     return parser.parse_args(argv)
 
 
@@ -68,10 +69,7 @@ def main():
         log.printer('    bp --help')
         sys.exit(-1)
 
-    if args.verbose and args.loglevel != 'frame':
-        log.set_log_level('debug')
-    else:
-        log.set_log_level(args.loglevel)
+    common_flags.execute_args(args)
 
     try:
         return run(args) or 0
diff --git a/bibliopixel/main/set.py b/bibliopixel/main/set.py
index b7d109b..6403518 100644
--- a/bibliopixel/main/set.py
+++ b/bibliopixel/main/set.py
@@ -12,7 +12,7 @@ section=value
 Example:
 
 ``````
-bp set numbers=float dimensions=80
+bp set numbers=float shape=80
 ``````
 """
 
diff --git a/bibliopixel/project/cleanup.py b/bibliopixel/project/cleanup.py
index eb3ca1a..7c2fa14 100644
--- a/bibliopixel/project/cleanup.py
+++ b/bibliopixel/project/cleanup.py
@@ -1,6 +1,6 @@
 import copy
 from . import alias_lists, construct, merge
-from .. import layout
+from .. import layout, util
 
 DEFAULT_DRIVERS = [construct.to_type('simpixel')]
 
@@ -71,19 +71,23 @@ def cleanup_drivers(desc):
     return desc
 
 
-def cleanup_dimensions(desc):
+def cleanup_shape(desc):
     desc = copy.deepcopy(desc)
     dimensions = desc.pop('dimensions', None)
-    if not dimensions:
+    if dimensions:
+        util.deprecate.deprecate('Project section "dimensions"')
+
+    shape = desc.pop('shape', dimensions)
+    if not shape:
         return desc
 
     if len(desc['drivers']) != 1:
         raise ValueError('Cannot use dimensions with more than one driver')
 
     try:
-        d = [int(dimensions)]
+        d = [int(shape)]
     except:
-        d = list(dimensions)
+        d = list(shape)
 
     ldesc = construct.to_type_constructor(desc.get('layout') or {})
     driver = desc['drivers'][0]
@@ -113,7 +117,7 @@ def cleanup(desc):
     desc = cleanup_aliases(desc)
     desc = cleanup_animation(desc)
     desc = cleanup_drivers(desc)
-    desc = cleanup_dimensions(desc)  # Must come after cleanup_drivers
+    desc = cleanup_shape(desc)  # Must come after cleanup_drivers
     desc = cleanup_numbers(desc)
 
     return desc
diff --git a/bibliopixel/project/merge.py b/bibliopixel/project/merge.py
index bdba41b..a2c9c6e 100644
--- a/bibliopixel/project/merge.py
+++ b/bibliopixel/project/merge.py
@@ -13,7 +13,7 @@ DEFAULT_PROJECT = collections.OrderedDict((
     ('maker', 'bibliopixel.project.data_maker.Maker'),
     ('driver', {}),
     ('drivers', []),
-    ('dimensions', ()),
+    ('shape', ()),
     ('layout', {}),
     ('run', {}),
     ('animation', {}),
@@ -22,7 +22,7 @@ DEFAULT_PROJECT = collections.OrderedDict((
 PROJECT_SECTIONS = tuple(DEFAULT_PROJECT.keys())
 
 NOT_MERGEABLE = (
-    'controls', 'datatype', 'dimensions', 'drivers', 'numbers', 'path',
+    'controls', 'datatype', 'shape', 'drivers', 'numbers', 'path',
     'typename')
 
 SECTION_ISNT_DICT_ERROR = 'Project section "%s" is %s, should be dictionary'
diff --git a/bibliopixel/util/attribute_dict.py b/bibliopixel/util/attribute_dict.py
index 96824a9..e9c58bf 100644
--- a/bibliopixel/util/attribute_dict.py
+++ b/bibliopixel/util/attribute_dict.py
@@ -1,3 +1,6 @@
+from . import deprecate
+
+
 class AttributeDict(object):
     """
     A dict that exposes its values as attributes.
@@ -6,6 +9,7 @@ class AttributeDict(object):
     """
 
     def __init__(self, *args, **kwds):
+        deprecate.deprecate('util.AttributeDict')
         for k, v in dict(*args, **kwds).items():
             setattr(self, k, v)
 
diff --git a/bibliopixel/util/deprecate.py b/bibliopixel/util/deprecate.py
new file mode 100644
index 0000000..af8d2b1
--- /dev/null
+++ b/bibliopixel/util/deprecate.py
@@ -0,0 +1,30 @@
+from . import log
+
+CHOICES = 'ignore', 'fail', 'warn', 'warn_once'
+DEFAULT = 'warn'
+
+# This gets changed by --deprecate
+ACTION = DEFAULT
+
+HELP = """
+Specify what to do when a project uses deprecated features:
+
+  ignore: do nothing
+  warn: print warning messages for each feature
+  warn_once: print a warning message, but only once for each type of feature
+  fail: throw an exception
+"""
+
+DEPRECATED = set()
+
+
+def deprecate(msg, *args, **kwds):
+    if ACTION == 'ignore' or (ACTION == 'warn_once' and msg in DEPRECATED):
+        return
+
+    formatted = (msg + ' is DEPRECATED').format(*args, **kwds)
+    if ACTION == 'fail':
+        raise ValueError(formatted)
+
+    DEPRECATED.add(msg)
+    log.warning(formatted)
diff --git a/bibliopixel/util/image/gif.py b/bibliopixel/util/image/gif.py
index bf5d6cd..70a4a9d 100644
--- a/bibliopixel/util/image/gif.py
+++ b/bibliopixel/util/image/gif.py
@@ -1,5 +1,5 @@
 import os, pathlib
-from .. import log
+from .. import log, deprecate
 
 
 # PIL has a limit on how many image files can be open at once - you get an
@@ -79,16 +79,22 @@ in BiblioPixelAnimations.matrix.ImageAnim
 
 def convert_mode(image, mode='RGB'):
     """Return an image in the given mode."""
+    deprecate.deprecate('util.gif.convert_model')
+
     return image if (image.mode == mode) else image.convert(mode=mode)
 
 
 def image_to_colorlist(image, container=list):
     """Given a PIL.Image, returns a ColorList of its pixels."""
+    deprecate.deprecate('util.gif.image_to_colorlist')
+
     return container(convert_mode(image).getdata())
 
 
 def animated_gif_to_colorlists(image, container=list):
     """Given an animated GIF, return a list with a colorlist for each frame."""
+    deprecate.deprecate('util.gif.animated_gif_to_colorlists')
+
     from PIL import ImageSequence
 
     it = ImageSequence.Iterator(image)
diff --git a/bibliopixel/util/image/renderer.py b/bibliopixel/util/image/renderer.py
index afce68f..0c5dff6 100644
--- a/bibliopixel/util/image/renderer.py
+++ b/bibliopixel/util/image/renderer.py
@@ -9,17 +9,17 @@ def renderer(layout, color, pixel_width, pixel_height,
 
     from PIL import Image, ImageDraw
 
-    dims = layout.dimensions
-    if len(dims) == 1:
+    shape = layout.shape
+    if len(shape) == 1:
         if vertical:
-            dims, getter = (1, dims[0]), lambda x, y: layout.get(y)
+            shape, getter = (1, shape[0]), lambda x, y: layout.get(y)
         else:
-            dims, getter = (dims[0], 1), lambda x, y: layout.get(x)
+            shape, getter = (shape[0], 1), lambda x, y: layout.get(x)
     else:
         getter = layout.get
 
     # LED width and height
-    width, height = dims
+    width, height = shape
 
     # width and height for individual pixels
     pw, ph = pixel_width, pixel_width
diff --git a/bibliopixel/util/log.py b/bibliopixel/util/log.py
index 7c18912..da39075 100644
--- a/bibliopixel/util/log.py
+++ b/bibliopixel/util/log.py
@@ -124,5 +124,8 @@ frame, debug, info, warning, error = (
 # re-assign this variable if you need to redirect your printing.
 printer = print
 
-# DEPRECATED
-setLogLevel = set_log_level
+
+def setLogLevel(level):
+    from . import deprecate
+    deprecate.deprecate('util.setLogLevel')
+    set_log_level(level)


Test patch:
diff --git a/test/bibliopixel/control/address_test.py b/test/bibliopixel/control/address_test.py
index fe7dbef..181faa4 100644
--- a/test/bibliopixel/control/address_test.py
+++ b/test/bibliopixel/control/address_test.py
@@ -4,9 +4,14 @@ from bibliopixel.control.address import Address
 
 class AddressTest(unittest.TestCase):
 
-    def test_empty_error(self):
+    def test_empty(self):
+        address = Address('')
+        self.assertFalse(address)
+        self.assertFalse(address.address)
+        self.assertFalse(address.assignment)
+        self.assertEqual(address.get(23), 23)
         with self.assertRaises(ValueError):
-            Address('')
+            address.set(self, 23)
 
     def test_attrib(self):
         address = Address('attr')
diff --git a/test/bibliopixel/project/cleanup_test.py b/test/bibliopixel/project/cleanup_test.py
index 816af52..1c45a6c 100644
--- a/test/bibliopixel/project/cleanup_test.py
+++ b/test/bibliopixel/project/cleanup_test.py
@@ -58,7 +58,7 @@ class CleanupTest(unittest.TestCase):
         actual = cleanup_after(copy.deepcopy(source))
         expected = {
             'aliases': {},
-            'dimensions': (),
+            'shape': (),
             'driver': {},
             'drivers': [],
             'layout': {
@@ -73,7 +73,7 @@ class CleanupTest(unittest.TestCase):
         self.assertEqual(actual, dict(BASE, **expected))
 
     def test_1_dimension(self):
-        source = {'dimensions': 3}
+        source = {'shape': 3}
         actual = cleanup_before(source)
         expected = {
             'drivers': [{'num': 3, 'typename': 'simpixel'}],
@@ -81,12 +81,12 @@ class CleanupTest(unittest.TestCase):
         }
         self.assertEqual(actual, dict(BASE, **expected))
 
-        source = {'dimensions': [3]}
+        source = {'shape': [3]}
         actual = cleanup_before(source)
         self.assertEqual(actual, dict(BASE, **expected))
 
-    def test_2_dimensions(self):
-        source = {'dimensions': [2, 5]}
+    def test_2_shape(self):
+        source = {'shape': [2, 5]}
         actual = cleanup_before(source)
         expected = {
             'drivers': [{'num': 10, 'typename': 'simpixel'}],
@@ -94,8 +94,8 @@ class CleanupTest(unittest.TestCase):
         }
         self.assertEqual(actual, dict(BASE, **expected))
 
-    def test_3_dimensions(self):
-        source = {'dimensions': [2, 3, 7]}
+    def test_3_shape(self):
+        source = {'shape': [2, 3, 7]}
         actual = cleanup_before(source)
         expected = {
             'drivers': [{'num': 42, 'typename': 'simpixel'}],
diff --git a/test/bibliopixel/project/defaults_test.py b/test/bibliopixel/project/defaults_test.py
index 5509ead..8396107 100644
--- a/test/bibliopixel/project/defaults_test.py
+++ b/test/bibliopixel/project/defaults_test.py
@@ -16,12 +16,12 @@ class DefaultsTest(unittest.TestCase):
         self.run_failure()
 
     def test_json(self):
-        self.run_test({"dimensions": 5}, '{"dimensions": 5}')
-        self.run_failure('{"dimensionX": 5}')
+        self.run_test({"shape": 5}, '{"shape": 5}')
+        self.run_failure('{"shapeX": 5}')
 
     def test_sections1(self):
-        self.run_test({"dimensions": 5}, 'dimensions=5')
-        self.run_failure('dimensionX=5')
+        self.run_test({"shape": 5}, 'shape=5')
+        self.run_failure('shapeX=5')
 
     def test_sections2(self):
         data = 'animation={"typename": "feedback", "master": 2}'
diff --git a/test/bibliopixel/project/merge_test.py b/test/bibliopixel/project/merge_test.py
index 10b0404..2e4195a 100644
--- a/test/bibliopixel/project/merge_test.py
+++ b/test/bibliopixel/project/merge_test.py
@@ -32,7 +32,7 @@ class MergeTest(unittest.TestCase):
             'aliases': {},
             'animation': {'typename': 'bfoo'},
             'controls': [],
-            'dimensions': (),
+            'shape': (),
             'driver': {'typename': 'bar'},
             'drivers': ['bang', 'bop'],
             'layout': {},