Jonathan Eunice avatar Jonathan Eunice committed ebf39de

clean repo; first commit

Comments (0)

Files changed (8)

+syntax: glob
+*.swp.{py,txt,html,css,js}
+*.pyc
+.DS_Store
+build/*
+dist/*
+*.egg-info
+setup.cfg
+PKG-INFO
+Attribute-accesible collections inspired by `stuf
+<http://pypi.python.org/pypi/stuf>`_. Implements ``chainstuf`` and
+``counterstuf``: versions of ``ChainMap`` and ``Counter`` that expose their keys as
+attributes.
+
+The ultimate goal of this module is to have these functions available in the
+``stuf`` module, and this sidecar to be retired.
+
+Usage
+=====
+
+Use these just like you would ``ChainMap`` and ``Counter``, except that
+you get attribute-style access as well.
+
+For ``chainstuf``::
+
+    from otherstuf import chainstuf
+    
+    d1 = dict(this=1, that=2)
+    d2 = dict(roger=99, that=100)
+    
+    # test simple attribute equivalence
+    c = chainstuf(d1, d2)
+    
+    assert c.this == 1
+    assert c.roger == 99
+    
+    c.roger = 'wilco'
+    assert c.roger
+    print "roger", c.roger
+    
+    d1.update(feeling='fancypants!')
+    print "i'm feeling", c.feeling     # passed through, since d2 lacks 'feeling'
+
+For ``counterstuf``::
+
+    from otherstuf import counterstuf
+    
+    c = counterstuf()
+    c.update("this and this is this but that isn't this".split())
+    c.total = sum(c.values())
+    
+    print "everything:", c.total
+    print "'this' mentioned", c.this, "times"
+    print "'bozo' mentioned", c.bozo, "times"
+    print c
+    
+Installation
+============
+
+::
+
+    pip install otherstuf
+    
+(You may need to prefix this with "sudo " to authorize installation.)
+from chainstuf import *
+from counterstuf import *
+"""
+Sidecar to `stuf` that adds `ChainMap`-like 
+container `chainstuf`
+"""
+
+from stuf.collects import ChainMap
+
+class chainstuf(ChainMap):
+    """
+    A stuf-like surfacing of the ChainMap collection
+    (multi-layer dict) introduced in Python 3. Uses a
+    workalike replacement under Python 2.
+    """
+    def __init__(self, *maps):
+        ChainMap.__init__(self, *maps)
+        
+    def __getattr__(self, key):
+        # handle normal object attributes
+        if key in self.__dict__:
+            return self.__dict__[key]
+        # handle special attributes
+        else:
+            for m in self.maps:
+                try:
+                    return m[key]
+                except KeyError:
+                    pass
+            raise KeyError(key)
+    
+    def __setattr__(self, key, value):
+        # handle normal object attributes
+        if key == 'maps' or key in self.__dict__:
+            ChainMap.__setattr__(self, key, value)
+        else:
+            self.maps[0][key] = value
+"""
+Sidecar to `stuf` that adds `Counter`-like 
+container `counterstuf`
+"""
+
+from collections import Counter
+
+class counterstuf(Counter):
+    """stuf-like surfacing of Counter"""
+    
+    def __getattr__(self, key):
+        if key in self.__dict__:
+            return self.__dict__[key]
+        else:
+            return self[key]
+
+    def __setattr__(self, key, value):
+        if key in self.__dict__:
+            self.__dict__[key] = value
+        else:
+            self[key] = value
+        
+    def update_self(self, *args, **kwargs):
+        self.update(*args, **kwargs)
+        return self
+    
+    def copy(self):
+        return counterstuf(Counter.copy(self))
+from otherstuf import chainstuf
+
+d1 = dict(this=1, that=2)
+d2 = dict(roger=99, that=100)
+
+# test simple attribute equivalence
+c = chainstuf(d1, d2)
+
+assert c.this == 1
+assert c.roger == 99
+
+c.roger = 'wilco'
+assert c.roger
+print "roger", c.roger
+
+d1.update(feeling='fancypants!')
+print "i'm feeling", c.feeling     # passed through, since d2 lacks 'feeling'
+
+
+print "---"
+
+from otherstuf import counterstuf
+ 
+c = counterstuf()
+c.update("this and this is this but that isn't this".split())
+c.total = sum(c.values())
+
+print "everything:", c.total
+print "'this' mentioned", c.this, "times"
+print "'bozo' mentioned", c.bozo, "times"
+print c
+#! /usr/bin/env python
+
+from setuptools import setup
+from decimal import Decimal
+import re
+
+def linelist(text):
+    """
+    Returns each non-blank line in text enclosed in a list.
+    """
+    return [ l.strip() for l in text.strip().splitlines() if l.split() ]
+    
+    # The double-mention of l.strip() is yet another fine example of why
+    # Python needs en passant aliasing.
+
+
+def verno(s):
+    """
+    Update the version number passed in by extending it to the 
+    thousands place and adding 1/1000, then returning that result
+    and as a side-effect updating setup.py
+
+    Dangerous, self-modifying, and also, helps keep version numbers
+    ascending without human intervention.
+    """
+    d = Decimal(s)
+    increment = Decimal('0.001')
+    d = d.quantize(increment) + increment
+    dstr = str(d)
+    setup = open('setup.py', 'r').read()
+    setup = re.sub('verno\(\w*[\'"]([\d\.]+)[\'"]', 'verno("' + dstr + '"', setup)
+    open('setup.py', 'w').write(setup)
+    return dstr
+
+setup(
+    name='otherstuf',
+    version=verno("0.5"),
+    author='Jonathan Eunice',
+    author_email='jonathan.eunice@gmail.com',
+    description='Attributes-accessible mappings chainstuf (like ChainMap) & counterstuf (like Counter)',
+    long_description=open('README.rst').read(),
+    url='',
+    py_modules=['counterstuf', 'chainstuf'],
+    install_requires=['stuf'],
+    classifiers=linelist("""
+        Development Status :: 3 - Alpha
+        Operating System :: OS Independent
+        License :: OSI Approved :: BSD License
+        Intended Audience :: Developers
+        Programming Language :: Python
+        Topic :: Software Development :: Libraries :: Python Modules
+    """)
+)
+
+from testharness import import_from_parent, test_run
+
+import_from_parent()
+
+from otherstuf import *
+
+def test_chainstuf():
+    """Test chainstuf class"""
+    
+    # make some base dicts
+    d1 = dict(this=1, that=2)
+    d2 = dict(roger=99, that=100)
+    
+    # test simple attribute equivalence
+    dd = chainstuf(d1, 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 on chainstuf, ensure properly set, in top dict
+    dd.roger = 'wilco'
+    assert dd.roger == 'wilco'
+    assert dd.roger == d1['roger']
+    
+    # test new_child
+    dd2 = dd.new_child()
+    dd2.smorg = 44
+    assert dd2.smorg == 44
+    dd.roger = 'roger'
+    assert dd2.roger == 'roger'
+    
+def test_counterstuf():
+    """Test counterstuf class"""
+    c = counterstuf()
+    c.update("this and this is this but that isn't this".split())
+    c.total = sum(c.values())
+    assert c.total == 9
+    assert c.this == 4
+    assert c.but == 1
+    assert c.bozo == 0
+    c2 = counterstuf().update_self("big big small big medium xlarge".split())
+    assert c2.medium == 1
+    assert c2.most_common() == [('big', 3), ('small', 1), ('medium', 1), ('xlarge', 1)]
+    
+if __name__ == '__main__':
+    test_run()
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.