Jonathan Eunice avatar Jonathan Eunice committed 794092f

reorganzied; repackaged as package; tweaked api; updated docs and tests

Comments (0)

Files changed (13)

 Subclassing
 ===========
 
-Subclasses may have a different set of options than the superclass. In this case,
-the subclass should ``add()`` to the superclass's options. This creates a layered
+Subclass options may differ from superclass options. Usually they will share
+many options, but some may be added, and others removed. To modify the set of
+available options, the subclass defines its options with the ``add()`` method to
+the superclass options. This creates a layered
 effect, just like ``push()`` for an instance. The difference is, ``push()`` does
 not allow new options (keys) to be defined; ``add()`` does. It is also possible to
 assign the special null object ``Prohibited``, which will disallow instances of the
         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
+options."
 
-An alternative is to copy (or restart) the superclass's options. That suits cases
-where changes to the superclass's options should not effect the subclass's options.
+An alternative is to copy (or restate) the superclass's options. That suits
+cases where the subclass is highly independent, and where changes to the
+superclass's options should not effect the subclass's options.
 With ``add()``, they remain linked in the same way as instances and classes are.
 
 Flat Arguments
 
 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'])
+        if args:
+            self.options.addflat(args, ['prefix', 'suffix'])
         
-to consume optional ``prefix`` and ``suffix`` flat arguments.
+to consume optional ``prefix`` and ``suffix`` flat arguments. This makes the following
+equivalent::
+
+    q1 = Quoter('[', ']')
+    q2 = Quoter(prefix='[', suffix=']')
+
+As a design
+decision, an explicit ``addflat()`` method is provided not as much for Zen of
+Python reasons ("Explicit is better than implicit."), but because flat arguments
+are commonly combined with abbreviation/shorthand conventions, which may require
+some logic to implement. For example, if only a ``prefix`` is given as a flat
+argument, you meay want to use the same value to implicitly set the ``suffix``.
+To this end, addflat returns the set of keys that it consumed::
+
+        if args:
+            used = self.options.addflat(args, ['prefix', 'suffix'])
+            if 'suffix' not in used:
+                self.options.suffix = self.options.prefix
 
 Notes
 =====
    `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? Discovered use case for this: "Abbreviation"
+   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.
    
+ * 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
    
  * Versions subsequent to 0.200 require a late-model version of ``stuf`` to
    avoid a problem its earlier iterations had with file objects.
+   
+ * Now packaged as a package, not a set of modules. ``six`` module now required only for testing.
  
- * The author, `Jonathan Eunice <mailto:jonathan.eunice@gmail.com>`_ or
-   `@jeunice on Twitter <http://twitter.com/jeunice>`_
-   welcomes your comments and suggestions.
-
+ * API for ``push()`` and ``addflat()`` cleaned up to explicitly delink those methods.
+ 
 Installation
 ============
 

__init__.py

-from options import *
-

funclike.py

-
-"""
-Helper functions for function-like objects.
-"""
-
-function_type = type(lambda: True)   # define our own because not def'd in py26
-
-def is_function(obj):
-    """
-    Answers, is obj a function?
-    """
-    return isinstance(obj, function_type)
-    
-def real_func(flike):
-    """
-    Given a function-like object (function or staticmethod), return the real function.
-    """
-    if is_function(flike):
-        return flike
-    elif hasattr(flike, 'im_func'):      # Not clear this still useful
-        return flike.im_func
-    elif hasattr(flike, '__func__'):    # static method
-        return flike.__func__
-    elif hasattr(flike, '__get__'):     # static method in py26
-        return flike.__get__(True)
-    else:
-        raise ValueError("doesn't seem to be a function-like object")
-    
-def func_code(flike):
-    """
-    Given a function like object (function, static method, etc.), return its
-    functional code object. Attempts to bridge the gap between different versions'
-    introspective namings.
-    """
-
-    if hasattr(flike, 'func_code'):  # Python 2.6 or 2.7, most functions
-        return flike.func_code
-    elif hasattr(flike, '__code__'):   # Python 3
-        return flike.__code__
-    else:       
-        raise ValueError("don't know where to find function's code")

nulltype.py

