More unit tests for cacher.FirstArg.
authorNeil Moore <neil@s-z.org>
Tue, 27 May 2008 08:28:30 +0000 (04:28 -0400)
committerNeil Moore <neil@s-z.org>
Tue, 27 May 2008 08:28:30 +0000 (04:28 -0400)
Add a number of extra unit tests (key immutability, distinctness of
  subclasses, etc.).
Use doctest to run the unit tests when invoked directly.

cacher.py

index 20b6dfe..c126672 100644 (file)
--- a/cacher.py
+++ b/cacher.py
@@ -1,30 +1,88 @@
 class FirstArg(type):
     """Metaclass to cache object creation by a key, which must be the
-    first constructor parameter.  Once an object with a given key has been
-    instantiated, subsequent calls to the constructor using the same first
-    argument will return the same object, even if the other arguments differ.
-
-    Examples / Tests
-    ================
-
+    first constructor parameter.
+    
+    >>> import cacher
     >>> class MyClass (object):
     ...     __metaclass__ = cacher.FirstArg
-    ...     def __init__(self, name, extra): self.extra = extra
+    ...     def __init__(self, name, extra):
+    ...         self.name = name
+    ...         self.extra = extra
+    >>> obj = MyClass('foo', 42)
+
+
+    The constructor must be called with the correct number of arguments:
+
     >>> bad = MyClass('bad')
     Traceback (most recent call last):
         ....
     TypeError: __init__() takes exactly 3 arguments (2 given)
-    >>> obj = MyClass('foo', 42)
-    >>> MyClass('foo') is obj
+
+
+    Subsequent calls to the constructor that have the same first argument
+    yield the same object:
+
+    >>> MyClass('foo', 42) is obj
     True
+
+    
+    This is true even if the other parameters differ:
+
     >>> MyClass('foo', None) is obj
     True
-    >>> MyClass('bar', 18) is obj
-    False
     >>> MyClass('foo', 'twelve').extra
     42
+
+
+    In fact, if an instance has been constructed, the remaining
+    parameters are disregarded, and can be omitted
+
+    >>> MyClass('foo') is obj
+    True
+    >>> MyClass('foo', 1, 2, 3, 4, 5, 6, 7) is obj
+    True
+
+
+    If the first parameter differs, the constructed object will differ:
+
+    >>> MyClass('bar', 18) is obj
+    False
     >>> MyClass('bar').extra
     18
+
+    Any immutable key may be used:
+
+    >>> pairobj = MyClass((1,2), {})
+    >>> MyClass((1,2)).name
+    (1, 2)
+    >>> MyClass((1,2)).extra
+    {}
+    >>> badobj = MyClass([1,2], 26)
+    Traceback (most recent call last):
+        ....
+    TypeError: list objects are unhashable
+
+   
+    The objects themselves can be mutable:
+
+    >>> MyClass('foo').extra = 9
+    >>> obj.extra
+    9
+    >>> MyClass('bar').name = 'something else'
+    >>> MyClass('bar').name
+    'something else'
+
+    
+    The cache is not shared with subclasses:
+
+    >>> class SubClass (MyClass):
+    ...     def __init__(self, name, extra):
+    ...         super(SubClass,self).__init__(name, extra)
+    >>> sub = SubClass('foo', 76)
+    >>> sub is obj
+    False
+    >>> sub.extra
+    76
     """
     def __init__(cls, name, bases, cdict):
         super(FirstArg, cls).__init__(name, bases, cdict)
@@ -35,3 +93,7 @@ class FirstArg(type):
             cls.__instcache[name] = super(FirstArg, cls).__call__(
                     name, *args, **kw )
         return cls.__instcache[name]
+
+if __name__ == "__main__":
+    import doctest
+    doctest.testmod()