Commits

Jonathan Eunice committed ae7c5d7

Improved docs. Ready to push 1.0.2

Comments (0)

Files changed (9)

+Change Log
+==========
 
+1.0.2 (September 19, 2013)
+''''''''''''''''''''''''''
 
-1.0.1
-=====
+  * Improved ``setdefault`` and ``update`` methods, and added tests,
+    primarily in effort to work around bug that appears in ``stuf``,
+    ``orderedstuf``, or ``chainstuf`` when a mapping value is a
+    generator.
+  * Documentation improved.
+
+1.0.1 (September 2013)
+''''''''''''''''''''''
 
   * Moved main documentation to Sphinx format in ./docs, and hosted
     the long-form documentation on readthedocs.org. README.rst now
     an abridged version/teaser for the module.
 
-1.0
-===
+1.0 (Septemer 2013)
+'''''''''''''''''''
 
   * Cleaned up source for better PEP8 conformance
-  * Bumped version number to 1.0 as part of move to `semantic 
+  * Bumped version number to 1.0 as part of move to `semantic
     versioning <http://semver.org>`_, or at least enough of it so
     as to not screw up Python installation procedures (which don't
     seem to understand 0.401 is a lesser version that 0.5, because
     401 > 5).
-
-A module that helps encapsulate option and configuration data using a
-multi-layer stacking (a.k.a. nested context) model.
+options
+=======
 
-Classes are expected to define default option values. When instances are
-created, they can be instantiated with "override" values. For any option that
-the instances doesn't override, the class default "shines through" and remains
-in effect. Similarly, individual method calls can set transient values that
-apply just for the duration of that call. If the call doesn't set a value, the
-instance value applies. If the instance didn't set a
-value, the class default applies. Python's ``with`` statement can be used to
-tweak options for essentially arbitrary duration.
+``options`` helps encapsulate options and configuration data using a
+layered stacking model (a.k.a. nested contexts).
 
-This layered or stacked approach is particularly helpful for highly
-functional classes that aim for "reasonable" or "intelligent" defaults and
-behaviors, that allow users to override those defaults at any time, and that
-aim for a simple, unobtrusive API. It can also be used to provide flexible
-option handling for functions.
+For most functions and many classes, ``options``
+is overkill and not recommended.
+Python's already-flexible function arguments, ``*args``,
+``**kwargs``, and inheritance patterns are elegant and sufficient
+for 99.9% of all development situations.
 
-This option-handling pattern is based on delegation rather than inheritance.
-It's described in `this StackOverflow.com discussion of "configuration sprawl" 
-<http://stackoverflow.com/questions/11702437/where-to-keep-options-values-paths-to-important-files-etc/11703813#11703813>`_.
+``options``
+is intended for the 0.1%: highly
+functional classes that have many different options, that
+aim for "reasonable" or "intelligent" defaults and
+behaviors, that allow users to override those defaults at any time, and yet that
+aim for a simple, unobtrusive API.
 
-Unfortunately, it's a bit hard to demonstrate the virtues of this approach with
-simple code. Python already supports flexible function arguments, including
-variable number of arguments (``*args``) and optional keyword arguments
-(``**kwargs``). Combined with object inheritance, base Python features already
-cover a large number of use cases and requirements. But when you have a large
-number of configuration and instance variables, and when you might want to
-temporarily override either class or instance settings, things get dicey. This
-messy, complicated space is where ``options`` truly begins to shine.
+In those cases, Python's simpler built-in, inheritance-based
+model leads to fairly complex code as non-trivial options and argument-management
+code spreads through many individual methods. This is where
+``options``'s delegation-based approach begins to shine.
+
 
 .. image:: https://pypip.in/d/options/badge.png
     :target: https://crate.io/packages/options/
 Usage
 =====
 
+In a typical use, your class defines default option values. Subclasses
+can add, remove, or override options. Instances
+use class defaults, but they can be overridden when each instance
+is created. For any option an instance doesn't override, the class
+default "shines through."
+
+So far, this isn't very different from a typical use of Python's
+standard instance and
+class variables.  The next step is where ``options`` gets interesting.
+
+Individual method calls can similarly override instance and class defaults.
+The options stated in each method call obtain
+only for the duration of the method's execution.
+If the call doesn't set a value, the
+instance value applies. If the instance didn't set a
+value, its class default applies (and so on, to its superclasses, if any).
+
+One step further, Python's ``with`` statement can be used to
+set option values for essentially arbitrary duration. As soon as the
+``with`` block exists, the option values automagically fall back to
+what they were before the with block. (In general, if any option is unset,
+its value falls back to what it was in the next higher layer.)
+
+To recap: Python easily layers class, subclass, and instance settings.
+``options`` layers class, subclass, and instance settings as well, but also
+adds method and transient settings. When Python mechanisms override a setting,
+they do so destructively; they cannot be "unset" without additional code.
+When a program using ``options`` overrides a setting, it does so non-destructively,
+layering the new settings atop the previous ones. When attributes are unset,
+they immediately fall back to their prior value (at whatever higher level it
+was last set).
+
+Unfortunately, because this is a capability designed for high-end, edge-case
+situations, it's hard to demonstrate its virtues with
+simple code. But we'll give it a shot.
+
 ::
 
     from options import Options, attrs
-    
+
     class Shape(object):
-    
+
         options = Options(
             name   = None,
             color  = 'white',
             height = 10,
             width  = 10,
         )
-        
+
         def __init__(self, **kwargs):
             self.options = Shape.options.push(kwargs)
-        
+
         def draw(self, **kwargs):
             opts = self.options.push(kwargs)
             print attrs(opts)
     one.draw()
     one.draw(color='red')
     one.draw(color='green', width=22)
-    
+
 yielding::
 
     color='white', width=10, name='one', height=10
             color  = kwargs.get('color',  self.color)
             height = kwargs.get('height', self.height)
             width  = kwargs.get('width',  self.width)
-            print "color='{}', width={}, name='{}', height={}".format(color, width, name, height)
-        
+            print "color='{0}', width={1}, name='{2}', height={3}".format(color, width, name, height)
+
 One problem here is that we broke apart the values provided to
 ``__init__()`` into separate instance variables, now we need to
 re-assemble them into something unified.  And we need to explicitly
             color  = color  or self.color
             height = height or self.height
             width  = width  or self.width
-            print "color='{}', width={}, name='{}', height={}".format(color, width, name, height)
+            print "color='{0}', width={1}, name='{2}', height={3}".format(color, width, name, height)
 
 If we add just a few more instance variables, we have the `Mr. Creosote
-<http://en.wikipedia.org/wiki/Mr_Creosote>`_ of class design on our hands. Not
-good. Things get worse if we want to set default values for all shapes in the
+<http://en.wikipedia.org/wiki/Mr_Creosote>`_ of class design on our hands. Every
+possible setting has to be managed in every method. It's neither elegant nor
+scalable. Things get even worse if we want to set default values for all shapes in the
 class. We have to rework every method that uses values, the ``__init__`` method,
 *et cetera*. We've entered "just one more wafer-thin mint..." territory.
 
     one.draw()
     one.draw(height=100)
     one.draw(height=44, color='yellow')
-    
+
 yields::
 
     color='blue', width=10, name='one', height=10
     color='blue', width=10, name='one', height=100
     color='yellow', width=10, name='one', height=44
 
-In one line, we reset the default for all ``Shape`` objects.
+In one line, we reset the default for all ``Shape`` objects. (In typical usage
+we'd also define ``Shape.set()`` to transparently forward
+to ``Shape.options.set()`` for an even simpler resulting API.)
 
 The more options and settings a class has, the more unwieldy the class and
 instance variable approach becomes, and the more desirable the delegation
 alternative. Inheritance is a great software pattern for many kinds of data and
-program structures, but it's a bad pattern for complex option and configuration
-handling. For richly featured classes, the delegation pattern ``options`` proves
-simpler. Supporting even a large number of options requires almost no additional
-code and imposes no additional complexity or failure modes. By consolidating
-options into one place, and by allowing neat, attribute-style access, everything
-is kept tidy. We can add new options or methods with confidence::
+program structures--but it's a bad, or at least incomplete,
+pattern for complex option and configuration
+handling.
+
+For richly-featured classes, ``options``'s delegation pattern is
+simpler. As the number of options grows, almost no additional
+code is required. More options
+impose no additional complexity and introduce no additional failure modes.
+Consolidating
+options into one place, and providing
+neat attribute-style access, keeps everything
+tidy. We can add new options or methods with confidence::
 
     def is_tall(self, **kwargs):
         opts = self.options.push(kwargs)
         return opts.height > 100
 
 Under the covers, ``options`` uses a variation on the ``ChainMap`` data
-structure (a multi-layer dictionary) to provide its option stacking. Every
+structure (a multi-layer dictionary) to provide option stacking. Every
 option set is stacked on top of previously set option sets, with lower-level
 values shining through if they're not set at higher levels. This stacking or
 overlay model resembles how local and global variables are managed in many
 programming languages.
 
-For more, please see the full installation and usage documentation 
-on `Read the Docs <http://options.readthedocs.org/en/latest/>`_.
+This makes advanced use cases, such as temporary value changes, easy::
 
+    with one.settings(height=200, color='purple'):
+        one.draw()
+        if is_tall(one):
+            ...         # it is, but only within the ``with`` context
+
+    if is_tall(one):    # nope, not here!
+        ...
+
+Full disclosure: Doing temporary settings took more class setup code
+than is shown above. Four lines of code, to be precise.
+
+As one final feature, consider "magical" parameters. Add the following
+code::
+
+    options.magic(
+        height = lambda v, cur: cur.height + int(v) if isinstance(v, str) else v,
+        width  = lambda v, cur: cur.width  + int(v) if isinstance(v, str) else v
+    )
+
+Now, in addition to absolute ``height`` and ``width`` parameters specified with
+``int`` (integer/numeric) values, your module
+auto-magically supports relative parameters for ``height`` and ``width``.::
+
+    one.draw(width='+200')
+
+yields::
+
+    color='blue', width=210, name='one', height=10
+
+Neat, huh?
+
+For more, see `this StackOverflow.com discussion of how to combat "configuration sprawl"
+<http://stackoverflow.com/questions/11702437/where-to-keep-options-values-paths-to-important-files-etc/11703813#11703813>`_
+and ``options`` full documentation
+on `Read the Docs <http://options.readthedocs.org/en/latest/>`_. For examples of ``options``
+in use, see `say <https://pypi.python.org/pypi/say>`_ and `show <https://pypi.python.org/pypi/show>`_.
+Change Log
+==========
+
+1.0.2 (September 19, 2013)
+''''''''''''''''''''''''''
+
+  * Improved ``setdefault`` and ``update`` methods, and added tests,
+    primarily in effort to work around bug that appears in ``stuf``,
+    ``orderedstuf``, or ``chainstuf`` when a mapping value is a
+    generator.
+  * Documentation improved.
+
+1.0.1 (September 2013)
+''''''''''''''''''''''
+
+  * Moved main documentation to Sphinx format in ./docs, and hosted
+    the long-form documentation on readthedocs.org. README.rst now
+    an abridged version/teaser for the module.
+
+1.0 (Septemer 2013)
+'''''''''''''''''''
+
+  * Cleaned up source for better PEP8 conformance
+  * Bumped version number to 1.0 as part of move to `semantic
+    versioning <http://semver.org>`_, or at least enough of it so
+    as to not screw up Python installation procedures (which don't
+    seem to understand 0.401 is a lesser version that 0.5, because
+    401 > 5).
 # The short X.Y version.
 version = '1.0'
 # The full version, including alpha/beta/rc tags.