-import six
-
-class NullType(object):
-    """
-    A 'null' type different from, but parallel to, None. Core function
-    is representing emptyness in a way that doesn't overload None.
-    
-    Instantiate to create desired Null values.
-    """
-    def __init__(self, name=None):
-        self.name = name
-        
-    def __repr__(self):
-        if self.name is not None:
-            return self.name
-        else:
-            return repr(self)
-        
-    if six.PY3:
-        def bool(self):
-            """I am always False."""
-            return False
-    else:
-        def __nonzero__(self):
-            """I am always False."""
-            return False

options.py

-"""Options"""
-
-from stuf import orderedstuf
-from chainstuf import chainstuf
-from nulltype import NullType
-from funclike import *
-import sys
-
-Unset      = NullType('Unset')
-Prohibited = NullType('Prohibited')
-
-def attrs(m, first=[], underscores=False):
-    """
-    Given a mapping m, return a string listing its values in a
-    key=value format. Items with underscores are, by default, not
-    listed. If you want some things listed first, include them in
-    the list first.
-    """
-    keys = first[:]
-    for k in m.keys():
-        if not underscores and k.startswith('_'):
-            continue
-        if k not in first:
-            keys.append(k)
-    return ', '.join([ "{0}={1}".format(k, repr(m[k])) for k in keys ])
-
-class Options(orderedstuf):
-    """
-    Options handler.
-    """
-    
-    def __init__(self, *args, **kwargs):
-        orderedstuf.__init__(self, *args, **kwargs)
-        self._magic = {}
-    
-    def __repr__(self):
-        return "{0}({1})".format(self.__class__.__name__, attrs(self))
-    
-    def set(self, **kwargs):
-        # To set values at the base options level, create a temporary next level,
-        # which will have the magical option interpretation. Then copy the resulting
-        # values here. Do in original ordering.
-        oc = OptionsChain(self, kwargs)
-        for key in self.keys():
-            self[key] = oc[key] 
-    
-    def push(self, *args):
-        """
-        Create the next layer down. Intended for instances to call during
-        ``__init__()``. If just one arg is given, it's the kwargs. If 3,
-        it's args, keys, kwargs.
-        
-        """
-        if len(args) == 1:
-            return OptionsChain(self, args[0])
-        elif len(args) == 3:
-            return OptionsChain(self, args[2])._addflat(args[0], args[1])
-        else:
-            raise ValueError('either 1 args or 3')
-
-  
-    def add(self, **kwargs):
-        """
-        Create the next layer down. Like ``push()``, but accepts full kwargs
-        not just a dict. Intended for subclasses to call when defining their
-        own class options. Not for instances to call.
-        """
-        child = OptionsChain(self, {})
-        for key, value in kwargs.items():
-            child[key] = value
-        return child
-    
-    def magic(self, **kwargs):
-        """
-        Set some options as having 'magical' update properties. In a sense, this
-        is like Python ``properties`` that have a setter.  NB no magical
-        processing is done to the base Options. These are assumed to have whatever
-        adjustments are needed when they are originally set.
-        """
-        self.setdefault('_magic', {})
-        for k, v in kwargs.items():
-            self._magic[k] = real_func(v)
-    
-    def magical(self, key):
-        """
-        Instance based decorator, specifying a function in the using module
-        as a magical function. Note that the magical methods will be called
-        with a self of None. 
-        """
-            
-        def my_decorator(func):
-            self._magic[key] = func
-            return func
-        
-        return my_decorator
-    
-        # not sure why we take such care to get the order right for Options, because the
-        # dict handed to Options.__init__ is not in order!
-        
-        # should there be a leftovers_ok option that raises an error on push()
-        # if there are leftovers?    
-
-class OptionsChain(chainstuf):
-    
-    def __init__(self, bottom, kwargs):
-        """
-        Create an OptionsChain, pushing one level down.
-        """
-        chainstuf.__init__(self, bottom)
-        processed = self._process(bottom, kwargs)
-        self.maps = [ processed, bottom ]
-        
-    def _magicalized(self, key, value):
-        """
-        Get the magically processed value for a single key value pair.
-        If there is no magical processing to be done, just returns value.
-        """
-        magicfn = self._magic.get(key, None)
-        if magicfn is None:
-            return value
-        argcount = func_code(magicfn).co_argcount
-        if argcount == 1:
-            return magicfn(value)
-        elif argcount == 2:
-            return magicfn(value, self)
-        elif argcount == 3:
-            return magicfn(None, value, self)
-        else:
-            raise ValueError('magic function should have 1-3 arguments, not {0}'.format(argcount))
-
-    def _process(self, base, kwargs):
-        """
-        Given kwargs, removes any key:value pairs corresponding to this set of
-        options. Those pairs are interpreted according to'paramater
-        interpretation magic' if needed, then returned as dict. Any key:value
-        pairs remaining in kwargs are not options related to this class, and may
-        be used for other purposes.
-        """
-
-        opts = {}
-        for key, value in kwargs.items():
-            if key in base:
-                opts[key] = self._magicalized(key, value)
-                
-        # NB base identical to self when called from set(), but not when called
-        # from __init__()
-        
-        # empty kwargs of 'taken' options
-        for key in opts:
-            del kwargs[key]
-
-        return opts
-    
-    def __repr__(self):
-        """
-        Get repr() of OptionsChain. Dig down to find earliest ancestor, which
-        contains the right ordering of keys.
-        """
-        grandpa = self.maps[-1]
-        n_layers  = len(self.maps)
-        while not isinstance(grandpa, Options):
-            n_layers += len(grandpa.maps) - 1
-            grandpa = grandpa.maps[-1]
-            
-        guts = attrs(self, first=list(grandpa.keys()), underscores=True)
-        return "{0}({1} layers: {2})".format(self.__class__.__name__, n_layers, guts)
-    
-    def push(self, *args):
-        """
-        Create the next layer down. Intended for instances to call during
-        ``__init__()``. If just one arg is given, it's the kwargs. If 3,
-        it's args, keys, kwargs.
-        
-        """
-        if len(args) == 1:
-            return OptionsChain(self, args[0])
-        elif len(args) == 3:
-            return OptionsChain(self, args[2])._addflat(args[0], args[1])
-        else:
-            raise ValueError('either 1 args or 3')
-
-    def add(self, **kwargs):
-        """
-        Create the next layer down. Like ``push()``, but accepts full kwargs
-        not just a dict. Intended for subclasses to call when defining their
-        own class options. Not for instances to call.
-        """
-        child = OptionsChain(self, {})
-        for key, value in kwargs.items():
-            child[key] = value
-        return child
-    
-    def set(self, **kwargs):
-        newopts = self._process(self, kwargs)
-        for k, v in newopts.items():
-            if v is Unset:
-                del self.maps[0][k]
-            elif self[k] is Prohibited:
-                raise KeyError("changes to '{0}' are prohibited".format(k))
-            else:
-                self.maps[0][k] = v
-                
-                
-    # TBD Need to examine all the places we need to check for Prohibited
-    # when setting values - eg, what about in Options
-    
-    # Do we want to define a more specific exception for Prohibited?
-
-    # TBD when using Prohibited values, __getattr__ and __getitem_ should raise
-    # an exception when being accessed extenrally
-
-    #  TBD Handle the unsetting of magic in subclasses
-
-    def _addflat(self, args, keys):
-        """
-        Sometimes kwargs aren't the most elegant way to provide options. In those
-        cases, this routine helps map flat args to kwargs. Provide the actual args,
-        followed by keys in the order in which they should be consumed. There can
-        be more keys than args.
-        """
-        if len(args) > len(keys):
-            raise ValueError('More args than keys not allowed')
-        additional = dict(zip(keys, args))
-        self.update(additional)
-        return self
-  
-    #def __setattr__(self, name, value):
-    #    print "OptionsChain.__setattr__() name:", name, "value:", value
-    #    if name in self:
-    #        print "    in self"
-    #        if value is Unset and name in self.maps[0]:
-    #            # only unset the very top level
-    #            del self.maps[0][name]
-    #        else:
-    #            self[name] = self._magicalized(name, value)
-    #    else:
-    #        print "    not in self; punt to superclass"
-    #        chainstuf.__setattr__(self, name, value)
-
-    # could possibly extend set() magic to setattr (but would have to be
-    # careful of recursions). Current draft doesn't work - infinite recursion
-    
-class OptionsContext(object):
-    """
-    Context manager so that modules that use Options can easily implement
-    a `with x.settings(...):` capability. In x's class:
-    
-    def settings(self, **kwargs):
-        return OptionsContext(self, kwargs)
-    """
-
-    def __init__(self, caller, kwargs):
-        """
-        When `with x.method(*args, **kwargs)` is called, it creates an OptionsContext
-        passing in its **kwargs. 
-        """
-        self.caller = caller        
-        if 'opts' in kwargs:
-            newopts = OptionsChain(caller.options, kwargs['opts'])
-            newopts.maps.insert(0, caller._process(newopts, kwargs))
-        else:
-            newopts = OptionsChain(caller.options, kwargs)
-        caller.options = newopts
-
-    def __enter__(self):
-        """
-        Called when the `with` is about to be 'entered'. Whatever this returns
-        will be the value of `x` if the `as x` construction is used. Not generally
-        needed for option setting, but might be needed in a subclass.
-        """            
-        return self.caller
-    
-    def __exit__(self, exc_type, exc_value, traceback):
-        """
-        Called when leaving the `with`. Reset caller's options to what they were
-        before we entered.
-        """
-        self.caller.options = self.caller.options.maps[-1]
-
-    # Using a with statement and OptionsContext can effectively reduce the
-    # thread safety of an object, even to NIL. This is because the object is
-    # modified for an indeterminate period. It would be possible to improve
-    # thread safety with an with..as construction, if what was returned by as
-    # were a proxy that didn't modify the original object. Another approach
-    # might be to provide per-thread options, with _get_options() looking
-    # options up in a tid-indexed hash, and set() operations creating a copy
-    # (copy-on-write, per thread, essentially).
-    
-    # with module quoter have found some interesting use cases like abbreviation
-    # args (MultiSetter), and have further explored how subclasses will integrate
-    # with the delegation-based options. Have discovered that some subclasses will
-    # want flat args, prohibit superclass args. Also, some args could potentially
-    # allow setting at instantiation time but not call/use time.
-    
-    # Would it make sense to support magicalized class setting -- for subclasses?
-    # even if it would, how to accomplish neatly? probably would require a
-    # property like object that lets you assign the magic along with the initial value
-    
-    # next step: integrate setter from testprop.py
-    # consider adding _for key pointing to object that options are for
-    # also providing access to whole dict for arguments like chars
-    # and clean up HTMLQuoter using the improved functions & access
-    
-    # quoter might provide the usecase for a getter, too - in that suffix is == prefix
-    # if suffix itself not given
-    
-    # instead of Magic() or Setter() maybe Property() or Prop()

