Commits

Jonathan Eunice  committed 88087f6

added Prohibited, add, addflat based on quoter needs

  • Participants
  • Parent commits 75a8c49

Comments (0)

Files changed (5)

 very often or for every parameter--but when 
 it's useful, it's *enormously* useful and highly leveraged, leading
 to much simpler, much higher function
-APIs.
+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.
+
 
 Design Considerations
 =====================
 The net is that you can provide just about any kind of callable.
 But the meta-programming of the magic interpretation API could use a little work.
 
+
+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
+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
+subclass from setting those values.  
+
+    options = Superclass.options.add(
+        func   = None,
+        prefix = Prohibited,
+        suffix = Prohibited,
+    )
+
+  * AlernativelyBy copying or restating the superclass's options. In this case, the subclass
+    is "starting fresh" with its options, which have no connection to the 
+
+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'])
+        
+to consume optional ``prefix`` and ``suffix`` flat arguments.
+
 Notes
 =====
 
- * This is a work in progress. The underlying have
+ * 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? Discovered 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>`_
     
     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."""
 from chainstuf import chainstuf
 from nulltype import NullType
 
-Unset = NullType()
+Unset      = NullType('Unset')
+Prohibited = NullType('Prohibited')
 
 def attrs(m, first=[], underscores=False):
     """
         for key in self.keys():
             self[key] = oc[key] 
     
-    def push(self, kwargs):        
+    def push(self, kwargs):
+        """
+        Create the next layer down. Intended for instances to call during
+        ``__init__()``
+        """
         return OptionsChain(self, kwargs)
     
+    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. NB no magical
+        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.
         """
         return "{}({} layers: {})".format(self.__class__.__name__, n_layers, guts)
     
     def push(self, kwargs):
+        """
+        Create the next layer down. Intended for instances to call during
+        ``__init__()``
+        """
         return OptionsChain(self, kwargs)
-        
+
+    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 '{}' 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)
   
     #def __setattr__(self, name, value):
     #    print "OptionsChain.__setattr__() name:", name, "value:", 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
     # 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).
+    # (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
 
 setup(
     name='options',
-    version=verno("0.012"),
+    version=verno("0.024"),
     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='',
+    url='https://bitbucket.org/jeunice/options',
     py_modules=['options', 'nulltype'],
     install_requires=['stuf', 'otherstuf', 'six'],
     classifiers=linelist("""
+from stuf import stuf
+class A(object):
+    def __init__(self):
+        self.s = stuf()
+
+    def __getattr__(self, key):
+        # handle normal object attributes
+        if key in self.__dict__:
+            return self.__dict__[key]
+        # handle special attributes
+        else:
+            return self.s[key]
+    
+    def __setattr__(self, key, value):
+        # handle normal object attributes
+        if key == 's' or key in self.__dict__:
+            object.__setattr__(self, key, value)
+        else:
+            self.s[key] = value
+            
+    def magic(self, key, func):
+        def fget(key):
+            return self.s[key]
+        def fset(key, value):
+            self.s[key] = func(value)
+        def fdel(key):
+            del self.s[key]
+        self.__dict__[key] = property(fget, fset, fdel, 'magic for {}'.format(key))
+        print "setting magic"
+        
+    @property
+    def slick(self):
+        return self.s['slick']
+    
+    @slick.setter
+    def slick(self, value):
+        self.s['slick'] = 2 * value
+
+class C(object):
+    def __init__(self):
+        self._x = None
+
+    @property
+    def x(self):
+        """I'm the 'x' property."""
+        return self._x
+
+    @x.setter
+    def x(self, value):
+        self._x = value * 2
+
+    @x.deleter
+    def x(self):
+        del self._x
+        
+class D(object):
+    def __init__(self):
+        self.s = stuf()
+
+    @property
+    def x(self):
+        """I'm the 'x' property."""
+        return self.s['x']
+    
+    @x.setter
+    def x(self, value):
+        self.s['x'] = value * 2
+
+    @x.deleter
+    def x(self):
+        del self.s['x']
+        
+class E(object):
+    def __init__(self):
+        self.s = stuf()
+        
+    def magic(self, key, func):
+        def fget(key):
+            return self.s[key]
+        def fset(key, value):
+            self.s[key] = func(value)
+        def fdel(key):
+            del self.s[key]
+        self.__class__.x = property(fget, fset, fdel, 'magic for {}'.format(key))
+    
+    # nb must set property in __class__ (here, E) not in instance (e)
+    
+class Setter(object):
+    def __init__(self, initial, updater):
+        self.initial = initial
+        self.updater = updater
+    
+class Stuffer(object):
+    def __init__(self, **kwargs):
+        self.s = stuf()
+        self.magic = dict()
+        for k,v in kwargs.items():
+            if isinstance(v, Setter):
+                self.s[k] = v.initial
+                self.magic[k] = v.updater
+            else:
+                self.s[k] = v
+
+    def __getattr__(self, key):
+        # handle normal object attributes
+        if key in self.__dict__:
+            return self.__dict__[key]
+        # handle special attributes
+        else:
+            return self.s[key]
+    
+    def __setattr__(self, key, value):
+        # handle normal object attributes
+        if key == 's' or key == 'magic' or key in self.__dict__:
+            object.__setattr__(self, key, value)
+        elif key in self.magic:
+            value = self.magic[key](value)
+            self.s[key] = value
+        else:
+            self.s[key] = value
+            
+class Stufer(stuf):
+    def __init__(self, **kwargs):
+        stuf.__init__(self)
+        self._magic = dict()
+        for k,v in kwargs.items():
+            if isinstance(v, Setter):
+                self[k] = v.initial
+                self._magic[k] = v.updater
+            else:
+                self[k] = v
+    
+    def __setattr__(self, key, value):
+        # handle normal object attributes
+        if key == '_magic' or key in self.__dict__:
+            stuf.__setattr__(self, key, value)
+        elif key in self._magic:
+            value = self._magic[key](value)
+            self[key] = value
+        else:
+            self[key] = value
+            
+if __name__ == '__main__':
+    a = A()
+    a.something = 144
+    print a.something
+    a.magic('woobers', lambda x: 2 * x)
+    a.woobers = 10
+    print a.woobers
+    a.slick = 10
+    print a.slick
+    print "--"
+    c = C()
+    c.x = 12
+    print c.x
+    print "---"
+    d = D()
+    d.x = 5
+    print d.x
+    print "---"
+    e = E()
+    e.magic('x', lambda value: value * 2)
+    e.x = 9
+    print e.x
+    print e.s
+    print "==="
+    s = Stuffer(this=1, that=2, name='joe')
+    print s.name
+    t = Stuffer(this=12, name=Setter('joe', lambda x: x.upper()))
+    print t.name
+    t.name = 'frank'
+    print t.name
+    print "===***"
+    s = Stufer(this=1, that=2, name='joe')
+    print s.name
+    t = Stufer(this=12, name=Setter('joe', lambda x: x.upper()))
+    print t.name
+    t.name = 'frank'
+    print t.name
+  
+# playing with options for property setting