-release = '1.0.1'
+release = '1.0.2'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
 options
 =======
 
-A module that helps encapsulate option and configuration data using a
-multi-layer stacking (a.k.a. nested context) model.
+``options`` helps encapsulate options and configuration data using a
+layered stacking model (a.k.a. nested contexts).
 
-Classes are expected to define default option values. When instances are
-created, they can be instantiated with "override" values. For any option that
-the instances doesn't override, the class default "shines through" and remains
-in effect. Similarly, individual method calls can set transient values that
-apply just for the duration of that call. If the call doesn't set a value, the
-instance value applies. If the instance didn't set a
-value, the class default applies. Python's ``with`` statement can be used to
-tweak options for essentially arbitrary duration.
+For most functions and many classes, ``options``
+is overkill and not recommended.
+Python's already-flexible function arguments, ``*args``,
+``**kwargs``, and inheritance patterns are elegant and sufficient
+for 99.9% of all development situations.
 
-This layered or stacked approach is particularly helpful for highly
-functional classes that aim for "reasonable" or "intelligent" defaults and
-behaviors, that allow users to override those defaults at any time, and that
-aim for a simple, unobtrusive API. It can also be used to provide flexible
-option handling for functions.
+``options``
+is intended for the 0.1%: highly
+functional classes that have many different options, that
+aim for "reasonable" or "intelligent" defaults and
+behaviors, that allow users to override those defaults at any time, and yet that
+aim for a simple, unobtrusive API.
 
-This option-handling pattern is based on delegation rather than inheritance.
-It's described in `this StackOverflow.com discussion of "configuration sprawl" 
-<http://stackoverflow.com/questions/11702437/where-to-keep-options-values-paths-to-important-files-etc/11703813#11703813>`_.
+In those cases, Python's simpler built-in, inheritance-based
+model leads to fairly complex code as non-trivial options and argument-management
+code spreads through many individual methods. This is where
+``options``'s delegation-based approach begins to shine.
 