options/__init__.py

+try:
+    from options.options import *
+except ImportError:
+    from options import *
+
+__all__ = 'Unset Prohibited attrs Options OptionsChain OptionsContext'.split()

options/funclike.py

+
+"""
+Helper functions for function-like objects.
+"""
+
+function_type = type(lambda: True)   # define our own because not def'd in py26
+
+def is_function(obj):
+    """
+    Determines: Is obj a function?
+    """
+    return isinstance(obj, function_type)
+    
+def real_func(flike):
+    """
+    Given a function-like object (function or staticmethod), return the real function.
+    """
+    if is_function(flike):
+        return flike
+    elif hasattr(flike, 'im_func'):     # Not clear this still useful; was for methods < py26 
+        return flike.im_func
+    elif hasattr(flike, '__func__'):    # method (static or otherwise) >= p26
+        return flike.__func__
+    elif hasattr(flike, '__get__'):     # static method in py26
+        return flike.__get__(True)
+    else:
+        raise ValueError("doesn't seem to be a function-like object")
+    
+    # Introspection: Isn't it fun?! It's valuable...but changing in the underlying
+    # data model or implementation => oy vey!
+    
+def func_code(flike):
+    """
+    Given a function like object (function, static method, etc.), return its
+    functional code object. Attempts to bridge the gap between different versions'
+    introspective namings.
+    """
+
+    if hasattr(flike, 'func_code'):    # Python 2.6 or 2.7, most functions
+        return flike.func_code
+    elif hasattr(flike, '__code__'):   # Python 3
+        return flike.__code__
+    else:       
+        raise ValueError("don't know where to find function's code")