-Unfortunately, it's a bit hard to demonstrate the virtues of this approach with
-simple code. Python already supports flexible function arguments, including
-variable number of arguments (``*args``) and optional keyword arguments
-(``**kwargs``). Combined with object inheritance, base Python features already
-cover a large number of use cases and requirements. But when you have a large
-number of configuration and instance variables, and when you might want to
-temporarily override either class or instance settings, things get dicey. This
-messy, complicated space is where ``options`` truly begins to shine.
+.. image:: https://pypip.in/d/options/badge.png
+    :target: https://crate.io/packages/options/
 
 Usage
 =====
 
+In a typical use, your class defines default option values. Subclasses
+can add, remove, or override options. Instances
+use class defaults, but they can be overridden when each instance
+is created. For any option an instance doesn't override, the class
+default "shines through."
+
+So far, this isn't very different from a typical use of Python's
+standard instance and
+class variables.  The next step is where ``options`` gets interesting.
+
+Individual method calls can similarly override instance and class defaults.
+The options stated in each method call obtain
+only for the duration of the method's execution.
+If the call doesn't set a value, the
+instance value applies. If the instance didn't set a
+value, its class default applies (and so on, to its superclasses, if any).
+
+One step further, Python's ``with`` statement can be used to
+set option values for essentially arbitrary duration. As soon as the
+``with`` block exists, the option values automagically fall back to
+what they were before the with block. (In general, if any option is unset,
+its value falls back to what it was in the next higher layer.)
+
+To recap: Python easily layers class, subclass, and instance settings.
+``options`` layers class, subclass, and instance settings as well, but also
+adds method and transient settings. When Python mechanisms override a setting,
+they do so destructively; they cannot be "unset" without additional code.
+When a program using ``options`` overrides a setting, it does so non-destructively,
+layering the new settings atop the previous ones. When attributes are unset,
+they immediately fall back to their prior value (at whatever higher level it
+was last set).
+
+Unfortunately, because this is a capability designed for high-end, edge-case
+situations, it's hard to demonstrate its virtues with
+simple code. But we'll give it a shot.
+
 ::
 
     from options import Options, attrs
-    
+
     class Shape(object):
-    
+
         options = Options(
             name   = None,
             color  = 'white',
             height = 10,
             width  = 10,
         )
-        
+
         def __init__(self, **kwargs):
             self.options = Shape.options.push(kwargs)
-        
+
         def draw(self, **kwargs):
             opts = self.options.push(kwargs)
             print attrs(opts)
     one.draw()
     one.draw(color='red')
     one.draw(color='green', width=22)
-    
+
 yielding::
 
     color='white', width=10, name='one', height=10
             color  = kwargs.get('color',  self.color)
             height = kwargs.get('height', self.height)
             width  = kwargs.get('width',  self.width)
-            print "color='{}', width={}, name='{}', height={}".format(color, width, name, height)
-        
-One problem here is that we broke apart the values provided to ``__init__()`` into
-separate instance variables, now we need to re-assemble them into something unified.
-And we need to explicitly choose between the ``**kwargs`` and the instance variables.
-It gets repetitive, and is not pretty. Another classic alternative, using
-native keyword arguments, is no
-better::
+            print "color='{0}', width={1}, name='{2}', height={3}".format(color, width, name, height)
+
+One problem here is that we broke apart the values provided to
+``__init__()`` into separate instance variables, now we need to
+re-assemble them into something unified.  And we need to explicitly
+choose between the ``**kwargs`` and the instance variables.  It
+gets repetitive, and is not pretty. Another classic alternative,
+using native keyword arguments, is no better::
 
         def draw2(self, name=None, color=None, height=None, width=None):
             name   = name   or self.name
             color  = color  or self.color
             height = height or self.height
             width  = width  or self.width
-            print "color='{}', width={}, name='{}', height={}".format(color, width, name, height)
+            print "color='{0}', width={1}, name='{2}', height={3}".format(color, width, name, height)
 
 If we add just a few more instance variables, we have the `Mr. Creosote
-<http://en.wikipedia.org/wiki/Mr_Creosote>`_ of class design on our hands. Not
-good. Things get worse if we want to set default values for all shapes in the
+<http://en.wikipedia.org/wiki/Mr_Creosote>`_ of class design on our hands. Every
+possible setting has to be managed in every method. It's neither elegant nor
+scalable. Things get even worse if we want to set default values for all shapes in the
 class. We have to rework every method that uses values, the ``__init__`` method,
 *et cetera*. We've entered "just one more wafer-thin mint..." territory.
 
     one.draw()
     one.draw(height=100)
     one.draw(height=44, color='yellow')
-    
+
 yields::
 
     color='blue', width=10, name='one', height=10
     color='blue', width=10, name='one', height=100
     color='yellow', width=10, name='one', height=44
 
-In one line, we reset the default for all ``Shape`` objects.
+In one line, we reset the default for all ``Shape`` objects. (In typical usage
+we'd also define ``Shape.set()`` to transparently forward
+to ``Shape.options.set()`` for an even simpler resulting API.)
 
 The more options and settings a class has, the more unwieldy the class and
 instance variable approach becomes, and the more desirable the delegation
 alternative. Inheritance is a great software pattern for many kinds of data and
-program structures, but it's a bad pattern for complex option and configuration
-handling. For richly featured classes, the delegation pattern ``options`` proves
-simpler. Supporting even a large number of options requires almost no additional
-code and imposes no additional complexity or failure modes. By consolidating
-options into one place, and by allowing neat, attribute-style access, everything
-is kept tidy. We can add new options or methods with confidence::
+program structures--but it's a bad, or at least incomplete,
+pattern for complex option and configuration
+handling.
+
+For richly-featured classes, ``options``'s delegation pattern is
+simpler. As the number of options grows, almost no additional
+code is required. More options
+impose no additional complexity and introduce no additional failure modes.
+Consolidating
+options into one place, and providing
+neat attribute-style access, keeps everything
+tidy. We can add new options or methods with confidence::
 
     def is_tall(self, **kwargs):
         opts = self.options.push(kwargs)
         return opts.height > 100
 
 Under the covers, ``options`` uses a variation on the ``ChainMap`` data
-structure (a multi-layer dictionary) to provide its option stacking. Every
+structure (a multi-layer dictionary) to provide option stacking. Every
 option set is stacked on top of previously set option sets, with lower-level
 values shining through if they're not set at higher levels. This stacking or
 overlay model resembles how local and global variables are managed in many
 programming languages.
 
-Magic Parameters
-================
+This makes advanced use cases, such as temporary value changes, easy::
 
-Python's ``*args`` variable-number of arguments and ``**kwargs`` keyword
-arguments are sometimes called "magic" arguments. ``options`` takes this up a
-notch, allowing setters much like Python's ``property`` function or
-``@property`` decorator. This allows arguments to be interpreted on the fly.
-This is useful, for instance, to provide relative rather than just absolute
-values. As an example, say that we added this code after ``Shape.options`` was
-defined::
+    with one.settings(height=200, color='purple'):
+        one.draw()
+        if is_tall(one):
+            ...         # it is, but only within the ``with`` context
+
+    if is_tall(one):    # nope, not here!
+        ...
+
+Full disclosure: Doing temporary settings took more class setup code
+than is shown above. Four lines of code, to be precise.
+
+As one final feature, consider "magical" parameters. Add the following
+code::
 
     options.magic(
         height = lambda v, cur: cur.height + int(v) if isinstance(v, str) else v,
         width  = lambda v, cur: cur.width  + int(v) if isinstance(v, str) else v
     )
-    
-Now, in addition to absolute ``height`` and ``width`` parameters which are
-provided by specifying ``int`` (integer/numeric) values, your module
+
+Now, in addition to absolute ``height`` and ``width`` parameters specified with
+``int`` (integer/numeric) values, your module
 auto-magically supports relative parameters for ``height`` and ``width``.::
 
     one.draw(width='+200')
-    
+
 yields::
 
     color='blue', width=210, name='one', height=10
-    
-This can be as fancy as you like, defining an entire domain-specific expression language.
-But even small functions can give you a great bump in expressive power. For example,
-add this and you get full relative arithmetic capability (``+``, ``-``, ``*``, and ``/``)::
 
-    def relmath(value, currently):
-        if isinstance(value, str):
-            if value.startswith('*'):
-                return currently * int(value[1:])
-            elif value.startswith('/'):
-                return currently / int(value[1:])
-            else:
-                return currently + int(value)
-        else:
-            return value
-    
-    ...
-    
-    options.magic(
-        height = lambda v, cur: relmath(v, cur.height),
-        width  = lambda v, cur: relmath(v, cur.width)
-    )
+Neat, huh?
 
-Then::
-
-    one.draw(width='*4', height='/2')
-
-yields::
-
-    color='blue', width=40, name='one', height=5
-    
-Magically interpreted parameters are the sort of thing that one doesn't need
-very often or for every parameter--but when they're useful, they're *enormously*
-useful and highly leveraged, leading to much simpler, much higher function APIs.
-We call them 'magical' here because of the "auto-magical" interpretation, but
-they are really just analogs of Python object properties. The magic function is
-basically a "setter" for a dictionary element.
+For more, see `this StackOverflow.com discussion of how to combat "configuration
+sprawl"
+<http://stackoverflow.com/questions/11702437/where-to-keep-options-values-paths-to-important-files-etc/11703813#11703813>`_.
+For examples of ``options`` in use, see `say
+<https://pypi.python.org/pypi/say>`_ and `show
+<https://pypi.python.org/pypi/show>`_.
 
 Design Considerations
 =====================
     def _attrs(self, opts):
         nicekeys = [ k for k in opts.keys() if not k.startswith('_') ]
         return ', '.join([ "{}={}".format(k, repr(opts[k])) for k in nicekeys ])
-   
+
     def draw(self, **kwargs):
         opts = self.options.push(kwargs)
         print self._attrs(opts)
-        
+
 ``draw()``, being the outward-facing API, accepts general arguments and
 manages their stacking (by ``push``ing ``kwargs`` onto the instance options).
 When the internal ``_attrs()`` method is called, it is handed a pre-digested
 
     def set(self, **kwargs):
         self.options.set(**kwargs)
-        
+
     one.set(width='*10', color='orange')
     one.draw()
-    
+
 yields::
 
     color='orange', width=100, name='one', height=10
 
     one.set(color=Unset)
     one.draw()
-    
+
 yields::
 
     color='blue', width=100, name='one', height=10
-    
+
 Because ``'blue'`` was the color to which ``Shape`` had be most recently set.
 With the color of the instance unset, the color of the class shines through.
 
-**NOTA BENE** while options are ideally accessed with an attribute notion,
-the preferred of setting options is through method calls: ``set()`` if
-accessing directly, or ``push()`` if stacking values as part of a method call.
-These perform the interpretation and unsetting magic;
-straight assignment does not. In the future, ``options`` may provide an
-equivalent ``__setattr__()`` method to allow assignment--but not yet.
+.. note:: While options are ideally accessed with an attribute notion,
+    the preferred of setting options is through method calls: ``set()`` if
+    accessing directly, or ``push()`` if stacking values as part of a method call.
+    These perform the interpretation and unsetting magic;
+    straight assignment does not. In the future, ``options`` may provide an
+    equivalent ``__setattr__()`` method to allow assignment--but not yet.
 
 Leftovers
 =========
 representing very large buckets of functionality, with many options. Some of
 those options are relevant to the current instance, while others are intended as
 pass-throughs for lower-level modules/objects. This may seem a doubly rarefied
-case--and it is, relatively speaking. But it does happen, and when you need
+case--and it is, relatively speaking. But `it does happen
+<https://pypi.python.org/pypi/show>`_--and when you need
 multi-level processing, it's really, really super amazingly handy to
 have it.
 
 
 As yet, there is no automatic check for leftovers.
 
+Magic Parameters
+================
+
+Python's ``*args`` variable-number of arguments and ``**kwargs`` keyword
+arguments are sometimes called "magic" arguments. ``options`` takes this up a
+notch, allowing setters much like Python's ``property`` function or
+``@property`` decorator. This allows arguments to be interpreted on the fly.
+This is useful, for instance, to provide relative rather than just absolute
+values. As an example, say that we added this code after ``Shape.options`` was
+defined::
+
+    options.magic(
+        height = lambda v, cur: cur.height + int(v) if isinstance(v, str) else v,
+        width  = lambda v, cur: cur.width  + int(v) if isinstance(v, str) else v
+    )
+
+Now, in addition to absolute ``height`` and ``width`` parameters which are
+provided by specifying ``int`` (integer/numeric) values, your module
+auto-magically supports relative parameters for ``height`` and ``width``.::
+
+    one.draw(width='+200')
+
+yields::
+
+    color='blue', width=210, name='one', height=10
+
+This can be as fancy as you like, defining an entire domain-specific expression language.
+But even small functions can give you a great bump in expressive power. For example,
+add this and you get full relative arithmetic capability (``+``, ``-``, ``*``, and ``/``)::
+
+    def relmath(value, currently):
+        if isinstance(value, str):
+            if value.startswith('*'):
+                return currently * int(value[1:])
+            elif value.startswith('/'):
+                return currently / int(value[1:])
+            else:
+                return currently + int(value)
+        else:
+            return value
+
+    ...
+
+    options.magic(
+        height = lambda v, cur: relmath(v, cur.height),
+        width  = lambda v, cur: relmath(v, cur.width)
+    )
+
+Then::
+
+    one.draw(width='*4', height='/2')
+
+yields::
+
+    color='blue', width=40, name='one', height=5
+
+Magically interpreted parameters are the sort of thing that one doesn't need
+very often or for every parameter--but when they're useful, they're *enormously*
+useful and highly leveraged, leading to much simpler, much higher function APIs.
+We call them 'magical' here because of the "auto-magical" interpretation, but
+they are really just analogs of Python object properties. The magic function is
+basically a "setter" for a dictionary element.
+
 The Magic APIs
 ==============
 
         prefix = Prohibited,  # was available in superclass, but not here
         suffix = Prohibited,  # ditto
     )