options/nulltype.py

+import sys
+
+_PY3 = sys.version_info[0] == 3
+
+class NullType(object):
+    """
+    A 'null' type different from, but parallel to, None. Core function
+    is representing emptyness in a way that doesn't overload None.
+    This helps create designated identifiers with specific meanings
+    such as Passthrough, Prohibited, and Undefined.
+    
+    Instantiate to create desired null singletons. While they are
+    singletons, depends on usage convention rather than strict
+    enforcement to maintain their singleton-ness. This is a problem
+    roughly 0% of the time.
+    """
+    def __init__(self, name=None):
+        self.name = name
+        
+    def __repr__(self):
+        if self.name is not None:
+            return self.name
+        else:
+            return repr(self)
+        
+    if _PY3:
+        def bool(self):
+            """I am always False."""
+            return False
+    else:
+        def __nonzero__(self):
+            """I am always False."""
+            return False

options/options.py

+"""Options"""
+
+import sys
+
+from stuf import orderedstuf
+from chainstuf import chainstuf
+
+try:
+    from options.nulltype import NullType
+except ImportError:
+    from nulltype import NullType
+    
+try:
+    from options.funclike import *
+except ImportError:
+    from funclike import *
+    
+# Do not understand why we need these try/except and old-style
+# relative import fallbacks. But if they're not there, tests
+# fail. With them there, everything works great. So... ?
+
+    
+Unset      = NullType('Unset')
+Prohibited = NullType('Prohibited')
+
+def attrs(m, first=[], underscores=False):
+    """
+    Given a mapping m, return a string listing its values in a
+    key=value format. Items with underscores are, by default, not
+    listed. If you want some things listed first, include them in
+    the list first.
+    """
+    keys = first[:]
+    for k in m.keys():
+        if not underscores and k.startswith('_'):
+            continue
+        if k not in first:
+            keys.append(k)
+    return ', '.join([ "{0}={1}".format(k, repr(m[k])) for k in keys ])
+
+class Options(orderedstuf):
+    """
+    Options handler.
+    """
+    
+    def __init__(self, *args, **kwargs):
+        orderedstuf.__init__(self, *args, **kwargs)
+        self._magic = {}
+    
+    def __repr__(self):
+        return "{0}({1})".format(self.__class__.__name__, attrs(self))
+    
+    def set(self, **kwargs):
+        # To set values at the base options level, create a temporary next level,
+        # which will have the magical option interpretation. Then copy the resulting
+        # values here. Do in original ordering.
+        oc = OptionsChain(self, kwargs)
+        for key in self.keys():
+            self[key] = oc[key] 
+    
+    def push(self, kwargs):
+        """
+        Create the next layer down. Intended for instances to call during
+        ``__init__()``. 
+        
+        """
+        return OptionsChain(self, kwargs)
+    
+        # NB the implicit addflat() call is gone
+
+  
+    def add(self, **kwargs):
+        """
+        Create the next layer down. Like ``push()``, but accepts full kwargs
+        not just a dict. Intended for subclasses to call when defining their
+        own class options. Not for instances to call.
+        """
+        child = OptionsChain(self, {})
+        for key, value in kwargs.items():
+            child[key] = value
+        return child
+    
+    def magic(self, **kwargs):
+        """
+        Set some options as having 'magical' update properties. In a sense, this
+        is like Python ``properties`` that have a setter.  NB no magical
+        processing is done to the base Options. These are assumed to have whatever
+        adjustments are needed when they are originally set.
+        """
+        self.setdefault('_magic', {})
+        for k, v in kwargs.items():
+            self._magic[k] = real_func(v)
+    
+    def magical(self, key):
+        """
+        Instance based decorator, specifying a function in the using module
+        as a magical function. Note that the magical methods will be called
+        with a self of None. 
+        """
+            
+        def my_decorator(func):
+            self._magic[key] = func
+            return func
+        
+        return my_decorator
+    
+        # not sure why we take such care to get the order right for Options, because the
+        # dict handed to Options.__init__ is not in order!
+        
+        # should there be a leftovers_ok option that raises an error on push()
+        # if there are leftovers?    
+
+class OptionsChain(chainstuf):
+    
+    def __init__(self, bottom, kwargs):
+        """
+        Create an OptionsChain, pushing one level down.
+        """
+        chainstuf.__init__(self, bottom)
+        processed = self._process(bottom, kwargs)
+        self.maps = [ processed, bottom ]
+        
+    def _magicalized(self, key, value):
+        """
+        Get the magically processed value for a single key value pair.
+        If there is no magical processing to be done, just returns value.
+        """
+        magicfn = self._magic.get(key, None)
+        if magicfn is None:
+            return value
+        argcount = func_code(magicfn).co_argcount
+        if argcount == 1:
+            return magicfn(value)
+        elif argcount == 2:
+            return magicfn(value, self)
+        elif argcount == 3:
+            return magicfn(None, value, self)
+        else:
+            raise ValueError('magic function should have 1-3 arguments, not {0}'.format(argcount))
+
+    def _process(self, base, kwargs):
+        """
+        Given kwargs, removes any key:value pairs corresponding to this set of
+        options. Those pairs are interpreted according to'paramater
+        interpretation magic' if needed, then returned as dict. Any key:value
+        pairs remaining in kwargs are not options related to this class, and may
+        be used for other purposes.
+        """
+
+        opts = {}
+        for key, value in kwargs.items():
+            if key in base:
+                opts[key] = self._magicalized(key, value)
+                
+        # NB base identical to self when called from set(), but not when called
+        # from __init__()
+        
+        # empty kwargs of 'taken' options
+        for key in opts:
+            del kwargs[key]
+
+        return opts
+    
+    def __repr__(self):
+        """
+        Get repr() of OptionsChain. Dig down to find earliest ancestor, which
+        contains the right ordering of keys.
+        """
+        grandpa = self.maps[-1]
+        n_layers  = len(self.maps)
+        while not isinstance(grandpa, Options):
+            n_layers += len(grandpa.maps) - 1
+            grandpa = grandpa.maps[-1]
+            
+        guts = attrs(self, first=list(grandpa.keys()), underscores=True)
+        return "{0}({1} layers: {2})".format(self.__class__.__name__, n_layers, guts)
+    
+    def push(self, kwargs):
+        """
+        Create the next layer down. Intended for instances to call
+        individual method invocations, where ``self.options`` is already
+        an OptionChain. 
+        
+        """
+        return OptionsChain(self, kwargs)
+    
+        # NB Implicit addflat() has been removed
+
+    def addflat(self, args, keys):
+        """
+        Sometimes kwargs aren't the most elegant way to provide options. In those
+        cases, this routine helps map flat args to kwargs. Provide the actual args,
+        followed by keys in the order in which they should be consumed. There can
+        be more keys than args, but not the other way around. Returns a list of
+        keys used (from which one can also determine if a key was not used).
+        """
+        if len(args) > len(keys):
+            raise ValueError('More args than keys not allowed')
+        additional = dict(zip(keys, args))
+        self.update(additional)
+        keys_used = list(additional.keys())
+        return keys_used 
+
+    def add(self, **kwargs):
+        """
+        Create the next layer down. Like ``push()``, but accepts full kwargs
+        not just a dict. Intended for subclasses to call when defining their
+        own class options. Not for instances to call.
+        """
+        child = OptionsChain(self, {})
+        for key, value in kwargs.items():
+            child[key] = value
+        return child
+    
+    def set(self, **kwargs):
+        newopts = self._process(self, kwargs)
+        for k, v in newopts.items():
+            if v is Unset:
+                del self.maps[0][k]
+            elif self[k] is Prohibited:
+                raise KeyError("changes to '{0}' are prohibited".format(k))
+            else:
+                self.maps[0][k] = v
+                
+                
+    # TBD Need to examine all the places we need to check for Prohibited
+    # when setting values - eg, what about in Options
+    
+    # Do we want to define a more specific exception for Prohibited?
+
+    # TBD when using Prohibited values, __getattr__ and __getitem_ should raise
+    # an exception when being accessed extenrally
+
+    #  TBD Handle the unsetting of magic in subclasses
+
+ 
+  
+    #def __setattr__(self, name, value):
+    #    print "OptionsChain.__setattr__() name:", name, "value:", value
+    #    if name in self:
+    #        print "    in self"
+    #        if value is Unset and name in self.maps[0]:
+    #            # only unset the very top level
+    #            del self.maps[0][name]
+    #        else:
+    #            self[name] = self._magicalized(name, value)
+    #    else:
+    #        print "    not in self; punt to superclass"
+    #        chainstuf.__setattr__(self, name, value)
+
+    # could possibly extend set() magic to setattr (but would have to be
+    # careful of recursions). Current draft doesn't work - infinite recursion
+    
+class OptionsContext(object):
+    """
+    Context manager so that modules that use Options can easily implement
+    a `with x.settings(...):` capability. In x's class:
+    
+    def settings(self, **kwargs):
+        return OptionsContext(self, kwargs)
+    """
+
+    def __init__(self, caller, kwargs):
+        """
+        When `with x.method(*args, **kwargs)` is called, it creates an OptionsContext
+        passing in its **kwargs. 
+        """
+        self.caller = caller        
+        if 'opts' in kwargs:
+            newopts = OptionsChain(caller.options, kwargs['opts'])
+            newopts.maps.insert(0, caller._process(newopts, kwargs))
+        else:
+            newopts = OptionsChain(caller.options, kwargs)
+        caller.options = newopts
+
+    def __enter__(self):
+        """
+        Called when the `with` is about to be 'entered'. Whatever this returns
+        will be the value of `x` if the `as x` construction is used. Not generally
+        needed for option setting, but might be needed in a subclass.
+        """            
+        return self.caller
+    
+    def __exit__(self, exc_type, exc_value, traceback):
+        """
+        Called when leaving the `with`. Reset caller's options to what they were
+        before we entered.
+        """
+        self.caller.options = self.caller.options.maps[-1]
+
+    # Using a with statement and OptionsContext can effectively reduce the
+    # thread safety of an object, even to NIL. This is because the object is
+    # modified for an indeterminate period. It would be possible to improve
+    # thread safety with an with..as construction, if what was returned by as
+    # were a proxy that didn't modify the original object.  Another approach
+    # might be to provide per-thread options, with _get_options() looking
+    # options up in a tid-indexed hash, and set() operations creating a copy
+    # (copy-on-write, per thread, essentially).
+    
+    # with module quoter have found some interesting use cases like abbreviation
+    # args (MultiSetter), and have further explored how subclasses will integrate
+    # with the delegation-based options. Have discovered that some subclasses will
+    # want flat args, prohibit superclass args. Also, some args could potentially
+    # allow setting at instantiation time but not call/use time.
+    
+    # Would it make sense to support magicalized class setting -- for subclasses?
+    # even if it would, how to accomplish neatly? probably would require a
+    # property like object that lets you assign the magic along with the initial value
+    
+    # next step: integrate setter from testprop.py
+    # consider adding _for key pointing to object that options are for
+    # also providing access to whole dict for arguments like chars
+    # and clean up HTMLQuoter using the improved functions & access
+    
+    # quoter might provide the usecase for a getter, too - in that suffix is == prefix
+    # if suffix itself not given
+    
+    # instead of Magic() or Setter() maybe Property() or Prop()
 #! /usr/bin/env python
 