-    
+
 Because some of the "additions" can be prohibitions (i.e. removing
 particular options from being set or used), this is "adding to" the superclass's
 options in the sense of "adding a layer onto" rather than strict "adding
 
 Sometimes it's more elegant to provide some arguments as flat, sequential values
 rather than by keyword. In this case, use the ``addflat()`` method::
-    
+
     def __init__(self, *args, **kwargs):
         self.options = Quoter.options.push(kwargs)
         self.options.addflat(args, ['prefix', 'suffix'])
-        
+
 to consume optional ``prefix`` and ``suffix`` flat arguments. This makes the following
 equivalent::
 
 ============
 
 A huge amount of work, both in Python and beyond, has gone into
-the effective management of configuration information. 
+the effective management of configuration information.
 
  * Program defaults. Values pre-established by developers, often
-   as ``ALL_UPPERCASE_IDENTIFIERS`` or as keyword default to 
+   as ``ALL_UPPERCASE_IDENTIFIERS`` or as keyword default to
    functions.
 
  * Configuration file format parsers/formatters.
    Huge amounts of the INI, JSON, XML, and YAML specifications and
-   toolchains, for example, are configuration-related. 
+   toolchains, for example, are configuration-related.
    There are many. `anyconfig <https://pypi.python.org/pypi/anyconfig>`_
    is perhaps of interest for its flexibility.
    You could
-   probably lump into this group binary data marshalling schemes
+   probably lump into this group binary data marshaling schemes
    such as ``pickle``.
 
  * Command-line argument parsers. These are all about taking configuration
-   information from the command line. 
+   information from the command line.
    `argh <https://pypi.python.org/pypi/argh>`_ is one I particularly
    like for its simple, declarative nature.
    (`aaargh <https://pypi.python.org/pypi/aaargh>`_ is similar.)
    <https://pypi.python.org/pypi/stuf>`_ and `treedict
    <https://pypi.python.org/pypi/treedict>`_ are cream-of-the-crop
    implementations of this idea. I have not tried `confetti
-   <https://pypi.python.org/pypi/confetti>`_ or 
+   <https://pypi.python.org/pypi/confetti>`_ or
    `Yaco <https://pypi.python.org/pypi/Yaco>`_, but they look like
    variations on the same theme.
 
- * The portion of Web frameworks concerned with getting and setting 
-   cookies, URL query and hash attributes, form variables, and/or 
-   HTML5 local stroage. Not that 
-   these are pariticularly secure, scalable, or robust sources...but they're 
+ * The portion of Web frameworks concerned with getting and setting
+   cookies, URL query and hash attributes, form variables, and/or
+   HTML5 local storage. Not that
+   these are particularly secure, scalable, or robust sources...but they're
    important configuration information nonetheless.
 
- * While slighly afield, database interface modules are often used for
+ * While slightly afield, database interface modules are often used for
    querying configuration information from SQL or NoSQL databases.
 
  * Some object metaprogramming systems. That's a mouthful, right?
    Well some modules implement metaclasses that change the basic
    behavior of objects. `value <https://pypi.python.org/pypi/value>`_
    for example provides very common-sense treatment of object
-   instantiation with out all the Javaesque 
+   instantiation with out all the Javaesque
    ``self.x = x; self.y = y; self.z = z`` repetition.
    ``options`` similarly redesigns how parameters should be passed
    and object values stored.
 
  * Combomatics. Many configuration-related modules combine two
-   or more of these approaches. E.g. 
+   or more of these approaches. E.g.
    `yconf <https://pypi.python.org/pypi/yconf>`_
    combines YAML config file parsing with ``argparse`` command
    line parsing. In the future, ``options`` will also follow
-   this path. There's no need to take programmer time and 
+   this path. There's no need to take programmer time and
    attention for several different low-level
    configuration-related tasks.
 
    and composed-of-many-different-components your system is, the
    more valuable the "DI pattern" becomes.
 
- * Other things. 
+ * Other things.
    `conflib <https://pypi.python.org/pypi/conflib>`_, uses dictionary
    updates to stack
-   default, global, and 
+   default, global, and
    local settings; it also provides a measure of validation.
 
-
 This diversity, while occasionally frustrating, makes some sense.
-Configruation data, after all, is just "state," and "managing state"
+Configuration data, after all, is just "state," and "managing state"
 is pretty much what all computing is about.
 Pretty much every program has to do it. That so many use so
 many different, home-grown ways is why there's such a good opportunity.
 
-`Flask's documentation <http://flask.pocoo.org/docs/config/#configuring-from-files>`_ is a real-world example of how "spread everywhere" this can be,
+`Flask's documentation <http://flask.pocoo.org/docs/config/#configuring-from-files>`_ is
+a real-world example of how "spread everywhere" this can be,
 with some data coming from the program, some from files, some from
 environment variables, some from Web-JSON, etc.
 
 =====
 
  * This is a work in progress. The underlying techniques have been successfully
-   used in multiple projects, but it remains in an evolving state as a
-   standalone module. The API may change over time. Swim at your own risk.
-   
- * Open question: Could "magic" parameter processing be
-   improved with a properties-based approach akin to that of `basicproperty <http://pypi.python.org/pypi/basicproperty>`_,
-   `propertylib <http://pypi.python.org/pypi/propertylib>`_,
-   `classproperty <http://pypi.python.org/pypi/classproperty>`_, and `realproperty <http://pypi.python.org/pypi/rwproperty>`_.
-   
- * Open question: Should "magic" parameter setters be allow to change
-   multiple options at once? A use case for this: "Abbreviation"
-   options that combine multiple changes into one compact option. These would
-   probably not have stored values themselves. It would require setting the
-   "dependent" option values via side-effect rather than functional return values.
-   
+   used in multiple projects, but it remains in active development.
+   The API may change over time. Swim at your own risk.
+
+ * ``options`` undergoes frequent automated multi-version testing with
+   `pytest <http://pypi.python.org/pypi/pytest>`_
+   and `tox <http://pypi.python.org/pypi/tox>`_. It is
+   successfully packaged for, and tested against, Python 2.6, 2.7, 3.2, and 3.3.
+   It additionally runs under, and is tested against, PyPy 2.1 (based on 2.7.3)
+   though it has to work around a few bugs in the underlying ``stuf`` module
+   to do so.
+
  * The author, `Jonathan Eunice <mailto:jonathan.eunice@gmail.com>`_ or
    `@jeunice on Twitter <http://twitter.com/jeunice>`_
    welcomes your comments and suggestions.
 Recent Changes
 ==============
 
- * Commenced automated multi-version testing with
-   `pytest <http://pypi.python.org/pypi/pytest>`_
-   and `tox <http://pypi.python.org/pypi/tox>`_. Now
-   successfully packaged for, and tested against, Python 2.6, 2.7, 3.2, and 3.3.
-   
- * Options is now packaged for, and tested against, PyPy 1.9 (based on 2.7.2).
-   The underlying ``stuf`` module and ``orderedstuf`` class is not
-   certified for PyPy, and it exhibits a bug with file objects on PyPy.
-   ``options`` works around this bug, and tests fine on PyPy. Still, 
-   buyer beware. 
-   
- * Versions subsequent to 0.200 require a late-model version of ``stuf`` to
-   avoid a problem its earlier iterations had with file objects. Versions after 0.320
-   depend on ``stuf`` for ``chainstuf``, rather than the ``otherstuf`` sidecar.
-
- * Now packaged as a package, not a set of modules. ``six`` module now required only for testing.
- 
- * API for ``push()`` and ``addflat()`` cleaned up to explicitly delink those methods.
- 
 Installation
 ============
 
 To ``easy_install`` under a specific Python version (3.3 in this example)::
 
     python3.3 -m easy_install options
-    
+
 (You may need to prefix these with "sudo " to authorize installation.)
 
 .. toctree::
    :maxdepth: 2
 
-
+   CHANGES

options/__init__.py

-from options.core import * 
+from options.core import *
 
-__all__ = 'Unset Prohibited Transient attrs Options OptionsChain OptionsContext'.split()
+__all__ = 'Unset Prohibited Transient attrs ' \
+          'Options OptionsChain OptionsContext'.split()
 import sys
 
 from stuf import orderedstuf, chainstuf
-# from otherstuf import chainstuf
 from options.configparser import ConfigParser
 from options.nulltype import NullType
 from options.funclike import *
 
+# Define sentinel objects
 Unset      = NullType('Unset')
 Prohibited = NullType('Prohibited')
 Transient  = NullType('Transient')
         # orderedstuf.__init__(self, *args, **kwargs) should do the trick. And it
         # does, on most platforms. But on PyPy, stuf(a=sys.stdout) fails in much
         # the same way used to on Python 3. Until stuf/orderedstuf is fixed for
-        # PyPy, this workaround fixes the issue for Options.
-
-    def __repr__(self):
-        return "{0}({1})".format(self.__class__.__name__, attrs(self))
+        # PyPy, this workaround fixes the issue for Options. (Reconfirmed bug
+        # still exists in PyPy 2.1 with stuf 0.9.12 in Sept 2013. Patch
+        # retained.)
 
     def set(self, **kwargs):
         # To set values at the base options level, create a temporary next level,
         for key in self.keys():
             self[key] = oc[key]
 
+    def setdefault(self, key, default):
+        """
+        If key is in the dictionary, return its value. If not, insert key with a
+        value of default and return default. If the value is Prohibited, no
+        change will be allowed, and a KeyError will be raised. If the value is
+        Transient or Unset, it will be as if the value were never there, and the
+        default will be set. NB ``setdefault`` does not participate in "magical"
+        value interpretation.
+        """
+        try:
+            value = self[key]
+            if value is Prohibited:
+                raise KeyError("changes to {0!r} are prohibited".format(key))
+            elif value is Transient or value is Unset:
+                self[key] = default
+                return default
+            else:
+                return value
+        except KeyError as e:
+            # All keys must be defined in initial object construction,
+            # even if defined as Unset or Transient. So if *no* value
+            # is available for a key, definite error. Stricter than
+            # general dict / mapping practice.
+            raise e
+
+        # In general usage, ``setdefault`` will be called only on ``OptionsChain``
+        # instances, but defined for ``Options`` as well for completeness.
+
+    def update(self, *args, **kwargs):
+        """
+        Update self with the given data. Necessary to avoid apparent bug in
+        ``stuf`` when updating with iterable objects, especially generators. NB
+        ``update`` does not participate in "magical" value interpretation.
+        """
+        for a in args:
+            for k, v in a.items():
+                self[k] = v
+        for k, v in kwargs.items():
+            self[k] = v
+
     def push(self, kwargs):
         """
         Create the next layer down. Intended for instances to call during
         elif argcount == 3:
             return magicfn(None, value, self)
         else:
-            raise ValueError('magic function should have 1-3 arguments, not {0}'.format(argcount))
+            msg = 'magic functions take 1-3 arguments, not {0}'.format(argcount)
+            raise ValueError(msg)
 
     def _process(self, base, kwargs):
         """
         guts = attrs(self, first=list(grandpa.keys()), underscores=True)
         return "{0}({1} layers: {2})".format(self.__class__.__name__, n_layers, guts)
 
-    #def __iter__(self):
+    # def __iter__(self):
     #    for k in super(OptionsChain, self).keys():
     #        if not k.startswith('_'):
     #            yield k
             else:
                 self.maps[0][k] = v
 
-    def setdefault(self, key, default=None):
+    def setdefault(self, key, default):
         """
-        If key is in the dictionary, return its value. If not,
-        insert key with a value of default and return default.
-        default defaults to None. If the value is Prohibited,
-        no change will be allowed, and a KeyError will be raised.
-        If the value is Transient, it will be as if the value
-        were never there, and the default will be set.
+        If key is in the dictionary, return its value. If not, insert key with a
+        value of default and return default. If the value is Prohibited, no
+        change will be allowed, and a KeyError will be raised. If the value is
+        Transient or Unset, it will be as if the value were never there, and the
+        default will be set. NB ``setdefault`` does not participate in "magical"
+        value interpretation.
         """
         try:
             value = self[key]
             if value is Prohibited:
-                raise KeyError("changes to '{0}' are prohibited".format(key))
-            elif value is Transient:
+                raise KeyError("changes to {0!r} are prohibited".format(key))
+            elif value is Transient or value is Unset:
                 self[key] = default
                 return default
-            return value
+            else:
+                return value
         except KeyError as e:
+            # All keys must be defined in initial object construction,
+            # even if defined as Unset or Transient. So if *no* value
+            # is available for a key, definite error. Stricter than
+            # general dict / mapping practice.
             raise e
 