-from setuptools import setup
+from setuptools import setup, find_packages
 from decimal import Decimal
 import re
 
 
 setup(
     name='options',
-    version=verno("0.204"),
+    version=verno("0.264"),
     author='Jonathan Eunice',
     author_email='jonathan.eunice@gmail.com',
     description='Container for flexible class, instance, and function call options',
     long_description=open('README.rst').read(),
     url='https://bitbucket.org/jeunice/options',
-    py_modules=['options', 'nulltype', 'funclike'],
-    install_requires=['stuf>=0.9.9', 'otherstuf>=0.65', 'six'],
-    tests_require = ['tox', 'pytest'],
+    packages=find_packages(),
+    install_requires=['stuf>=0.9.10', 'otherstuf>=0.65', 'six'],
+    tests_require = ['tox', 'pytest', 'six'],
     zip_safe = True,
     keywords='options config configuration parameters arguments',
     classifiers=linelist("""
 
 from options import *
-import sys
-import six
+import sys, six, pytest
     
 def test_good_chainstuf():
     """Test options class for being faithful subclass of chainstuf"""
     assert t.options.man == 'BOY'
     t.set(man='girl')
     assert t.options.man == 'GIRL'
-    
-
    
 def test_push():
     
     assert ooo.a is f1
     assert ooo.b is f3
     assert ooo.c == 12
+    
+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!
+    
+

test/test_funclike.py

 
-from funclike import *
+from options.funclike import *
 
 ### Setup
 
 
 [testenv]
 changedir=test
-deps=pytest       # install pytest in the venvs
+deps=
+    six
+    pytest
 commands=py.test {posargs}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.