+    def update(self, *args, **kwargs):
+        """
+        Update self with the given data. Necessary to avoid apparent bug in
+        ``stuf``, ``orderedstuf``, or ``chainstuf`` when updating with iterable
+        objects, especially generators. NB ``update`` does not participate in "magical"
+        value interpretation.
+        """
+        for a in args:
+            for k, v in a.items():
+                self[k] = v
+        for k, v in kwargs.items():
+            self[k] = v
+
     def copy(self):
         """
         Return a copy of the self. That means a copy of just the top layer,
     #  TBD Handle the unsetting of magic in subclasses
 
 
-
-    #def __setattr__(self, name, value):
+    # def __setattr__(self, name, value):
     #    print "OptionsChain.__setattr__() name:", name, "value:", value
     #    if name in self:
     #        print "    in self"
 
 setup(
     name='options',
-    version='1.0.1',
+    version='1.0.2',
     author='Jonathan Eunice',
     author_email='jonathan.eunice@gmail.com',
     description='Container for flexible class, instance, and function call options',
 
 from options import *
 import sys, six, pytest, platform, os
-    
+from pytest import raises
+
 def test_good_chainstuf():
     """Test options class for being faithful subclass of chainstuf"""
-    
+
     # make some base dictsw
     d1 = Options(this=1, that=2, roger=None)
     d2 = dict(roger=99, that=100)
-    
+
     # test simple attribute equivalence
     dd = d1.push(d2)
     assert dd.this == 1
     assert dd.roger == 99
     assert dd.this == dd['this']
     assert dd.that == dd['that']
-    
+
     assert dd.roger == dd['roger']
-    
+
     # set value, ensure properly set, in top dict
     dd.roger = 'wilco'
     assert dd.roger == 'wilco'
 
+def test_setdefault():
+    o = Options(ok=1, prohib=Prohibited, none=None,
+                trans=Transient, unset=Unset)
+
+    # regular set value
+    assert o.setdefault('ok', 2) == 1
+    assert o.ok == 1
+
+    # regular set value
+    assert 'unknown' not in o
+    with raises(KeyError):
+        o.setdefault('unknown', 2)
+
+    # None value
+    assert o.setdefault('none', 44) is None
+    assert o.none is None
+
+    # Unset value
+    assert o.setdefault('unset', 33) == 33
+    assert o.unset == 33
+
+    # Transient value
+    assert o.setdefault('trans', -99) == -99
+    assert o.trans == -99
+
+    # Prohibited value
+    with raises(KeyError):
+        o.setdefault('prohib', 15)
+    assert o.prohib is Prohibited
+
+    oo = o.push({'a': 'first letter'})
+
+    # regular set value
+    assert oo.setdefault('ok', 2) == 1
+    assert oo.ok == 1
+
+    # regular set value
+    assert 'unknown' not in o
+    with raises(KeyError):
+        oo.setdefault('unknown', 2)
+
+    # None value
+    assert oo.setdefault('none', 44) is None
+    assert oo.none is None
+
+    # Unset value
+    assert oo.setdefault('unset', 33) == 33
+    assert oo.unset == 33
+
+    # Transient value
+    assert oo.setdefault('trans', -99) == -99
+    assert oo.trans == -99
+
+    # Prohibited value
+    with raises(KeyError):
+        oo.setdefault('prohib', 15)
+    assert oo.prohib is Prohibited
+
 def test_unset():
     d1 = Options(this=1, that=2, roger=None)
     d2 = dict(roger=99, that=100)
     dd.set(that=Unset)
     assert dd.that == 2
     assert de.that == 2
-    
+
 def test_magic():
-    
+
     o = Options(this=1, slick='slack', blik=99)
     o['_magic'] = { 'slick': lambda x: x.capitalize() }
-    
+
     # test that magic doesnt effect everything
     o.blik = "wubber"
     assert o.blik == 'wubber'
     # test that it does effect the right things
     o.set(slick='mack')
     assert o.slick == 'Mack'  # note magical capitalization
-    
+
     p = o.push(dict(this=2))
     assert p.this == 2
     assert p.slick == 'Mack'
-    
-   
+
+
 def test_magic_designation():
-    
+
     class T(object):
         options = Options(
             this=1,
             blik=99,
             man='man'
         )
-        
+
         # technique 1: lambda expression or function
         options.magic(
             slick = lambda v: v.capitalize(),
         )
-        
+
         def __init__(self, *args, **kwargs):
             self.options = T.options.push(kwargs)
             self.data = args
-            
+
         def set(self, **kwargs):
             """
             Uplevel the set operation. A set() on this object is converted into
             a set on its options.
             """
             self.options.set(**kwargs)
-            
+
         # technique 2 - a static method with after-the-fact inclusion
-        
+
         @staticmethod
         def nick_magic(v, cur):
             return v.upper()
-    
+
         options.magic(nick=nick_magic)
 
         # technique 3 - a decorated method
-        
+
         @options.magical('man')
         def man_magic(self, v, cur):
             return v.upper()
 
-    
+
     t = T()
     assert t.options.this == 1
     assert t.options.blik == 99
     assert t.options.slick =='Wack'
     t.set(nick='flick')
     assert t.options.nick == 'FLICK'
-    
+
     t.set(man='boy')
     assert t.options.man == 'BOY'
     t.set(man='girl')
     assert t.options.man == 'GIRL'
-   
+
 def test_push():
-    
+
     class T2(object):
         options = Options(
             this=1,
             blik=99,
             man='man'
         )
-        
+
         def __init__(self, *args, **kwargs):
             self.options = T2.options.push(kwargs)
             self.data = args
-            
+
         def set(self, **kwargs):
             """
             Uplevel the set operation. A set() on this object is converted into
             a set on its options.
             """
             self.options.set(**kwargs)
-        
+
         def write(self, **kwargs):
             opts = self.options.push(kwargs)
             six.print_(opts.nick)
-            
+
         def push1(self, **kwargs):
             opts = self.options.push(kwargs)
-            
+
             # persistent data test
             assert T2.options.nick == 'nack'
             assert T2.options.slick == 'slack'
             # transient data test
             assert opts.nick == 44
             assert opts.slick == 55
-    
+
     t = T2(nick='N', slick='S')
     assert T2.options.nick == 'nack'
     assert T2.options.slick == 'slack'
     assert t.options.nick == 'N'
     assert t.options.slick == 'S'
     t.push1(nick=44, slick=55)
-    
+
 def test_dictify():
-    
+
     o = Options(
             this=1,
             slick='slack',
     d = dict(o.items())
     assert not [ k for k in d.keys() if k.startswith('_') ]  # _values hidden
     assert o.this == d['this'] == 1
-    
+
     oo = o.push(dict(this=99, _grim='grey'))
     dd = dict(oo.items())
     assert not [ k for k in dd.keys() if k.startswith('_') ]  # _values hidden
     assert oo.this == dd['this'] == 99
-    
+
     # Wish we could just do ``dict(o)`` without passing through ``items()``. But how?
 
-# @pytest.mark.skipif('platform.python_implementation() == "PyPy" ')
 def test_files():
     # The underlying stuf container used to have (<0.9.9) a problem with files
     # being assigned in a stuf() constructor. This tests that we're over that
     # problem.
-    
+
     # Get names of files that won't be munged by py.test's capturing mechanism
     # (sys.stdout and sys.stderr definitely will be overtaken by py.test, but
     # their primitive double-underscore names won't be). This doesn't seem to
     # be an issue with Python 2.x, but spuriously screws up the test otherwise
     # in Python 3.x (gives false negative, saying module not working when it is)
-    
+
     f1 = sys.__stdout__
     f2 = sys.__stderr__
     f3 = sys.__stdin__
-    
+
     o = Options(a=f1, b=f2, c=[f2, f3])
 
     assert o.a is f1
     assert len(o.c) == 2
     assert o.c[0] is f2
     assert o.c[1] is f3
-    
+
     # first push
     oo = o.push(dict(b=f1, c=12))
     assert oo.a is f1
     assert oo.b is f1
     assert oo.c == 12
-    
+
     # second push
     ooo = oo.push(dict(a=f2, b=f3))
     assert ooo.a is f2
     assert ooo.b is f3
     assert ooo.c == 12
-    
+
     # partial unset
     ooo.set(a=Unset)
     assert ooo.a is f1
     assert ooo.b is f3
     assert ooo.c == 12
-    
-    # Test fails under PyPy.  
-    
+
+    # Test fails under PyPy.
+
+def test_generators():
+    # Stuf seems to have trouble sometimes with generators being assigned
+    # in update operations. Options and OptionChain implement update
+    # themselves as a workaround. This test confirms the workaround works.
+
+    import types
+
+    def gen():
+        n = 1
+        while True:
+            yield n
+            n += 1
+
+    _PY3 = sys.version_info[0] == 3
+
+    def next(g):
+       return g.__next__() if _PY3 else g.next()
+
+    g = gen()
+
+    o = Options(a=1, b=2, gen=gen, g=g)
+    assert type(o.g) == types.GeneratorType
+    assert next(o.g) == 1
+    assert next(o.g) == 2
+    o.g = g
+    assert next(o.g) == 3
+    o.update(g=g)
+    assert next(o.g) == 4
+    o.update({'g': g})
+    assert next(o.g) == 5
+
+    # oo = o.push()
+    # assert next(oo.g) == 6
+
+
 def test_addflat():
     class AF(object):
         options = Options(
             prefix = None,
             suffix = None
         )
-        
+
         def __init__(self, *args, **kwargs):
             self.options = AF.options.push(kwargs)
             if args:
                 used = self.options.addflat(args, ['prefix', 'suffix'])
                 if 'suffix' not in used:
                     self.options.suffix = self.options.prefix
-    
+
     a = AF(prefix='[', suffix=']')
     assert a.options.prefix == '['
     assert a.options.suffix == ']'
-    
+
     b = AF('|')
     assert b.options.prefix == '|' == b.options.suffix
-    
+
     c = AF('{', '}')
     assert c.options.prefix == '{'
     assert c.options.suffix == '}'
-    
+
     with pytest.raises(ValueError):
         d = AF('a', 'b', 'c')  # too many values!
-        
+
 def test_copy():
     options = Options(
             this=1,
     o1 = options.copy()
     assert o1 is not options
     assert o1 == options
-    
+
     o2 = options.push(dict(this=4, blik='rubber'))
     assert o2.nick == 'nack'
     assert o2.slick == 'slack'
     assert o2.blik  == 'rubber'
     assert o2.this  == 4
-    
+
     # now make a copy - check data is the same
     o3 = o2.copy()
     assert o3 is not o2
     assert o3.slick == 'slack'
     assert o3.blik  == 'rubber'
     assert o3.this  == 4
-    
+
     # make sure copy is not using sam
     o2.slick = 999
     assert o2.slick == 999
     assert o3.slick == 'slack'
 
+def test_docs_example():
+
+    def dicty(s):
+        """
+        Test helper. Helps test dict equivalence. Given different versions of Python,
+        stringified dicts may have different item order. This gets us back to real
+        dicts, which have more logical equality operations.
+        """
+        d = eval('dict({0})'.format(s))
+        if '_magic' in d:
+            del d['_magic']
+        return d
+
+    class Shape(object):
+
+        options = Options(
+            name   = None,
+            color  = 'white',
+            height = 10,
+            width  = 10,
+        )
+
+        def __init__(self, **kwargs):
+            self.options = Shape.options.push(kwargs)
+
+        def draw(self, **kwargs):
+            opts = self.options.push(kwargs)
+            # in this case, return actual, simplified dict, not string (docs shows string return)
+            d = dict(opts)
+            if '_magic' in d:
+                del d['_magic']
+            return d
+
+        def settings(self, **kwargs):
+            return ShapeContext(self, kwargs)
+
+    class ShapeContext(OptionsContext):
+        pass
+
+    one = Shape(name='one')
+    assert one.draw() == dicty("color='white', width=10, name='one', height=10")
+    assert one.draw(color='red') == dicty("color='red', width=10, name='one', height=10")
+    assert one.draw(color='green', width=22) == dicty("color='green', width=22, name='one', height=10")
+
+
+    Shape.options.set(color='blue')
+    assert one.draw() == dicty("color='blue', width=10, name='one', height=10")
+    assert one.draw(height=100) == dicty("color='blue', width=10, name='one', height=100")
+    assert one.draw(height=44, color='yellow') == dicty("color='yellow', width=10, name='one', height=44")
+
+    def is_tall(self, **kwargs):
+        opts = self.options.push(kwargs)
+        return opts.height > 100
+
+    assert not is_tall(one)
+
+    with one.settings(height=200, color='purple'):
+        assert one.draw() == dicty("color='purple', width=10, name='one', height=200")
+        assert is_tall(one)
+
+    assert not is_tall(one)
+
 #def test_child():
 #    options = Options(
 #            this=1,
 #    o1 = options.child()
 #    assert o1 is not options
 #    assert o1 == options
-#    
+#
 #    o2 = options.push(dict(this=4, blik='rubber'))
 #    assert o2.nick == 'nack'
 #    assert o2.slick == 'slack'
 #    assert o2.blik  == 'rubber'
 #    assert o2.this  == 4
-#    
+#
 #    # now make a copy - check data is the same
 #    o3 = o2.child()
 #    assert o3 is not o2
 #    assert o3.slick == 'slack'
 #    assert o3.blik  == 'rubber'
 #    assert o3.this  == 4
-#    
+#
 #    # make sure copy is not using sam
 #    o2.slick = 999
 #    assert o2.slick == 999
 #    assert o3.slick == 'slack'
-#    
+#
 #    # but make sure some things shine through
 #    options.set(man='MAN')
 #    assert o2.man == 'MAN'
 #    assert o.slick == e.slick
 #    assert o.nick  == e.nick
 #    assert o.blik  == e.blik
-    
+
     # Need to figure out py.path.LocalPath better
     # http://doc.pylib.org/en/latest/path.html#reference-documentation
     # specifically, how to get wreal file names out easily