Commit 914ec970 authored by Tim Peters's avatar Tim Peters

Prepare to stitch in current Zope3 versions.

parent b4754201
Metadata-Version: 1.0
Name: zope.interface
Summary: Zope 3 Interface Infrastructure
Author: Zope Corporation and Contributors
Author-email: zope3-dev@zope.org
License: ZPL 2.1
Description:
The implementation of interface definitions for Zope 3.
==========
Interfaces
==========
.. contents::
Interfaces are objects that specify (document) the external behavior
of objects that "provide" them. An interface specifies behavior
through:
- Informal documentation in a doc string
- Attribute definitions
- Invariants, which are conditions that must hold for objects that
provide the interface
Attribute definitions specify specific attributes. They define the
attribute name and provide documentation and constraints of attribute
values. Attribute definitions can take a number of forms, as we'll
see below.
Defining interfaces
===================
Interfaces are defined using Python class statements:
>>> import zope.interface
>>> class IFoo(zope.interface.Interface):
... """Foo blah blah"""
...
... x = zope.interface.Attribute("""X blah blah""")
...
... def bar(q, r=None):
... """bar blah blah"""
In the example above, we've created an interface, `IFoo`. We
subclassed `zope.interface.Interface`, which is an ancestor interface for
all interfaces, much as `object` is an ancestor of all new-style
classes [#create]_. The interface is not a class, it's an Interface,
an instance of `InterfaceClass`::
>>> type(IFoo)
<class 'zope.interface.interface.InterfaceClass'>
We can ask for the interfaces documentation::
>>> IFoo.__doc__
'Foo blah blah'
and it's name::
>>> IFoo.__name__
'IFoo'
and even it's module::
>>> IFoo.__module__
'__main__'
The interface defined two attributes:
`x`
This is the simplest form of attribute definition. It has a name
and a doc string. It doesn't formally specify anything else.
`bar`
This is a method. A method is defined via a function definition. A
method is simply an attribute constrained to be a callable with a
particular signature, as provided by the function definition.
Note that `bar` doesn't take a `self` argument. Interfaces document
how an object is *used*. When calling instance methods, you don't
pass a `self` argument, so a `self` argument isn't included in the
interface signature. The `self` argument in instance methods is
really an implementation detail of Python instances. Other objects,
besides instances can provide interfaces and their methods might not
be instance methods. For example, modules can provide interfaces and
their methods are usually just functions. Even instances can have
methods that are not instance methods.
You can access the attributes defined by an interface using mapping
syntax::
>>> x = IFoo['x']
>>> type(x)
<class 'zope.interface.interface.Attribute'>
>>> x.__name__
'x'
>>> x.__doc__
'X blah blah'
>>> IFoo.get('x').__name__
'x'
>>> IFoo.get('y')
You can use `in` to determine if an interface defines a name::
>>> 'x' in IFoo
True
You can iterate over interfaces to get the names they define::
>>> names = list(IFoo)
>>> names.sort()
>>> names
['bar', 'x']
Remember that interfaces aren't classes. You can't access attribute
definitions as attributes of interfaces::
>>> IFoo.x
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: 'InterfaceClass' object has no attribute 'x'
Methods provide access to the method signature::
>>> bar = IFoo['bar']
>>> bar.getSignatureString()
'(q, r=None)'
TODO
Methods really should have a better API. This is something that
needs to be improved.
Declaring interfaces
====================
Having defined interfaces, we can *declare* that objects provide
them. Before we describe the details, lets define some some terms:
*provide*
We say that objects *provide* interfaces. If an object provides an
interface, then the interface specifies the behavior of the
object. In other words, interfaces specify the behavior of the
objects that provide them.
*implement*
We normally say that classes *implement* interfaces. If a class
implements an interface, then the instances of the class provide
the interface. Objects provide interfaces that their classes
implement [#factory]_. (Objects can provide interfaces directly,
in addition to what their classes implement.)
It is important to note that classes don't usually provide the
interfaces that the implement.
Now that we've defined these terms, we can talk about the API for
declaring interfaces.
The most common way to declare interfaces is using the implements
function in a class statement::
>>> class Foo:
... zope.interface.implements(IFoo)
...
... def __init__(self, x=None):
... self.x = x
...
... def bar(self, q, r=None):
... return q, r, self.x
...
... def __repr__(self):
... return "Foo(%s)" % self.x
In this example, we declared that `Foo` implements `IFoo`. This means
that instances of `Foo` provide `IFoo`. Having made this declaration,
there are several ways we can introspect the declarations. First, we
can ask an interface whether it is implemented by a class::
>>> IFoo.implementedBy(Foo)
True
And we can ask whether an interface is provided by an object::
>>> foo = Foo()
>>> IFoo.providedBy(foo)
True
Of course, `Foo` doesn't provide `IFoo`, it implements it::
>>> IFoo.providedBy(Foo)
False
We can also ask what interfaces are implemented by an object::
>>> list(zope.interface.implementedBy(Foo))
[<InterfaceClass __main__.IFoo>]
It's an error to ask for interfaces implemented by a non-class::
>>> IFoo.implementedBy(foo)
Traceback (most recent call last):
...
TypeError: ('ImplementedBy called for non-type', Foo(None))
>>> list(zope.interface.implementedBy(foo))
Traceback (most recent call last):
...
TypeError: ('ImplementedBy called for non-type', Foo(None))
Similarly, we can ask what interfaces are provided by an object::
>>> list(zope.interface.providedBy(foo))
[<InterfaceClass __main__.IFoo>]
>>> list(zope.interface.providedBy(Foo))
[]
We can declare interfaces directly provided by objects. Suppose that
we want to document what the `__init__` method of the `Foo` class
does. It's not *really* part of `IFoo`. You wouldn't normally call
the `__init__` method on Foo instances. Rather, the `__init__` method
is part of the `Foo`'s `__call__` method::
>>> class IFooFactory(zope.interface.Interface):
... """Create foos"""
...
... def __call__(x=None):
... """Create a foo
...
... The argument provides the initial value for x ...
... """
It's the class that provides this interface, so we declare the
interface on the class::
>>> zope.interface.directlyProvides(Foo, IFooFactory)
And then, we'll see that Foo provides some interfaces::
>>> list(zope.interface.providedBy(Foo))
[<InterfaceClass __main__.IFooFactory>]
>>> IFooFactory.providedBy(Foo)
True
Declaring class interfaces is common enough that there's a special
declaration function for it, `classProvides`, that allows the
declaration from within a class statement::
>>> class Foo2:
... zope.interface.implements(IFoo)
... zope.interface.classProvides(IFooFactory)
...
... def __init__(self, x=None):
... self.x = x
...
... def bar(self, q, r=None):
... return q, r, self.x
...
... def __repr__(self):
... return "Foo(%s)" % self.x
>>> list(zope.interface.providedBy(Foo2))
[<InterfaceClass __main__.IFooFactory>]
>>> IFooFactory.providedBy(Foo2)
True
There's a similar function, `moduleProvides`, that supports interface
declarations from within module definitions. For example, see the use
of `moduleProvides` call in `zope.interface.__init__`, which declares that
the package `zope.interface` provides `IInterfaceDeclaration`.
Sometimes, we want to declare interfaces on instances, even though
those instances get interfaces from their classes. Suppose we create
a new interface, `ISpecial`::
>>> class ISpecial(zope.interface.Interface):
... reason = zope.interface.Attribute("Reason why we're special")
... def brag():
... "Brag about being special"
We can make a an existing foo instance special by providing `reason`
and `brag` attributes::
>>> foo.reason = 'I just am'
>>> def brag():
... return "I'm special!"
>>> foo.brag = brag
>>> foo.reason
'I just am'
>>> foo.brag()
"I'm special!"
and by declaring the interface::
>>> zope.interface.directlyProvides(foo, ISpecial)
then the new interface is included in the provided interfaces::
>>> ISpecial.providedBy(foo)
True
>>> list(zope.interface.providedBy(foo))
[<InterfaceClass __main__.ISpecial>, <InterfaceClass __main__.IFoo>]
We can find out what interfaces are directly provided by an object::
>>> list(zope.interface.directlyProvidedBy(foo))
[<InterfaceClass __main__.ISpecial>]
>>> newfoo = Foo()
>>> list(zope.interface.directlyProvidedBy(newfoo))
[]
Inherited declarations
----------------------
Normally, declarations are inherited::
>>> class SpecialFoo(Foo):
... zope.interface.implements(ISpecial)
... reason = 'I just am'
... def brag(self):
... return "I'm special because %s" % self.reason
>>> list(zope.interface.implementedBy(SpecialFoo))
[<InterfaceClass __main__.ISpecial>, <InterfaceClass __main__.IFoo>]
>>> list(zope.interface.providedBy(SpecialFoo()))
[<InterfaceClass __main__.ISpecial>, <InterfaceClass __main__.IFoo>]
Sometimes, you don't want to inherit declarations. In that case, you
can use `implementsOnly`, instead of `implements`::
>>> class Special(Foo):
... zope.interface.implementsOnly(ISpecial)
... reason = 'I just am'
... def brag(self):
... return "I'm special because %s" % self.reason
>>> list(zope.interface.implementedBy(Special))
[<InterfaceClass __main__.ISpecial>]
>>> list(zope.interface.providedBy(Special()))
[<InterfaceClass __main__.ISpecial>]
External declarations
---------------------
Normally, we make implementation declarations as part of a class
definition. Sometimes, we may want to make declarations from outside
the class definition. For example, we might want to declare interfaces
for classes that we didn't write. The function `classImplements` can
be used for this purpose::
>>> class C:
... pass
>>> zope.interface.classImplements(C, IFoo)
>>> list(zope.interface.implementedBy(C))
[<InterfaceClass __main__.IFoo>]
We can use `classImplementsOnly` to exclude inherited interfaces::
>>> class C(Foo):
... pass
>>> zope.interface.classImplementsOnly(C, ISpecial)
>>> list(zope.interface.implementedBy(C))
[<InterfaceClass __main__.ISpecial>]
Declaration Objects
-------------------
When we declare interfaces, we create *declaration* objects. When we
query declarations, declaration objects are returned::
>>> type(zope.interface.implementedBy(Special))
<class 'zope.interface.declarations.Implements'>
Declaration objects and interface objects are similar in many ways. In
fact, they share a common base class. The important thing to realize
about them is that they can be used where interfaces are expected in
declarations. Here's a silly example::
>>> class Special2(Foo):
... zope.interface.implementsOnly(
... zope.interface.implementedBy(Foo),
... ISpecial,
... )
... reason = 'I just am'
... def brag(self):
... return "I'm special because %s" % self.reason
The declaration here is almost the same as
``zope.interface.implements(ISpecial)``, except that the order of
interfaces in the resulting declaration is different::
>>> list(zope.interface.implementedBy(Special2))
[<InterfaceClass __main__.IFoo>, <InterfaceClass __main__.ISpecial>]
Interface Inheritance
=====================
Interfaces can extend other interfaces. They do this simply by listing
the other interfaces as base interfaces::
>>> class IBlat(zope.interface.Interface):
... """Blat blah blah"""
...
... y = zope.interface.Attribute("y blah blah")
... def eek():
... """eek blah blah"""
>>> IBlat.__bases__
(<InterfaceClass zope.interface.Interface>,)
>>> class IBaz(IFoo, IBlat):
... """Baz blah"""
... def eek(a=1):
... """eek in baz blah"""
...
>>> IBaz.__bases__
(<InterfaceClass __main__.IFoo>, <InterfaceClass __main__.IBlat>)
>>> names = list(IBaz)
>>> names.sort()
>>> names
['bar', 'eek', 'x', 'y']
Note that `IBaz` overrides eek::
>>> IBlat['eek'].__doc__
'eek blah blah'
>>> IBaz['eek'].__doc__
'eek in baz blah'
We were careful to override eek in a compatible way. When an
extending an interface, the extending interface should be compatible
[#compat]_ with the extended interfaces.
We can ask whether one interface extends another::
>>> IBaz.extends(IFoo)
True
>>> IBlat.extends(IFoo)
False
Note that interfaces don't extend themselves::
>>> IBaz.extends(IBaz)
False
Sometimes we wish they did, but we can, instead use `isOrExtends`::
>>> IBaz.isOrExtends(IBaz)
True
>>> IBaz.isOrExtends(IFoo)
True
>>> IFoo.isOrExtends(IBaz)
False
When we iterate over an interface, we get all of the names it defines,
including names defined by base interfaces. Sometimes, we want *just*
the names defined by the interface directly. We bane use the `names`
method for that::
>>> list(IBaz.names())
['eek']
Inheritance if attribute specifications
---------------------------------------
An interface may override attribute definitions frob base interfaces.
If two base interfaces define the same attribute, the attribute is
inherited from the most specific interface. For example, with:
>>> class IBase(zope.interface.Interface):
...
... def foo():
... "base foo doc"
>>> class IBase1(IBase):
... pass
>>> class IBase2(IBase):
...
... def foo():
... "base2 foo doc"
>>> class ISub(IBase1, IBase2):
... pass
ISub's definition of foo is the one from IBase2, since IBase2 is more
specific that IBase:
>>> ISub['foo'].__doc__
'base2 foo doc'
Note that this differs from a depth-first search.
Sometimes, it's useful to ask whether an interface defines an
attribute directly. You can use the direct method to get a directly
defined definitions:
>>> IBase.direct('foo').__doc__
'base foo doc'
>>> ISub.direct('foo')
Specifications
--------------
Interfaces and declarations are both special cases of specifications.
What we described above for interface inheritence applies to both
declarations and specifications. Declarations actually extend the
interfaces that they declare:
>>> class Baz:
... zope.interface.implements(IBaz)
>>> baz_implements = zope.interface.implementedBy(Baz)
>>> baz_implements.__bases__
(<InterfaceClass __main__.IBaz>,)
>>> baz_implements.extends(IFoo)
True
>>> baz_implements.isOrExtends(IFoo)
True
>>> baz_implements.isOrExtends(baz_implements)
True
Specifications (interfaces and declarations) provide an `__sro__`
that lists the specification and all of it's ancestors:
>>> baz_implements.__sro__
(<implementedBy __main__.Baz>,
<InterfaceClass __main__.IBaz>,
<InterfaceClass __main__.IFoo>,
<InterfaceClass __main__.IBlat>,
<InterfaceClass zope.interface.Interface>)
Tagged Values
=============
Interfaces and attribute descriptions support an extension mechanism,
borrowed from UML, called "tagged values" that lets us store extra
data::
>>> IFoo.setTaggedValue('date-modified', '2004-04-01')
>>> IFoo.setTaggedValue('author', 'Jim Fulton')
>>> IFoo.getTaggedValue('date-modified')
'2004-04-01'
>>> IFoo.queryTaggedValue('date-modified')
'2004-04-01'
>>> IFoo.queryTaggedValue('datemodified')
>>> tags = list(IFoo.getTaggedValueTags())
>>> tags.sort()
>>> tags
['author', 'date-modified']
Function attributes are converted to tagged values when method
attribute definitions are created::
>>> class IBazFactory(zope.interface.Interface):
... def __call__():
... "create one"
... __call__.return_type = IBaz
>>> IBazFactory['__call__'].getTaggedValue('return_type')
<InterfaceClass __main__.IBaz>
Invariants
==========
Interfaces can express conditions that must hold for objects that
provide them. These conditions are expressed using one or more
invariants. Invariants are callable objects that will be called with
an object that provides an interface. An invariant raises an `Invalid`
exception if the condition doesn't hold. Here's an example::
>>> class RangeError(zope.interface.Invalid):
... """A range has invalid limits"""
... def __repr__(self):
... return "RangeError(%r)" % self.args
>>> def range_invariant(ob):
... if ob.max < ob.min:
... raise RangeError(ob)
Given this invariant, we can use it in an interface definition::
>>> class IRange(zope.interface.Interface):
... min = zope.interface.Attribute("Lower bound")
... max = zope.interface.Attribute("Upper bound")
...
... zope.interface.invariant(range_invariant)
Interfaces have a method for checking their invariants::
>>> class Range(object):
... zope.interface.implements(IRange)
...
... def __init__(self, min, max):
... self.min, self.max = min, max
...
... def __repr__(self):
... return "Range(%s, %s)" % (self.min, self.max)
>>> IRange.validateInvariants(Range(1,2))
>>> IRange.validateInvariants(Range(1,1))
>>> IRange.validateInvariants(Range(2,1))
Traceback (most recent call last):
...
RangeError: Range(2, 1)
If you have multiple invariants, you may not want to stop checking
after the first error. If you pass a list to `validateInvariants`,
then a single `Invalid` exception will be raised with the list of
exceptions as it's argument::
>>> errors = []
>>> IRange.validateInvariants(Range(2,1), errors)
Traceback (most recent call last):
...
Invalid: [RangeError(Range(2, 1))]
And the list will be filled with the individual exceptions::
>>> errors
[RangeError(Range(2, 1))]
.. [#create] The main reason we subclass `Interface` is to cause the
Python class statement to create an interface, rather
than a class.
It's possible to create interfaces by calling a special
interface class directly. Doing this, it's possible
(and, on rare occasions, useful) to create interfaces
that don't descend from `Interface`. Using this
technique is beyond the scope of this document.
.. [#factory] Classes are factories. They can be called to create
their instances. We expect that we will eventually
extend the concept of implementation to other kinds of
factories, so that we can declare the interfaces
provided by the objects created.
.. [#compat] The goal is substitutability. An object that provides an
extending interface should be substitutable for an object
that provides the extended interface. In our example, an
object that provides IBaz should be usable whereever an
object that provides IBlat is expected.
The interface implementation doesn't enforce this. XXX
but maybe it should do some checks.
# Extension information for zpkg:
<extension _zope_interface_coptimizations>
source _zope_interface_coptimizations.c
</extension>
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Interfaces
This package implements the Python "scarecrow" proposal.
The package exports two objects, `Interface` and `Attribute` directly. It also
exports several helper methods. Interface is used to create an interface with
a class statement, as in:
class IMyInterface(Interface):
'''Interface documentation
'''
def meth(arg1, arg2):
'''Documentation for meth
'''
# Note that there is no self argument
To find out what you can do with interfaces, see the interface
interface, `IInterface` in the `interfaces` module.
The package has several public modules:
o `declarations` provides utilities to declare interfaces on objects. It
also provides a wide range of helpful utilities that aid in managing
declared interfaces. Most of its public names are however imported here.
o `document` has a utility for documenting an interface as structured text.
o `exceptions` has the interface-defined exceptions
o `interfaces` contains a list of all public interfaces for this package.
o `verify` has utilities for verifying implementations of interfaces.
See the module doc strings for more information.
$Id$
"""
from zope.interface.interface import Interface, _wire
# Need to actually get the interface elements to implement the right interfaces
_wire()
del _wire
from zope.interface.interface import Attribute, invariant
from zope.interface.declarations import providedBy, implementedBy
from zope.interface.declarations import classImplements, classImplementsOnly
from zope.interface.declarations import directlyProvidedBy, directlyProvides
from zope.interface.declarations import implements, implementsOnly
from zope.interface.declarations import classProvides, moduleProvides
from zope.interface.declarations import Declaration
from zope.interface.exceptions import Invalid
# The following are to make spec pickles cleaner
from zope.interface.declarations import Provides
from zope.interface.interfaces import IInterfaceDeclaration
moduleProvides(IInterfaceDeclaration)
__all__ = ('Interface', 'Attribute') + tuple(IInterfaceDeclaration)
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Adapter-style interface registry
See Adapter class.
$Id$
"""
from zope.interface import Declaration
def _flatten(implements, include_None=0):
try:
r = implements.flattened()
except AttributeError:
if implements is None:
r=()
else:
r = Declaration(implements).flattened()
if not include_None:
return r
r = list(r)
r.append(None)
return r
/*###########################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
############################################################################*/
#include "Python.h"
#include "structmember.h"
#define TYPE(O) ((PyTypeObject*)(O))
#define OBJECT(O) ((PyObject*)(O))
#define CLASSIC(O) ((PyClassObject*)(O))
static PyObject *str__dict__, *str__implemented__, *strextends;
static PyObject *BuiltinImplementationSpecifications, *str__provides__;
static PyObject *str__class__, *str__providedBy__, *strisOrExtends;
static PyObject *empty, *fallback, *str_implied, *str_cls, *str_implements;
static PyTypeObject *Implements;
static int imported_declarations = 0;
static int
import_declarations(void)
{
PyObject *declarations, *i;
declarations = PyImport_ImportModule("zope.interface.declarations");
if (declarations == NULL)
return -1;
BuiltinImplementationSpecifications = PyObject_GetAttrString(
declarations, "BuiltinImplementationSpecifications");
if (BuiltinImplementationSpecifications == NULL)
return -1;
empty = PyObject_GetAttrString(declarations, "_empty");
if (empty == NULL)
return -1;
fallback = PyObject_GetAttrString(declarations, "implementedByFallback");
if (fallback == NULL)
return -1;
i = PyObject_GetAttrString(declarations, "Implements");
if (i == NULL)
return -1;
if (! PyType_Check(i))
{
PyErr_SetString(PyExc_TypeError,
"zope.declarations.Implements is not a type");
return -1;
}
Implements = (PyTypeObject *)i;
Py_DECREF(declarations);
imported_declarations = 1;
return 0;
}
extern PyTypeObject SpecType; /* Forward */
static PyObject *
implementedByFallback(PyObject *cls)
{
if (imported_declarations == 0 && import_declarations() < 0)
return NULL;
return PyObject_CallFunctionObjArgs(fallback, cls, NULL);
}
static PyObject *
implementedBy(PyObject *ignored, PyObject *cls)
{
/* Fast retrieval of implements spec, if possible, to optimize
common case. Use fallback code if we get stuck.
*/
PyObject *dict = NULL, *spec;
if (PyType_Check(cls))
{
dict = TYPE(cls)->tp_dict;
Py_XINCREF(dict);
}
if (dict == NULL)
dict = PyObject_GetAttr(cls, str__dict__);
if (dict == NULL)
{
/* Probably a security proxied class, use more expensive fallback code */
PyErr_Clear();
return implementedByFallback(cls);
}
spec = PyObject_GetItem(dict, str__implemented__);
Py_DECREF(dict);
if (spec)
{
if (imported_declarations == 0 && import_declarations() < 0)
return NULL;
if (PyObject_TypeCheck(spec, Implements))
return spec;
/* Old-style declaration, use more expensive fallback code */
Py_DECREF(spec);
return implementedByFallback(cls);
}
PyErr_Clear();
/* Maybe we have a builtin */
if (imported_declarations == 0 && import_declarations() < 0)
return NULL;
spec = PyDict_GetItem(BuiltinImplementationSpecifications, cls);
if (spec != NULL)
{
Py_INCREF(spec);
return spec;
}
/* We're stuck, use fallback */
return implementedByFallback(cls);
}
static PyObject *
getObjectSpecification(PyObject *ignored, PyObject *ob)
{
PyObject *cls, *result;
result = PyObject_GetAttr(ob, str__provides__);
if (result != NULL)
return result;
PyErr_Clear();
/* We do a getattr here so as not to be defeated by proxies */
cls = PyObject_GetAttr(ob, str__class__);
if (cls == NULL)
{
PyErr_Clear();
if (imported_declarations == 0 && import_declarations() < 0)
return NULL;
Py_INCREF(empty);
return empty;
}
result = implementedBy(NULL, cls);
Py_DECREF(cls);
return result;
}
static PyObject *
providedBy(PyObject *ignored, PyObject *ob)
{
PyObject *result, *cls, *cp;
result = PyObject_GetAttr(ob, str__providedBy__);
if (result == NULL)
{
PyErr_Clear();
return getObjectSpecification(NULL, ob);
}
/* We want to make sure we have a spec. We can't do a type check
because we may have a proxy, so we'll just try to get the
only attribute.
*/
if (PyObject_HasAttr(result, strextends))
return result;
/*
The object's class doesn't understand descriptors.
Sigh. We need to get an object descriptor, but we have to be
careful. We want to use the instance's __provides__,l if
there is one, but only if it didn't come from the class.
*/
Py_DECREF(result);
cls = PyObject_GetAttr(ob, str__class__);
if (cls == NULL)
return NULL;
result = PyObject_GetAttr(ob, str__provides__);
if (result == NULL)
{
/* No __provides__, so just fall back to implementedBy */
PyErr_Clear();
result = implementedBy(NULL, cls);
Py_DECREF(cls);
return result;
}
cp = PyObject_GetAttr(cls, str__provides__);
if (cp == NULL)
{
/* The the class has no provides, assume we're done: */
PyErr_Clear();
Py_DECREF(cls);
return result;
}
if (cp == result)
{
/*
Oops, we got the provides from the class. This means
the object doesn't have it's own. We should use implementedBy
*/
Py_DECREF(result);
result = implementedBy(NULL, cls);
}
Py_DECREF(cls);
Py_DECREF(cp);
return result;
}
static PyObject *
inst_attr(PyObject *self, PyObject *name)
{
/* Get an attribute from an inst dict. Return a borrowed reference.
*/
PyObject **dictp, *v;
dictp = _PyObject_GetDictPtr(self);
if (dictp && *dictp && (v = PyDict_GetItem(*dictp, name)))
return v;
PyErr_SetObject(PyExc_AttributeError, name);
return NULL;
}
static PyObject *
Spec_extends(PyObject *self, PyObject *other)
{
PyObject *implied;
implied = inst_attr(self, str_implied);
if (implied == NULL)
return NULL;
#ifdef Py_True
if (PyDict_GetItem(implied, other) != NULL)
{
Py_INCREF(Py_True);
return Py_True;
}
Py_INCREF(Py_False);
return Py_False;
#else
return PyInt_FromLong(PyDict_GetItem(implied, other) != NULL);
#endif
}
static char Spec_extends__doc__[] =
"Test whether a specification is or extends another"
;
static char Spec_providedBy__doc__[] =
"Test whether an interface is implemented by the specification"
;
static PyObject *
Spec_providedBy(PyObject *self, PyObject *ob)
{
PyObject *decl, *item;
decl = providedBy(NULL, ob);
if (decl == NULL)
return NULL;
if (PyObject_TypeCheck(ob, &SpecType))
item = Spec_extends(decl, self);
else
/* decl is probably a security proxy. We have to go the long way
around.
*/
item = PyObject_CallMethodObjArgs(decl, strisOrExtends, self, NULL);
Py_DECREF(decl);
return item;
}
static char Spec_implementedBy__doc__[] =
"Test whether the specification is implemented by instances of a class"
;
static PyObject *
Spec_implementedBy(PyObject *self, PyObject *cls)
{
PyObject *decl, *item;
decl = implementedBy(NULL, cls);
if (decl == NULL)
return NULL;
if (PyObject_TypeCheck(decl, &SpecType))
item = Spec_extends(decl, self);
else
item = PyObject_CallMethodObjArgs(decl, strisOrExtends, self, NULL);
Py_DECREF(decl);
return item;
}
static struct PyMethodDef Spec_methods[] = {
{"providedBy",
(PyCFunction)Spec_providedBy, METH_O,
Spec_providedBy__doc__},
{"implementedBy",
(PyCFunction)Spec_implementedBy, METH_O,
Spec_implementedBy__doc__},
{"isOrExtends", (PyCFunction)Spec_extends, METH_O,
Spec_extends__doc__},
{NULL, NULL} /* sentinel */
};
static PyTypeObject SpecType = {
PyObject_HEAD_INIT(NULL)
/* ob_size */ 0,
/* tp_name */ "_interface_coptimizations."
"SpecificationBase",
/* tp_basicsize */ 0,
/* tp_itemsize */ 0,
/* tp_dealloc */ (destructor)0,
/* tp_print */ (printfunc)0,
/* tp_getattr */ (getattrfunc)0,
/* tp_setattr */ (setattrfunc)0,
/* tp_compare */ (cmpfunc)0,
/* tp_repr */ (reprfunc)0,
/* tp_as_number */ 0,
/* tp_as_sequence */ 0,
/* tp_as_mapping */ 0,
/* tp_hash */ (hashfunc)0,
/* tp_call */ (ternaryfunc)0,
/* tp_str */ (reprfunc)0,
/* tp_getattro */ (getattrofunc)0,
/* tp_setattro */ (setattrofunc)0,
/* tp_as_buffer */ 0,
/* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
"Base type for Specification objects",
/* tp_traverse */ (traverseproc)0,
/* tp_clear */ (inquiry)0,
/* tp_richcompare */ (richcmpfunc)0,
/* tp_weaklistoffset */ (long)0,
/* tp_iter */ (getiterfunc)0,
/* tp_iternext */ (iternextfunc)0,
/* tp_methods */ Spec_methods,
};
static PyObject *
OSD_descr_get(PyObject *self, PyObject *inst, PyObject *cls)
{
PyObject *provides;
if (inst == NULL)
return getObjectSpecification(NULL, cls);
provides = PyObject_GetAttr(inst, str__provides__);
if (provides != NULL)
return provides;
PyErr_Clear();
return implementedBy(NULL, cls);
}
static PyTypeObject OSDType = {
PyObject_HEAD_INIT(NULL)
/* ob_size */ 0,
/* tp_name */ "_interface_coptimizations."
"ObjectSpecificationDescriptor",
/* tp_basicsize */ 0,
/* tp_itemsize */ 0,
/* tp_dealloc */ (destructor)0,
/* tp_print */ (printfunc)0,
/* tp_getattr */ (getattrfunc)0,
/* tp_setattr */ (setattrfunc)0,
/* tp_compare */ (cmpfunc)0,
/* tp_repr */ (reprfunc)0,
/* tp_as_number */ 0,
/* tp_as_sequence */ 0,
/* tp_as_mapping */ 0,
/* tp_hash */ (hashfunc)0,
/* tp_call */ (ternaryfunc)0,
/* tp_str */ (reprfunc)0,
/* tp_getattro */ (getattrofunc)0,
/* tp_setattro */ (setattrofunc)0,
/* tp_as_buffer */ 0,
/* tp_flags */ Py_TPFLAGS_DEFAULT
| Py_TPFLAGS_BASETYPE ,
"Object Specification Descriptor",
/* tp_traverse */ (traverseproc)0,
/* tp_clear */ (inquiry)0,
/* tp_richcompare */ (richcmpfunc)0,
/* tp_weaklistoffset */ (long)0,
/* tp_iter */ (getiterfunc)0,
/* tp_iternext */ (iternextfunc)0,
/* tp_methods */ 0,
/* tp_members */ 0,
/* tp_getset */ 0,
/* tp_base */ 0,
/* tp_dict */ 0, /* internal use */
/* tp_descr_get */ (descrgetfunc)OSD_descr_get,
};
static PyObject *
CPB_descr_get(PyObject *self, PyObject *inst, PyObject *cls)
{
PyObject *mycls, *implements;
mycls = inst_attr(self, str_cls);
if (mycls == NULL)
return NULL;
if (cls == mycls)
{
if (inst == NULL)
{
Py_INCREF(self);
return OBJECT(self);
}
implements = inst_attr(self, str_implements);
Py_XINCREF(implements);
return implements;
}
PyErr_SetObject(PyExc_AttributeError, str__provides__);
return NULL;
}
static PyTypeObject CPBType = {
PyObject_HEAD_INIT(NULL)
/* ob_size */ 0,
/* tp_name */ "_interface_coptimizations."
"ClassProvidesBase",
/* tp_basicsize */ 0,
/* tp_itemsize */ 0,
/* tp_dealloc */ (destructor)0,
/* tp_print */ (printfunc)0,
/* tp_getattr */ (getattrfunc)0,
/* tp_setattr */ (setattrfunc)0,
/* tp_compare */ (cmpfunc)0,
/* tp_repr */ (reprfunc)0,
/* tp_as_number */ 0,
/* tp_as_sequence */ 0,
/* tp_as_mapping */ 0,
/* tp_hash */ (hashfunc)0,
/* tp_call */ (ternaryfunc)0,
/* tp_str */ (reprfunc)0,
/* tp_getattro */ (getattrofunc)0,
/* tp_setattro */ (setattrofunc)0,
/* tp_as_buffer */ 0,
/* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
"C Base class for ClassProvides",
/* tp_traverse */ (traverseproc)0,
/* tp_clear */ (inquiry)0,
/* tp_richcompare */ (richcmpfunc)0,
/* tp_weaklistoffset */ (long)0,
/* tp_iter */ (getiterfunc)0,
/* tp_iternext */ (iternextfunc)0,
/* tp_methods */ 0,
/* tp_members */ 0,
/* tp_getset */ 0,
/* tp_base */ &SpecType,
/* tp_dict */ 0, /* internal use */
/* tp_descr_get */ (descrgetfunc)CPB_descr_get,
};
static struct PyMethodDef m_methods[] = {
{"implementedBy", (PyCFunction)implementedBy, METH_O,
"Interfaces implemented by instances of a class"},
{"getObjectSpecification", (PyCFunction)getObjectSpecification, METH_O,
"Get an object's interfaces (internal api)"},
{"providedBy", (PyCFunction)providedBy, METH_O,
"Get an object's interfaces"},
{NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */
};
#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
init_zope_interface_coptimizations(void)
{
PyObject *m;
#define DEFINE_STRING(S) \
if(! (str ## S = PyString_FromString(# S))) return
DEFINE_STRING(__dict__);
DEFINE_STRING(__implemented__);
DEFINE_STRING(__provides__);
DEFINE_STRING(__class__);
DEFINE_STRING(__providedBy__);
DEFINE_STRING(isOrExtends);
DEFINE_STRING(extends);
DEFINE_STRING(_implied);
DEFINE_STRING(_implements);
DEFINE_STRING(_cls);
#undef DEFINE_STRING
/* Initialize types: */
SpecType.tp_new = PyBaseObject_Type.tp_new;
if (PyType_Ready(&SpecType) < 0)
return;
OSDType.tp_new = PyBaseObject_Type.tp_new;
if (PyType_Ready(&OSDType) < 0)
return;
CPBType.tp_new = PyBaseObject_Type.tp_new;
if (PyType_Ready(&CPBType) < 0)
return;
/* Create the module and add the functions */
m = Py_InitModule3("_zope_interface_coptimizations", m_methods,
"C optimizations for zope.interface\n\n"
"$Id$");
if (m == NULL)
return;
/* Add types: */
if (PyModule_AddObject(m, "SpecificationBase", (PyObject *)&SpecType) < 0)
return;
if (PyModule_AddObject(m, "ObjectSpecificationDescriptor",
(PyObject *)&OSDType) < 0)
return;
if (PyModule_AddObject(m, "ClassProvidesBase", (PyObject *)&CPBType) < 0)
return;
}
############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
############################################################################
"""Adapter-style interface registry
This implementation is based on a notion of "surrogate" interfaces.
$Id$
"""
# Implementation notes
# We keep a collection of surrogates.
# A surrogate is a surrogate for a specification (interface or
# declaration). We use weak references in order to remove surrogates
# if the corresponding specification goes away.
# Each surrogate keeps track of:
# - The adapters registered directly for that surrogate, and
# - The "implied" adapters, which is the adapters that can be computed
# from instances of that surrogate.
# The later data structure takes into account adapters registered for
# specifications that the registered surrogate extends.
# The registrations are of the form:
# {(subscription, with, name, specification) -> factories}
# where:
# 'subscription' is a flag indicating if this registration is for
# subscription adapters.
# 'with' is a tuple of specs that is non-empty only in the case
# of multi-adapters.
# 'name' is a unicode adapter name. Unnamed adapters have an empty
# name.
# 'specification' is the interface being adapted to.
# 'factories' is normally a tuple of factories, but can be anything.
# (See the "raw" option to the query-adapter calls.) For subscription
# adapters, it is a tuple of tuples of factories.
# The implied adapters are held in a single dictionary. The items in the
# dictionary are of several forms:
# For single adapters:
#
# {specification -> {name -> object}
#
# where object is usually a sequence of factories
# For multiple adapters:
#
# {(specification, order) -> {name -> {with -> object}}}
# For single subscription adapters:
#
# {('s', specification) -> tuple([object])}
# For multiple-subscription adapters:
#
# {('s', specification, order) -> {with -> tuple([object])}}
from __future__ import generators
import weakref
from zope.interface.ro import ro
from zope.interface.declarations import providedBy
from zope.interface.interface import InterfaceClass, Interface
Default = InterfaceClass("Default", (), {})
Null = InterfaceClass("Null", (), {})
# 2.2 backwards compatability
try:
enumerate
except NameError:
def enumerate(l):
i = 0
for o in l:
yield i, o
i += 1
try:
basestring
except NameError:
basestring = (str, unicode)
class ReadProperty(object):
def __init__(self, func):
self.func = func
def __get__(self, inst, class_):
if inst is None:
return self
return self.func(inst)
class Surrogate(object):
"""Specification surrogate
A specification surrogate is used to hold adapter registrations on
behalf of a specification.
"""
def __init__(self, spec, registry):
self.spec = spec.weakref()
spec.subscribe(self)
self.adapters = {}
self.dependents = weakref.WeakKeyDictionary()
self.__bases__ = [registry.get(base) for base in spec.__bases__]
for base in self.__bases__:
base.subscribe(self)
def dirty(self):
if 'get' in self.__dict__:
# Not already dirty
del self.selfImplied
del self.multImplied
del self.get
for dependent in self.dependents.keys():
dependent.dirty()
def clean(self):
self.selfImplied, self.multImplied = adapterImplied(self.adapters)
implied = {}
ancestors = ro(self)
# Collect implied data in reverse order to have more specific data
# override less-specific data.
ancestors.reverse()
for ancestor in ancestors:
for key, v in ancestor.selfImplied.iteritems():
# key is specification or ('s', specification)
subscription = isinstance(key, tuple) and key[0] == 's'
if subscription:
# v is tuple of subs
implied[key] = implied.get(key, ()) + v
else:
oldbyname = implied.get(key)
if not oldbyname:
implied[key] = oldbyname = {}
# v is name -> object
oldbyname.update(v)
for key, v in ancestor.multImplied.iteritems():
# key is (specification, order)
# or ('s', specification, order)
subscription = key[0] == 's'
if subscription:
oldwithobs = implied.get(key)
if not oldwithobs:
oldwithobs = implied[key] = {}
# v is {with -> tuple([object])}
for with, objects in v.iteritems():
oldwithobs[with] = oldwithobs.get(with, ()) + objects
else:
oldbyname = implied.get(key)
if not oldbyname:
implied[key] = oldbyname = {}
# v is {name -> {with -> ?}}
for name, withobs in v.iteritems():
oldwithobs = oldbyname.get(name)
if not oldwithobs:
oldwithobs = oldbyname[name] = {}
# withobs is {with -> object}
oldwithobs.update(withobs)
# Now flatten with mappings to tuples
for key, v in implied.iteritems():
if isinstance(key, tuple) and key[0] == 's':
# subscriptions
if isinstance(v, dict):
implied[key] = v.items()
else:
byname = v
for name, value in byname.iteritems():
if isinstance(value, dict):
# We have {with -> value}
# convert it to sorted [(with, value]
byname[name] = orderwith(value)
self.get = implied.get
def get(self, key):
"""Get an implied value
This is only called when the surrogate is dirty
"""
self.clean()
return self.__dict__['get'](key)
def selfImplied(self):
"""Return selfImplied when dirty
"""
self.clean()
return self.__dict__['selfImplied']
selfImplied = ReadProperty(selfImplied)
def multiImplied(self):
"""Return _multiImplied when dirty
"""
self.clean()
return self.__dict__['multiImplied']
multiImplied = ReadProperty(multiImplied)
def subscribe(self, dependent):
self.dependents[dependent] = 1
def unsubscribe(self, dependent):
del self.dependents[dependent]
def _adaptTo(self, specification, object, name='', with=()):
if object is None:
try:
del self.adapters[False, tuple(with), name, specification]
except KeyError:
pass
else:
self.adapters[False, tuple(with), name, specification
] = object
self.dirty()
def _subscriptionAdaptTo(self, specification, object, with=()):
if object is None:
raise TypeError, ("Unregistering subscription adapters"
" isn't implemented")
key = (True, tuple(with), '', specification)
self.adapters[key] = self.adapters.get(key, ()) + (object, )
self.dirty()
def changed(self, which=None):
self.dirty()
def __repr__(self):
return '<%s(%s)>' % (self.__class__.__name__, self.spec())
def orderwith(bywith):
# Convert {with -> adapter} to withs, [(with, value)]
# such that there are no i, j, i < j, such that
# withs[j][0] extends withs[i][0].
withs = []
for with, value in bywith.iteritems():
for i, (w, v) in enumerate(withs):
if withextends(with, w):
withs.insert(i, (with, value))
break
else:
withs.append((with, value))
return withs
def withextends(with1, with2):
for spec1, spec2 in zip(with1, with2):
if spec1.extends(spec2):
return True
if spec1 != spec2:
break
return False
class AdapterLookup(object):
# Adapter lookup support
# We have a class here because we want to provide very
# fast lookup support in C and making this part of the adapter
# registry itself would provide problems if someone wanted to
# persistent adapter registries, because we want C slots for fast
# lookup that would clash with persistence-suppplied slots.
# so this class acts a little bit like a lookup adapter for the adapter
# registry.
def __init__(self, registry, surrogates, _remove):
self._registry = registry
self._surrogateClass = registry._surrogateClass
self._default = registry._default
self._null = registry._null
self._surrogates = surrogates
self._remove = _remove
def lookup(self, required, provided, name='', default=None):
order = len(required)
if order == 1:
# Simple adapter:
s = self.get(required[0])
byname = s.get(provided)
if byname:
value = byname.get(name)
else:
value = None
if value is None:
byname = self._default.get(provided)
if byname:
value = byname.get(name, default)
else:
return default
return value
elif order == 0:
# null adapter
byname = self._null.get(provided)
if byname:
return byname.get(name, default)
else:
return default
# Multi adapter
with = required[1:]
key = provided, order
for surrogate in self.get(required[0]), self._default:
byname = surrogate.get(key)
if not byname:
continue
bywith = byname.get(name)
if not bywith:
continue
# Selecting multi-adapters is not just a matter of matching the
# required interfaces of the adapter to the ones passed. Several
# adapters might match, but we only want the best one. We use a
# ranking algorithm to determine the best match.
# `best` carries the rank and value of the best found adapter.
best = None
for rwith, value in bywith:
# the `rank` describes how well the found adapter matches.
rank = []
for rspec, spec in zip(rwith, with):
if not spec.isOrExtends(rspec):
break # This one is no good
# Determine the rank of this particular specification.
rank.append(list(spec.__sro__).index(rspec))
else:
# If the new rank is better than the best previously
# recorded one, make the new adapter the best one found.
rank = tuple(rank)
if best is None or rank < best[0]:
best = rank, value
# If any match was found, return the best one.
if best:
return best[1]
return default
def lookup1(self, required, provided, name='', default=None):
return self.lookup((required,), provided, name, default)
def adapter_hook(self, interface, object, name='', default=None):
"""Hook function used when calling interfaces.
When called from Interface.__adapt__, only the interface and
object parameters will be passed.
If the factory produces `None`, then the default is returned. This
allows us to prevent adaptation (if desired) and make the factory
decide whether an adapter will be available.
"""
factory = self.lookup1(providedBy(object), interface, name)
if factory is not None:
adapter = factory(object)
if adapter is not None:
return adapter
return default
def queryAdapter(self, object, interface, name='', default=None):
# Note that we rarely call queryAdapter directly
# We usually end up calling adapter_hook
return self.adapter_hook(interface, object, name, default)
def subscriptions(self, required, provided):
if provided is None:
provided = Null
order = len(required)
if order == 1:
# Simple subscriptions:
s = self.get(required[0])
result = s.get(('s', provided))
if result:
result = list(result)
else:
result = []
default = self._default.get(('s', provided))
if default:
result.extend(default)
return result
elif order == 0:
result = self._null.get(('s', provided))
if result:
return list(result)
else:
return []
# Multi
key = 's', provided, order
with = required[1:]
result = []
for surrogate in self.get(required[0]), self._default:
bywith = surrogate.get(key)
if not bywith:
continue
for rwith, values in bywith:
for rspec, spec in zip(rwith, with):
if not spec.isOrExtends(rspec):
break # This one is no good
else:
# we didn't break, so we have a match
result.extend(values)
return result
def queryMultiAdapter(self, objects, interface, name='', default=None):
factory = self.lookup(map(providedBy, objects), interface, name)
if factory is not None:
return factory(*objects)
return default
def subscribers(self, objects, interface):
subscriptions = self.subscriptions(map(providedBy, objects), interface)
return [subscription(*objects) for subscription in subscriptions]
def get(self, declaration):
if declaration is None:
return self._default
ref = declaration.weakref(self._remove)
surrogate = self._surrogates.get(ref)
if surrogate is None:
surrogate = self._surrogateClass(declaration, self._registry)
self._surrogates[ref] = surrogate
return surrogate
class AdapterRegistry(object):
"""Adapter registry
"""
# Implementation note:
# We are like a weakref dict ourselves. We can't use a weakref
# dict because we have to use spec.weakref() rather than
# weakref.ref(spec) to get weak refs to specs.
_surrogateClass = Surrogate
def __init__(self):
default = self._surrogateClass(Default, self)
self._default = default
null = self._surrogateClass(Null, self)
self._null = null
# Create separate lookup object and copy it's methods
surrogates = {Default.weakref(): default, Null.weakref(): null}
def _remove(k):
try:
del surrogates[k]
except KeyError:
pass
lookup = AdapterLookup(self, surrogates, _remove)
for name in ('lookup', 'lookup1', 'queryAdapter', 'get',
'adapter_hook', 'subscriptions',
'queryMultiAdapter', 'subscribers',
):
setattr(self, name, getattr(lookup, name))
def register(self, required, provided, name, value):
if required:
with = []
for iface in required[1:]:
if iface is None:
iface = Interface
with.append(iface)
with = tuple(with)
required = self.get(required[0])
else:
with = ()
required = self._null
if not isinstance(name, basestring):
raise TypeError("The name provided to provideAdapter "
"must be a string or unicode")
required._adaptTo(provided, value, unicode(name), with)
def lookupAll(self, required, provided):
order = len(required)
if order == 1:
# Simple adapter:
s = self.get(required[0])
byname = s.get(provided)
if byname:
for item in byname.iteritems():
yield item
defbyname = self._default.get(provided)
if defbyname:
for name, value in defbyname.iteritems():
if name in byname:
continue
yield name, value
return
elif order == 0:
# null adapter
byname = self._null.get(provided)
if byname:
for item in byname.iteritems():
yield item
return
# Multi adapter
with = required[1:]
key = provided, order
first = ()
for surrogate in self.get(required[0]), self._default:
byname = surrogate.get(key)
if not byname:
continue
for name, bywith in byname.iteritems():
if not bywith or name in first:
continue
# See comments on lookup() above
best = None
for rwith, value in bywith:
# the `rank` describes how well the found adapter matches.
rank = []
for rspec, spec in zip(rwith, with):
if not spec.isOrExtends(rspec):
break # This one is no good
# Determine the rank of this particular specification.
rank.append(list(spec.__sro__).index(rspec))
else:
# If the new rank is better than the best previously
# recorded one, make the new adapter the best one found.
rank = tuple(rank)
if best is None or rank < best[0]:
best = rank, value
# If any match was found, return the best one.
if best:
yield name, best[1]
first = byname
def subscribe(self, required, provided, value):
if required:
required, with = self.get(required[0]), tuple(required[1:])
else:
required = self._null
with = ()
if provided is None:
provided = Null
required._subscriptionAdaptTo(provided, value, with)
def mextends(with, rwith):
if len(with) == len(rwith):
for w, r in zip(with, rwith):
if not w.isOrExtends(r):
break
else:
return True
return False
def adapterImplied(adapters):
implied = {}
multi = {}
# This dictionary is used to catch situations specific adapters
# override less specific adapters.
# Because subscriptions are cummulative, registered doesn't apply.
registered = {}
# Add adapters and interfaces directly implied by same:
for key, value in adapters.iteritems():
# TODO: Backward compatability
# Don't need to handle 3-tuples some day
try:
(subscription, with, name, target) = key
except ValueError:
(with, name, target) = key
subscription = False
if subscription:
if with:
_add_multi_sub_adapter(with, target, multi, value)
else:
_add_named_sub_adapter(target, implied, value)
else:
if with:
_add_multi_adapter(with, name, target, target, multi,
registered, value)
else:
_add_named_adapter(target, target, name, implied,
registered, value)
return implied, multi
def _add_named_adapter(target, provided, name, implied,
registered, value):
ikey = target
rkey = target, name
byname = implied.get(ikey)
if not byname:
byname = implied[ikey] = {}
if (name not in byname
or
(rkey in registered and registered[rkey].extends(provided))
):
registered[rkey] = provided
byname[name] = value
for b in target.__bases__:
_add_named_adapter(b, provided, name, implied,
registered, value)
def _add_multi_adapter(with, name, target, provided, implied,
registered, object):
ikey = target, (len(with) + 1)
byname = implied.get(ikey)
if not byname:
byname = implied[ikey] = {}
bywith = byname.get(name)
if not bywith:
bywith = byname[name] = {}
rkey = ikey, name, with # The full key has all 4
if (with not in bywith
or
(rkey not in registered or registered[rkey].extends(provided))
):
# This is either a new entry or it is an entry for a more
# general interface that is closer provided than what we had
# before
registered[rkey] = provided
bywith[with] = object
for b in target.__bases__:
_add_multi_adapter(with, name, b, provided, implied,
registered, object)
def _add_named_sub_adapter(target, implied, objects):
key = ('s', target)
implied[key] = implied.get(key, ()) + objects
for b in target.__bases__:
_add_named_sub_adapter(b, implied, objects)
def _add_multi_sub_adapter(with, target, implied, objects):
key = 's', target, (len(with) + 1)
bywith = implied.get(key)
if not bywith:
bywith = implied[key] = {}
bywith[with] = bywith.get(with, ()) + objects
for b in target.__bases__:
_add_multi_sub_adapter(with, b, implied, objects)
================
Adapter Registry
================
Adapter registries provide a way to register objects that depend on
one or more interface specifications and provide (perhaps indirectly)
some interface. In addition, the registrations have names. (You can
think of the names as qualifiers of the provided interfaces.)
The term "interface specification" refers both to interfaces and to
interface declarations, such as declarations of interfaces implemented
by a class.
Single Adapters
===============
Let's look at a simple example, using a single required specification::
>>> from zope.interface.adapter import AdapterRegistry
>>> import zope.interface
>>> class IR1(zope.interface.Interface):
... pass
>>> class IP1(zope.interface.Interface):
... pass
>>> class IP2(IP1):
... pass
>>> registry = AdapterRegistry()
We'll register an object that depends on IR1 and "provides" IP2::
>>> registry.register([IR1], IP2, '', 12)
Given the registration, we can look it up again::
>>> registry.lookup([IR1], IP2, '')
12
Note that we used an integer in the example. In real applications,
one would use some objects that actually depend on or provide
interfaces. The registry doesn't care about what gets registered, so
we'll use integers and strings to keep the examples simple. There is
one exception. Registering a value of None unregisters any
previously-registered value.
If an object depends on a specification, it can be looked up with a
specification that extends the specification that it depends on::
>>> class IR2(IR1):
... pass
>>> registry.lookup([IR2], IP2, '')
12
We can use a class implementation specification to look up the object::
>>> class C2:
... zope.interface.implements(IR2)
>>> registry.lookup([zope.interface.implementedBy(C2)], IP2, '')
12
and it can be looked up for interfaces that its provided interface
extends::
>>> registry.lookup([IR1], IP1, '')
12
>>> registry.lookup([IR2], IP1, '')
12
But if you require a specification that doesn't extend the specification the
object depends on, you won't get anything::
>>> registry.lookup([zope.interface.Interface], IP1, '')
By the way, you can pass a default value to lookup::
>>> registry.lookup([zope.interface.Interface], IP1, '', 42)
42
If you try to get an interface the object doesn't provide, you also
won't get anything::
>>> class IP3(IP2):
... pass
>>> registry.lookup([IR1], IP3, '')
You also won't get anything if you use the wrong name::
>>> registry.lookup([IR1], IP1, 'bob')
>>> registry.register([IR1], IP2, 'bob', "Bob's 12")
>>> registry.lookup([IR1], IP1, 'bob')
"Bob's 12"
You can leave the name off when doing a lookup::
>>> registry.lookup([IR1], IP1)
12
If we register an object that provides IP1::
>>> registry.register([IR1], IP1, '', 11)
then that object will be prefered over O(12)::
>>> registry.lookup([IR1], IP1, '')
11
Also, if we register an object for IR2, then that will be prefered
when using IR2::
>>> registry.register([IR2], IP1, '', 21)
>>> registry.lookup([IR2], IP1, '')
21
lookup1
-------
Lookup of single adapters is common enough that there is a specialized
version of lookup that takes a single required interface::
>>> registry.lookup1(IR2, IP1, '')
21
>>> registry.lookup1(IR2, IP1)
21
Actual Adaptation
-----------------
The adapter registry is intended to support adaptation, where one
object that implements an interface is adapted to another object that
supports a different interface. The adapter registry supports the
computation of adapters. In this case, we have to register adapter
factories::
>>> class IR(zope.interface.Interface):
... pass
>>> class X:
... zope.interface.implements(IR)
>>> class Y:
... zope.interface.implements(IP1)
... def __init__(self, context):
... self.context = context
>>> registry.register([IR], IP1, '', Y)
In this case, we registered a class as the factory. Now we can call
`queryAdapter` to get the adapted object::
>>> x = X()
>>> y = registry.queryAdapter(x, IP1)
>>> y.__class__.__name__
'Y'
>>> y.context is x
True
We can register and lookup by name too::
>>> class Y2(Y):
... pass
>>> registry.register([IR], IP1, 'bob', Y2)
>>> y = registry.queryAdapter(x, IP1, 'bob')
>>> y.__class__.__name__
'Y2'
>>> y.context is x
True
When the adapter factory produces `None`, then this is treated as if no
adapter has been found. This allows us to prevent adaptation (when desired)
and let the adapter factory determine whether adaptation is possible based on
the state of the object being adapted.
>>> def factory(context):
... if context.name == 'object':
... return 'adapter'
... return None
>>> class Object(object):
... zope.interface.implements(IR)
... name = 'object'
>>> registry.register([IR], IP1, 'conditional', factory)
>>> obj = Object()
>>> registry.queryAdapter(obj, IP1, 'conditional')
'adapter'
>>> obj.name = 'no object'
>>> registry.queryAdapter(obj, IP1, 'conditional') is None
True
>>> registry.queryAdapter(obj, IP1, 'conditional', 'default')
'default'
An alternate method that provides the same function as `queryAdapter()` is
`adapter_hook()`::
>>> y = registry.adapter_hook(IP1, x)
>>> y.__class__.__name__
'Y'
>>> y.context is x
True
>>> y = registry.adapter_hook(IP1, x, 'bob')
>>> y.__class__.__name__
'Y2'
>>> y.context is x
True
The `adapter_hook()` simply switches the order of the object and
interface arguments. It is used to hook into the interface call
mechanism.
Default Adapters
----------------
Sometimes, you want to provide an adapter that will adapt anything.
For that, provide None as the required interface::
>>> registry.register([None], IP1, '', 1)
then we can use that adapter for interfaces we don't have specific
adapters for::
>>> class IQ(zope.interface.Interface):
... pass
>>> registry.lookup([IQ], IP1, '')
1
Of course, specific adapters are still used when applicable::
>>> registry.lookup([IR2], IP1, '')
21
Class adapters
--------------
You can register adapters for class declarations, which is almost the
same as registering them for a class::
>>> registry.register([zope.interface.implementedBy(C2)], IP1, '', 'C21')
>>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '')
'C21'
Unregistering
-------------
You can unregister by registering None, rather than an object::
>>> registry.register([zope.interface.implementedBy(C2)], IP1, '', None)
>>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '')
21
Of course, this means that None can't be registered. This is an
exception to the statement, made earlier, that the registry doesn't
care what gets registered.
Multi-adapters
==============
You can adapt multiple specifications::
>>> registry.register([IR1, IQ], IP2, '', '1q2')
>>> registry.lookup([IR1, IQ], IP2, '')
'1q2'
>>> registry.lookup([IR2, IQ], IP1, '')
'1q2'
>>> class IS(zope.interface.Interface):
... pass
>>> registry.lookup([IR2, IS], IP1, '')
>>> class IQ2(IQ):
... pass
>>> registry.lookup([IR2, IQ2], IP1, '')
'1q2'
>>> registry.register([IR1, IQ2], IP2, '', '1q22')
>>> registry.lookup([IR2, IQ2], IP1, '')
'1q22'
Multi-adaptation
----------------
You can adapt multiple objects::
>>> class Q:
... zope.interface.implements(IQ)
As with single adapters, we register a factory, which is often a class::
>>> class IM(zope.interface.Interface):
... pass
>>> class M:
... zope.interface.implements(IM)
... def __init__(self, x, q):
... self.x, self.q = x, q
>>> registry.register([IR, IQ], IM, '', M)
And then we can call `queryMultiAdapter` to compute an adapter::
>>> q = Q()
>>> m = registry.queryMultiAdapter((x, q), IM)
>>> m.__class__.__name__
'M'
>>> m.x is x and m.q is q
True
and, of course, we can use names::
>>> class M2(M):
... pass
>>> registry.register([IR, IQ], IM, 'bob', M2)
>>> m = registry.queryMultiAdapter((x, q), IM, 'bob')
>>> m.__class__.__name__
'M2'
>>> m.x is x and m.q is q
True
Default Adapters
----------------
As with single adapters, you can define default adapters by specifying
None for the *first* specification::
>>> registry.register([None, IQ], IP2, '', 'q2')
>>> registry.lookup([IS, IQ], IP2, '')
'q2'
Null Adapters
=============
You can also adapt no specification::
>>> registry.register([], IP2, '', 2)
>>> registry.lookup([], IP2, '')
2
>>> registry.lookup([], IP1, '')
2
Listing named adapters
----------------------
Adapters are named. Sometimes, it's useful to get all of the named
adapters for given interfaces::
>>> adapters = list(registry.lookupAll([IR1], IP1))
>>> adapters.sort()
>>> adapters
[(u'', 11), (u'bob', "Bob's 12")]
This works for multi-adapters too::
>>> registry.register([IR1, IQ2], IP2, 'bob', '1q2 for bob')
>>> adapters = list(registry.lookupAll([IR2, IQ2], IP1))
>>> adapters.sort()
>>> adapters
[(u'', '1q22'), (u'bob', '1q2 for bob')]
And even null adapters::
>>> registry.register([], IP2, 'bob', 3)
>>> adapters = list(registry.lookupAll([], IP1))
>>> adapters.sort()
>>> adapters
[(u'', 2), (u'bob', 3)]
Subscriptions
=============
Normally, we want to look up an object that most-closely matches a
specification. Sometimes, we want to get all of the objects that
match some specification. We use subscriptions for this. We
subscribe objects against specifications and then later find all of
the subscribed objects::
>>> registry.subscribe([IR1], IP2, 'sub12 1')
>>> registry.subscriptions([IR1], IP2)
['sub12 1']
Note that, unlike regular adapters, subscriptions are unnamed.
The order of returned subscriptions is not specified.
You can have multiple subscribers for the same specification::
>>> registry.subscribe([IR1], IP2, 'sub12 2')
>>> subs = registry.subscriptions([IR1], IP2)
>>> subs.sort()
>>> subs
['sub12 1', 'sub12 2']
You can register subscribers for all specifications using None::
>>> registry.subscribe([None], IP1, 'sub_1')
>>> subs = registry.subscriptions([IR2], IP1)
>>> subs.sort()
>>> subs
['sub12 1', 'sub12 2', 'sub_1']
Subscriptions may be combined over multiple compatible specifications::
>>> subs = registry.subscriptions([IR2], IP1)
>>> subs.sort()
>>> subs
['sub12 1', 'sub12 2', 'sub_1']
>>> registry.subscribe([IR1], IP1, 'sub11')
>>> subs = registry.subscriptions([IR2], IP1)
>>> subs.sort()
>>> subs
['sub11', 'sub12 1', 'sub12 2', 'sub_1']
>>> registry.subscribe([IR2], IP2, 'sub22')
>>> subs = registry.subscriptions([IR2], IP1)
>>> subs.sort()
>>> subs
['sub11', 'sub12 1', 'sub12 2', 'sub22', 'sub_1']
>>> subs = registry.subscriptions([IR2], IP2)
>>> subs.sort()
>>> subs
['sub12 1', 'sub12 2', 'sub22']
Subscriptions can be on multiple specifications::
>>> registry.subscribe([IR1, IQ], IP2, 'sub1q2')
>>> registry.subscriptions([IR1, IQ], IP2)
['sub1q2']
As with single subscriptions and non-subscription adapters, you can
specify None for the first required interface, to specify a default::
>>> registry.subscribe([None, IQ], IP2, 'sub_q2')
>>> registry.subscriptions([IS, IQ], IP2)
['sub_q2']
>>> subs = registry.subscriptions([IR1, IQ], IP2)
>>> subs.sort()
>>> subs
['sub1q2', 'sub_q2']
You can have subscriptions that are indepenent of any specifications::
>>> registry.subscriptions([], IP1)
[]
>>> registry.subscribe([], IP2, 'sub2')
>>> registry.subscriptions([], IP1)
['sub2']
>>> registry.subscribe([], IP1, 'sub1')
>>> subs = registry.subscriptions([], IP1)
>>> subs.sort()
>>> subs
['sub1', 'sub2']
>>> registry.subscriptions([], IP2)
['sub2']
Subscription adapters
---------------------
We normally register adapter factories, which then allow us to compute
adapters, but with subscriptions, we get multiple adapters. Here's an
example of multiple-object subscribers::
>>> registry.subscribe([IR, IQ], IM, M)
>>> registry.subscribe([IR, IQ], IM, M2)
>>> subscribers = registry.subscribers((x, q), IM)
>>> len(subscribers)
2
>>> class_names = [s.__class__.__name__ for s in subscribers]
>>> class_names.sort()
>>> class_names
['M', 'M2']
>>> [(s.x is x and s.q is q) for s in subscribers]
[True, True]
Handlers
--------
A handler is a subscriber factory that doesn't produce any normal
output. It returns None. A handler is unlike adapters in that it does
all of it's work when the factory is called.
To register a handler, simply provide None as the provided interface::
>>> def handler(event):
... print 'handler', event
>>> registry.subscribe([IR1], None, handler)
>>> registry.subscriptions([IR1], None) == [handler]
True
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Class advice.
This module was adapted from 'protocols.advice', part of the Python
Enterprise Application Kit (PEAK). Please notify the PEAK authors
(pje@telecommunity.com and tsarna@sarna.org) if bugs are found or
Zope-specific changes are required, so that the PEAK version of this module
can be kept in sync.
PEAK is a Python application framework that interoperates with (but does
not require) Zope 3 and Twisted. It provides tools for manipulating UML
models, object-relational persistence, aspect-oriented programming, and more.
Visit the PEAK home page at http://peak.telecommunity.com for more information.
$Id$
"""
from types import ClassType, FunctionType
import sys
def getFrameInfo(frame):
"""Return (kind,module,locals,globals) for a frame
'kind' is one of "exec", "module", "class", "function call", or "unknown".
"""
f_locals = frame.f_locals
f_globals = frame.f_globals
sameNamespace = f_locals is f_globals
hasModule = '__module__' in f_locals
hasName = '__name__' in f_globals
sameName = hasModule and hasName
sameName = sameName and f_globals['__name__']==f_locals['__module__']
module = hasName and sys.modules.get(f_globals['__name__']) or None
namespaceIsModule = module and module.__dict__ is f_globals
if not namespaceIsModule:
# some kind of funky exec
kind = "exec"
elif sameNamespace and not hasModule:
kind = "module"
elif sameName and not sameNamespace:
kind = "class"
elif not sameNamespace:
kind = "function call"
else:
# How can you have f_locals is f_globals, and have '__module__' set?
# This is probably module-level code, but with a '__module__' variable.
kind = "unknown"
return kind, module, f_locals, f_globals
def addClassAdvisor(callback, depth=2):
"""Set up 'callback' to be passed the containing class upon creation
This function is designed to be called by an "advising" function executed
in a class suite. The "advising" function supplies a callback that it
wishes to have executed when the containing class is created. The
callback will be given one argument: the newly created containing class.
The return value of the callback will be used in place of the class, so
the callback should return the input if it does not wish to replace the
class.
The optional 'depth' argument to this function determines the number of
frames between this function and the targeted class suite. 'depth'
defaults to 2, since this skips this function's frame and one calling
function frame. If you use this function from a function called directly
in the class suite, the default will be correct, otherwise you will need
to determine the correct depth yourself.
This function works by installing a special class factory function in
place of the '__metaclass__' of the containing class. Therefore, only
callbacks *after* the last '__metaclass__' assignment in the containing
class will be executed. Be sure that classes using "advising" functions
declare any '__metaclass__' *first*, to ensure all callbacks are run."""
frame = sys._getframe(depth)
kind, module, caller_locals, caller_globals = getFrameInfo(frame)
# This causes a problem when zope interfaces are used from doctest.
# In these cases, kind == "exec".
#
#if kind != "class":
# raise SyntaxError(
# "Advice must be in the body of a class statement"
# )
previousMetaclass = caller_locals.get('__metaclass__')
defaultMetaclass = caller_globals.get('__metaclass__', ClassType)
def advise(name, bases, cdict):
if '__metaclass__' in cdict:
del cdict['__metaclass__']
if previousMetaclass is None:
if bases:
# find best metaclass or use global __metaclass__ if no bases
meta = determineMetaclass(bases)
else:
meta = defaultMetaclass
elif isClassAdvisor(previousMetaclass):
# special case: we can't compute the "true" metaclass here,
# so we need to invoke the previous metaclass and let it
# figure it out for us (and apply its own advice in the process)
meta = previousMetaclass
else:
meta = determineMetaclass(bases, previousMetaclass)
newClass = meta(name,bases,cdict)
# this lets the callback replace the class completely, if it wants to
return callback(newClass)
# introspection data only, not used by inner function
advise.previousMetaclass = previousMetaclass
advise.callback = callback
# install the advisor
caller_locals['__metaclass__'] = advise
def isClassAdvisor(ob):
"""True if 'ob' is a class advisor function"""
return isinstance(ob,FunctionType) and hasattr(ob,'previousMetaclass')
def determineMetaclass(bases, explicit_mc=None):
"""Determine metaclass from 1+ bases and optional explicit __metaclass__"""
meta = [getattr(b,'__class__',type(b)) for b in bases]
if explicit_mc is not None:
# The explicit metaclass needs to be verified for compatibility
# as well, and allowed to resolve the incompatible bases, if any
meta.append(explicit_mc)
if len(meta)==1:
# easy case
return meta[0]
candidates = minimalBases(meta) # minimal set of metaclasses
if not candidates:
# they're all "classic" classes
return ClassType
elif len(candidates)>1:
# We could auto-combine, but for now we won't...
raise TypeError("Incompatible metatypes",bases)
# Just one, return it
return candidates[0]
def minimalBases(classes):
"""Reduce a list of base classes to its ordered minimum equivalent"""
classes = [c for c in classes if c is not ClassType]
candidates = []
for m in classes:
for n in classes:
if issubclass(n,m) and m is not n:
break
else:
# m has no subclasses in 'classes'
if m in candidates:
candidates.remove(m) # ensure that we're later in the list
candidates.append(m)
return candidates
#
# This file is necessary to make this directory a package.
##############################################################################
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
##############################################################################
"""Datetime interfaces.
This module is called idatetime because if it were called datetime the import
of the real datetime would fail.
$Id$
"""
from zope.interface import Interface, Attribute
from zope.interface import classImplements, directlyProvides
from datetime import timedelta, date, datetime, time, tzinfo
class ITimeDeltaClass(Interface):
"""This is the timedelta class interface."""
min = Attribute("The most negative timedelta object")
max = Attribute("The most positive timedelta object")
resolution = Attribute(
"The smallest difference between non-equal timedelta objects")
class ITimeDelta(ITimeDeltaClass):
"""Represent the difference between two datetime objects.
Supported operators:
- add, subtract timedelta
- unary plus, minus, abs
- compare to timedelta
- multiply, divide by int/long
In addition, datetime supports subtraction of two datetime objects
returning a timedelta, and addition or subtraction of a datetime
and a timedelta giving a datetime.
Representation: (days, seconds, microseconds).
"""
days = Attribute("Days between -999999999 and 999999999 inclusive")
seconds = Attribute("Seconds between 0 and 86399 inclusive")
microseconds = Attribute("Microseconds between 0 and 999999 inclusive")
class IDateClass(Interface):
"""This is the date class interface."""
min = Attribute("The earliest representable date")
max = Attribute("The latest representable date")
resolution = Attribute(
"The smallest difference between non-equal date objects")
def today():
"""Return the current local time.
This is equivalent to date.fromtimestamp(time.time())"""
def fromtimestamp(timestamp):
"""Return the local date from a POSIX timestamp (like time.time())
This may raise ValueError, if the timestamp is out of the range of
values supported by the platform C localtime() function. It's common
for this to be restricted to years from 1970 through 2038. Note that
on non-POSIX systems that include leap seconds in their notion of a
timestamp, leap seconds are ignored by fromtimestamp().
"""
def fromordinal(ordinal):
"""Return the date corresponding to the proleptic Gregorian ordinal.
January 1 of year 1 has ordinal 1. ValueError is raised unless
1 <= ordinal <= date.max.toordinal().
For any date d, date.fromordinal(d.toordinal()) == d.
"""
class IDate(IDateClass):
"""Represents a date (year, month and day) in an idealized calendar.
Operators:
__repr__, __str__
__cmp__, __hash__
__add__, __radd__, __sub__ (add/radd only with timedelta arg)
"""
year = Attribute("Between MINYEAR and MAXYEAR inclusive.")
month = Attribute("Between 1 and 12 inclusive")
day = Attribute(
"Between 1 and the number of days in the given month of the given year.")
def replace(year, month, day):
"""Return a date with the same value.
Except for those members given new values by whichever keyword
arguments are specified. For example, if d == date(2002, 12, 31), then
d.replace(day=26) == date(2000, 12, 26).
"""
def timetuple():
"""Return a 9-element tuple of the form returned by time.localtime().
The hours, minutes and seconds are 0, and the DST flag is -1.
d.timetuple() is equivalent to
(d.year, d.month, d.day, 0, 0, 0, d.weekday(), d.toordinal() -
date(d.year, 1, 1).toordinal() + 1, -1)
"""
def toordinal():
"""Return the proleptic Gregorian ordinal of the date
January 1 of year 1 has ordinal 1. For any date object d,
date.fromordinal(d.toordinal()) == d.
"""
def weekday():
"""Return the day of the week as an integer.
Monday is 0 and Sunday is 6. For example,
date(2002, 12, 4).weekday() == 2, a Wednesday.
See also isoweekday().
"""
def isoweekday():
"""Return the day of the week as an integer.
Monday is 1 and Sunday is 7. For example,
date(2002, 12, 4).isoweekday() == 3, a Wednesday.
See also weekday(), isocalendar().
"""
def isocalendar():
"""Return a 3-tuple, (ISO year, ISO week number, ISO weekday).
The ISO calendar is a widely used variant of the Gregorian calendar.
See http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm for a good
explanation.
The ISO year consists of 52 or 53 full weeks, and where a week starts
on a Monday and ends on a Sunday. The first week of an ISO year is the
first (Gregorian) calendar week of a year containing a Thursday. This
is called week number 1, and the ISO year of that Thursday is the same
as its Gregorian year.
For example, 2004 begins on a Thursday, so the first week of ISO year
2004 begins on Monday, 29 Dec 2003 and ends on Sunday, 4 Jan 2004, so
that date(2003, 12, 29).isocalendar() == (2004, 1, 1) and
date(2004, 1, 4).isocalendar() == (2004, 1, 7).
"""
def isoformat():
"""Return a string representing the date in ISO 8601 format.
This is 'YYYY-MM-DD'.
For example, date(2002, 12, 4).isoformat() == '2002-12-04'.
"""
def __str__():
"""For a date d, str(d) is equivalent to d.isoformat()."""
def ctime():
"""Return a string representing the date.
For example date(2002, 12, 4).ctime() == 'Wed Dec 4 00:00:00 2002'.
d.ctime() is equivalent to time.ctime(time.mktime(d.timetuple()))
on platforms where the native C ctime() function
(which time.ctime() invokes, but which date.ctime() does not invoke)
conforms to the C standard.
"""
def strftime(format):
"""Return a string representing the date.
Controlled by an explicit format string. Format codes referring to
hours, minutes or seconds will see 0 values.
"""
class IDateTimeClass(Interface):
"""This is the datetime class interface."""
min = Attribute("The earliest representable datetime")
max = Attribute("The latest representable datetime")
resolution = Attribute(
"The smallest possible difference between non-equal datetime objects")
def today():
"""Return the current local datetime, with tzinfo None.
This is equivalent to datetime.fromtimestamp(time.time()).
See also now(), fromtimestamp().
"""
def now(tz=None):
"""Return the current local date and time.
If optional argument tz is None or not specified, this is like today(),
but, if possible, supplies more precision than can be gotten from going
through a time.time() timestamp (for example, this may be possible on
platforms supplying the C gettimeofday() function).
Else tz must be an instance of a class tzinfo subclass, and the current
date and time are converted to tz's time zone. In this case the result
is equivalent to tz.fromutc(datetime.utcnow().replace(tzinfo=tz)).
See also today(), utcnow().
"""
def utcnow():
"""Return the current UTC date and time, with tzinfo None.
This is like now(), but returns the current UTC date and time, as a
naive datetime object.
See also now().
"""
def fromtimestamp(timestamp, tz=None):
"""Return the local date and time corresponding to the POSIX timestamp.
Same as is returned by time.time(). If optional argument tz is None or
not specified, the timestamp is converted to the platform's local date
and time, and the returned datetime object is naive.
Else tz must be an instance of a class tzinfo subclass, and the
timestamp is converted to tz's time zone. In this case the result is
equivalent to
tz.fromutc(datetime.utcfromtimestamp(timestamp).replace(tzinfo=tz)).
fromtimestamp() may raise ValueError, if the timestamp is out of the
range of values supported by the platform C localtime() or gmtime()
functions. It's common for this to be restricted to years in 1970
through 2038. Note that on non-POSIX systems that include leap seconds
in their notion of a timestamp, leap seconds are ignored by
fromtimestamp(), and then it's possible to have two timestamps
differing by a second that yield identical datetime objects.
See also utcfromtimestamp().
"""
def utcfromtimestamp(timestamp):
"""Return the UTC datetime from the POSIX timestamp with tzinfo None.
This may raise ValueError, if the timestamp is out of the range of
values supported by the platform C gmtime() function. It's common for
this to be restricted to years in 1970 through 2038.
See also fromtimestamp().
"""
def fromordinal(ordinal):
"""Return the datetime from the proleptic Gregorian ordinal.
January 1 of year 1 has ordinal 1. ValueError is raised unless
1 <= ordinal <= datetime.max.toordinal().
The hour, minute, second and microsecond of the result are all 0, and
tzinfo is None.
"""
def combine(date, time):
"""Return a new datetime object.
Its date members are equal to the given date object's, and whose time
and tzinfo members are equal to the given time object's. For any
datetime object d, d == datetime.combine(d.date(), d.timetz()).
If date is a datetime object, its time and tzinfo members are ignored.
"""
class IDateTime(IDate, IDateTimeClass):
"""Object contains all the information from a date object and a time object.
"""
year = Attribute("Year between MINYEAR and MAXYEAR inclusive")
month = Attribute("Month between 1 and 12 inclusive")
day = Attribute(
"Day between 1 and the number of days in the given month of the year")
hour = Attribute("Hour in range(24)")
minute = Attribute("Minute in range(60)")
second = Attribute("Second in range(60)")
microsecond = Attribute("Microsecond in range(1000000)")
tzinfo = Attribute(
"""The object passed as the tzinfo argument to the datetime constructor
or None if none was passed""")
def date():
"""Return date object with same year, month and day."""
def time():
"""Return time object with same hour, minute, second, microsecond.
tzinfo is None. See also method timetz().
"""
def timetz():
"""Return time object with same hour, minute, second, microsecond,
and tzinfo.
See also method time().
"""
def replace(year, month, day, hour, minute, second, microsecond, tzinfo):
"""Return a datetime with the same members, except for those members
given new values by whichever keyword arguments are specified.
Note that tzinfo=None can be specified to create a naive datetime from
an aware datetime with no conversion of date and time members.
"""
def astimezone(tz):
"""Return a datetime object with new tzinfo member tz, adjusting the
date and time members so the result is the same UTC time as self, but
in tz's local time.
tz must be an instance of a tzinfo subclass, and its utcoffset() and
dst() methods must not return None. self must be aware (self.tzinfo
must not be None, and self.utcoffset() must not return None).
If self.tzinfo is tz, self.astimezone(tz) is equal to self: no
adjustment of date or time members is performed. Else the result is
local time in time zone tz, representing the same UTC time as self:
after astz = dt.astimezone(tz), astz - astz.utcoffset()
will usually have the same date and time members as dt - dt.utcoffset().
The discussion of class tzinfo explains the cases at Daylight Saving
Time transition boundaries where this cannot be achieved (an issue only
if tz models both standard and daylight time).
If you merely want to attach a time zone object tz to a datetime dt
without adjustment of date and time members, use dt.replace(tzinfo=tz).
If you merely want to remove the time zone object from an aware
datetime dt without conversion of date and time members, use
dt.replace(tzinfo=None).
Note that the default tzinfo.fromutc() method can be overridden in a
tzinfo subclass to effect the result returned by astimezone().
"""
def utcoffset():
"""Return the timezone offset in minutes east of UTC (negative west of
UTC)."""
def dst():
"""Return 0 if DST is not in effect, or the DST offset (in minutes
eastward) if DST is in effect.
"""
def tzname():
"""Return the timezone name."""
def timetuple():
"""Return a 9-element tuple of the form returned by time.localtime()."""
def utctimetuple():
"""Return UTC time tuple compatilble with time.gmtimr()."""
def toordinal():
"""Return the proleptic Gregorian ordinal of the date.
The same as self.date().toordinal().
"""
def weekday():
"""Return the day of the week as an integer.
Monday is 0 and Sunday is 6. The same as self.date().weekday().
See also isoweekday().
"""
def isoweekday():
"""Return the day of the week as an integer.
Monday is 1 and Sunday is 7. The same as self.date().isoweekday.
See also weekday(), isocalendar().
"""
def isocalendar():
"""Return a 3-tuple, (ISO year, ISO week number, ISO weekday).
The same as self.date().isocalendar().
"""
def isoformat(sep='T'):
"""Return a string representing the date and time in ISO 8601 format.
YYYY-MM-DDTHH:MM:SS.mmmmmm or YYYY-MM-DDTHH:MM:SS if microsecond is 0
If utcoffset() does not return None, a 6-character string is appended,
giving the UTC offset in (signed) hours and minutes:
YYYY-MM-DDTHH:MM:SS.mmmmmm+HH:MM or YYYY-MM-DDTHH:MM:SS+HH:MM
if microsecond is 0.
The optional argument sep (default 'T') is a one-character separator,
placed between the date and time portions of the result.
"""
def __str__():
"""For a datetime instance d, str(d) is equivalent to d.isoformat(' ').
"""
def ctime():
"""Return a string representing the date and time.
datetime(2002, 12, 4, 20, 30, 40).ctime() == 'Wed Dec 4 20:30:40 2002'.
d.ctime() is equivalent to time.ctime(time.mktime(d.timetuple())) on
platforms where the native C ctime() function (which time.ctime()
invokes, but which datetime.ctime() does not invoke) conforms to the
C standard.
"""
def strftime(format):
"""Return a string representing the date and time.
This is controlled by an explicit format string.
"""
class ITimeClass(Interface):
"""This is the time class interface."""
min = Attribute("The earliest representable time")
max = Attribute("The latest representable time")
resolution = Attribute(
"The smallest possible difference between non-equal time objects")
class ITime(ITimeClass):
"""Represent time with time zone.
Operators:
__repr__, __str__
__cmp__, __hash__
"""
hour = Attribute("Hour in range(24)")
minute = Attribute("Minute in range(60)")
second = Attribute("Second in range(60)")
microsecond = Attribute("Microsecond in range(1000000)")
tzinfo = Attribute(
"""The object passed as the tzinfo argument to the time constructor
or None if none was passed.""")
def replace(hour, minute, second, microsecond, tzinfo):
"""Return a time with the same value.
Except for those members given new values by whichever keyword
arguments are specified. Note that tzinfo=None can be specified
to create a naive time from an aware time, without conversion of the
time members.
"""
def isoformat():
"""Return a string representing the time in ISO 8601 format.
That is HH:MM:SS.mmmmmm or, if self.microsecond is 0, HH:MM:SS
If utcoffset() does not return None, a 6-character string is appended,
giving the UTC offset in (signed) hours and minutes:
HH:MM:SS.mmmmmm+HH:MM or, if self.microsecond is 0, HH:MM:SS+HH:MM
"""
def __str__():
"""For a time t, str(t) is equivalent to t.isoformat()."""
def strftime(format):
"""Return a string representing the time.
This is controlled by an explicit format string.
"""
def utcoffset():
"""Return the timezone offset in minutes east of UTC (negative west of
UTC).
If tzinfo is None, returns None, else returns
self.tzinfo.utcoffset(None), and raises an exception if the latter
doesn't return None or a timedelta object representing a whole number
of minutes with magnitude less than one day.
"""
def dst():
"""Return 0 if DST is not in effect, or the DST offset (in minutes
eastward) if DST is in effect.
If tzinfo is None, returns None, else returns self.tzinfo.dst(None),
and raises an exception if the latter doesn't return None, or a
timedelta object representing a whole number of minutes with
magnitude less than one day.
"""
def tzname():
"""Return the timezone name.
If tzinfo is None, returns None, else returns self.tzinfo.tzname(None),
or raises an exception if the latter doesn't return None or a string
object.
"""
class ITZInfo(Interface):
"""Time zone info class.
"""
def utcoffset(dt):
"""Return offset of local time from UTC, in minutes east of UTC.
If local time is west of UTC, this should be negative.
Note that this is intended to be the total offset from UTC;
for example, if a tzinfo object represents both time zone and DST
adjustments, utcoffset() should return their sum. If the UTC offset
isn't known, return None. Else the value returned must be a timedelta
object specifying a whole number of minutes in the range -1439 to 1439
inclusive (1440 = 24*60; the magnitude of the offset must be less
than one day).
"""
def dst(dt):
"""Return the daylight saving time (DST) adjustment, in minutes east
of UTC, or None if DST information isn't known.
"""
def tzname(dt):
"""Return the time zone name corresponding to the datetime object as
a string.
"""
def fromutc(dt):
"""Return an equivalent datetime in self's local time."""
classImplements(timedelta, ITimeDelta)
classImplements(date, IDate)
classImplements(datetime, IDateTime)
classImplements(time, ITime)
classImplements(tzinfo, ITZInfo)
## directlyProvides(timedelta, ITimeDeltaClass)
## directlyProvides(date, IDateClass)
## directlyProvides(datetime, IDateTimeClass)
## directlyProvides(time, ITimeClass)
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Interfaces for standard python exceptions
$Id$
"""
from zope.interface import Interface
from zope.interface import classImplements
class IException(Interface): pass
class IStandardError(IException): pass
class IWarning(IException): pass
class ISyntaxError(IStandardError): pass
class ILookupError(IStandardError): pass
class IValueError(IStandardError): pass
class IRuntimeError(IStandardError): pass
class IArithmeticError(IStandardError): pass
class IAssertionError(IStandardError): pass
class IAttributeError(IStandardError): pass
class IDeprecationWarning(IWarning): pass
class IEOFError(IStandardError): pass
class IEnvironmentError(IStandardError): pass
class IFloatingPointError(IArithmeticError): pass
class IIOError(IEnvironmentError): pass
class IImportError(IStandardError): pass
class IIndentationError(ISyntaxError): pass
class IIndexError(ILookupError): pass
class IKeyError(ILookupError): pass
class IKeyboardInterrupt(IStandardError): pass
class IMemoryError(IStandardError): pass
class INameError(IStandardError): pass
class INotImplementedError(IRuntimeError): pass
class IOSError(IEnvironmentError): pass
class IOverflowError(IArithmeticError): pass
class IOverflowWarning(IWarning): pass
class IReferenceError(IStandardError): pass
class IRuntimeWarning(IWarning): pass
class IStopIteration(IException): pass
class ISyntaxWarning(IWarning): pass
class ISystemError(IStandardError): pass
class ISystemExit(IException): pass
class ITabError(IIndentationError): pass
class ITypeError(IStandardError): pass
class IUnboundLocalError(INameError): pass
class IUnicodeError(IValueError): pass
class IUserWarning(IWarning): pass
class IZeroDivisionError(IArithmeticError): pass
classImplements(ArithmeticError, IArithmeticError)
classImplements(AssertionError, IAssertionError)
classImplements(AttributeError, IAttributeError)
classImplements(DeprecationWarning, IDeprecationWarning)
classImplements(EnvironmentError, IEnvironmentError)
classImplements(EOFError, IEOFError)
classImplements(Exception, IException)
classImplements(FloatingPointError, IFloatingPointError)
classImplements(ImportError, IImportError)
classImplements(IndentationError, IIndentationError)
classImplements(IndexError, IIndexError)
classImplements(IOError, IIOError)
classImplements(KeyboardInterrupt, IKeyboardInterrupt)
classImplements(KeyError, IKeyError)
classImplements(LookupError, ILookupError)
classImplements(MemoryError, IMemoryError)
classImplements(NameError, INameError)
classImplements(NotImplementedError, INotImplementedError)
classImplements(OSError, IOSError)
classImplements(OverflowError, IOverflowError)
classImplements(OverflowWarning, IOverflowWarning)
classImplements(ReferenceError, IReferenceError)
classImplements(RuntimeError, IRuntimeError)
classImplements(RuntimeWarning, IRuntimeWarning)
classImplements(StandardError, IStandardError)
classImplements(StopIteration, IStopIteration)
classImplements(SyntaxError, ISyntaxError)
classImplements(SyntaxWarning, ISyntaxWarning)
classImplements(SystemError, ISystemError)
classImplements(SystemExit, ISystemExit)
classImplements(TabError, ITabError)
classImplements(TypeError, ITypeError)
classImplements(UnboundLocalError, IUnboundLocalError)
classImplements(UnicodeError, IUnicodeError)
classImplements(UserWarning, IUserWarning)
classImplements(ValueError, IValueError)
classImplements(Warning, IWarning)
classImplements(ZeroDivisionError, IZeroDivisionError)
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Mapping Interfaces
$Id$
"""
from zope.interface import Interface
class IItemMapping(Interface):
"""Simplest readable mapping object
"""
def __getitem__(key):
"""Get a value for a key
A KeyError is raised if there is no value for the key.
"""
class IReadMapping(IItemMapping):
"""Basic mapping interface
"""
def get(key, default=None):
"""Get a value for a key
The default is returned if there is no value for the key.
"""
def __contains__(key):
"""Tell if a key exists in the mapping."""
class IWriteMapping(Interface):
"""Mapping methods for changing data"""
def __delitem__(key):
"""Delete a value from the mapping using the key."""
def __setitem__(key, value):
"""Set a new item in the mapping."""
class IEnumerableMapping(IReadMapping):
"""Mapping objects whose items can be enumerated.
"""
def keys():
"""Return the keys of the mapping object.
"""
def __iter__():
"""Return an iterator for the keys of the mapping object.
"""
def values():
"""Return the values of the mapping object.
"""
def items():
"""Return the items of the mapping object.
"""
def __len__():
"""Return the number of items.
"""
class IMapping(IReadMapping, IWriteMapping, IEnumerableMapping):
''' Full mapping interface '''
#
# This file is necessary to make this directory a package.
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Base Mapping tests
$Id$
"""
from operator import __getitem__
def testIReadMapping(self, inst, state, absent):
for key in state:
self.assertEqual(inst[key], state[key])
self.assertEqual(inst.get(key, None), state[key])
self.failUnless(key in inst)
for key in absent:
self.assertEqual(inst.get(key, None), None)
self.assertEqual(inst.get(key), None)
self.assertEqual(inst.get(key, self), self)
self.assertRaises(KeyError, __getitem__, inst, key)
def test_keys(self, inst, state):
# Return the keys of the mapping object
inst_keys = list(inst.keys()); inst_keys.sort()
state_keys = list(state.keys()) ; state_keys.sort()
self.assertEqual(inst_keys, state_keys)
def test_iter(self, inst, state):
# Return the keys of the mapping object
inst_keys = list(inst); inst_keys.sort()
state_keys = list(state.keys()) ; state_keys.sort()
self.assertEqual(inst_keys, state_keys)
def test_values(self, inst, state):
# Return the values of the mapping object
inst_values = list(inst.values()); inst_values.sort()
state_values = list(state.values()) ; state_values.sort()
self.assertEqual(inst_values, state_values)
def test_items(self, inst, state):
# Return the items of the mapping object
inst_items = list(inst.items()); inst_items.sort()
state_items = list(state.items()) ; state_items.sort()
self.assertEqual(inst_items, state_items)
def test___len__(self, inst, state):
# Return the number of items
self.assertEqual(len(inst), len(state))
def testIEnumerableMapping(self, inst, state):
test_keys(self, inst, state)
test_items(self, inst, state)
test_values(self, inst, state)
test___len__(self, inst, state)
class BaseTestIReadMapping(object):
def testIReadMapping(self):
inst = self._IReadMapping__sample()
state = self._IReadMapping__stateDict()
absent = self._IReadMapping__absentKeys()
testIReadMapping(self, inst, state, absent)
class BaseTestIEnumerableMapping(BaseTestIReadMapping):
# Mapping objects whose items can be enumerated
def test_keys(self):
# Return the keys of the mapping object
inst = self._IEnumerableMapping__sample()
state = self._IEnumerableMapping__stateDict()
test_keys(self, inst, state)
def test_values(self):
# Return the values of the mapping object
inst = self._IEnumerableMapping__sample()
state = self._IEnumerableMapping__stateDict()
test_values(self, inst, state)
def test_values(self):
# Return the values of the mapping object
inst = self._IEnumerableMapping__sample()
state = self._IEnumerableMapping__stateDict()
test_iter(self, inst, state)
def test_items(self):
# Return the items of the mapping object
inst = self._IEnumerableMapping__sample()
state = self._IEnumerableMapping__stateDict()
test_items(self, inst, state)
def test___len__(self):
# Return the number of items
inst = self._IEnumerableMapping__sample()
state = self._IEnumerableMapping__stateDict()
test___len__(self, inst, state)
def _IReadMapping__stateDict(self):
return self._IEnumerableMapping__stateDict()
def _IReadMapping__sample(self):
return self._IEnumerableMapping__sample()
def _IReadMapping__absentKeys(self):
return self._IEnumerableMapping__absentKeys()
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test for datetime interfaces
$Id$
"""
import unittest
from zope.interface.verify import verifyObject, verifyClass
from zope.interface.common.idatetime import ITimeDelta, ITimeDeltaClass
from zope.interface.common.idatetime import IDate, IDateClass
from zope.interface.common.idatetime import IDateTime, IDateTimeClass
from zope.interface.common.idatetime import ITime, ITimeClass, ITZInfo
from datetime import timedelta, date, datetime, time, tzinfo
class TestDateTimeInterfaces(unittest.TestCase):
def test_interfaces(self):
verifyObject(ITimeDelta, timedelta(minutes=20))
verifyObject(IDate, date(2000, 1, 2))
verifyObject(IDateTime, datetime(2000, 1, 2, 10, 20))
verifyObject(ITime, time(20, 30, 15, 1234))
verifyObject(ITZInfo, tzinfo())
verifyClass(ITimeDeltaClass, timedelta)
verifyClass(IDateClass, date)
verifyClass(IDateTimeClass, datetime)
verifyClass(ITimeClass, time)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestDateTimeInterfaces))
return suite
if __name__ == '__main__':
unittest.main()
##############################################################################
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
##############################################################################
"""Implementation of interface declarations
There are three flavors of declarations:
- Declarations are used to simply name declared interfaces.
- ImplementsDeclarations are used to express the interfaces that a
class implements (that instances of the class provides).
Implements specifications support inheriting interfaces.
- ProvidesDeclarations are used to express interfaces directly
provided by objects.
$Id$
"""
import sys
import weakref
from zope.interface.interface import InterfaceClass, Specification
from ro import mergeOrderings, ro
import exceptions
from types import ClassType
from zope.interface.advice import addClassAdvisor
# Registry of class-implementation specifications
BuiltinImplementationSpecifications = {}
class Declaration(Specification):
"""Interface declarations
"""
def __init__(self, *interfaces):
Specification.__init__(self, _normalizeargs(interfaces))
def changed(self):
Specification.changed(self)
try:
del self._v_attrs
except AttributeError:
pass
def __contains__(self, interface):
"""Test whether an interface is in the specification
for example::
>>> from zope.interface import Interface
>>> class I1(Interface): pass
...
>>> class I2(I1): pass
...
>>> class I3(Interface): pass
...
>>> class I4(I3): pass
...
>>> spec = Declaration(I2, I3)
>>> spec = Declaration(I4, spec)
>>> int(I1 in spec)
0
>>> int(I2 in spec)
1
>>> int(I3 in spec)
1
>>> int(I4 in spec)
1
"""
return self.extends(interface) and interface in self.interfaces()
def __iter__(self):
"""Return an iterator for the interfaces in the specification
for example::
>>> from zope.interface import Interface
>>> class I1(Interface): pass
...
>>> class I2(I1): pass
...
>>> class I3(Interface): pass
...
>>> class I4(I3): pass
...
>>> spec = Declaration(I2, I3)
>>> spec = Declaration(I4, spec)
>>> i = iter(spec)
>>> i.next().getName()
'I4'
>>> i.next().getName()
'I2'
>>> i.next().getName()
'I3'
>>> list(i)
[]
"""
return self.interfaces()
def flattened(self):
"""Return an iterator of all included and extended interfaces
for example::
>>> from zope.interface import Interface
>>> class I1(Interface): pass
...
>>> class I2(I1): pass
...
>>> class I3(Interface): pass
...
>>> class I4(I3): pass
...
>>> spec = Declaration(I2, I3)
>>> spec = Declaration(I4, spec)
>>> i = spec.flattened()
>>> i.next().getName()
'I4'
>>> i.next().getName()
'I2'
>>> i.next().getName()
'I1'
>>> i.next().getName()
'I3'
>>> i.next().getName()
'Interface'
>>> list(i)
[]
"""
return iter(self.__iro__)
def __sub__(self, other):
"""Remove interfaces from a specification
Examples::
>>> from zope.interface import Interface
>>> class I1(Interface): pass
...
>>> class I2(I1): pass
...
>>> class I3(Interface): pass
...
>>> class I4(I3): pass
...
>>> spec = Declaration()
>>> [iface.getName() for iface in spec]
[]
>>> spec -= I1
>>> [iface.getName() for iface in spec]
[]
>>> spec -= Declaration(I1, I2)
>>> [iface.getName() for iface in spec]
[]
>>> spec = Declaration(I2, I4)
>>> [iface.getName() for iface in spec]
['I2', 'I4']
>>> [iface.getName() for iface in spec - I4]
['I2']
>>> [iface.getName() for iface in spec - I1]
['I4']
>>> [iface.getName() for iface
... in spec - Declaration(I3, I4)]
['I2']
"""
return Declaration(
*[i for i in self.interfaces()
if not [j for j in other.interfaces()
if i.extends(j, 0)]
]
)
def __add__(self, other):
"""Add two specifications or a specification and an interface
Examples::
>>> from zope.interface import Interface
>>> class I1(Interface): pass
...
>>> class I2(I1): pass
...
>>> class I3(Interface): pass
...
>>> class I4(I3): pass
...
>>> spec = Declaration()
>>> [iface.getName() for iface in spec]
[]
>>> [iface.getName() for iface in spec+I1]
['I1']
>>> [iface.getName() for iface in I1+spec]
['I1']
>>> spec2 = spec
>>> spec += I1
>>> [iface.getName() for iface in spec]
['I1']
>>> [iface.getName() for iface in spec2]
[]
>>> spec2 += Declaration(I3, I4)
>>> [iface.getName() for iface in spec2]
['I3', 'I4']
>>> [iface.getName() for iface in spec+spec2]
['I1', 'I3', 'I4']
>>> [iface.getName() for iface in spec2+spec]
['I3', 'I4', 'I1']
"""
seen = {}
result = []
for i in self.interfaces():
if i not in seen:
seen[i] = 1
result.append(i)
for i in other.interfaces():
if i not in seen:
seen[i] = 1
result.append(i)
return Declaration(*result)
__radd__ = __add__
def __nonzero__(self):
"""Test whether there are any interfaces in a specification.
>>> from zope.interface import Interface
>>> class I1(Interface): pass
...
>>> spec = Declaration(I1)
>>> int(bool(spec))
1
>>> spec = Declaration()
>>> int(bool(spec))
0
"""
return bool(self.__iro__)
##############################################################################
#
# Implementation specifications
#
# These specify interfaces implemented by instances of classes
class Implements(Declaration):
inherit = None
declared = ()
__name__ = '?'
def __repr__(self):
return '<implementedBy %s>' % (self.__name__)
def implementedByFallback(cls):
"""Return the interfaces implemented for a class' instances
The value returned is an IDeclaration.
for example:
>>> from zope.interface import Interface
>>> class I1(Interface): pass
...
>>> class I2(I1): pass
...
>>> class I3(Interface): pass
...
>>> class I4(I3): pass
...
>>> class C1(object):
... implements(I2)
>>> class C2(C1):
... implements(I3)
>>> [i.getName() for i in implementedBy(C2)]
['I3', 'I2']
"""
# This also manages storage of implementation specifications
try:
spec = cls.__dict__.get('__implemented__')
except AttributeError:
# we can't get the class dict. This is probably due to a
# security proxy. If this is the case, then probably no
# descriptor was installed for the class.
# We don't want to depend directly on zope.secury in
# zope.interface, but we'll try to make reasonable
# accommodations in an indirect way.
# We'll check to see if there's an implements:
spec = getattr(cls, '__implemented__', None)
if spec is None:
# There's no spec stred in the class. Maybe its a builtin:
spec = BuiltinImplementationSpecifications.get(cls)
if spec is not None:
return spec
return _empty
if spec.__class__ == Implements:
# we defaulted to _empty or there was a spec. Good enough.
# Return it.
return spec
# TODO: need old style __implements__ compatibility?
# Hm, there's an __implemented__, but it's not a spec. Must be
# an old-style declaration. Just compute a spec for it
return Declaration(*_normalizeargs((spec, )))
if isinstance(spec, Implements):
return spec
if spec is None:
spec = BuiltinImplementationSpecifications.get(cls)
if spec is not None:
return spec
# TODO: need old style __implements__ comptability?
if spec is not None:
# old-style __implemented__ = foo declaration
spec = (spec, ) # tuplefy, as it might be just an int
spec = Implements(*_normalizeargs(spec))
spec.inherit = None # old-style implies no inherit
del cls.__implemented__ # get rid of the old-style declaration
else:
try:
bases = cls.__bases__
except AttributeError:
raise TypeError("ImplementedBy called for non-type", cls)
spec = Implements(*[implementedBy(c) for c in bases])
spec.inherit = cls
spec.__name__ = getattr(cls, '__module__', '?') + '.' + cls.__name__
try:
cls.__implemented__ = spec
if not hasattr(cls, '__providedBy__'):
cls.__providedBy__ = objectSpecificationDescriptor
if (isinstance(cls, DescriptorAwareMetaClasses)
and
'__provides__' not in cls.__dict__):
# Make sure we get a __provides__ descriptor
cls.__provides__ = ClassProvides(
cls,
getattr(cls, '__class__', type(cls)),
)
except TypeError:
if not isinstance(cls, type):
raise TypeError("ImplementedBy called for non-type", cls)
BuiltinImplementationSpecifications[cls] = spec
return spec
implementedBy = implementedByFallback
def classImplementsOnly(cls, *interfaces):
"""Declare the only interfaces implemented by instances of a class
The arguments after the class are one or more interfaces or
interface specifications (IDeclaration objects).
The interfaces given (including the interfaces in the
specifications) replace any previous declarations.
Consider the following example::
>>> from zope.interface import Interface
>>> class I1(Interface): pass
...
>>> class I2(Interface): pass
...
>>> class I3(Interface): pass
...
>>> class I4(Interface): pass
...
>>> class A(object):
... implements(I3)
>>> class B(object):
... implements(I4)
>>> class C(A, B):
... pass
>>> classImplementsOnly(C, I1, I2)
>>> [i.getName() for i in implementedBy(C)]
['I1', 'I2']
Instances of ``C`` provide only ``I1``, ``I2``, and regardless of
whatever interfaces instances of ``A`` and ``B`` implement.
"""
spec = implementedBy(cls)
spec.__bases__ = tuple(_normalizeargs(interfaces))
spec.inherit = None
def classImplements(cls, *interfaces):
"""Declare additional interfaces implemented for instances of a class
The arguments after the class are one or more interfaces or
interface specifications (IDeclaration objects).
The interfaces given (including the interfaces in the
specifications) are added to any interfaces previously
declared.
Consider the following example::
for example:
>>> from zope.interface import Interface
>>> class I1(Interface): pass
...
>>> class I2(Interface): pass
...
>>> class I3(Interface): pass
...
>>> class I4(Interface): pass
...
>>> class I5(Interface): pass
...
>>> class A(object):
... implements(I3)
>>> class B(object):
... implements(I4)
>>> class C(A, B):
... pass
>>> classImplements(C, I1, I2)
>>> [i.getName() for i in implementedBy(C)]
['I1', 'I2', 'I3', 'I4']
>>> classImplements(C, I5)
>>> [i.getName() for i in implementedBy(C)]
['I1', 'I2', 'I5', 'I3', 'I4']
Instances of ``C`` provide ``I1``, ``I2``, ``I5``, and whatever
interfaces instances of ``A`` and ``B`` provide.
"""
spec = implementedBy(cls)
spec.declared += tuple(_normalizeargs(interfaces))
# compute the bases
bases = []
seen = {}
for b in spec.declared:
if b not in seen:
seen[b] = 1
bases.append(b)
if spec.inherit is not None:
for c in spec.inherit.__bases__:
b = implementedBy(c)
if b not in seen:
seen[b] = 1
bases.append(b)
spec.__bases__ = tuple(bases)
def _implements_advice(cls):
interfaces, classImplements = cls.__dict__['__implements_advice_data__']
del cls.__implements_advice_data__
classImplements(cls, *interfaces)
return cls
def _implements(name, interfaces, classImplements):
frame = sys._getframe(2)
locals = frame.f_locals
# Try to make sure we were called from a class def. In 2.2.0 we can't
# check for __module__ since it doesn't seem to be added to the locals
# until later on.
if (locals is frame.f_globals) or (
('__module__' not in locals) and sys.version_info[:3] > (2, 2, 0)):
raise TypeError(name+" can be used only from a class definition.")
if '__implements_advice_data__' in locals:
raise TypeError(name+" can be used only once in a class definition.")
locals['__implements_advice_data__'] = interfaces, classImplements
addClassAdvisor(_implements_advice, depth=3)
def implements(*interfaces):
"""Declare interfaces implemented by instances of a class
This function is called in a class definition.
The arguments are one or more interfaces or interface
specifications (IDeclaration objects).
The interfaces given (including the interfaces in the
specifications) are added to any interfaces previously
declared.
Previous declarations include declarations for base classes
unless implementsOnly was used.
This function is provided for convenience. It provides a more
convenient way to call classImplements. For example::
implements(I1)
is equivalent to calling::
classImplements(C, I1)
after the class has been created.
Consider the following example::
>>> from zope.interface import Interface
>>> class IA1(Interface): pass
...
>>> class IA2(Interface): pass
...
>>> class IB(Interface): pass
...
>>> class IC(Interface): pass
...
>>> class A(object): implements(IA1, IA2)
...
>>> class B(object): implements(IB)
...
>>> class C(A, B):
... implements(IC)
>>> ob = C()
>>> int(IA1 in providedBy(ob))
1
>>> int(IA2 in providedBy(ob))
1
>>> int(IB in providedBy(ob))
1
>>> int(IC in providedBy(ob))
1
Instances of ``C`` implement ``I1``, ``I2``, and whatever interfaces
instances of ``A`` and ``B`` implement.
"""
_implements("implements", interfaces, classImplements)
def implementsOnly(*interfaces):
"""Declare the only interfaces implemented by instances of a class
This function is called in a class definition.
The arguments are one or more interfaces or interface
specifications (IDeclaration objects).
Previous declarations including declarations for base classes
are overridden.
This function is provided for convenience. It provides a more
convenient way to call classImplementsOnly. For example::
implementsOnly(I1)
is equivalent to calling::
classImplementsOnly(I1)
after the class has been created.
Consider the following example::
>>> from zope.interface import Interface
>>> class IA1(Interface): pass
...
>>> class IA2(Interface): pass
...
>>> class IB(Interface): pass
...
>>> class IC(Interface): pass
...
>>> class A(object): implements(IA1, IA2)
...
>>> class B(object): implements(IB)
...
>>> class C(A, B):
... implementsOnly(IC)
>>> ob = C()
>>> int(IA1 in providedBy(ob))
0
>>> int(IA2 in providedBy(ob))
0
>>> int(IB in providedBy(ob))
0
>>> int(IC in providedBy(ob))
1
Instances of ``C`` implement ``IC``, regardless of what
instances of ``A`` and ``B`` implement.
"""
_implements("implementsOnly", interfaces, classImplementsOnly)
##############################################################################
#
# Instance declarations
class Provides(Declaration): # Really named ProvidesClass
"""Implement __provides__, the instance-specific specification
When an object is pickled, we pickle the interfaces that it implements.
"""
def __init__(self, cls, *interfaces):
self.__args = (cls, ) + interfaces
self._cls = cls
Declaration.__init__(self, *(interfaces + (implementedBy(cls), )))
def __reduce__(self):
return Provides, self.__args
__module__ = 'zope.interface'
def __get__(self, inst, cls):
"""Make sure that a class __provides__ doesn't leak to an instance
For example::
>>> from zope.interface import Interface
>>> class IFooFactory(Interface): pass
...
>>> class C(object):
... pass
>>> C.__provides__ = ProvidesClass(C, IFooFactory)
>>> [i.getName() for i in C.__provides__]
['IFooFactory']
>>> getattr(C(), '__provides__', 0)
0
"""
if inst is None and cls is self._cls:
# We were accessed through a class, so we are the class'
# provides spec. Just return this object, but only if we are
# being called on the same class that we were defined for:
return self
raise AttributeError, '__provides__'
ProvidesClass = Provides
# Registry of instance declarations
# This is a memory optimization to allow objects to share specifications.
InstanceDeclarations = weakref.WeakValueDictionary()
def Provides(*interfaces):
"""Cache instance declarations
Instance declarations are shared among instances that have the
same declaration. The declarations are cached in an weak value
dictionary.
(Note that, in the examples below, we are going to make
assertions about the size of the weakvalue dictionary. For the
assertions to be meaningful, we need to force garbage
collection to make sure garbage objects are, indeed, removed
from the system. Depending on how Python is run, we may need to
make multiple calls to be sure. We provide a collect function
to help with this:
>>> import gc
>>> def collect():
... for i in range(4):
... gc.collect()
)
>>> collect()
>>> before = len(InstanceDeclarations)
>>> class C(object):
... pass
>>> from zope.interface import Interface
>>> class I(Interface):
... pass
>>> c1 = C()
>>> c2 = C()
>>> len(InstanceDeclarations) == before
1
>>> directlyProvides(c1, I)
>>> len(InstanceDeclarations) == before + 1
1
>>> directlyProvides(c2, I)
>>> len(InstanceDeclarations) == before + 1
1
>>> del c1
>>> collect()
>>> len(InstanceDeclarations) == before + 1
1
>>> del c2
>>> collect()
>>> len(InstanceDeclarations) == before
1
"""
spec = InstanceDeclarations.get(interfaces)
if spec is None:
spec = ProvidesClass(*interfaces)
InstanceDeclarations[interfaces] = spec
return spec
Provides.__safe_for_unpickling__ = True
DescriptorAwareMetaClasses = ClassType, type
def directlyProvides(object, *interfaces):
"""Declare interfaces declared directly for an object
The arguments after the object are one or more interfaces or
interface specifications (IDeclaration objects).
The interfaces given (including the interfaces in the
specifications) replace interfaces previously
declared for the object.
Consider the following example::
>>> from zope.interface import Interface
>>> class I1(Interface): pass
...
>>> class I2(Interface): pass
...
>>> class IA1(Interface): pass
...
>>> class IA2(Interface): pass
...
>>> class IB(Interface): pass
...
>>> class IC(Interface): pass
...
>>> class A(object): implements(IA1, IA2)
...
>>> class B(object): implements(IB)
...
>>> class C(A, B):
... implements(IC)
>>> ob = C()
>>> directlyProvides(ob, I1, I2)
>>> int(I1 in providedBy(ob))
1
>>> int(I2 in providedBy(ob))
1
>>> int(IA1 in providedBy(ob))
1
>>> int(IA2 in providedBy(ob))
1
>>> int(IB in providedBy(ob))
1
>>> int(IC in providedBy(ob))
1
The object, ``ob`` provides ``I1``, ``I2``, and whatever interfaces
instances have been declared for instances of ``C``.
To remove directly provided interfaces, use ``directlyProvidedBy`` and
subtract the unwanted interfaces. For example::
>>> directlyProvides(ob, directlyProvidedBy(ob)-I2)
>>> int(I1 in providedBy(ob))
1
>>> int(I2 in providedBy(ob))
0
removes I2 from the interfaces directly provided by
``ob``. The object, ``ob`` no longer directly provides ``I2``,
although it might still provide ``I2`` if it's class
implements ``I2``.
To add directly provided interfaces, use ``directlyProvidedBy`` and
include additional interfaces. For example::
>>> int(I2 in providedBy(ob))
0
>>> directlyProvides(ob, directlyProvidedBy(ob), I2)
adds I2 to the interfaces directly provided by ob::
>>> int(I2 in providedBy(ob))
1
"""
# We need to avoid setting this attribute on meta classes that
# don't support descriptors.
# We can do away with this check when we get rid of the old EC
cls = getattr(object, '__class__', None)
if cls is not None and getattr(cls, '__class__', None) is cls:
# It's a meta class (well, at least it it could be an extension class)
if not isinstance(object, DescriptorAwareMetaClasses):
raise TypeError("Attempt to make an interface declaration on a "
"non-descriptor-aware class")
interfaces = _normalizeargs(interfaces)
if cls is None:
cls = type(object)
issub = False
for damc in DescriptorAwareMetaClasses:
if issubclass(cls, damc):
issub = True
break
if issub:
# we have a class or type. We'll use a special descriptor
# that provides some extra caching
object.__provides__ = ClassProvides(object, cls, *interfaces)
else:
object.__provides__ = Provides(cls, *interfaces)
class ClassProvidesBasePy(object):
def __get__(self, inst, cls):
if cls is self._cls:
# We only work if called on the class we were defined for
if inst is None:
# We were accessed through a class, so we are the class'
# provides spec. Just return this object as is:
return self
return self._implements
raise AttributeError, '__provides__'
ClassProvidesBase = ClassProvidesBasePy
# Try to get C base:
try:
import _zope_interface_coptimizations
except ImportError:
pass
else:
from _zope_interface_coptimizations import ClassProvidesBase
class ClassProvides(Declaration, ClassProvidesBase):
"""Special descriptor for class __provides__
The descriptor caches the implementedBy info, so that
we can get declarations for objects without instance-specific
interfaces a bit quicker.
For example::
>>> from zope.interface import Interface
>>> class IFooFactory(Interface):
... pass
>>> class IFoo(Interface):
... pass
>>> class C(object):
... implements(IFoo)
... classProvides(IFooFactory)
>>> [i.getName() for i in C.__provides__]
['IFooFactory']
>>> [i.getName() for i in C().__provides__]
['IFoo']
"""
def __init__(self, cls, metacls, *interfaces):
self._cls = cls
self._implements = implementedBy(cls)
self.__args = (cls, metacls, ) + interfaces
Declaration.__init__(self, *(interfaces + (implementedBy(metacls), )))
def __reduce__(self):
return self.__class__, self.__args
# Copy base-class method for speed
__get__ = ClassProvidesBase.__get__
def directlyProvidedBy(object):
"""Return the interfaces directly provided by the given object
The value returned is an IDeclaration.
"""
provides = getattr(object, "__provides__", None)
if (provides is None # no spec
or
# We might have gotten the implements spec, as an
# optimization. If so, it's like having only one base, that we
# lop off to exclude class-supplied declarations:
isinstance(provides, Implements)
):
return _empty
# Strip off the class part of the spec:
return Declaration(provides.__bases__[:-1])
def classProvides(*interfaces):
"""Declare interfaces provided directly by a class
This function is called in a class definition.
The arguments are one or more interfaces or interface
specifications (IDeclaration objects).
The given interfaces (including the interfaces in the
specifications) are used to create the class's direct-object
interface specification. An error will be raised if the module
class has an direct interface specification. In other words, it is
an error to call this function more than once in a class
definition.
Note that the given interfaces have nothing to do with the
interfaces implemented by instances of the class.
This function is provided for convenience. It provides a more
convenient way to call directlyProvidedByProvides for a class. For
example::
classProvides(I1)
is equivalent to calling::
directlyProvides(theclass, I1)
after the class has been created.
For example::
>>> from zope.interface import Interface
>>> class IFoo(Interface): pass
...
>>> class IFooFactory(Interface): pass
...
>>> class C(object):
... implements(IFoo)
... classProvides(IFooFactory)
>>> [i.getName() for i in C.__providedBy__]
['IFooFactory']
>>> [i.getName() for i in C().__providedBy__]
['IFoo']
if equivalent to::
>>> from zope.interface import Interface
>>> class IFoo(Interface): pass
...
>>> class IFooFactory(Interface): pass
...
>>> class C(object):
... implements(IFoo)
>>> directlyProvides(C, IFooFactory)
>>> [i.getName() for i in C.__providedBy__]
['IFooFactory']
>>> [i.getName() for i in C().__providedBy__]
['IFoo']
"""
frame = sys._getframe(1)
locals = frame.f_locals
# Try to make sure we were called from a class def
if (locals is frame.f_globals) or ('__module__' not in locals):
raise TypeError(name+" can be used only from a class definition.")
if '__provides__' in locals:
raise TypeError(
"classProvides can only be used once in a class definition.")
locals["__provides__"] = _normalizeargs(interfaces)
addClassAdvisor(_classProvides_advice, depth=2)
def _classProvides_advice(cls):
interfaces = cls.__dict__['__provides__']
del cls.__provides__
directlyProvides(cls, *interfaces)
return cls
def moduleProvides(*interfaces):
"""Declare interfaces provided by a module
This function is used in a module definition.
The arguments are one or more interfaces or interface
specifications (IDeclaration objects).
The given interfaces (including the interfaces in the
specifications) are used to create the module's direct-object
interface specification. An error will be raised if the module
already has an interface specification. In other words, it is
an error to call this function more than once in a module
definition.
This function is provided for convenience. It provides a more
convenient way to call directlyProvides. For example::
moduleImplements(I1)
is equivalent to::
directlyProvides(sys.modules[__name__], I1)
"""
frame = sys._getframe(1)
locals = frame.f_locals
# Try to make sure we were called from a class def
if (locals is not frame.f_globals) or ('__name__' not in locals):
raise TypeError(
"moduleProvides can only be used from a module definition.")
if '__provides__' in locals:
raise TypeError(
"moduleProvides can only be used once in a module definition.")
module = sys.modules[__name__]
locals["__provides__"] = Provides(type(module),
*_normalizeargs(interfaces))
##############################################################################
#
# Declaration querying support
def ObjectSpecification(direct, cls):
"""Provide object specifications
These combine information for the object and for it's classes.
For example::
>>> from zope.interface import Interface
>>> class I1(Interface): pass
...
>>> class I2(Interface): pass
...
>>> class I3(Interface): pass
...
>>> class I31(I3): pass
...
>>> class I4(Interface): pass
...
>>> class I5(Interface): pass
...
>>> class A(object): implements(I1)
...
>>> class B(object): __implemented__ = I2
...
>>> class C(A, B): implements(I31)
...
>>> c = C()
>>> directlyProvides(c, I4)
>>> [i.getName() for i in providedBy(c)]
['I4', 'I31', 'I1', 'I2']
>>> [i.getName() for i in providedBy(c).flattened()]
['I4', 'I31', 'I3', 'I1', 'I2', 'Interface']
>>> int(I1 in providedBy(c))
1
>>> int(I3 in providedBy(c))
0
>>> int(providedBy(c).extends(I3))
1
>>> int(providedBy(c).extends(I31))
1
>>> int(providedBy(c).extends(I5))
0
>>> class COnly(A, B): implementsOnly(I31)
...
>>> class D(COnly): implements(I5)
...
>>> c = D()
>>> directlyProvides(c, I4)
>>> [i.getName() for i in providedBy(c)]
['I4', 'I5', 'I31']
>>> [i.getName() for i in providedBy(c).flattened()]
['I4', 'I5', 'I31', 'I3', 'Interface']
>>> int(I1 in providedBy(c))
0
>>> int(I3 in providedBy(c))
0
>>> int(providedBy(c).extends(I3))
1
>>> int(providedBy(c).extends(I1))
0
>>> int(providedBy(c).extends(I31))
1
>>> int(providedBy(c).extends(I5))
1
nonzero:
>>> from zope.interface import Interface
>>> class I1(Interface):
... pass
>>> class I2(Interface):
... pass
>>> class C(object):
... implements(I1)
>>> c = C()
>>> int(bool(providedBy(c)))
1
>>> directlyProvides(c, I2)
>>> int(bool(providedBy(c)))
1
>>> class C(object):
... pass
>>> c = C()
>>> int(bool(providedBy(c)))
0
>>> directlyProvides(c, I2)
>>> int(bool(providedBy(c)))
1
"""
return Provides(cls, direct)
def getObjectSpecification(ob):
provides = getattr(ob, '__provides__', None)
if provides is not None:
return provides
try:
cls = ob.__class__
except AttributeError:
# We can't get the class, so just consider provides
return _empty
return implementedBy(cls)
def providedBy(ob):
# Here we have either a special object, an old-style declaration
# or a descriptor
# Try to get __providedBy__
try:
r = ob.__providedBy__
except AttributeError:
# Not set yet. Fall back to lower-level thing that computes it
return getObjectSpecification(ob)
try:
# We might have gotten a descriptor from an instance of a
# class (like an ExtensionClass) that doesn't support
# descriptors. We'll make sure we got one by trying to get
# the only attribute, which all specs have.
r.extends
except AttributeError:
# The object's class doesn't understand descriptors.
# Sigh. We need to get an object descriptor, but we have to be
# careful. We want to use the instance's __provides__, if
# there is one, but only if it didn't come from the class.
try:
r = ob.__provides__
except AttributeError:
# No __provides__, so just fall back to implementedBy
return implementedBy(ob.__class__)
# We need to make sure we got the __provides__ from the
# instance. We'll do this by making sure we don't get the same
# thing from the class:
try:
cp = ob.__class__.__provides__
except AttributeError:
# The ob doesn't have a class or the class has no
# provides, assume we're done:
return r
if r is cp:
# Oops, we got the provides from the class. This means
# the object doesn't have it's own. We should use implementedBy
return implementedBy(ob.__class__)
return r
class ObjectSpecificationDescriptorPy(object):
"""Implement the __providedBy__ attribute
The __providedBy__ attribute computes the interfaces peovided by
an object.
"""
def __get__(self, inst, cls):
"""Get an object specification for an object
For example::
>>> from zope.interface import Interface
>>> class IFoo(Interface): pass
...
>>> class IFooFactory(Interface): pass
...
>>> class C(object):
... implements(IFoo)
... classProvides(IFooFactory)
>>> [i.getName() for i in C.__providedBy__]
['IFooFactory']
>>> [i.getName() for i in C().__providedBy__]
['IFoo']
"""
# Get an ObjectSpecification bound to either an instance or a class,
# depending on how we were accessed.
if inst is None:
return getObjectSpecification(cls)
provides = getattr(inst, '__provides__', None)
if provides is not None:
return provides
return implementedBy(cls)
ObjectSpecificationDescriptor = ObjectSpecificationDescriptorPy
##############################################################################
def _normalizeargs(sequence, output = None):
"""Normalize declaration arguments
Normalization arguments might contain Declarions, tuples, or single
interfaces.
Anything but individial interfaces or implements specs will be expanded.
"""
if output is None:
output = []
cls = sequence.__class__
if InterfaceClass in cls.__mro__ or Implements in cls.__mro__:
output.append(sequence)
else:
for v in sequence:
_normalizeargs(v, output)
return output
_empty = Declaration()
try:
import _zope_interface_coptimizations
except ImportError:
pass
else:
from _zope_interface_coptimizations import implementedBy, providedBy
from _zope_interface_coptimizations import getObjectSpecification
from _zope_interface_coptimizations import ObjectSpecificationDescriptor
objectSpecificationDescriptor = ObjectSpecificationDescriptor()
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" Pretty-Print an Interface object as structured text (Yum)
This module provides a function, asStructuredText, for rendering an
interface as structured text.
$Id$
"""
from string import maketrans
import zope.interface
def asStructuredText(I, munge=0):
""" Output structured text format. Note, this will wack any existing
'structured' format of the text. """
r = ["%s\n\n" % I.getName()]
outp = r.append
level = 1
if I.getDoc():
outp(_justify_and_indent(_trim_doc_string(I.getDoc()), level)+ "\n\n")
bases = [base
for base in I.__bases__
if base is not zope.interface.Interface
]
if bases:
outp((" " * level) + "This interface extends:\n\n")
level = level + 1
for b in bases:
item = "o %s" % b.getName()
outp(_justify_and_indent(_trim_doc_string(item), level, munge)
+ "\n\n")
level = level - 1
outp(_justify_and_indent("Attributes:", level, munge)+'\n\n')
level = level + 1
namesAndDescriptions = I.namesAndDescriptions()
namesAndDescriptions.sort()
for name, desc in namesAndDescriptions:
if not hasattr(desc, 'getSignatureString'): # ugh...
item = "%s -- %s" % (desc.getName(),
desc.getDoc() or 'no documentation')
outp(_justify_and_indent(_trim_doc_string(item), level, munge)
+ "\n\n")
level = level - 1
outp(_justify_and_indent("Methods:", level, munge)+'\n\n')
level = level + 1
for name, desc in namesAndDescriptions:
if hasattr(desc, 'getSignatureString'): # ugh...
item = "%s%s -- %s" % (desc.getName(),
desc.getSignatureString(),
desc.getDoc() or 'no documentation')
outp(_justify_and_indent(_trim_doc_string(item), level, munge)
+ "\n\n")
return "".join(r)
def _trim_doc_string(text):
"""
Trims a doc string to make it format
correctly with structured text.
"""
text = text.strip().replace('\r\n', '\n')
lines = text.split('\n')
nlines = [lines[0]]
if len(lines) > 1:
min_indent=None
for line in lines[1:]:
indent=len(line) - len(line.lstrip())
if indent < min_indent or min_indent is None:
min_indent=indent
for line in lines[1:]:
nlines.append(line[min_indent:])
return '\n'.join(nlines)
_trans = maketrans("\r\n", " ")
def _justify_and_indent(text, level, munge=0, width=72):
""" indent and justify text, rejustify (munge) if specified """
lines = []
if munge:
line = " " * level
text = text.translate(text, _trans).strip().split()
for word in text:
line = ' '.join([line, word])
if len(line) > width:
lines.append(line)
line = " " * level
else:
lines.append(line)
return "\n".join(lines)
else:
text = text.replace("\r\n", "\n").split("\n")
for line in text:
lines.append((" " * level) + line)
return '\n'.join(lines)
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Interface-specific exceptions
$Id$
"""
class Invalid(Exception):
"""An specification is violated
"""
class DoesNotImplement(Invalid):
""" This object does not implement """
def __init__(self, interface):
self.interface = interface
def __str__(self):
return """An object does not implement interface %(interface)s
""" % self.__dict__
class BrokenImplementation(Invalid):
"""An attribute is not completely implemented.
"""
def __init__(self, interface, name):
self.interface=interface
self.name=name
def __str__(self):
return """An object has failed to implement interface %(interface)s
The %(name)s attribute was not provided.
""" % self.__dict__
class BrokenMethodImplementation(Invalid):
"""An method is not completely implemented.
"""
def __init__(self, method, mess):
self.method=method
self.mess=mess
def __str__(self):
return """The implementation of %(method)s violates its contract
because %(mess)s.
""" % self.__dict__
class InvalidInterface(Exception):
"""The interface has invalid contents
"""
class BadImplements(TypeError):
"""An implementation assertion is invalid
because it doesn't contain an interface or a sequence of valid
implementation assertions.
"""
==========================
Using the Adapter Registry
==========================
This is a small demonstration of the zope.interface package including its
adapter registry. It is intended to provide a concrete but narrow example on
how to use interfaces and adapters outside of Zope 3.
First we have to import the interface package.
>>> import zope.interface
We now develop an interface for our object, which is a simple file in this
case. For now we simply support one attribute, the body, which contains the
actual file contents.
>>> class IFile(zope.interface.Interface):
...
... body = zope.interface.Attribute('Contents of the file.')
...
For statistical reasons we often want to know the size of a file. However, it
would be clumsy to implement the size directly in the file object, since the
size really represents meta-data. Thus we create another interface that
provides the size of something.
>>> class ISize(zope.interface.Interface):
...
... def getSize():
... 'Return the size of an object.'
...
Now we need to implement the file. It is essential that the object states
that it implements the `IFile` interface. We also provide a default body
value (just to make things simpler for this example).
>>> class File(object):
...
... zope.interface.implements(IFile)
... body = 'foo bar'
...
Next we implement an adapter that can provide the `ISize` interface given any
object providing `IFile`. By convention we use `__used_for__` to specify the
interface that we expect the adapted object to provide, in our case
`IFile`. However, this attribute is not used for anything. If you have
multiple interfaces for which an adapter is used, just specify the interfaces
via a tuple.
Again by convention, the constructor of an adapter takes one argument, the
context. The context in this case is an instance of `File` (providing `IFile`)
that is used to extract the size from. Also by convention the context is
stored in an attribute named `context` on the adapter. The twisted community
refers to the context as the `original` object. However, you may feel free to
use a specific argument name, such as `file`.
>>> class FileSize(object):
...
... zope.interface.implements(ISize)
... __used_for__ = IFile
...
... def __init__(self, context):
... self.context = context
...
... def getSize(self):
... return len(self.context.body)
...
Now that we have written our adapter, we have to register it with an adapter
registry, so that it can be looked up when needed. There is no such thing as a
global registry; thus we have to instantiate one for our example manually.
>>> from zope.interface.adapter import AdapterRegistry
>>> registry = AdapterRegistry()
The registry keeps a map of what adapters implement based on another
interface, the object already provides. Therefore, we next have to register an
adapter that adapts from `IFile` to `ISize`. The first argument to
the registry's `register()` method is a list of original interfaces.In our
cause we have only one original interface, `IFile`. A list makes sense, since
the interface package has the concept of multi-adapters, which are adapters
that require multiple objects to adapt to a new interface. In these
situations, your adapter constructor will require an argument for each
specified interface.
The second argument is the interface the adapter provides, in our case
`ISize`. The third argument in the name of the adapter. Since we do not care
about names, we simply leave it as an empty string. Names are commonly useful,
if you have adapters for the same set of interfaces, but they are useful in
different situations. The last argument is simply the adapter class.
>>> registry.register([IFile], ISize, '', FileSize)
You can now use the the registry to lookup the adapter.
>>> registry.lookup1(IFile, ISize, '')
<class '__main__.FileSize'>
Let's get a little bit more practical. Let's create a `File` instance and
create the adapter using a registry lookup. Then we see whether the adapter
returns the correct size by calling `getSize()`.
>>> file = File()
>>> size = registry.lookup1(IFile, ISize, '')(file)
>>> size.getSize()
7
However, this is not very practical, since I have to manually pass in the
arguments to the lookup method. There is some syntactic candy that will allow
us to get an adapter instance by simply calling `ISize(file)`. To make use of
this functionality, we need to add our registry to the adapter_hooks list,
which is a member of the adapters module. This list stores a collection of
callables that are automatically invoked when IFoo(obj) is called; their
purpose is to locate adapters that implement an interface for a certain
context instance.
You are required to implement your own adapter hook; this example covers one
of the simplest hooks that use the registry, but you could implement one that
used an adapter cache or persistent adapters, for instance. The helper hook is
required to expect as first argument the desired output interface (for us
`ISize`) and as the second argument the context of the adapter (here
`file`). The function returns an adapter, i.e. a `FileSize` instance.
>>> def hook(provided, object):
... adapter = registry.lookup1(zope.interface.providedBy(object),
... provided, '')
... return adapter(object)
...
We now just add the hook to an `adapter_hooks` list.
>>> from zope.interface.interface import adapter_hooks
>>> adapter_hooks.append(hook)
Once the hook is registered, you can use the desired syntax.
>>> size = ISize(file)
>>> size.getSize()
7
Now we have to cleanup after ourselves, so that others after us have a clean
`adapter_hooks` list.
>>> adapter_hooks.remove(hook)
That's it. I have intentionally left out a discussion of named adapters and
multi-adapters, since this text is intended as a practical and simple
introduction to Zope 3 interfaces and adapters. You might want to read the
`adapter.txt` in the `zope.interface` package for a more formal, referencial
and complete treatment of the package. Warning: People have reported that
`adapter.txt` makes their brain feel soft!
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Interface object implementation
$Id$
"""
from __future__ import generators
import sys
import warnings
import weakref
from types import FunctionType
from ro import ro
from zope.interface.exceptions import Invalid
CO_VARARGS = 4
CO_VARKEYWORDS = 8
TAGGED_DATA = '__interface_tagged_values__'
def invariant(call):
f_locals = sys._getframe(1).f_locals
tags = f_locals.get(TAGGED_DATA)
if tags is None:
tags = f_locals[TAGGED_DATA] = {}
invariants = tags.get('invariants')
if invariants is None:
invariants = tags['invariants'] = []
invariants.append(call)
class Element(object):
# We can't say this yet because we don't have enough
# infrastructure in place.
#
#implements(IElement)
def __init__(self, __name__, __doc__=''):
"""Create an 'attribute' description
"""
if not __doc__ and __name__.find(' ') >= 0:
__doc__ = __name__
__name__ = None
self.__name__=__name__
self.__doc__=__doc__
self.__tagged_values = {}
def getName(self):
""" Returns the name of the object. """
return self.__name__
def getDoc(self):
""" Returns the documentation for the object. """
return self.__doc__
def getTaggedValue(self, tag):
""" Returns the value associated with 'tag'. """
return self.__tagged_values[tag]
def queryTaggedValue(self, tag, default=None):
""" Returns the value associated with 'tag'. """
return self.__tagged_values.get(tag, default)
def getTaggedValueTags(self):
""" Returns a list of all tags. """
return self.__tagged_values.keys()
def setTaggedValue(self, tag, value):
""" Associates 'value' with 'key'. """
self.__tagged_values[tag] = value
class SpecificationBasePy(object):
def providedBy(self, ob):
"""Is the interface implemented by an object
>>> from zope.interface import *
>>> class I1(Interface):
... pass
>>> class C(object):
... implements(I1)
>>> c = C()
>>> class X(object):
... pass
>>> x = X()
>>> I1.providedBy(x)
False
>>> I1.providedBy(C)
False
>>> I1.providedBy(c)
True
>>> directlyProvides(x, I1)
>>> I1.providedBy(x)
True
>>> directlyProvides(C, I1)
>>> I1.providedBy(C)
True
"""
spec = providedBy(ob)
return self in spec._implied
def implementedBy(self, cls):
"""Do instances of the given class implement the interface?"""
spec = implementedBy(cls)
return self in spec._implied
def isOrExtends(self, interface):
"""Is the interface the same as or extend the given interface
Examples::
>>> from zope.interface import Interface
>>> from zope.interface.declarations import Declaration
>>> class I1(Interface): pass
...
>>> class I2(I1): pass
...
>>> class I3(Interface): pass
...
>>> class I4(I3): pass
...
>>> spec = Declaration()
>>> int(spec.extends(Interface))
0
>>> spec = Declaration(I2)
>>> int(spec.extends(Interface))
1
>>> int(spec.extends(I1))
1
>>> int(spec.extends(I2))
1
>>> int(spec.extends(I3))
0
>>> int(spec.extends(I4))
0
"""
return interface in self._implied
SpecificationBase = SpecificationBasePy
try:
from _zope_interface_coptimizations import SpecificationBase
except ImportError:
pass
class Specification(SpecificationBase):
"""Specifications
An interface specification is used to track interface declarations
and component registrations.
This class is a base class for both interfaces themselves and for
interface specifications (declarations).
Specifications are mutable. If you reassign their cases, their
relations with other specifications are adjusted accordingly.
For example:
>>> from zope.interface import Interface
>>> class I1(Interface):
... pass
>>> class I2(I1):
... pass
>>> class I3(I2):
... pass
>>> [i.__name__ for i in I1.__bases__]
['Interface']
>>> [i.__name__ for i in I2.__bases__]
['I1']
>>> I3.extends(I1)
1
>>> I2.__bases__ = (Interface, )
>>> [i.__name__ for i in I2.__bases__]
['Interface']
>>> I3.extends(I1)
0
"""
# Copy some base class methods for speed
isOrExtends = SpecificationBase.isOrExtends
providedBy = SpecificationBase.providedBy
#########################################################################
# BBB 2004-07-13: Backward compatabilty. These methods have been
# deprecated in favour of providedBy and implementedBy.
def isImplementedByInstancesOf(self, cls):
warnings.warn(
"isImplementedByInstancesOf has been renamed to implementedBy",
DeprecationWarning, stacklevel=2,
)
return self.implementedBy(cls)
def isImplementedBy(self, ob):
warnings.warn(
"isImplementedBy has been renamed to providedBy",
DeprecationWarning, stacklevel=2,
)
return self.providedBy(ob)
#
#########################################################################
def __init__(self, bases=()):
self._implied = {}
self.dependents = weakref.WeakKeyDictionary()
self.__bases__ = tuple(bases)
def subscribe(self, dependent):
self.dependents[dependent] = 1
def unsubscribe(self, dependent):
del self.dependents[dependent]
def __setBases(self, bases):
# Register ourselves as a dependent of our old bases
for b in self.__bases__:
b.unsubscribe(self)
# Register ourselves as a dependent of our bases
self.__dict__['__bases__'] = bases
for b in bases:
b.subscribe(self)
self.changed()
__bases__ = property(
lambda self: self.__dict__.get('__bases__', ()),
__setBases,
)
def changed(self):
"""We, or something we depend on, have changed
"""
implied = self._implied
implied.clear()
ancestors = ro(self)
self.__sro__ = tuple(ancestors)
self.__iro__ = tuple([ancestor for ancestor in ancestors
if isinstance(ancestor, InterfaceClass)
])
for ancestor in ancestors:
# We directly imply our ancestors:
implied[ancestor] = ()
# Now, advise our dependents of change:
for dependent in self.dependents.keys():
dependent.changed()
def interfaces(self):
"""Return an iterator for the interfaces in the specification
for example::
>>> from zope.interface import Interface
>>> class I1(Interface): pass
...
>>> class I2(I1): pass
...
>>> class I3(Interface): pass
...
>>> class I4(I3): pass
...
>>> spec = Specification((I2, I3))
>>> spec = Specification((I4, spec))
>>> i = spec.interfaces()
>>> i.next().getName()
'I4'
>>> i.next().getName()
'I2'
>>> i.next().getName()
'I3'
>>> list(i)
[]
"""
seen = {}
for base in self.__bases__:
for interface in base.interfaces():
if interface not in seen:
seen[interface] = 1
yield interface
def extends(self, interface, strict=True):
"""Does the specification extend the given interface?
Test whether an interface in the specification extends the
given interface
Examples::
>>> from zope.interface import Interface
>>> from zope.interface.declarations import Declaration
>>> class I1(Interface): pass
...
>>> class I2(I1): pass
...
>>> class I3(Interface): pass
...
>>> class I4(I3): pass
...
>>> spec = Declaration()
>>> int(spec.extends(Interface))
0
>>> spec = Declaration(I2)
>>> int(spec.extends(Interface))
1
>>> int(spec.extends(I1))
1
>>> int(spec.extends(I2))
1
>>> int(spec.extends(I3))
0
>>> int(spec.extends(I4))
0
>>> I2.extends(I2)
0
>>> I2.extends(I2, False)
1
>>> I2.extends(I2, strict=False)
1
"""
return ((interface in self._implied)
and
((not strict) or (self != interface))
)
def weakref(self, callback=None):
if callback is None:
return weakref.ref(self)
else:
return weakref.ref(self, callback)
def get(self, name, default=None):
"""Query for an attribute description
"""
try:
attrs = self._v_attrs
except AttributeError:
attrs = self._v_attrs = {}
attr = attrs.get(name)
if attr is None:
for iface in self.__iro__:
attr = iface.direct(name)
if attr is not None:
attrs[name] = attr
break
if attr is None:
return default
else:
return attr
class InterfaceClass(Element, Specification):
"""Prototype (scarecrow) Interfaces Implementation."""
# We can't say this yet because we don't have enough
# infrastructure in place.
#
#implements(IInterface)
def __init__(self, name, bases=(), attrs=None, __doc__=None,
__module__=None):
if __module__ is None:
if (attrs is not None and
('__module__' in attrs) and
isinstance(attrs['__module__'], str)
):
__module__ = attrs['__module__']
del attrs['__module__']
else:
try:
# Figure out what module defined the interface.
# This is how cPython figures out the module of
# a class, but of course it does it in C. :-/
__module__ = sys._getframe(1).f_globals['__name__']
except (AttributeError, KeyError):
pass
self.__module__ = __module__
if attrs is None:
attrs = {}
d = attrs.get('__doc__')
if d is not None:
if not isinstance(d, Attribute):
if __doc__ is None:
__doc__ = d
del attrs['__doc__']
if __doc__ is None:
__doc__ = ''
Element.__init__(self, name, __doc__)
if attrs.has_key(TAGGED_DATA):
tagged_data = attrs[TAGGED_DATA]
del attrs[TAGGED_DATA]
else:
tagged_data = None
if tagged_data is not None:
for key, val in tagged_data.items():
self.setTaggedValue(key, val)
for base in bases:
if not isinstance(base, InterfaceClass):
raise TypeError, 'Expected base interfaces'
Specification.__init__(self, bases)
# Make sure that all recorded attributes (and methods) are of type
# `Attribute` and `Method`
for name, attr in attrs.items():
if isinstance(attr, Attribute):
attr.interface = self
if not attr.__name__:
attr.__name__ = name
elif isinstance(attr, FunctionType):
attrs[name] = fromFunction(attr, self, name=name)
else:
raise InvalidInterface("Concrete attribute, %s" %name)
self.__attrs = attrs
self.__identifier__ = "%s.%s" % (self.__module__, self.__name__)
def interfaces(self):
"""Return an iterator for the interfaces in the specification
for example::
>>> from zope.interface import Interface
>>> class I1(Interface): pass
...
>>>
>>> i = I1.interfaces()
>>> i.next().getName()
'I1'
>>> list(i)
[]
"""
yield self
def getBases(self):
return self.__bases__
def isEqualOrExtendedBy(self, other):
"""Same interface or extends?"""
if self == other:
return True
return other.extends(self)
def names(self, all=False):
"""Return the attribute names defined by the interface."""
if not all:
return self.__attrs.keys()
r = {}
for name in self.__attrs.keys():
r[name] = 1
for base in self.__bases__:
for name in base.names(all):
r[name] = 1
return r.keys()
def __iter__(self):
return iter(self.names(all=True))
def namesAndDescriptions(self, all=False):
"""Return attribute names and descriptions defined by interface."""
if not all:
return self.__attrs.items()
r = {}
for name, d in self.__attrs.items():
r[name] = d
for base in self.__bases__:
for name, d in base.namesAndDescriptions(all):
if name not in r:
r[name] = d
return r.items()
def getDescriptionFor(self, name):
"""Return the attribute description for the given name."""
r = self.get(name)
if r is not None:
return r
raise KeyError, name
__getitem__ = getDescriptionFor
def __contains__(self, name):
return self.get(name) is not None
def direct(self, name):
return self.__attrs.get(name)
def queryDescriptionFor(self, name, default=None):
return self.get(name, default)
def deferred(self):
"""Return a defered class corresponding to the interface."""
if hasattr(self, "_deferred"): return self._deferred
klass={}
exec "class %s: pass" % self.__name__ in klass
klass=klass[self.__name__]
self.__d(klass.__dict__)
self._deferred=klass
return klass
def validateInvariants(self, obj, errors=None):
"""validate object to defined invariants."""
for call in self.queryTaggedValue('invariants', []):
try:
call(obj)
except Invalid, e:
if errors is None:
raise
else:
errors.append(e)
for base in self.__bases__:
try:
base.validateInvariants(obj, errors)
except Invalid:
if errors is None:
raise
pass
if errors:
raise Invalid(errors)
def _getInterface(self, ob, name):
"""Retrieve a named interface."""
return None
def __d(self, dict):
for k, v in self.__attrs.items():
if isinstance(v, Method) and not (k in dict):
dict[k]=v
for b in self.__bases__: b.__d(dict)
def __repr__(self):
r = getattr(self, '_v_repr', self)
if r is self:
name = self.__name__
m = self.__module__
if m:
name = '%s.%s' % (m, name)
r = "<%s %s>" % (self.__class__.__name__, name)
self._v_repr = r
return r
def __call__():
# TRICK! Create the call method
#
# An embedded function is used to allow an optional argument to
# __call__ without resorting to a global marker.
#
# The evility of this trick is a reflection of the underlying
# evility of "optional" arguments, arguments whos presense or
# absense changes the behavior of the methos.
#
# I think the evil is necessary, and perhaps desireable to
# provide some consistencey with the PEP 246 adapt method.
marker = object()
def __call__(self, obj, alternate=marker):
"""Adapt an object to the interface
The sematics based on those of the PEP 246 adapt function.
If an object cannot be adapted, then a TypeError is raised::
>>> import zope.interface
>>> class I(zope.interface.Interface):
... pass
>>> I(0)
Traceback (most recent call last):
...
TypeError: ('Could not adapt', 0, """ \
"""<InterfaceClass zope.interface.interface.I>)
unless an alternate value is provided as a second
positional argument::
>>> I(0, 'bob')
'bob'
If an object already implements the interface, then it will be
returned::
>>> class C(object):
... zope.interface.implements(I)
>>> obj = C()
>>> I(obj) is obj
True
If an object implements __conform__, then it will be used::
>>> class C(object):
... zope.interface.implements(I)
... def __conform__(self, proto):
... return 0
>>> I(C())
0
Adapter hooks (see __adapt__) will also be used, if present:
>>> from zope.interface.interface import adapter_hooks
>>> def adapt_0_to_42(iface, obj):
... if obj == 0:
... return 42
>>> adapter_hooks.append(adapt_0_to_42)
>>> I(0)
42
>>> adapter_hooks.remove(adapt_0_to_42)
>>> I(0)
Traceback (most recent call last):
...
TypeError: ('Could not adapt', 0, """ \
"""<InterfaceClass zope.interface.interface.I>)
"""
conform = getattr(obj, '__conform__', None)
if conform is not None:
try:
adapter = conform(self)
except TypeError:
# We got a TypeError. It might be an error raised by
# the __conform__ implementation, or *we* may have
# made the TypeError by calling an unbound method
# (object is a class). In the later case, we behave
# as though there is no __conform__ method. We can
# detect this case by checking whether there is more
# than one traceback object in the traceback chain:
if sys.exc_info()[2].tb_next is not None:
# There is more than one entry in the chain, so
# reraise the error:
raise
# This clever trick is from Phillip Eby
else:
if adapter is not None:
return adapter
adapter = self.__adapt__(obj)
if adapter is None:
if alternate is not marker:
return alternate
raise TypeError("Could not adapt", obj, self)
return adapter
return __call__
__call__ = __call__() # TRICK! Make the *real* __call__ method
def __adapt__(self, obj):
"""Adapt an object to the reciever
This method is normally not called directly. It is called by
the PEP 246 adapt framework and by the interface __call__
operator.
The adapt method is responsible for adapting an object to
the reciever.
The default version returns None::
>>> import zope.interface
>>> class I(zope.interface.Interface):
... pass
>>> I.__adapt__(0)
unless the object given provides the interface::
>>> class C(object):
... zope.interface.implements(I)
>>> obj = C()
>>> I.__adapt__(obj) is obj
True
Adapter hooks can be provided (or removed) to provide custom
adaptation. We'll install a silly hook that adapts 0 to 42.
We install a hook by simply adding it to the adapter_hooks
list::
>>> from zope.interface.interface import adapter_hooks
>>> def adapt_0_to_42(iface, obj):
... if obj == 0:
... return 42
>>> adapter_hooks.append(adapt_0_to_42)
>>> I.__adapt__(0)
42
Hooks must either return an adapter, or None if no adapter can
be found.
Hooks can be uninstalled by removing them from the list::
>>> adapter_hooks.remove(adapt_0_to_42)
>>> I.__adapt__(0)
"""
if self.providedBy(obj):
return obj
for hook in adapter_hooks:
adapter = hook(self, obj)
if adapter is not None:
return adapter
def __reduce__(self):
return self.__name__
def __cmp(self, o1, o2):
# Yes, I did mean to name this __cmp, rather than __cmp__.
# It is a private method used by __lt__ and __gt__.
# I don't want to override __eq__ because I want the default
# __eq__, which is really fast.
"""Make interfaces sortable
TODO: It would ne nice if:
More specific interfaces should sort before less specific ones.
Otherwise, sort on name and module.
But this is too complicated, and we're going to punt on it
for now.
For now, sort on interface and module name.
None is treated as a pseudo interface that implies the loosest
contact possible, no contract. For that reason, all interfaces
sort before None.
"""
if o1 == o2:
return 0
if o1 is None:
return 1
if o2 is None:
return -1
n1 = (getattr(o1, '__name__', ''),
getattr(getattr(o1, '__module__', None), '__name__', ''))
n2 = (getattr(o2, '__name__', ''),
getattr(getattr(o2, '__module__', None), '__name__', ''))
return cmp(n1, n2)
def __lt__(self, other):
c = self.__cmp(self, other)
#print '<', self, other, c < 0, c
return c < 0
def __gt__(self, other):
c = self.__cmp(self, other)
#print '>', self, other, c > 0, c
return c > 0
adapter_hooks = []
Interface = InterfaceClass("Interface", __module__ = 'zope.interface')
class Attribute(Element):
"""Attribute descriptions
"""
# We can't say this yet because we don't have enough
# infrastructure in place.
#
# implements(IAttribute)
interface = None
class Method(Attribute):
"""Method interfaces
The idea here is that you have objects that describe methods.
This provides an opportunity for rich meta-data.
"""
# We can't say this yet because we don't have enough
# infrastructure in place.
#
# implements(IMethod)
def __call__(self, *args, **kw):
raise BrokenImplementation(self.interface, self.__name__)
def getSignatureInfo(self):
return {'positional': self.positional,
'required': self.required,
'optional': self.optional,
'varargs': self.varargs,
'kwargs': self.kwargs,
}
def getSignatureString(self):
sig = "("
for v in self.positional:
sig = sig + v
if v in self.optional.keys():
sig = sig + "=%s" % `self.optional[v]`
sig = sig + ", "
if self.varargs:
sig = sig + ("*%s, " % self.varargs)
if self.kwargs:
sig = sig + ("**%s, " % self.kwargs)
# slice off the last comma and space
if self.positional or self.varargs or self.kwargs:
sig = sig[:-2]
sig = sig + ")"
return sig
def fromFunction(func, interface=None, imlevel=0, name=None):
name = name or func.__name__
method = Method(name, func.__doc__)
defaults = func.func_defaults or ()
code = func.func_code
# Number of positional arguments
na = code.co_argcount-imlevel
names = code.co_varnames[imlevel:]
opt = {}
# Number of required arguments
nr = na-len(defaults)
if nr < 0:
defaults=defaults[-nr:]
nr = 0
# Determine the optional arguments.
for i in range(len(defaults)):
opt[names[i+nr]] = defaults[i]
method.positional = names[:na]
method.required = names[:nr]
method.optional = opt
argno = na
# Determine the function's variable argument's name (i.e. *args)
if code.co_flags & CO_VARARGS:
method.varargs = names[argno]
argno = argno + 1
else:
method.varargs = None
# Determine the function's keyword argument's name (i.e. **kw)
if code.co_flags & CO_VARKEYWORDS:
method.kwargs = names[argno]
else:
method.kwargs = None
method.interface = interface
for key, value in func.__dict__.items():
method.setTaggedValue(key, value)
return method
def fromMethod(meth, interface=None, name=None):
func = meth.im_func
return fromFunction(func, interface, imlevel=1, name=name)
# Now we can create the interesting interfaces and wire them up:
def _wire():
from zope.interface.declarations import classImplements
from zope.interface.interfaces import IAttribute
classImplements(Attribute, IAttribute)
from zope.interface.interfaces import IMethod
classImplements(Method, IMethod)
from zope.interface.interfaces import IInterface
classImplements(InterfaceClass, IInterface)
# We import this here to deal with module dependencies.
from zope.interface.declarations import providedBy, implementedBy
from zope.interface.exceptions import InvalidInterface
from zope.interface.exceptions import BrokenImplementation
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Interface Package Interfaces
$Id$
"""
from zope.interface import Interface
from zope.interface.interface import Attribute
class IElement(Interface):
"""Objects that have basic documentation and tagged values.
"""
__name__ = Attribute('__name__', 'The object name')
__doc__ = Attribute('__doc__', 'The object doc string')
def getTaggedValue(tag):
"""Returns the value associated with 'tag'.
Raise a KeyError of the tag isn't set
"""
def queryTaggedValue(tag, default=None):
"""Returns the value associated with 'tag'.
Return the default value of the tag isn't set.
"""
def getTaggedValueTags():
"""Returns a list of all tags."""
def setTaggedValue(tag, value):
"""Associates 'value' with 'key'."""
class IAttribute(IElement):
"""Attribute descriptors"""
interface = Attribute('interface',
'Stores the interface instance in which the '
'attribute is located.')
class IMethod(IAttribute):
"""Method attributes
"""
def getSignatureInfo():
"""Returns the signature information.
This method returns a dictionary with the following keys:
o `positional` - All positional arguments.
o `required` - A list of all required arguments.
o `optional` - A list of all optional arguments.
o `varargs' - The name of the varargs argument.
o `kwargs` - The name of the kwargs argument.
"""
def getSignatureString():
"""Return a signature string suitable for inclusion in documentation.
This method returns the function signature string. For example, if you
have `func(a, b, c=1, d='f')`, then the signature string is `(a, b,
c=1, d='f')`.
"""
class ISpecification(Interface):
"""Object Behavioral specifications
"""
def extends(other, strict=True):
"""Test whether a specification extends another
The specification extends other if it has other as a base
interface or if one of it's bases extends other.
If strict is false, then the specification extends itself.
"""
def isOrExtends(other):
"""Test whether the specification is or extends another
"""
def weakref(callback=None):
"""Return a weakref to the specification
This method is, regrettably, needed to allow weakrefs to be
computed to security-proxied specifications. While the
zope.interface package does not require zope.security or
zope.proxy, it has to be able to coexist with it.
"""
__bases__ = Attribute("""Base specifications
A tuple if specifications from which this specification is
directly derived.
""")
__sro__ = Attribute("""Specification-resolution order
A tuple of the specification and all of it's ancestor
specifications from most specific to least specific.
(This is similar to the method-resolution order for new-style classes.)
""")
def get(name, default=None):
"""Look up the description for a name
If the named attribute is not defined, the default is
returned.
"""
class IInterface(ISpecification, IElement):
"""Interface objects
Interface objects describe the behavior of an object by containing
useful information about the object. This information includes:
o Prose documentation about the object. In Python terms, this
is called the "doc string" of the interface. In this element,
you describe how the object works in prose language and any
other useful information about the object.
o Descriptions of attributes. Attribute descriptions include
the name of the attribute and prose documentation describing
the attributes usage.
o Descriptions of methods. Method descriptions can include:
o Prose "doc string" documentation about the method and its
usage.
o A description of the methods arguments; how many arguments
are expected, optional arguments and their default values,
the position or arguments in the signature, whether the
method accepts arbitrary arguments and whether the method
accepts arbitrary keyword arguments.
o Optional tagged data. Interface objects (and their attributes and
methods) can have optional, application specific tagged data
associated with them. Examples uses for this are examples,
security assertions, pre/post conditions, and other possible
information you may want to associate with an Interface or its
attributes.
Not all of this information is mandatory. For example, you may
only want the methods of your interface to have prose
documentation and not describe the arguments of the method in
exact detail. Interface objects are flexible and let you give or
take any of these components.
Interfaces are created with the Python class statement using
either Interface.Interface or another interface, as in::
from zope.interface import Interface
class IMyInterface(Interface):
'''Interface documentation
'''
def meth(arg1, arg2):
'''Documentation for meth
'''
# Note that there is no self argument
class IMySubInterface(IMyInterface):
'''Interface documentation
'''
def meth2():
'''Documentation for meth2
'''
You use interfaces in two ways:
o You assert that your object implement the interfaces.
There are several ways that you can assert that an object
implements an interface::
1. Call zope.interface.implements in your class definition.
2. Call zope.interfaces.directlyProvides on your object.
3. Call 'zope.interface.classImplements' to assert that instances
of a class implement an interface.
For example::
from zope.interface import classImplements
classImplements(some_class, some_interface)
This approach is useful when it is not an option to modify
the class source. Note that this doesn't affect what the
class itself implements, but only what its instances
implement.
o You query interface meta-data. See the IInterface methods and
attributes for details.
"""
def providedBy(object):
"""Test whether the interface is implemented by the object
Return true of the object asserts that it implements the
interface, including asserting that it implements an extended
interface.
"""
def implementedBy(class_):
"""Test whether the interface is implemented by instances of the class
Return true of the class asserts that its instances implement the
interface, including asserting that they implement an extended
interface.
"""
def names(all=False):
"""Get the interface attribute names
Return a sequence of the names of the attributes, including
methods, included in the interface definition.
Normally, only directly defined attributes are included. If
a true positional or keyword argument is given, then
attributes defined by base classes will be included.
"""
def namesAndDescriptions(all=False):
"""Get the interface attribute names and descriptions
Return a sequence of the names and descriptions of the
attributes, including methods, as name-value pairs, included
in the interface definition.
Normally, only directly defined attributes are included. If
a true positional or keyword argument is given, then
attributes defined by base classes will be included.
"""
def __getitem__(name):
"""Get the description for a name
If the named attribute is not defined, a KeyError is raised.
"""
def direct(name):
"""Get the description for the name if it was defined by the interface
If the interface doesn't define the name, returns None.
"""
def validateInvariants(obj, errors=None):
"""Validate invariants
Validate object to defined invariants. If errors is None,
raises first Invalid error; if errors is a list, appends all errors
to list, then raises Invalid with the errors as the first element
of the "args" tuple."""
def __contains__(name):
"""Test whether the name is defined by the interface"""
def __iter__():
"""Return an iterator over the names defined by the interface
The names iterated include all of the names defined by the
interface directly and indirectly by base interfaces.
"""
__module__ = Attribute("""The name of the module defining the interface""")
class IDeclaration(ISpecification):
"""Interface declaration
Declarations are used to express the interfaces implemented by
classes or provided by objects.
"""
def __contains__(interface):
"""Test whether an interface is in the specification
Return true if the given interface is one of the interfaces in
the specification and false otherwise.
"""
def __iter__():
"""Return an iterator for the interfaces in the specification
"""
def flattened():
"""Return an iterator of all included and extended interfaces
An iterator is returned for all interfaces either included in
or extended by interfaces included in the specifications
without duplicates. The interfaces are in "interface
resolution order". The interface resolution order is such that
base interfaces are listed after interfaces that extend them
and, otherwise, interfaces are included in the order that they
were defined in the specification.
"""
def __sub__(interfaces):
"""Create an interface specification with some interfaces excluded
The argument can be an interface or an interface
specifications. The interface or interfaces given in a
specification are subtracted from the interface specification.
Removing an interface that is not in the specification does
not raise an error. Doing so has no effect.
Removing an interface also removes sub-interfaces of the interface.
"""
def __add__(interfaces):
"""Create an interface specification with some interfaces added
The argument can be an interface or an interface
specifications. The interface or interfaces given in a
specification are added to the interface specification.
Adding an interface that is already in the specification does
not raise an error. Doing so has no effect.
"""
def __nonzero__():
"""Return a true value of the interface specification is non-empty
"""
class IInterfaceDeclaration(Interface):
"""Declare and check the interfaces of objects
The functions defined in this interface are used to declare the
interfaces that objects provide and to query the interfaces that have
been declared.
Interfaces can be declared for objects in two ways:
- Interfaces are declared for instances of the object's class
- Interfaces are declared for the object directly.
The interfaces declared for an object are, therefore, the union of
interfaces declared for the object directly and the interfaces
declared for instances of the object's class.
Note that we say that a class implements the interfaces provided
by it's instances. An instance can also provide interfaces
directly. The interfaces provided by an object are the union of
the interfaces provided directly and the interfaces implemented by
the class.
"""
def providedBy(ob):
"""Return the interfaces provided by an object
This is the union of the interfaces directly provided by an
object and interfaces implemented by it's class.
The value returned is an IDeclaration.
"""
def implementedBy(class_):
"""Return the interfaces implemented for a class' instances
The value returned is an IDeclaration.
"""
def classImplements(class_, *interfaces):
"""Declare additional interfaces implemented for instances of a class
The arguments after the class are one or more interfaces or
interface specifications (IDeclaration objects).
The interfaces given (including the interfaces in the
specifications) are added to any interfaces previously
declared.
Consider the following example::
class C(A, B):
...
classImplements(C, I1, I2)
Instances of ``C`` provide ``I1``, ``I2``, and whatever interfaces
instances of ``A`` and ``B`` provide.
"""
def classImplementsOnly(class_, *interfaces):
"""Declare the only interfaces implemented by instances of a class
The arguments after the class are one or more interfaces or
interface specifications (IDeclaration objects).
The interfaces given (including the interfaces in the
specifications) replace any previous declarations.
Consider the following example::
class C(A, B):
...
classImplements(C, IA, IB. IC)
classImplementsOnly(C. I1, I2)
Instances of ``C`` provide only ``I1``, ``I2``, and regardless of
whatever interfaces instances of ``A`` and ``B`` implement.
"""
def directlyProvidedBy(object):
"""Return the interfaces directly provided by the given object
The value returned is an IDeclaration.
"""
def directlyProvides(object, *interfaces):
"""Declare interfaces declared directly for an object
The arguments after the object are one or more interfaces or
interface specifications (IDeclaration objects).
The interfaces given (including the interfaces in the
specifications) replace interfaces previously
declared for the object.
Consider the following example::
class C(A, B):
...
ob = C()
directlyProvides(ob, I1, I2)
The object, ``ob`` provides ``I1``, ``I2``, and whatever interfaces
instances have been declared for instances of ``C``.
To remove directly provided interfaces, use ``directlyProvidedBy`` and
subtract the unwanted interfaces. For example::
directlyProvides(ob, directlyProvidedBy(ob)-I2)
removes I2 from the interfaces directly provided by
``ob``. The object, ``ob`` no longer directly provides ``I2``,
although it might still provide ``I2`` if it's class
implements ``I2``.
To add directly provided interfaces, use ``directlyProvidedBy`` and
include additional interfaces. For example::
directlyProvides(ob, directlyProvidedBy(ob), I2)
adds I2 to the interfaces directly provided by ob.
"""
def implements(*interfaces):
"""Declare interfaces implemented by instances of a class
This function is called in a class definition.
The arguments are one or more interfaces or interface
specifications (IDeclaration objects).
The interfaces given (including the interfaces in the
specifications) are added to any interfaces previously
declared.
Previous declarations include declarations for base classes
unless implementsOnly was used.
This function is provided for convenience. It provides a more
convenient way to call classImplements. For example::
implements(I1)
is equivalent to calling::
classImplements(C, I1)
after the class has been created.
Consider the following example::
class C(A, B):
implements(I1, I2)
Instances of ``C`` implement ``I1``, ``I2``, and whatever interfaces
instances of ``A`` and ``B`` implement.
"""
def implementsOnly(*interfaces):
"""Declare the only interfaces implemented by instances of a class
This function is called in a class definition.
The arguments are one or more interfaces or interface
specifications (IDeclaration objects).
Previous declarations including declarations for base classes
are overridden.
This function is provided for convenience. It provides a more
convenient way to call classImplementsOnly. For example::
implementsOnly(I1)
is equivalent to calling::
classImplementsOnly(I1)
after the class has been created.
Consider the following example::
class C(A, B):
implementsOnly(I1, I2)
Instances of ``C`` implement ``I1``, ``I2``, regardless of what
instances of ``A`` and ``B`` implement.
"""
def classProvides(*interfaces):
"""Declare interfaces provided directly by a class
This function is called in a class definition.
The arguments are one or more interfaces or interface
specifications (IDeclaration objects).
The given interfaces (including the interfaces in the
specifications) are used to create the class's direct-object
interface specification. An error will be raised if the module
class has an direct interface specification. In other words, it is
an error to call this function more than once in a class
definition.
Note that the given interfaces have nothing to do with the
interfaces implemented by instances of the class.
This function is provided for convenience. It provides a more
convenient way to call directlyProvides for a class. For example::
classProvides(I1)
is equivalent to calling::
directlyProvides(theclass, I1)
after the class has been created.
"""
def moduleProvides(*interfaces):
"""Declare interfaces provided by a module
This function is used in a module definition.
The arguments are one or more interfaces or interface
specifications (IDeclaration objects).
The given interfaces (including the interfaces in the
specifications) are used to create the module's direct-object
interface specification. An error will be raised if the module
already has an interface specification. In other words, it is
an error to call this function more than once in a module
definition.
This function is provided for convenience. It provides a more
convenient way to call directlyProvides for a module. For example::
moduleImplements(I1)
is equivalent to::
directlyProvides(sys.modules[__name__], I1)
"""
def Declaration(*interfaces):
"""Create an interface specification
The arguments are one or more interfaces or interface
specifications (IDeclaration objects).
A new interface specification (IDeclaration) with
the given interfaces is returned.
"""
class IAdapterRegistry(Interface):
"""Provide an interface-based registry for adapters
This registry registers objects that are in some sense "from" a
sequence of specification to an interface and a name.
No specific semantics are assumed for the registered objects,
however, the most common application will be to register factories
that adapt objects providing required specifications to a provided
interface.
"""
def register(required, provided, name, value):
"""Register a value
A value is registered for a *sequence* of required specifications, a
provided interface, and a name.
"""
def lookup(required, provided, name, default=None):
"""Lookup a value
A value is looked up based on a *sequence* of required
specifications, a provided interface, and a name.
"""
def lookupAll(required, provided):
"""Find all adapters from the required to the provided interfaces
An iterable object is returned that provides name-value two-tuples.
"""
def names(required, provided):
"""Return the names for which there are registered objects
"""
def subscribe(required, provided, subscriber):
"""Register a subscriber
A subscriber is registered for a *sequence* of required
specifications, a provided interface, and a name.
Multiple subscribers may be registered for the same (or
equivalent) interfaces.
"""
def subscriptions(required, provided):
"""Get a sequence of subscribers
Subscribers for a *sequence* of required interfaces, and a provided
interface are returned.
"""
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Compute a resolution order for an object and it's bases
$Id$
"""
def ro(object):
"""Compute a "resolution order" for an object
"""
return mergeOrderings([_flatten(object, [])])
def mergeOrderings(orderings, seen=None):
"""Merge multiple orderings so that within-ordering order is preserved
Orderings are constrained in such a way that if an object appears
in two or more orderings, then the suffix that begins with the
object must be in both orderings.
For example:
>>> _mergeOrderings([
... ['x', 'y', 'z'],
... ['q', 'z'],
... [1, 3, 5],
... ['z']
... ])
['x', 'y', 'q', 1, 3, 5, 'z']
"""
if seen is None:
seen = {}
result = []
orderings.reverse()
for ordering in orderings:
ordering = list(ordering)
ordering.reverse()
for o in ordering:
if o not in seen:
seen[o] = 1
result.append(o)
result.reverse()
return result
def _flatten(ob, result):
result.append(ob)
for base in ob.__bases__:
_flatten(base, result)
return result
#
# This file is necessary to make this directory a package.
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Dummy Module
$Id$
"""
from zope.interface import moduleProvides
from zope.interface.tests.ifoo import IFoo
from zope.interface import moduleProvides
moduleProvides(IFoo)
def bar(baz):
pass
================================
Food-based subscription examples
================================
This file gives more subscription examples using a cooking-based example::
>>> from zope.interface.adapter import AdapterRegistry
>>> registry = AdapterRegistry()
>>> import zope.interface
>>> class IAnimal(zope.interface.Interface):
... pass
>>> class IPoultry(IAnimal):
... pass
>>> class IChicken(IPoultry):
... pass
>>> class ISeafood(IAnimal):
... pass
Adapting to some other interface for which there is no
subscription adapter returns an empty sequence::
>>> class IRecipe(zope.interface.Interface):
... pass
>>> class ISausages(IRecipe):
... pass
>>> class INoodles(IRecipe):
... pass
>>> class IKFC(IRecipe):
... pass
>>> list(registry.subscriptions([IPoultry], IRecipe))
[]
unless we define a subscription::
>>> registry.subscribe([IAnimal], ISausages, 'sausages')
>>> list(registry.subscriptions([IPoultry], ISausages))
['sausages']
And define another subscription adapter::
>>> registry.subscribe([IPoultry], INoodles, 'noodles')
>>> meals = list(registry.subscriptions([IPoultry], IRecipe))
>>> meals.sort()
>>> meals
['noodles', 'sausages']
>>> registry.subscribe([IChicken], IKFC, 'kfc')
>>> meals = list(registry.subscriptions([IChicken], IRecipe))
>>> meals.sort()
>>> meals
['kfc', 'noodles', 'sausages']
And the answer for poultry hasn't changed::
>>> meals = list(registry.subscriptions([IPoultry], IRecipe))
>>> meals.sort()
>>> meals
['noodles', 'sausages']
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""IFoo test module
$Id$
"""
from zope.interface import Interface
class IFoo(Interface):
"""
Dummy interface for unit tests.
"""
def bar(baz):
"""
Just a note.
"""
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test module that declares an interface
$Id$
"""
from zope.interface import Interface, moduleProvides
class I1(Interface): pass
class I2(Interface): pass
moduleProvides(I1, I2)
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test module that doesn't declare an interface
$Id$
"""
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Odd meta class that doesn't subclass type.
This is used for testing support for ExtensionClass in new interfaces.
>>> class A(object):
... __metaclass__ = MetaClass
... a = 1
...
>>> A.__name__
'A'
>>> A.__bases__
()
>>> class B(object):
... __metaclass__ = MetaClass
... b = 1
...
>>> class C(A, B): pass
...
>>> C.__name__
'C'
>>> int(C.__bases__ == (A, B))
1
>>> a = A()
>>> aa = A()
>>> a.a
1
>>> aa.a
1
>>> aa.a = 2
>>> a.a
1
>>> aa.a
2
>>> c = C()
>>> c.a
1
>>> c.b
1
>>> c.b = 2
>>> c.b
2
>>> C.c = 1
>>> c.c
1
>>> from types import ClassType
>>> int(isinstance(C, (type, ClassType)))
0
>>> int(C.__class__.__class__ is C.__class__)
1
$Id$
"""
# class OddClass is an odd meta class
class MetaMetaClass(type):
def __getattribute__(self, name):
if name == '__class__':
return self
return type.__getattribute__(self, name)
class MetaClass(object):
"""Odd classes
"""
__metaclass__ = MetaMetaClass
def __init__(self, name, bases, dict):
self.__name__ = name
self.__bases__ = bases
self.__dict__.update(dict)
def __call__(self):
return OddInstance(self)
def __getattr__(self, name):
for b in self.__bases__:
v = getattr(b, name, self)
if v is not self:
return v
raise AttributeError, name
def __repr__(self):
return "<odd class %s at %s>" % (self.__name__, hex(id(self)))
class OddInstance(object):
def __init__(self, cls):
self.__dict__['__class__'] = cls
def __getattribute__(self, name):
dict = object.__getattribute__(self, '__dict__')
if name == '__dict__':
return dict
v = dict.get(name, self)
if v is not self:
return v
return getattr(dict['__class__'], name)
def __setattr__(self, name, v):
self.__dict__[name] = v
def __delattr__(self, name):
del self.__dict__[name]
def __repr__(self):
return "<odd %s instance at %s>" % (
self.__class__.__name__, hex(id(self)))
# DocTest:
if __name__ == "__main__":
import doctest, __main__
doctest.testmod(__main__, isprivate=lambda *a: False)
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Adapter registry tests
$Id$
"""
import unittest, doctest
import zope.interface
from zope.interface.adapter import AdapterRegistry
import zope.interface
class IF0(zope.interface.Interface):
pass
class IF1(IF0):
pass
class IB0(zope.interface.Interface):
pass
class IB1(IB0):
pass
class IR0(zope.interface.Interface):
pass
class IR1(IR0):
pass
def test_orderwith():
"""
>>> Interface = zope.interface.Interface
>>> bywith = {(Interface, Interface): 'A0',
... (IF0, Interface): 'A1',
... (Interface, IB0): 'A2',
... (IF0, IB0): 'A3',
... (IF1, IB0): 'A4',
... (IF0, IB1): 'A5',
... (IF1, IB1): 'A6',
... }
>>> [value for spec, value in zope.interface.adapter.orderwith(bywith)]
['A6', 'A4', 'A5', 'A3', 'A1', 'A2', 'A0']
"""
def test_multi_adapter_get_best_match():
"""
>>> registry = AdapterRegistry()
>>> class IB2(IB0):
... pass
>>> class IB3(IB2, IB1):
... pass
>>> class IB4(IB1, IB2):
... pass
>>> registry.register([None, IB1], IR0, '', 'A1')
>>> registry.register([None, IB0], IR0, '', 'A0')
>>> registry.register([None, IB2], IR0, '', 'A2')
>>> registry.lookup((IF1, IB1), IR0, '')
'A1'
>>> registry.lookup((IF1, IB2), IR0, '')
'A2'
>>> registry.lookup((IF1, IB0), IR0, '')
'A0'
>>> registry.lookup((IF1, IB3), IR0, '')
'A2'
>>> registry.lookup((IF1, IB4), IR0, '')
'A1'
"""
def test_multi_adapter_lookupAll_get_best_matches():
"""
>>> registry = AdapterRegistry()
>>> class IB2(IB0):
... pass
>>> class IB3(IB2, IB1):
... pass
>>> class IB4(IB1, IB2):
... pass
>>> registry.register([None, IB1], IR0, '', 'A1')
>>> registry.register([None, IB0], IR0, '', 'A0')
>>> registry.register([None, IB2], IR0, '', 'A2')
>>> registry.lookupAll((IF1, IB1), IR0).next()[1]
'A1'
>>> registry.lookupAll((IF1, IB2), IR0).next()[1]
'A2'
>>> registry.lookupAll((IF1, IB0), IR0).next()[1]
'A0'
>>> registry.lookupAll((IF1, IB3), IR0).next()[1]
'A2'
>>> registry.lookupAll((IF1, IB4), IR0).next()[1]
'A1'
"""
def test_multi_adapter_w_default():
"""
>>> registry = AdapterRegistry()
>>> registry.register([None, None], IB1, 'bob', 'A0')
>>> registry.lookup((IF1, IR1), IB0, 'bob')
'A0'
>>> registry.register([None, IR0], IB1, 'bob', 'A1')
>>> registry.lookup((IF1, IR1), IB0, 'bob')
'A1'
>>> registry.lookup((IF1, IR1), IB0, 'bruce')
>>> registry.register([None, IR1], IB1, 'bob', 'A2')
>>> registry.lookup((IF1, IR1), IB0, 'bob')
'A2'
"""
def test_multi_adapter_w_inherited_and_multiple_registrations():
"""
>>> registry = AdapterRegistry()
>>> class IX(zope.interface.Interface):
... pass
>>> registry.register([IF0, IR0], IB1, 'bob', 'A1')
>>> registry.register([IF1, IX], IB1, 'bob', 'AX')
>>> registry.lookup((IF1, IR1), IB0, 'bob')
'A1'
"""
def test_named_adapter_with_default():
"""Query a named simple adapter
>>> registry = AdapterRegistry()
If we ask for a named adapter, we won't get a result unless there
is a named adapter, even if the object implements the interface:
>>> registry.lookup([IF1], IF0, 'bob')
>>> registry.register([None], IB1, 'bob', 'A1')
>>> registry.lookup([IF1], IB0, 'bob')
'A1'
>>> registry.lookup([IF1], IB0, 'bruce')
>>> registry.register([None], IB0, 'bob', 'A2')
>>> registry.lookup([IF1], IB0, 'bob')
'A2'
"""
def test_multi_adapter_gets_closest_provided():
"""
>>> registry = AdapterRegistry()
>>> registry.register([IF1, IR0], IB0, 'bob', 'A1')
>>> registry.register((IF1, IR0), IB1, 'bob', 'A2')
>>> registry.lookup((IF1, IR1), IB0, 'bob')
'A1'
>>> registry = AdapterRegistry()
>>> registry.register([IF1, IR0], IB1, 'bob', 'A2')
>>> registry.register([IF1, IR0], IB0, 'bob', 'A1')
>>> registry.lookup([IF1, IR0], IB0, 'bob')
'A1'
>>> registry = AdapterRegistry()
>>> registry.register([IF1, IR0], IB0, 'bob', 'A1')
>>> registry.register([IF1, IR1], IB1, 'bob', 'A2')
>>> registry.lookup([IF1, IR1], IB0, 'bob')
'A2'
>>> registry = AdapterRegistry()
>>> registry.register([IF1, IR1], IB1, 'bob', 2)
>>> registry.register([IF1, IR0], IB0, 'bob', 1)
>>> registry.lookup([IF1, IR1], IB0, 'bob')
2
"""
def test_multi_adapter_check_non_default_dont_hide_default():
"""
>>> registry = AdapterRegistry()
>>> class IX(zope.interface.Interface):
... pass
>>> registry.register([None, IR0], IB0, 'bob', 1)
>>> registry.register([IF1, IX], IB0, 'bob', 2)
>>> registry.lookup([IF1, IR1], IB0, 'bob')
1
"""
def test_adapter_hook_with_factory_producing_None():
"""
>>> registry = AdapterRegistry()
>>> default = object()
>>> class Object1(object):
... zope.interface.implements(IF0)
>>> class Object2(object):
... zope.interface.implements(IF0)
>>> def factory(context):
... if isinstance(context, Object1):
... return 'adapter'
... return None
>>> registry.register([IF0], IB0, '', factory)
>>> registry.adapter_hook(IB0, Object1())
'adapter'
>>> registry.adapter_hook(IB0, Object2()) is None
True
>>> registry.adapter_hook(IB0, Object2(), default=default) is default
True
"""
def test_suite():
from zope.testing.doctestunit import DocFileSuite
return unittest.TestSuite((
DocFileSuite('../adapter.txt', '../human.txt', 'foodforthought.txt',
globs={'__name__': '__main__'}),
doctest.DocTestSuite(),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Tests for advice
This module was adapted from 'protocols.tests.advice', part of the Python
Enterprise Application Kit (PEAK). Please notify the PEAK authors
(pje@telecommunity.com and tsarna@sarna.org) if bugs are found or
Zope-specific changes are required, so that the PEAK version of this module
can be kept in sync.
PEAK is a Python application framework that interoperates with (but does
not require) Zope 3 and Twisted. It provides tools for manipulating UML
models, object-relational persistence, aspect-oriented programming, and more.
Visit the PEAK home page at http://peak.telecommunity.com for more information.
$Id$
"""
from unittest import TestCase, makeSuite, TestSuite
from zope.interface.advice import *
from types import ClassType
import sys
def ping(log, value):
def pong(klass):
log.append((value,klass))
return [klass]
addClassAdvisor(pong)
class ClassicClass:
__metaclass__ = ClassType
classLevelFrameInfo = getFrameInfo(sys._getframe())
class NewStyleClass:
__metaclass__ = type
classLevelFrameInfo = getFrameInfo(sys._getframe())
moduleLevelFrameInfo = getFrameInfo(sys._getframe())
class FrameInfoTest(TestCase):
classLevelFrameInfo = getFrameInfo(sys._getframe())
def checkModuleInfo(self):
kind, module, f_locals, f_globals = moduleLevelFrameInfo
self.assertEquals(kind, "module")
for d in module.__dict__, f_locals, f_globals:
self.assert_(d is globals())
def checkClassicClassInfo(self):
kind, module, f_locals, f_globals = ClassicClass.classLevelFrameInfo
self.assertEquals(kind, "class")
self.assert_(f_locals is ClassicClass.__dict__) # ???
for d in module.__dict__, f_globals:
self.assert_(d is globals())
def checkNewStyleClassInfo(self):
kind, module, f_locals, f_globals = NewStyleClass.classLevelFrameInfo
self.assertEquals(kind, "class")
for d in module.__dict__, f_globals:
self.assert_(d is globals())
def checkCallInfo(self):
kind, module, f_locals, f_globals = getFrameInfo(sys._getframe())
self.assertEquals(kind, "function call")
self.assert_(f_locals is locals()) # ???
for d in module.__dict__, f_globals:
self.assert_(d is globals())
class AdviceTests(TestCase):
def checkOrder(self):
log = []
class Foo(object):
ping(log, 1)
ping(log, 2)
ping(log, 3)
# Strip the list nesting
for i in 1,2,3:
self.assert_(isinstance(Foo, list))
Foo, = Foo
self.assertEquals(log, [(1, Foo), (2, [Foo]), (3, [[Foo]])])
def XXXcheckOutside(self):
# Disabled because the check does not work with doctest tests.
try:
ping([], 1)
except SyntaxError:
pass
else:
raise AssertionError(
"Should have detected advice outside class body"
)
def checkDoubleType(self):
if sys.hexversion >= 0x02030000:
return # you can't duplicate bases in 2.3
class aType(type,type):
ping([],1)
aType, = aType
self.assert_(aType.__class__ is type)
def checkSingleExplicitMeta(self):
class M(type):
pass
class C(M):
__metaclass__ = M
ping([],1)
C, = C
self.assert_(C.__class__ is M)
def checkMixedMetas(self):
class M1(type): pass
class M2(type): pass
class B1: __metaclass__ = M1
class B2: __metaclass__ = M2
try:
class C(B1,B2):
ping([],1)
except TypeError:
pass
else:
raise AssertionError("Should have gotten incompatibility error")
class M3(M1,M2): pass
class C(B1,B2):
__metaclass__ = M3
ping([],1)
self.assert_(isinstance(C,list))
C, = C
self.assert_(isinstance(C,M3))
def checkMetaOfClass(self):
class metameta(type):
pass
class meta(type):
__metaclass__ = metameta
self.assertEquals(determineMetaclass((meta, type)), metameta)
TestClasses = (AdviceTests, FrameInfoTest)
def test_suite():
return TestSuite([makeSuite(t,'check') for t in TestClasses])
if __name__ == '__main__':
unittest.main(defaultTest=test_suite)
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test the new API for making and checking interface declarations
$Id$
"""
import unittest
from zope.interface import *
from zope.testing.doctestunit import DocTestSuite
from zope.interface import Interface
class I1(Interface): pass
class I2(Interface): pass
class I3(Interface): pass
class I4(Interface): pass
class I5(Interface): pass
class A(object):
implements(I1)
class B(object):
implements(I2)
class C(A, B):
implements(I3)
class COnly(A, B):
implementsOnly(I3)
class COnly_old(A, B):
__implemented__ = I3
class D(COnly):
implements(I5)
def test_ObjectSpecification_Simple():
"""
>>> c = C()
>>> directlyProvides(c, I4)
>>> [i.__name__ for i in providedBy(c)]
['I4', 'I3', 'I1', 'I2']
"""
def test_ObjectSpecification_Simple_w_only():
"""
>>> c = COnly()
>>> directlyProvides(c, I4)
>>> [i.__name__ for i in providedBy(c)]
['I4', 'I3']
"""
def test_ObjectSpecification_Simple_old_style():
"""
>>> c = COnly_old()
>>> directlyProvides(c, I4)
>>> [i.__name__ for i in providedBy(c)]
['I4', 'I3']
"""
class Test(unittest.TestCase):
# Note that most of the tests are in the doc strings of the
# declarations module.
def test_backward_compat(self):
class C1(object): __implemented__ = I1
class C2(C1): __implemented__ = I2, I5
class C3(C2): __implemented__ = I3, C2.__implemented__
self.assert_(C3.__implemented__.__class__ is tuple)
self.assertEqual(
[i.getName() for i in providedBy(C3())],
['I3', 'I2', 'I5'],
)
class C4(C3):
implements(I4)
self.assertEqual(
[i.getName() for i in providedBy(C4())],
['I4', 'I3', 'I2', 'I5'],
)
self.assertEqual(
[i.getName() for i in C4.__implemented__],
['I4', 'I3', 'I2', 'I5'],
)
# Note that C3.__implemented__ should now be a sequence of interfaces
self.assertEqual(
[i.getName() for i in C3.__implemented__],
['I3', 'I2', 'I5'],
)
self.failIf(C3.__implemented__.__class__ is tuple)
def test_module(self):
import zope.interface.tests.m1
import zope.interface.tests.m2
directlyProvides(zope.interface.tests.m2,
zope.interface.tests.m1.I1,
zope.interface.tests.m1.I2,
)
self.assertEqual(list(providedBy(zope.interface.tests.m1)),
list(providedBy(zope.interface.tests.m2)),
)
def test_builtins(self):
# Setup
intspec = implementedBy(int)
olddeclared = intspec.declared
classImplements(int, I1)
class myint(int):
implements(I2)
x = 42
self.assertEqual([i.getName() for i in providedBy(x)],
['I1'])
x = myint(42)
directlyProvides(x, I3)
self.assertEqual([i.getName() for i in providedBy(x)],
['I3', 'I2', 'I1'])
# cleanup
intspec.declared = olddeclared
classImplements(int)
x = 42
self.assertEqual([i.getName() for i in providedBy(x)],
[])
def test_signature_w_no_class_interfaces():
"""
>>> from zope.interface import *
>>> class C(object):
... pass
>>> c = C()
>>> list(providedBy(c))
[]
>>> class I(Interface):
... pass
>>> directlyProvides(c, I)
>>> list(providedBy(c)) == list(directlyProvidedBy(c))
1
"""
def test_classImplement_on_deeply_nested_classes():
"""This test is in response to a bug found, which is why it's a bit
contrived
>>> from zope.interface import *
>>> class B1(object):
... pass
>>> class B2(B1):
... pass
>>> class B3(B2):
... pass
>>> class D(object):
... implements()
>>> class S(B3, D):
... implements()
This failed due to a bug in the code for finding __providedBy__
descriptors for old-style classes.
"""
def test_pickle_provides_specs():
"""
>>> from pickle import dumps, loads
>>> a = A()
>>> I2.providedBy(a)
0
>>> directlyProvides(a, I2)
>>> I2.providedBy(a)
1
>>> a2 = loads(dumps(a))
>>> I2.providedBy(a2)
1
"""
def test_that_we_dont_inherit_class_provides():
"""
>>> class X(object):
... classProvides(I1)
>>> class Y(X):
... pass
>>> [i.__name__ for i in X.__provides__]
['I1']
>>> Y.__provides__
Traceback (most recent call last):
...
AttributeError: __provides__
"""
def test_that_we_dont_inherit_provides_optimizations():
"""
When we make a declaration for a class, we install a __provides__
descriptors that provides a default for instances that don't have
instance-specific declarations:
>>> class A(object):
... implements(I1)
>>> class B(object):
... implements(I2)
>>> [i.__name__ for i in A().__provides__]
['I1']
>>> [i.__name__ for i in B().__provides__]
['I2']
But it's important that we don't use this for subclasses without
declarations. This would cause incorrect results:
>>> class X(A, B):
... pass
>>> X().__provides__
Traceback (most recent call last):
...
AttributeError: __provides__
However, if we "induce" a declaration, by calling implementedBy
(even indirectly through providedBy):
>>> [i.__name__ for i in providedBy(X())]
['I1', 'I2']
then the optimization will work:
>>> [i.__name__ for i in X().__provides__]
['I1', 'I2']
"""
def test_classProvides_before_implements():
"""Special descriptor for class __provides__
The descriptor caches the implementedBy info, so that
we can get declarations for objects without instance-specific
interfaces a bit quicker.
For example::
>>> from zope.interface import Interface
>>> class IFooFactory(Interface):
... pass
>>> class IFoo(Interface):
... pass
>>> class C(object):
... classProvides(IFooFactory)
... implements(IFoo)
>>> [i.getName() for i in C.__provides__]
['IFooFactory']
>>> [i.getName() for i in C().__provides__]
['IFoo']
"""
def test_getting_spec_for_proxied_builtin_class():
"""
In general, we should be able to get a spec
for a proxied class if someone has declared or
asked for a spec before.
We don't want to depend on proxies in this (zope.interface)
package, but we do want to work with proxies. Proxies have the
effect that a class's __dict__ cannot be gotten. Further, for
built-in classes, we can't save, and thus, cannot get, any class
attributes. We'll emulate this by treating a plain object as a class:
>>> cls = object()
We'll create an implements specification:
>>> import zope.interface.declarations
>>> impl = zope.interface.declarations.Implements(I1, I2)
Now, we'll emulate a declaration for a built-in type by putting
it in BuiltinImplementationSpecifications:
>>> zope.interface.declarations.BuiltinImplementationSpecifications[
... cls] = impl
Now, we should be able to get it back:
>>> implementedBy(cls) is impl
True
"""
def test_declaration_get():
"""
We can get definitions from a declaration:
>>> import zope.interface
>>> class I1(zope.interface.Interface):
... a11 = zope.interface.Attribute('a11')
... a12 = zope.interface.Attribute('a12')
>>> class I2(zope.interface.Interface):
... a21 = zope.interface.Attribute('a21')
... a22 = zope.interface.Attribute('a22')
... a12 = zope.interface.Attribute('a212')
>>> class I11(I1):
... a11 = zope.interface.Attribute('a111')
>>> decl = Declaration(I11, I2)
>>> decl.get('a11') is I11.get('a11')
True
>>> decl.get('a12') is I1.get('a12')
True
>>> decl.get('a21') is I2.get('a21')
True
>>> decl.get('a22') is I2.get('a22')
True
>>> decl.get('a')
>>> decl.get('a', 42)
42
We get None even with no interfaces:
>>> decl = Declaration()
>>> decl.get('a11')
>>> decl.get('a11', 42)
42
We get new data if e change interface bases:
>>> decl.__bases__ = I11, I2
>>> decl.get('a11') is I11.get('a11')
True
"""
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(Test))
suite.addTest(DocTestSuite("zope.interface.declarations"))
suite.addTest(DocTestSuite())
return suite
if __name__ == '__main__':
unittest.main()
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Documentation tests.
$Id$
"""
from unittest import TestCase, main, makeSuite
from zope.interface import Interface, Attribute
class Test(TestCase):
def testBlech(self):
from zope.interface.document import asStructuredText
self.assertEqual(asStructuredText(I2), '''\
I2
I2 doc
This interface extends:
o _I1
Attributes:
a1 -- no documentation
a2 -- a2 doc
Methods:
f21() -- f21 doc
f22() -- no documentation
f23() -- f23 doc
''')
def test_suite():
return makeSuite(Test)
class _I1(Interface):
def f11(): pass
def f12(): pass
class I2(_I1):
"I2 doc"
a1 = Attribute('a1')
a2 = Attribute('a2', 'a2 doc')
def f21(): "f21 doc"
def f22(): pass
def f23(): "f23 doc"
if __name__=='__main__':
main(defaultTest='test_suite')
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test Element meta-class.
$Id$
"""
import unittest
from zope.interface.interface import Element
class TestElement(unittest.TestCase):
def test_taggedValues(self):
"""Test that we can update tagged values of more than one element
"""
e1 = Element("foo")
e2 = Element("bar")
e1.setTaggedValue("x", 1)
e2.setTaggedValue("x", 2)
self.assertEqual(e1.getTaggedValue("x"), 1)
self.assertEqual(e2.getTaggedValue("x"), 2)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestElement))
return suite
if __name__ == '__main__':
unittest.main(defaultTest=test_suite)
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test Interface implementation
$Id$
"""
import unittest
from zope.testing.doctestunit import DocTestSuite
from zope.interface.tests.unitfixtures import * # hehehe
from zope.interface.exceptions import BrokenImplementation, Invalid
from zope.interface import implementedBy, providedBy
from zope.interface import Interface, directlyProvides, Attribute
class InterfaceTests(unittest.TestCase):
def testInterfaceSetOnAttributes(self):
self.assertEqual(FooInterface['foobar'].interface,
FooInterface)
self.assertEqual(FooInterface['aMethod'].interface,
FooInterface)
def testClassImplements(self):
self.assert_(IC.implementedBy(C))
self.assert_(I1.implementedBy(A))
self.assert_(I1.implementedBy(B))
self.assert_(not I1.implementedBy(C))
self.assert_(I1.implementedBy(D))
self.assert_(I1.implementedBy(E))
self.assert_(not I2.implementedBy(A))
self.assert_(I2.implementedBy(B))
self.assert_(not I2.implementedBy(C))
# No longer after interfacegeddon
# self.assert_(not I2.implementedBy(D))
self.assert_(not I2.implementedBy(E))
def testUtil(self):
self.assert_(IC in implementedBy(C))
self.assert_(I1 in implementedBy(A))
self.assert_(not I1 in implementedBy(C))
self.assert_(I2 in implementedBy(B))
self.assert_(not I2 in implementedBy(C))
self.assert_(IC in providedBy(C()))
self.assert_(I1 in providedBy(A()))
self.assert_(not I1 in providedBy(C()))
self.assert_(I2 in providedBy(B()))
self.assert_(not I2 in providedBy(C()))
def testObjectImplements(self):
self.assert_(IC.providedBy(C()))
self.assert_(I1.providedBy(A()))
self.assert_(I1.providedBy(B()))
self.assert_(not I1.providedBy(C()))
self.assert_(I1.providedBy(D()))
self.assert_(I1.providedBy(E()))
self.assert_(not I2.providedBy(A()))
self.assert_(I2.providedBy(B()))
self.assert_(not I2.providedBy(C()))
# Not after interface geddon
# self.assert_(not I2.providedBy(D()))
self.assert_(not I2.providedBy(E()))
def testDeferredClass(self):
a = A()
self.assertRaises(BrokenImplementation, a.ma)
def testInterfaceExtendsInterface(self):
self.assert_(BazInterface.extends(BobInterface))
self.assert_(BazInterface.extends(BarInterface))
self.assert_(BazInterface.extends(FunInterface))
self.assert_(not BobInterface.extends(FunInterface))
self.assert_(not BobInterface.extends(BarInterface))
self.assert_(BarInterface.extends(FunInterface))
self.assert_(not BarInterface.extends(BazInterface))
def testVerifyImplementation(self):
from zope.interface.verify import verifyClass
self.assert_(verifyClass(FooInterface, Foo))
self.assert_(Interface.providedBy(I1))
def test_names(self):
names = list(_I2.names()); names.sort()
self.assertEqual(names, ['f21', 'f22', 'f23'])
names = list(_I2.names(all=True)); names.sort()
self.assertEqual(names, ['a1', 'f11', 'f12', 'f21', 'f22', 'f23'])
def test_namesAndDescriptions(self):
names = [nd[0] for nd in _I2.namesAndDescriptions()]; names.sort()
self.assertEqual(names, ['f21', 'f22', 'f23'])
names = [nd[0] for nd in _I2.namesAndDescriptions(1)]; names.sort()
self.assertEqual(names, ['a1', 'f11', 'f12', 'f21', 'f22', 'f23'])
for name, d in _I2.namesAndDescriptions(1):
self.assertEqual(name, d.__name__)
def test_getDescriptionFor(self):
self.assertEqual(_I2.getDescriptionFor('f11').__name__, 'f11')
self.assertEqual(_I2.getDescriptionFor('f22').__name__, 'f22')
self.assertEqual(_I2.queryDescriptionFor('f33', self), self)
self.assertRaises(KeyError, _I2.getDescriptionFor, 'f33')
def test___getitem__(self):
self.assertEqual(_I2['f11'].__name__, 'f11')
self.assertEqual(_I2['f22'].__name__, 'f22')
self.assertEqual(_I2.get('f33', self), self)
self.assertRaises(KeyError, _I2.__getitem__, 'f33')
def test___contains__(self):
self.failUnless('f11' in _I2)
self.failIf('f33' in _I2)
def test___iter__(self):
names = list(iter(_I2))
names.sort()
self.assertEqual(names, ['a1', 'f11', 'f12', 'f21', 'f22', 'f23'])
def testAttr(self):
description = _I2.getDescriptionFor('a1')
self.assertEqual(description.__name__, 'a1')
self.assertEqual(description.__doc__, 'This is an attribute')
def testFunctionAttributes(self):
# Make sure function attributes become tagged values.
meth = _I1['f12']
self.assertEqual(meth.getTaggedValue('optional'), 1)
def testInvariant(self):
# set up
o = InvariantC()
directlyProvides(o, IInvariant)
# a helper
def errorsEqual(self, o, error_len, error_msgs, interface=None):
if interface is None:
interface = IInvariant
self.assertRaises(Invalid, interface.validateInvariants, o)
e = []
try:
interface.validateInvariants(o, e)
except Invalid, error:
self.assertEquals(error.args[0], e)
else:
self._assert(0) # validateInvariants should always raise
# Invalid
self.assertEquals(len(e), error_len)
msgs = [error.args[0] for error in e]
msgs.sort()
for msg in msgs:
self.assertEquals(msg, error_msgs.pop(0))
# the tests
self.assertEquals(IInvariant.getTaggedValue('invariants'),
[ifFooThenBar])
self.assertEquals(IInvariant.validateInvariants(o), None)
o.bar = 27
self.assertEquals(IInvariant.validateInvariants(o), None)
o.foo = 42
self.assertEquals(IInvariant.validateInvariants(o), None)
del o.bar
errorsEqual(self, o, 1, ['If Foo, then Bar!'])
# nested interfaces with invariants:
self.assertEquals(ISubInvariant.getTaggedValue('invariants'),
[BarGreaterThanFoo])
o = InvariantC()
directlyProvides(o, ISubInvariant)
o.foo = 42
# even though the interface has changed, we should still only have one
# error.
errorsEqual(self, o, 1, ['If Foo, then Bar!'], ISubInvariant)
# however, if we set foo to 0 (Boolean False) and bar to a negative
# number then we'll get the new error
o.foo = 2
o.bar = 1
errorsEqual(self, o, 1, ['Please, Boo MUST be greater than Foo!'],
ISubInvariant)
# and if we set foo to a positive number and boo to 0, we'll
# get both errors!
o.foo = 1
o.bar = 0
errorsEqual(self, o, 2, ['If Foo, then Bar!',
'Please, Boo MUST be greater than Foo!'],
ISubInvariant)
# for a happy ending, we'll make the invariants happy
o.foo = 1
o.bar = 2
self.assertEquals(IInvariant.validateInvariants(o), None) # woohoo
# now we'll do two invariants on the same interface,
# just to make sure that a small
# multi-invariant interface is at least minimally tested.
o = InvariantC()
directlyProvides(o, IInvariant)
o.foo = 42
old_invariants = IInvariant.getTaggedValue('invariants')
invariants = old_invariants[:]
invariants.append(BarGreaterThanFoo) # if you really need to mutate,
# then this would be the way to do it. Probably a bad idea, though. :-)
IInvariant.setTaggedValue('invariants', invariants)
#
# even though the interface has changed, we should still only have one
# error.
errorsEqual(self, o, 1, ['If Foo, then Bar!'])
# however, if we set foo to 0 (Boolean False) and bar to a negative
# number then we'll get the new error
o.foo = 2
o.bar = 1
errorsEqual(self, o, 1, ['Please, Boo MUST be greater than Foo!'])
# and if we set foo to a positive number and boo to 0, we'll
# get both errors!
o.foo = 1
o.bar = 0
errorsEqual(self, o, 2, ['If Foo, then Bar!',
'Please, Boo MUST be greater than Foo!'])
# for another happy ending, we'll make the invariants happy again
o.foo = 1
o.bar = 2
self.assertEquals(IInvariant.validateInvariants(o), None) # bliss
# clean up
IInvariant.setTaggedValue('invariants', old_invariants)
def test___doc___element(self):
class I(Interface):
"xxx"
self.assertEqual(I.__doc__, "xxx")
self.assertEqual(list(I), [])
class I(Interface):
"xxx"
__doc__ = Attribute('the doc')
self.assertEqual(I.__doc__, "")
self.assertEqual(list(I), ['__doc__'])
def testIssue228(self):
# Test for http://collector.zope.org/Zope3-dev/228
class I(Interface):
"xxx"
class Bad:
__providedBy__ = None
# Old style classes don't have a '__class__' attribute
self.failUnlessRaises(AttributeError, I.providedBy, Bad)
class _I1(Interface):
a1 = Attribute("This is an attribute")
def f11(): pass
def f12(): pass
f12.optional = 1
class _I1_(_I1): pass
class _I1__(_I1_): pass
class _I2(_I1__):
def f21(): pass
def f22(): pass
f23 = f22
def test_suite():
from zope.testing import doctest
suite = unittest.makeSuite(InterfaceTests)
suite.addTest(doctest.DocTestSuite("zope.interface.interface"))
suite.addTest(doctest.DocFileSuite(
'../README.txt',
globs={'__name__': '__main__'},
optionflags=doctest.NORMALIZE_WHITESPACE,
))
return suite
def main():
unittest.TextTestRunner().run(test_suite())
if __name__=="__main__":
main()
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test interface declarations against ExtensionClass-like classes.
These tests are to make sure we do something sane in the presense of
classic ExtensionClass classes and instances.
$Id$
"""
import unittest, odd
from zope.interface import Interface, implements, implementsOnly
from zope.interface import directlyProvides, providedBy, directlyProvidedBy
from zope.interface import classImplements, classImplementsOnly, implementedBy
class I1(Interface): pass
class I2(Interface): pass
class I3(Interface): pass
class I31(I3): pass
class I4(Interface): pass
class I5(Interface): pass
class Odd(object): __metaclass__ = odd.MetaClass
class B(Odd): __implemented__ = I2
# TODO: We are going to need more magic to make classProvides work with odd
# classes. This will work in the next iteration. For now, we'll use
# a different mechanism.
# from zope.interface import classProvides
class A(Odd):
implements(I1)
class C(A, B):
implements(I31)
class Test(unittest.TestCase):
def test_ObjectSpecification(self):
c = C()
directlyProvides(c, I4)
self.assertEqual([i.getName() for i in providedBy(c)],
['I4', 'I31', 'I1', 'I2']
)
self.assertEqual([i.getName() for i in providedBy(c).flattened()],
['I4', 'I31', 'I3', 'I1', 'I2', 'Interface']
)
self.assert_(I1 in providedBy(c))
self.failIf(I3 in providedBy(c))
self.assert_(providedBy(c).extends(I3))
self.assert_(providedBy(c).extends(I31))
self.failIf(providedBy(c).extends(I5))
class COnly(A, B):
implementsOnly(I31)
class D(COnly):
implements(I5)
classImplements(D, I5)
c = D()
directlyProvides(c, I4)
self.assertEqual([i.getName() for i in providedBy(c)],
['I4', 'I5', 'I31'])
self.assertEqual([i.getName() for i in providedBy(c).flattened()],
['I4', 'I5', 'I31', 'I3', 'Interface'])
self.failIf(I1 in providedBy(c))
self.failIf(I3 in providedBy(c))
self.assert_(providedBy(c).extends(I3))
self.failIf(providedBy(c).extends(I1))
self.assert_(providedBy(c).extends(I31))
self.assert_(providedBy(c).extends(I5))
class COnly(A, B): __implemented__ = I31
class D(COnly):
implements(I5)
classImplements(D, I5)
c = D()
directlyProvides(c, I4)
self.assertEqual([i.getName() for i in providedBy(c)],
['I4', 'I5', 'I31'])
self.assertEqual([i.getName() for i in providedBy(c).flattened()],
['I4', 'I5', 'I31', 'I3', 'Interface'])
self.failIf(I1 in providedBy(c))
self.failIf(I3 in providedBy(c))
self.assert_(providedBy(c).extends(I3))
self.failIf(providedBy(c).extends(I1))
self.assert_(providedBy(c).extends(I31))
self.assert_(providedBy(c).extends(I5))
def test_classImplements(self):
class A(Odd):
implements(I3)
class B(Odd):
implements(I4)
class C(A, B):
pass
classImplements(C, I1, I2)
self.assertEqual([i.getName() for i in implementedBy(C)],
['I1', 'I2', 'I3', 'I4'])
classImplements(C, I5)
self.assertEqual([i.getName() for i in implementedBy(C)],
['I1', 'I2', 'I5', 'I3', 'I4'])
def test_classImplementsOnly(self):
class A(Odd):
implements(I3)
class B(Odd):
implements(I4)
class C(A, B):
pass
classImplementsOnly(C, I1, I2)
self.assertEqual([i.__name__ for i in implementedBy(C)],
['I1', 'I2'])
def test_directlyProvides(self):
class IA1(Interface): pass
class IA2(Interface): pass
class IB(Interface): pass
class IC(Interface): pass
class A(Odd):
implements(IA1, IA2)
class B(Odd):
implements(IB)
class C(A, B):
implements(IC)
ob = C()
directlyProvides(ob, I1, I2)
self.assert_(I1 in providedBy(ob))
self.assert_(I2 in providedBy(ob))
self.assert_(IA1 in providedBy(ob))
self.assert_(IA2 in providedBy(ob))
self.assert_(IB in providedBy(ob))
self.assert_(IC in providedBy(ob))
directlyProvides(ob, directlyProvidedBy(ob)-I2)
self.assert_(I1 in providedBy(ob))
self.failIf(I2 in providedBy(ob))
self.failIf(I2 in providedBy(ob))
directlyProvides(ob, directlyProvidedBy(ob), I2)
self.assert_(I2 in providedBy(ob))
def test_directlyProvides_fails_for_odd_class(self):
self.assertRaises(TypeError, directlyProvides, C, I5)
# XXX see above
def XXX_test_classProvides_fails_for_odd_class(self):
try:
class A(Odd):
classProvides(I1)
except TypeError:
pass # Sucess
self.assert_(False,
"Shouldn't be able to use directlyProvides on odd class."
)
def test_implementedBy(self):
class I2(I1): pass
class C1(Odd):
implements(I2)
class C2(C1):
implements(I3)
self.assertEqual([i.getName() for i in implementedBy(C2)],
['I3', 'I2'])
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(Test))
return suite
if __name__ == '__main__':
unittest.main()
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test interface sorting
$Id$
"""
from unittest import TestCase, TestSuite, main, makeSuite
from zope.interface import Interface
class I1(Interface): pass
class I2(I1): pass
class I3(I1): pass
class I4(Interface): pass
class I5(I4): pass
class I6(I2): pass
class Test(TestCase):
def test(self):
l = [I1, I3, I5, I6, I4, I2]
l.sort()
self.assertEqual(l, [I1, I2, I3, I4, I5, I6])
def test_w_None(self):
l = [I1, None, I3, I5, None, I6, I4, I2]
l.sort()
self.assertEqual(l, [I1, I2, I3, I4, I5, I6, None, None])
def test_suite():
return TestSuite((
makeSuite(Test),
))
if __name__=='__main__':
main(defaultTest='test_suite')
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Interface Verify tests
$Id$
"""
from zope.interface import Interface, implements, classImplements, Attribute
from zope.interface.verify import verifyClass, verifyObject
from zope.interface.exceptions import DoesNotImplement, BrokenImplementation
from zope.interface.exceptions import BrokenMethodImplementation
import unittest
class Test(unittest.TestCase):
def testNotImplemented(self):
class C(object): pass
class I(Interface): pass
self.assertRaises(DoesNotImplement, verifyClass, I, C)
classImplements(C, I)
verifyClass(I, C)
def testMissingAttr(self):
class I(Interface):
def f(): pass
class C(object):
implements(I)
self.assertRaises(BrokenImplementation, verifyClass, I, C)
C.f=lambda self: None
verifyClass(I, C)
def testMissingAttr_with_Extended_Interface(self):
class II(Interface):
def f():
pass
class I(II):
pass
class C(object):
implements(I)
self.assertRaises(BrokenImplementation, verifyClass, I, C)
C.f=lambda self: None
verifyClass(I, C)
def testWrongArgs(self):
class I(Interface):
def f(a): pass
class C(object):
def f(self, b): pass
implements(I)
# We no longer require names to match.
#self.assertRaises(BrokenMethodImplementation, verifyClass, I, C)
C.f=lambda self, a: None
verifyClass(I, C)
C.f=lambda self, **kw: None
self.assertRaises(BrokenMethodImplementation, verifyClass, I, C)
C.f=lambda self, a, *args: None
verifyClass(I, C)
C.f=lambda self, a, *args, **kw: None
verifyClass(I, C)
C.f=lambda self, *args: None
verifyClass(I, C)
def testExtraArgs(self):
class I(Interface):
def f(a): pass
class C(object):
def f(self, a, b): pass
implements(I)
self.assertRaises(BrokenMethodImplementation, verifyClass, I, C)
C.f=lambda self, a: None
verifyClass(I, C)
C.f=lambda self, a, b=None: None
verifyClass(I, C)
def testNoVar(self):
class I(Interface):
def f(a, *args): pass
class C(object):
def f(self, a): pass
implements(I)
self.assertRaises(BrokenMethodImplementation, verifyClass, I, C)
C.f=lambda self, a, *foo: None
verifyClass(I, C)
def testNoKW(self):
class I(Interface):
def f(a, **args): pass
class C(object):
def f(self, a): pass
implements(I)
self.assertRaises(BrokenMethodImplementation, verifyClass, I, C)
C.f=lambda self, a, **foo: None
verifyClass(I, C)
def testModule(self):
from zope.interface.tests.ifoo import IFoo
from zope.interface.tests import dummy
verifyObject(IFoo, dummy)
def testMethodForAttr(self):
class IFoo(Interface):
foo = Attribute("The foo Attribute")
class Foo:
implements(IFoo)
def foo(self):
pass
verifyClass(IFoo, Foo)
def testNonMethodForMethod(self):
class IBar(Interface):
def foo():
pass
class Bar:
implements(IBar)
foo = 1
self.assertRaises(BrokenMethodImplementation, verifyClass, IBar, Bar)
def test_suite():
loader=unittest.TestLoader()
return loader.loadTestsFromTestCase(Test)
if __name__=='__main__':
unittest.TextTestRunner().run(test_suite())
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Unit Test Fixtures
$Id$
"""
from zope.interface import Interface, invariant
from zope.interface.interface import Attribute
from zope.interface.exceptions import Invalid
class mytest(Interface):
pass
class C(object):
def m1(self, a, b):
"return 1"
return 1
def m2(self, a, b):
"return 2"
return 2
# testInstancesOfClassImplements
# YAGNI IC=Interface.impliedInterface(C)
class IC(Interface):
def m1(a, b):
"return 1"
def m2(a, b):
"return 2"
C.__implemented__=IC
class I1(Interface):
def ma():
"blah"
class I2(I1): pass
class I3(Interface): pass
class I4(Interface): pass
class A(I1.deferred()):
__implemented__=I1
class B(object):
__implemented__=I2, I3
class D(A, B): pass
class E(A, B):
__implemented__ = A.__implemented__, C.__implemented__
class FooInterface(Interface):
""" This is an Abstract Base Class """
foobar = Attribute("fuzzed over beyond all recognition")
def aMethod(foo, bar, bingo):
""" This is aMethod """
def anotherMethod(foo=6, bar="where you get sloshed", bingo=(1,3,)):
""" This is anotherMethod """
def wammy(zip, *argues):
""" yadda yadda """
def useless(**keywords):
""" useless code is fun! """
class Foo(object):
""" A concrete class """
__implemented__ = FooInterface,
foobar = "yeah"
def aMethod(self, foo, bar, bingo):
""" This is aMethod """
return "barf!"
def anotherMethod(self, foo=6, bar="where you get sloshed", bingo=(1,3,)):
""" This is anotherMethod """
return "barf!"
def wammy(self, zip, *argues):
""" yadda yadda """
return "barf!"
def useless(self, **keywords):
""" useless code is fun! """
return "barf!"
foo_instance = Foo()
class Blah(object):
pass
new = Interface.__class__
FunInterface = new('FunInterface')
BarInterface = new('BarInterface', [FunInterface])
BobInterface = new('BobInterface')
BazInterface = new('BazInterface', [BobInterface, BarInterface])
# fixtures for invariant tests
def ifFooThenBar(obj):
if getattr(obj, 'foo', None) and not getattr(obj, 'bar', None):
raise Invalid('If Foo, then Bar!')
class IInvariant(Interface):
foo = Attribute('foo')
bar = Attribute('bar; must eval to Boolean True if foo does')
invariant(ifFooThenBar)
def BarGreaterThanFoo(obj):
foo = getattr(obj, 'foo', None)
bar = getattr(obj, 'bar', None)
if foo is not None and isinstance(foo, type(bar)):
# type checking should be handled elsewhere (like, say,
# schema); these invariants should be intra-interface
# constraints. This is a hacky way to do it, maybe, but you
# get the idea
if not bar > foo:
raise Invalid('Please, Boo MUST be greater than Foo!')
class ISubInvariant(IInvariant):
invariant(BarGreaterThanFoo)
class InvariantC(object):
pass
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Verify interface implementations
$Id$
"""
from zope.interface.exceptions import BrokenImplementation, DoesNotImplement
from zope.interface.exceptions import BrokenMethodImplementation
from types import FunctionType, MethodType
from zope.interface.interface import fromMethod, fromFunction, Method
# This will be monkey-patched when running under Zope 2, so leave this
# here:
MethodTypes = (MethodType, )
def _verify(iface, candidate, tentative=0, vtype=None):
"""Verify that 'candidate' might correctly implements 'iface'.
This involves:
o Making sure the candidate defines all the necessary methods
o Making sure the methods have the correct signature
o Making sure the candidate asserts that it implements the interface
Note that this isn't the same as verifying that the class does
implement the interface.
If optional tentative is true, suppress the "is implemented by" test.
"""
if vtype == 'c':
tester = iface.implementedBy
else:
tester = iface.providedBy
if not tentative and not tester(candidate):
raise DoesNotImplement(iface)
# Here the `desc` is either an `Attribute` or `Method` instance
for name, desc in iface.namesAndDescriptions(1):
if not hasattr(candidate, name):
if (not isinstance(desc, Method)) and vtype == 'c':
# We can't verify non-methods on classes, since the
# class may provide attrs in it's __init__.
continue
raise BrokenImplementation(iface, name)
attr = getattr(candidate, name)
if not isinstance(desc, Method):
# If it's not a method, there's nothing else we can test
continue
if isinstance(attr, FunctionType):
# should never get here, since classes should not provide functions
meth = fromFunction(attr, iface, name=name)
elif (isinstance(attr, MethodTypes)
and type(attr.im_func) is FunctionType):
meth = fromMethod(attr, iface, name)
else:
if not callable(attr):
raise BrokenMethodImplementation(name, "Not a method")
# sigh, it's callable, but we don't know how to intrspect it, so
# we have to give it a pass.
continue
# Make sure that the required and implemented method signatures are
# the same.
desc = desc.getSignatureInfo()
meth = meth.getSignatureInfo()
mess = _incompat(desc, meth)
if mess:
raise BrokenMethodImplementation(name, mess)
return True
def verifyClass(iface, candidate, tentative=0):
return _verify(iface, candidate, tentative, vtype='c')
def verifyObject(iface, candidate, tentative=0):
return _verify(iface, candidate, tentative, vtype='o')
def _incompat(required, implemented):
#if (required['positional'] !=
# implemented['positional'][:len(required['positional'])]
# and implemented['kwargs'] is None):
# return 'imlementation has different argument names'
if len(implemented['required']) > len(required['required']):
return 'implementation requires too many arguments'
if ((len(implemented['positional']) < len(required['positional']))
and not implemented['varargs']):
return "implementation doesn't allow enough arguments"
if required['kwargs'] and not implemented['kwargs']:
return "implementation doesn't support keyword arguments"
if required['varargs'] and not implemented['varargs']:
return "implementation doesn't support variable arguments"
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Set up testing environment
$Id$
"""
import os
def patchTracebackModule():
"""Use the ExceptionFormatter to show more info in tracebacks.
"""
from zope.exceptions.exceptionformatter import format_exception
import traceback
traceback.format_exception = format_exception
# Don't use the new exception formatter by default, since it
# doesn't show filenames.
if os.environ.get('NEW_ZOPE_EXCEPTION_FORMATTER', 0):
patchTracebackModule()
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Provide a standard cleanup registry
Unit tests that change global data should include the CleanUp base
class, which provides simpler setUp and tearDown methods that call
global-data cleanup routines::
class Test(CleanUp, unittest.TestCase):
....
If custom setUp or tearDown are needed, then the base routines should
be called, as in::
def tearDown(self):
super(Test, self).tearDown()
....
Cleanup routines for global data should be registered by passing them to
addCleanup::
addCleanUp(pigRegistry._clear)
$Id$
"""
_cleanups = []
def addCleanUp(func, args=(), kw={}):
"""Register a cleanup routines
Pass a function to be called to cleanup global data.
Optional argument tuple and keyword arguments may be passed.
"""
_cleanups.append((func, args, kw))
class CleanUp(object):
"""Mix-in class providing clean-up setUp and tearDown routines."""
def cleanUp(self):
"""Clean up global data."""
for func, args, kw in _cleanups:
func(*args, **kw)
setUp = tearDown = cleanUp
# Module doctest.
# Released to the public domain 16-Jan-2001, by Tim Peters (tim@python.org).
# Major enhancements and refactoring by:
# Jim Fulton
# Edward Loper
# Provided as-is; use at your own risk; no warranty; no promises; enjoy!
r"""Module doctest -- a framework for running examples in docstrings.
NORMAL USAGE
In simplest use, end each module M to be tested with:
def _test():
import doctest
return doctest.testmod()
if __name__ == "__main__":
_test()
Then running the module as a script will cause the examples in the
docstrings to get executed and verified:
python M.py
This won't display anything unless an example fails, in which case the
failing example(s) and the cause(s) of the failure(s) are printed to stdout
(why not stderr? because stderr is a lame hack <0.2 wink>), and the final
line of output is "Test failed.".
Run it with the -v switch instead:
python M.py -v
and a detailed report of all examples tried is printed to stdout, along
with assorted summaries at the end.
You can force verbose mode by passing "verbose=True" to testmod, or prohibit
it by passing "verbose=False". In either of those cases, sys.argv is not
examined by testmod.
In any case, testmod returns a 2-tuple of ints (f, t), where f is the
number of docstring examples that failed and t is the total number of
docstring examples attempted.
There are a variety of other ways to run doctests, including integration
with the unittest framework, and support for running non-Python text
files containing doctests. There are also many ways to override parts
of doctest's default behaviors. See the Library Reference Manual for
details.
WHICH DOCSTRINGS ARE EXAMINED?
+ M.__doc__.
+ f.__doc__ for all functions f in M.__dict__.values(), except those
defined in other modules.
+ C.__doc__ for all classes C in M.__dict__.values(), except those
defined in other modules.
+ If M.__test__ exists and "is true", it must be a dict, and
each entry maps a (string) name to a function object, class object, or
string. Function and class object docstrings found from M.__test__
are searched, and strings are searched directly as if they were docstrings.
In output, a key K in M.__test__ appears with name
<name of M>.__test__.K
Any classes found are recursively searched similarly, to test docstrings in
their contained methods and nested classes.
WHAT'S THE EXECUTION CONTEXT?
By default, each time testmod finds a docstring to test, it uses a *copy*
of M's globals (so that running tests on a module doesn't change the
module's real globals, and so that one test in M can't leave behind crumbs
that accidentally allow another test to work). This means examples can
freely use any names defined at top-level in M. It also means that sloppy
imports (see above) can cause examples in external docstrings to use
globals inappropriate for them.
You can force use of your own dict as the execution context by passing
"globs=your_dict" to testmod instead. Presumably this would be a copy of
M.__dict__ merged with the globals from other imported modules.
WHAT ABOUT EXCEPTIONS?
No problem, as long as the only output generated by the example is the
traceback itself. For example:
>>> [1, 2, 3].remove(42)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ValueError: list.remove(x): x not in list
>>>
Note that only the exception type and value are compared.
SO WHAT DOES A DOCTEST EXAMPLE LOOK LIKE ALREADY!?
Oh ya. It's easy! In most cases a copy-and-paste of an interactive
console session works fine -- just make sure the leading whitespace is
rigidly consistent (you can mix tabs and spaces if you're too lazy to do it
right, but doctest is not in the business of guessing what you think a tab
means).
>>> # comments are ignored
>>> x = 12
>>> x
12
>>> if x == 13:
... print "yes"
... else:
... print "no"
... print "NO"
... print "NO!!!"
...
no
NO
NO!!!
>>>
Any expected output must immediately follow the final ">>>" or "..." line
containing the code, and the expected output (if any) extends to the next
">>>" or all-whitespace line. That's it.
Bummers:
+ Output to stdout is captured, but not output to stderr (exception
tracebacks are captured via a different means).
+ If you continue a line via backslashing in an interactive session,
or for any other reason use a backslash, you should use a raw
docstring, which will preserve your backslahses exactly as you type
them:
>>> def f(x):
... r'''Backslashes in a raw docstring: m\n'''
>>> print f.__doc__
Backslashes in a raw docstring: m\n
Otherwise, the backslash will be interpreted as part of the string.
E.g., the "\n" above would be interpreted as a newline character.
Alternatively, you can double each backslash in the doctest version
(and not use a raw string):
>>> def f(x):
... '''Backslashes in a raw docstring: m\\n'''
>>> print f.__doc__
Backslashes in a raw docstring: m\n
The starting column doesn't matter:
>>> assert "Easy!"
>>> import math
>>> math.floor(1.9)
1.0
and as many leading whitespace characters are stripped from the expected
output as appeared in the initial ">>>" line that triggered it.
If you execute this very file, the examples above will be found and
executed.
"""
__docformat__ = 'reStructuredText en'
__all__ = [
# 0, Option Flags
'register_optionflag',
'DONT_ACCEPT_TRUE_FOR_1',
'DONT_ACCEPT_BLANKLINE',
'NORMALIZE_WHITESPACE',
'ELLIPSIS',
'REPORT_UDIFF',
'REPORT_CDIFF',
'REPORT_NDIFF',
'REPORT_ONLY_FIRST_FAILURE',
# 1. Utility Functions
'is_private',
# 2. Example & DocTest
'Example',
'DocTest',
# 3. Doctest Parser
'DocTestParser',
# 4. Doctest Finder
'DocTestFinder',
# 5. Doctest Runner
'DocTestRunner',
'OutputChecker',
'DocTestFailure',
'UnexpectedException',
'DebugRunner',
# 6. Test Functions
'testmod',
'run_docstring_examples',
# 7. Tester
'Tester',
# 8. Unittest Support
'DocTestCase',
'DocTestSuite',
'DocFileCase',
'DocFileTest',
'DocFileSuite',
# 9. Debugging Support
'script_from_examples',
'testsource',
'debug_src',
'debug_script',
'debug',
]
import __future__
import sys, traceback, inspect, linecache, os, re, types
import unittest, difflib, pdb, tempfile
import warnings
from StringIO import StringIO
# Don't whine about the deprecated is_private function in this
# module's tests.
warnings.filterwarnings("ignore", "is_private", DeprecationWarning,
__name__, 0)
real_pdb_set_trace = pdb.set_trace
# There are 4 basic classes:
# - Example: a <source, want> pair, plus an intra-docstring line number.
# - DocTest: a collection of examples, parsed from a docstring, plus
# info about where the docstring came from (name, filename, lineno).
# - DocTestFinder: extracts DocTests from a given object's docstring and
# its contained objects' docstrings.
# - DocTestRunner: runs DocTest cases, and accumulates statistics.
#
# So the basic picture is:
#
# list of:
# +------+ +---------+ +-------+
# |object| --DocTestFinder-> | DocTest | --DocTestRunner-> |results|
# +------+ +---------+ +-------+
# | Example |
# | ... |
# | Example |
# +---------+
# Option constants.
OPTIONFLAGS_BY_NAME = {}
def register_optionflag(name):
flag = 1 << len(OPTIONFLAGS_BY_NAME)
OPTIONFLAGS_BY_NAME[name] = flag
return flag
DONT_ACCEPT_TRUE_FOR_1 = register_optionflag('DONT_ACCEPT_TRUE_FOR_1')
DONT_ACCEPT_BLANKLINE = register_optionflag('DONT_ACCEPT_BLANKLINE')
NORMALIZE_WHITESPACE = register_optionflag('NORMALIZE_WHITESPACE')
ELLIPSIS = register_optionflag('ELLIPSIS')
REPORT_UDIFF = register_optionflag('REPORT_UDIFF')
REPORT_CDIFF = register_optionflag('REPORT_CDIFF')
REPORT_NDIFF = register_optionflag('REPORT_NDIFF')
REPORT_ONLY_FIRST_FAILURE = register_optionflag('REPORT_ONLY_FIRST_FAILURE')
# Special string markers for use in `want` strings:
BLANKLINE_MARKER = '<BLANKLINE>'
ELLIPSIS_MARKER = '...'
######################################################################
## Table of Contents
######################################################################
# 1. Utility Functions
# 2. Example & DocTest -- store test cases
# 3. DocTest Parser -- extracts examples from strings
# 4. DocTest Finder -- extracts test cases from objects
# 5. DocTest Runner -- runs test cases
# 6. Test Functions -- convenient wrappers for testing
# 7. Tester Class -- for backwards compatibility
# 8. Unittest Support
# 9. Debugging Support
# 10. Example Usage
######################################################################
## 1. Utility Functions
######################################################################
def is_private(prefix, base):
"""prefix, base -> true iff name prefix + "." + base is "private".
Prefix may be an empty string, and base does not contain a period.
Prefix is ignored (although functions you write conforming to this
protocol may make use of it).
Return true iff base begins with an (at least one) underscore, but
does not both begin and end with (at least) two underscores.
>>> is_private("a.b", "my_func")
False
>>> is_private("____", "_my_func")
True
>>> is_private("someclass", "__init__")
False
>>> is_private("sometypo", "__init_")
True
>>> is_private("x.y.z", "_")
True
>>> is_private("_x.y.z", "__")
False
>>> is_private("", "") # senseless but consistent
False
"""
warnings.warn("is_private is deprecated; it wasn't useful; "
"examine DocTestFinder.find() lists instead",
DeprecationWarning, stacklevel=2)
return base[:1] == "_" and not base[:2] == "__" == base[-2:]
def _extract_future_flags(globs):
"""
Return the compiler-flags associated with the future features that
have been imported into the given namespace (globs).
"""
flags = 0
for fname in __future__.all_feature_names:
feature = globs.get(fname, None)
if feature is getattr(__future__, fname):
flags |= feature.compiler_flag
return flags
def _normalize_module(module, depth=2):
"""
Return the module specified by `module`. In particular:
- If `module` is a module, then return module.
- If `module` is a string, then import and return the
module with that name.
- If `module` is None, then return the calling module.
The calling module is assumed to be the module of
the stack frame at the given depth in the call stack.
"""
if inspect.ismodule(module):
return module
elif isinstance(module, (str, unicode)):
return __import__(module, globals(), locals(), ["*"])
elif module is None:
return sys.modules[sys._getframe(depth).f_globals['__name__']]
else:
raise TypeError("Expected a module, string, or None")
def _indent(s, indent=4):
"""
Add the given number of space characters to the beginning every
non-blank line in `s`, and return the result.
"""
# This regexp matches the start of non-blank lines:
return re.sub('(?m)^(?!$)', indent*' ', s)
def _exception_traceback(exc_info):
"""
Return a string containing a traceback message for the given
exc_info tuple (as returned by sys.exc_info()).
"""
# Get a traceback message.
excout = StringIO()
exc_type, exc_val, exc_tb = exc_info
traceback.print_exception(exc_type, exc_val, exc_tb, file=excout)
return excout.getvalue()
# Override some StringIO methods.
class _SpoofOut(StringIO):
def getvalue(self):
result = StringIO.getvalue(self)
# If anything at all was written, make sure there's a trailing
# newline. There's no way for the expected output to indicate
# that a trailing newline is missing.
if result and not result.endswith("\n"):
result += "\n"
# Prevent softspace from screwing up the next test case, in
# case they used print with a trailing comma in an example.
if hasattr(self, "softspace"):
del self.softspace
return result
def truncate(self, size=None):
StringIO.truncate(self, size)
if hasattr(self, "softspace"):
del self.softspace
# Worst-case linear-time ellipsis matching.
def _ellipsis_match(want, got):
"""
Essentially the only subtle case:
>>> _ellipsis_match('aa...aa', 'aaa')
False
"""
if ELLIPSIS_MARKER not in want:
return want == got
# Find "the real" strings.
ws = want.split(ELLIPSIS_MARKER)
assert len(ws) >= 2
# Deal with exact matches possibly needed at one or both ends.
startpos, endpos = 0, len(got)
w = ws[0]
if w: # starts with exact match
if got.startswith(w):
startpos = len(w)
del ws[0]
else:
return False
w = ws[-1]
if w: # ends with exact match
if got.endswith(w):
endpos -= len(w)
del ws[-1]
else:
return False
if startpos > endpos:
# Exact end matches required more characters than we have, as in
# _ellipsis_match('aa...aa', 'aaa')
return False
# For the rest, we only need to find the leftmost non-overlapping
# match for each piece. If there's no overall match that way alone,
# there's no overall match period.
for w in ws:
# w may be '' at times, if there are consecutive ellipses, or
# due to an ellipsis at the start or end of `want`. That's OK.
# Search for an empty string succeeds, and doesn't change startpos.
startpos = got.find(w, startpos, endpos)
if startpos < 0:
return False
startpos += len(w)
return True
def _comment_line(line):
"Return a commented form of the given line"
line = line.rstrip()
if line:
return '# '+line
else:
return '#'
class _OutputRedirectingPdb(pdb.Pdb):
"""
A specialized version of the python debugger that redirects stdout
to a given stream when interacting with the user. Stdout is *not*
redirected when traced code is executed.
"""
def __init__(self, out):
self.__out = out
pdb.Pdb.__init__(self)
def trace_dispatch(self, *args):
# Redirect stdout to the given stream.
save_stdout = sys.stdout
sys.stdout = self.__out
# Call Pdb's trace dispatch method.
pdb.Pdb.trace_dispatch(self, *args)
# Restore stdout.
sys.stdout = save_stdout
######################################################################
## 2. Example & DocTest
######################################################################
## - An "example" is a <source, want> pair, where "source" is a
## fragment of source code, and "want" is the expected output for
## "source." The Example class also includes information about
## where the example was extracted from.
##
## - A "doctest" is a collection of examples, typically extracted from
## a string (such as an object's docstring). The DocTest class also
## includes information about where the string was extracted from.
class Example:
"""
A single doctest example, consisting of source code and expected
output. `Example` defines the following attributes:
- source: A single Python statement, always ending with a newline.
The constructor adds a newline if needed.
- want: The expected output from running the source code (either
from stdout, or a traceback in case of exception). `want` ends
with a newline unless it's empty, in which case it's an empty
string. The constructor adds a newline if needed.
- exc_msg: The exception message generated by the example, if
the example is expected to generate an exception; or `None` if
it is not expected to generate an exception. This exception
message is compared against the return value of
`traceback.format_exception_only()`. `exc_msg` ends with a
newline unless it's `None`. The constructor adds a newline
if needed.
- lineno: The line number within the DocTest string containing
this Example where the Example begins. This line number is
zero-based, with respect to the beginning of the DocTest.
- indent: The example's indentation in the DocTest string.
I.e., the number of space characters that preceed the
example's first prompt.
- options: A dictionary mapping from option flags to True or
False, which is used to override default options for this
example. Any option flags not contained in this dictionary
are left at their default value (as specified by the
DocTestRunner's optionflags). By default, no options are set.
"""
def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
options=None):
# Normalize inputs.
if not source.endswith('\n'):
source += '\n'
if want and not want.endswith('\n'):
want += '\n'
if exc_msg is not None and not exc_msg.endswith('\n'):
exc_msg += '\n'
# Store properties.
self.source = source
self.want = want
self.lineno = lineno
self.indent = indent
if options is None: options = {}
self.options = options
self.exc_msg = exc_msg
class DocTest:
"""
A collection of doctest examples that should be run in a single
namespace. Each `DocTest` defines the following attributes:
- examples: the list of examples.
- globs: The namespace (aka globals) that the examples should
be run in.
- name: A name identifying the DocTest (typically, the name of
the object whose docstring this DocTest was extracted from).
- filename: The name of the file that this DocTest was extracted
from, or `None` if the filename is unknown.
- lineno: The line number within filename where this DocTest
begins, or `None` if the line number is unavailable. This
line number is zero-based, with respect to the beginning of
the file.
- docstring: The string that the examples were extracted from,
or `None` if the string is unavailable.
"""
def __init__(self, examples, globs, name, filename, lineno, docstring):
"""
Create a new DocTest containing the given examples. The
DocTest's globals are initialized with a copy of `globs`.
"""
assert not isinstance(examples, basestring), \
"DocTest no longer accepts str; use DocTestParser instead"
self.examples = examples
self.docstring = docstring
self.globs = globs.copy()
self.name = name
self.filename = filename
self.lineno = lineno
def __repr__(self):
if len(self.examples) == 0:
examples = 'no examples'
elif len(self.examples) == 1:
examples = '1 example'
else:
examples = '%d examples' % len(self.examples)
return ('<DocTest %s from %s:%s (%s)>' %
(self.name, self.filename, self.lineno, examples))
# This lets us sort tests by name:
def __cmp__(self, other):
if not isinstance(other, DocTest):
return -1
return cmp((self.name, self.filename, self.lineno, id(self)),
(other.name, other.filename, other.lineno, id(other)))
######################################################################
## 3. DocTestParser
######################################################################
class DocTestParser:
"""
A class used to parse strings containing doctest examples.
"""
# This regular expression is used to find doctest examples in a
# string. It defines three groups: `source` is the source code
# (including leading indentation and prompts); `indent` is the
# indentation of the first (PS1) line of the source code; and
# `want` is the expected output (including leading indentation).
_EXAMPLE_RE = re.compile(r'''
# Source consists of a PS1 line followed by zero or more PS2 lines.
(?P<source>
(?:^(?P<indent> [ ]*) >>> .*) # PS1 line
(?:\n [ ]* \.\.\. .*)*) # PS2 lines
\n?
# Want consists of any non-blank lines that do not start with PS1.
(?P<want> (?:(?![ ]*$) # Not a blank line
(?![ ]*>>>) # Not a line starting with PS1
.*$\n? # But any other line
)*)
''', re.MULTILINE | re.VERBOSE)
# A regular expression for handling `want` strings that contain
# expected exceptions. It divides `want` into three pieces:
# - the traceback header line (`hdr`)
# - the traceback stack (`stack`)
# - the exception message (`msg`), as generated by
# traceback.format_exception_only()
# `msg` may have multiple lines. We assume/require that the
# exception message is the first non-indented line starting with a word
# character following the traceback header line.
_EXCEPTION_RE = re.compile(r"""
# Grab the traceback header. Different versions of Python have
# said different things on the first traceback line.
^(?P<hdr> Traceback\ \(
(?: most\ recent\ call\ last
| innermost\ last
) \) :
)
\s* $ # toss trailing whitespace on the header.
(?P<stack> .*?) # don't blink: absorb stuff until...
^ (?P<msg> \w+ .*) # a line *starts* with alphanum.
""", re.VERBOSE | re.MULTILINE | re.DOTALL)
# A callable returning a true value iff its argument is a blank line
# or contains a single comment.
_IS_BLANK_OR_COMMENT = re.compile(r'^[ ]*(#.*)?$').match
def parse(self, string, name='<string>'):
"""
Divide the given string into examples and intervening text,
and return them as a list of alternating Examples and strings.
Line numbers for the Examples are 0-based. The optional
argument `name` is a name identifying this string, and is only
used for error messages.
"""
string = string.expandtabs()
# If all lines begin with the same indentation, then strip it.
min_indent = self._min_indent(string)
if min_indent > 0:
string = '\n'.join([l[min_indent:] for l in string.split('\n')])
output = []
charno, lineno = 0, 0
# Find all doctest examples in the string:
for m in self._EXAMPLE_RE.finditer(string):
# Add the pre-example text to `output`.
output.append(string[charno:m.start()])
# Update lineno (lines before this example)
lineno += string.count('\n', charno, m.start())
# Extract info from the regexp match.
(source, options, want, exc_msg) = \
self._parse_example(m, name, lineno)
# Create an Example, and add it to the list.
if not self._IS_BLANK_OR_COMMENT(source):
output.append( Example(source, want, exc_msg,
lineno=lineno,
indent=min_indent+len(m.group('indent')),
options=options) )
# Update lineno (lines inside this example)
lineno += string.count('\n', m.start(), m.end())
# Update charno.
charno = m.end()
# Add any remaining post-example text to `output`.
output.append(string[charno:])
return output
def get_doctest(self, string, globs, name, filename, lineno):
"""
Extract all doctest examples from the given string, and
collect them into a `DocTest` object.
`globs`, `name`, `filename`, and `lineno` are attributes for
the new `DocTest` object. See the documentation for `DocTest`
for more information.
"""
return DocTest(self.get_examples(string, name), globs,
name, filename, lineno, string)
def get_examples(self, string, name='<string>'):
"""
Extract all doctest examples from the given string, and return
them as a list of `Example` objects. Line numbers are
0-based, because it's most common in doctests that nothing
interesting appears on the same line as opening triple-quote,
and so the first interesting line is called \"line 1\" then.
The optional argument `name` is a name identifying this
string, and is only used for error messages.
"""
return [x for x in self.parse(string, name)
if isinstance(x, Example)]
def _parse_example(self, m, name, lineno):
"""
Given a regular expression match from `_EXAMPLE_RE` (`m`),
return a pair `(source, want)`, where `source` is the matched
example's source code (with prompts and indentation stripped);
and `want` is the example's expected output (with indentation
stripped).
`name` is the string's name, and `lineno` is the line number
where the example starts; both are used for error messages.
"""
# Get the example's indentation level.
indent = len(m.group('indent'))
# Divide source into lines; check that they're properly
# indented; and then strip their indentation & prompts.
source_lines = m.group('source').split('\n')
self._check_prompt_blank(source_lines, indent, name, lineno)
self._check_prefix(source_lines[1:], ' '*indent + '.', name, lineno)
source = '\n'.join([sl[indent+4:] for sl in source_lines])
# Divide want into lines; check that it's properly indented; and
# then strip the indentation. Spaces before the last newline should
# be preserved, so plain rstrip() isn't good enough.
want = m.group('want')
want_lines = want.split('\n')
if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
del want_lines[-1] # forget final newline & spaces after it
self._check_prefix(want_lines, ' '*indent, name,
lineno + len(source_lines))
want = '\n'.join([wl[indent:] for wl in want_lines])
# If `want` contains a traceback message, then extract it.
m = self._EXCEPTION_RE.match(want)
if m:
exc_msg = m.group('msg')
else:
exc_msg = None
# Extract options from the source.
options = self._find_options(source, name, lineno)
return source, options, want, exc_msg
# This regular expression looks for option directives in the
# source code of an example. Option directives are comments
# starting with "doctest:". Warning: this may give false
# positives for string-literals that contain the string
# "#doctest:". Eliminating these false positives would require
# actually parsing the string; but we limit them by ignoring any
# line containing "#doctest:" that is *followed* by a quote mark.
_OPTION_DIRECTIVE_RE = re.compile(r'#\s*doctest:\s*([^\n\'"]*)$',
re.MULTILINE)
def _find_options(self, source, name, lineno):
"""
Return a dictionary containing option overrides extracted from
option directives in the given source string.
`name` is the string's name, and `lineno` is the line number
where the example starts; both are used for error messages.
"""
options = {}
# (note: with the current regexp, this will match at most once:)
for m in self._OPTION_DIRECTIVE_RE.finditer(source):
option_strings = m.group(1).replace(',', ' ').split()
for option in option_strings:
if (option[0] not in '+-' or
option[1:] not in OPTIONFLAGS_BY_NAME):
raise ValueError('line %r of the doctest for %s '
'has an invalid option: %r' %
(lineno+1, name, option))
flag = OPTIONFLAGS_BY_NAME[option[1:]]
options[flag] = (option[0] == '+')
if options and self._IS_BLANK_OR_COMMENT(source):
raise ValueError('line %r of the doctest for %s has an option '
'directive on a line with no example: %r' %
(lineno, name, source))
return options
# This regular expression finds the indentation of every non-blank
# line in a string.
_INDENT_RE = re.compile('^([ ]*)(?=\S)', re.MULTILINE)
def _min_indent(self, s):
"Return the minimum indentation of any non-blank line in `s`"
indents = [len(indent) for indent in self._INDENT_RE.findall(s)]
if len(indents) > 0:
return min(indents)
else:
return 0
def _check_prompt_blank(self, lines, indent, name, lineno):
"""
Given the lines of a source string (including prompts and
leading indentation), check to make sure that every prompt is
followed by a space character. If any line is not followed by
a space character, then raise ValueError.
"""
for i, line in enumerate(lines):
if len(line) >= indent+4 and line[indent+3] != ' ':
raise ValueError('line %r of the docstring for %s '
'lacks blank after %s: %r' %
(lineno+i+1, name,
line[indent:indent+3], line))
def _check_prefix(self, lines, prefix, name, lineno):
"""
Check that every line in the given list starts with the given
prefix; if any line does not, then raise a ValueError.
"""
for i, line in enumerate(lines):
if line and not line.startswith(prefix):
raise ValueError('line %r of the docstring for %s has '
'inconsistent leading whitespace: %r' %
(lineno+i+1, name, line))
######################################################################
## 4. DocTest Finder
######################################################################
class DocTestFinder:
"""
A class used to extract the DocTests that are relevant to a given
object, from its docstring and the docstrings of its contained
objects. Doctests can currently be extracted from the following
object types: modules, functions, classes, methods, staticmethods,
classmethods, and properties.
"""
def __init__(self, verbose=False, parser=DocTestParser(),
recurse=True, _namefilter=None):
"""
Create a new doctest finder.
The optional argument `parser` specifies a class or
function that should be used to create new DocTest objects (or
objects that implement the same interface as DocTest). The
signature for this factory function should match the signature
of the DocTest constructor.
If the optional argument `recurse` is false, then `find` will
only examine the given object, and not any contained objects.
"""
self._parser = parser
self._verbose = verbose
self._recurse = recurse
# _namefilter is undocumented, and exists only for temporary backward-
# compatibility support of testmod's deprecated isprivate mess.
self._namefilter = _namefilter
def find(self, obj, name=None, module=None, globs=None,
extraglobs=None):
"""
Return a list of the DocTests that are defined by the given
object's docstring, or by any of its contained objects'
docstrings.
The optional parameter `module` is the module that contains
the given object. If the module is not specified or is None, then
the test finder will attempt to automatically determine the
correct module. The object's module is used:
- As a default namespace, if `globs` is not specified.
- To prevent the DocTestFinder from extracting DocTests
from objects that are imported from other modules.
- To find the name of the file containing the object.
- To help find the line number of the object within its
file.
Contained objects whose module does not match `module` are ignored.
If `module` is False, no attempt to find the module will be made.
This is obscure, of use mostly in tests: if `module` is False, or
is None but cannot be found automatically, then all objects are
considered to belong to the (non-existent) module, so all contained
objects will (recursively) be searched for doctests.
The globals for each DocTest is formed by combining `globs`
and `extraglobs` (bindings in `extraglobs` override bindings
in `globs`). A new copy of the globals dictionary is created
for each DocTest. If `globs` is not specified, then it
defaults to the module's `__dict__`, if specified, or {}
otherwise. If `extraglobs` is not specified, then it defaults
to {}.
"""
# If name was not specified, then extract it from the object.
if name is None:
name = getattr(obj, '__name__', None)
if name is None:
raise ValueError("DocTestFinder.find: name must be given "
"when obj.__name__ doesn't exist: %r" %
(type(obj),))
# Find the module that contains the given object (if obj is
# a module, then module=obj.). Note: this may fail, in which
# case module will be None.
if module is False:
module = None
elif module is None:
module = inspect.getmodule(obj)
# Read the module's source code. This is used by
# DocTestFinder._find_lineno to find the line number for a
# given object's docstring.
try:
file = inspect.getsourcefile(obj) or inspect.getfile(obj)
source_lines = linecache.getlines(file)
if not source_lines:
source_lines = None
except TypeError:
source_lines = None
# Initialize globals, and merge in extraglobs.
if globs is None:
if module is None:
globs = {}
else:
globs = module.__dict__.copy()
else:
globs = globs.copy()
if extraglobs is not None:
globs.update(extraglobs)
# Recursively expore `obj`, extracting DocTests.
tests = []
self._find(tests, obj, name, module, source_lines, globs, {})
return tests
def _filter(self, obj, prefix, base):
"""
Return true if the given object should not be examined.
"""
return (self._namefilter is not None and
self._namefilter(prefix, base))
def _from_module(self, module, object):
"""
Return true if the given object is defined in the given
module.
"""
if module is None:
return True
elif inspect.isfunction(object):
return module.__dict__ is object.func_globals
elif inspect.isclass(object):
return module.__name__ == object.__module__
elif inspect.getmodule(object) is not None:
return module is inspect.getmodule(object)
elif hasattr(object, '__module__'):
return module.__name__ == object.__module__
elif isinstance(object, property):
return True # [XX] no way not be sure.
else:
raise ValueError("object must be a class or function")
def _find(self, tests, obj, name, module, source_lines, globs, seen):
"""
Find tests for the given object and any contained objects, and
add them to `tests`.
"""
if self._verbose:
print 'Finding tests in %s' % name
# If we've already processed this object, then ignore it.
if id(obj) in seen:
return
seen[id(obj)] = 1
# Find a test for this object, and add it to the list of tests.
test = self._get_test(obj, name, module, globs, source_lines)
if test is not None:
tests.append(test)
# Look for tests in a module's contained objects.
if inspect.ismodule(obj) and self._recurse:
for valname, val in obj.__dict__.items():
# Check if this contained object should be ignored.
if self._filter(val, name, valname):
continue
valname = '%s.%s' % (name, valname)
# Recurse to functions & classes.
if ((inspect.isfunction(val) or inspect.isclass(val)) and
self._from_module(module, val)):
self._find(tests, val, valname, module, source_lines,
globs, seen)
# Look for tests in a module's __test__ dictionary.
if inspect.ismodule(obj) and self._recurse:
for valname, val in getattr(obj, '__test__', {}).items():
if not isinstance(valname, basestring):
raise ValueError("DocTestFinder.find: __test__ keys "
"must be strings: %r" %
(type(valname),))
if not (inspect.isfunction(val) or inspect.isclass(val) or
inspect.ismethod(val) or inspect.ismodule(val) or
isinstance(val, basestring)):
raise ValueError("DocTestFinder.find: __test__ values "
"must be strings, functions, methods, "
"classes, or modules: %r" %
(type(val),))
valname = '%s.%s' % (name, valname)
self._find(tests, val, valname, module, source_lines,
globs, seen)
# Look for tests in a class's contained objects.
if inspect.isclass(obj) and self._recurse:
for valname, val in obj.__dict__.items():
# Check if this contained object should be ignored.
if self._filter(val, name, valname):
continue
# Special handling for staticmethod/classmethod.
if isinstance(val, staticmethod):
val = getattr(obj, valname)
if isinstance(val, classmethod):
val = getattr(obj, valname).im_func
# Recurse to methods, properties, and nested classes.
if ((inspect.isfunction(val) or inspect.isclass(val) or
isinstance(val, property)) and
self._from_module(module, val)):
valname = '%s.%s' % (name, valname)
self._find(tests, val, valname, module, source_lines,
globs, seen)
def _get_test(self, obj, name, module, globs, source_lines):
"""
Return a DocTest for the given object, if it defines a docstring;
otherwise, return None.
"""
# Extract the object's docstring. If it doesn't have one,
# then return None (no test for this object).
if isinstance(obj, basestring):
docstring = obj
else:
try:
if obj.__doc__ is None:
return None
docstring = str(obj.__doc__)
except (TypeError, AttributeError):
return None
# Don't bother if the docstring is empty.
if not docstring:
return None
# Find the docstring's location in the file.
lineno = self._find_lineno(obj, source_lines)
# Return a DocTest for this object.
if module is None:
filename = None
else:
filename = getattr(module, '__file__', module.__name__)
if filename[-4:] in (".pyc", ".pyo"):
filename = filename[:-1]
return self._parser.get_doctest(docstring, globs, name,
filename, lineno)
def _find_lineno(self, obj, source_lines):
"""
Return a line number of the given object's docstring. Note:
this method assumes that the object has a docstring.
"""
lineno = None
# Find the line number for modules.
if inspect.ismodule(obj):
lineno = 0
# Find the line number for classes.
# Note: this could be fooled if a class is defined multiple
# times in a single file.
if inspect.isclass(obj):
if source_lines is None:
return None
pat = re.compile(r'^\s*class\s*%s\b' %
getattr(obj, '__name__', '-'))
for i, line in enumerate(source_lines):
if pat.match(line):
lineno = i
break
# Find the line number for functions & methods.
if inspect.ismethod(obj): obj = obj.im_func
if inspect.isfunction(obj): obj = obj.func_code
if inspect.istraceback(obj): obj = obj.tb_frame
if inspect.isframe(obj): obj = obj.f_code
if inspect.iscode(obj):
lineno = getattr(obj, 'co_firstlineno', None)-1
# Find the line number where the docstring starts. Assume
# that it's the first line that begins with a quote mark.
# Note: this could be fooled by a multiline function
# signature, where a continuation line begins with a quote
# mark.
if lineno is not None:
if source_lines is None:
return lineno+1
pat = re.compile('(^|.*:)\s*\w*("|\')')
for lineno in range(lineno, len(source_lines)):
if pat.match(source_lines[lineno]):
return lineno
# We couldn't find the line number.
return None
######################################################################
## 5. DocTest Runner
######################################################################
class DocTestRunner:
"""
A class used to run DocTest test cases, and accumulate statistics.
The `run` method is used to process a single DocTest case. It
returns a tuple `(f, t)`, where `t` is the number of test cases
tried, and `f` is the number of test cases that failed.
>>> tests = DocTestFinder().find(_TestClass)
>>> runner = DocTestRunner(verbose=False)
>>> for test in tests:
... print runner.run(test)
(0, 2)
(0, 1)
(0, 2)
(0, 2)
The `summarize` method prints a summary of all the test cases that
have been run by the runner, and returns an aggregated `(f, t)`
tuple:
>>> runner.summarize(verbose=1)
4 items passed all tests:
2 tests in _TestClass
2 tests in _TestClass.__init__
2 tests in _TestClass.get
1 tests in _TestClass.square
7 tests in 4 items.
7 passed and 0 failed.
Test passed.
(0, 7)
The aggregated number of tried examples and failed examples is
also available via the `tries` and `failures` attributes:
>>> runner.tries
7
>>> runner.failures
0
The comparison between expected outputs and actual outputs is done
by an `OutputChecker`. This comparison may be customized with a
number of option flags; see the documentation for `testmod` for
more information. If the option flags are insufficient, then the
comparison may also be customized by passing a subclass of
`OutputChecker` to the constructor.
The test runner's display output can be controlled in two ways.
First, an output function (`out) can be passed to
`TestRunner.run`; this function will be called with strings that
should be displayed. It defaults to `sys.stdout.write`. If
capturing the output is not sufficient, then the display output
can be also customized by subclassing DocTestRunner, and
overriding the methods `report_start`, `report_success`,
`report_unexpected_exception`, and `report_failure`.
"""
# This divider string is used to separate failure messages, and to
# separate sections of the summary.
DIVIDER = "*" * 70
def __init__(self, checker=None, verbose=None, optionflags=0):
"""
Create a new test runner.
Optional keyword arg `checker` is the `OutputChecker` that
should be used to compare the expected outputs and actual
outputs of doctest examples.
Optional keyword arg 'verbose' prints lots of stuff if true,
only failures if false; by default, it's true iff '-v' is in
sys.argv.
Optional argument `optionflags` can be used to control how the
test runner compares expected output to actual output, and how
it displays failures. See the documentation for `testmod` for
more information.
"""
self._checker = checker or OutputChecker()
if verbose is None:
verbose = '-v' in sys.argv
self._verbose = verbose
self.optionflags = optionflags
self.original_optionflags = optionflags
# Keep track of the examples we've run.
self.tries = 0
self.failures = 0
self._name2ft = {}
# Create a fake output target for capturing doctest output.
self._fakeout = _SpoofOut()
#/////////////////////////////////////////////////////////////////
# Reporting methods
#/////////////////////////////////////////////////////////////////
def report_start(self, out, test, example):
"""
Report that the test runner is about to process the given
example. (Only displays a message if verbose=True)
"""
if self._verbose:
if example.want:
out('Trying:\n' + _indent(example.source) +
'Expecting:\n' + _indent(example.want))
else:
out('Trying:\n' + _indent(example.source) +
'Expecting nothing\n')
def report_success(self, out, test, example, got):
"""
Report that the given example ran successfully. (Only
displays a message if verbose=True)
"""
if self._verbose:
out("ok\n")
def report_failure(self, out, test, example, got):
"""
Report that the given example failed.
"""
out(self._failure_header(test, example) +
self._checker.output_difference(example, got, self.optionflags))
def report_unexpected_exception(self, out, test, example, exc_info):
"""
Report that the given example raised an unexpected exception.
"""
out(self._failure_header(test, example) +
'Exception raised:\n' + _indent(_exception_traceback(exc_info)))
def _failure_header(self, test, example):
out = [self.DIVIDER]
if test.filename:
if test.lineno is not None and example.lineno is not None:
lineno = test.lineno + example.lineno + 1
else:
lineno = '?'
out.append('File "%s", line %s, in %s' %
(test.filename, lineno, test.name))
else:
out.append('Line %s, in %s' % (example.lineno+1, test.name))
out.append('Failed example:')
source = example.source
out.append(_indent(source))
return '\n'.join(out)
#/////////////////////////////////////////////////////////////////
# DocTest Running
#/////////////////////////////////////////////////////////////////
def __run(self, test, compileflags, out):
"""
Run the examples in `test`. Write the outcome of each example
with one of the `DocTestRunner.report_*` methods, using the
writer function `out`. `compileflags` is the set of compiler
flags that should be used to execute examples. Return a tuple
`(f, t)`, where `t` is the number of examples tried, and `f`
is the number of examples that failed. The examples are run
in the namespace `test.globs`.
"""
# Keep track of the number of failures and tries.
failures = tries = 0
# Save the option flags (since option directives can be used
# to modify them).
original_optionflags = self.optionflags
# Process each example.
for examplenum, example in enumerate(test.examples):
# If REPORT_ONLY_FIRST_FAILURE is set, then supress
# reporting after the first failure.
quiet = (self.optionflags & REPORT_ONLY_FIRST_FAILURE and
failures > 0)
# Merge in the example's options.
self.optionflags = original_optionflags
if example.options:
for (optionflag, val) in example.options.items():
if val:
self.optionflags |= optionflag
else:
self.optionflags &= ~optionflag
# Record that we started this example.
tries += 1
if not quiet:
self.report_start(out, test, example)
# Use a special filename for compile(), so we can retrieve
# the source code during interactive debugging (see
# __patched_linecache_getlines).
filename = '<doctest %s[%d]>' % (test.name, examplenum)
# Run the example in the given context (globs), and record
# any exception that gets raised. (But don't intercept
# keyboard interrupts.)
try:
# Don't blink! This is where the user's code gets run.
exec compile(example.source, filename, "single",
compileflags, 1) in test.globs
self.debugger.set_continue() # ==== Example Finished ====
exception = None
except KeyboardInterrupt:
raise
except:
exception = sys.exc_info()
self.debugger.set_continue() # ==== Example Finished ====
got = self._fakeout.getvalue() # the actual output
self._fakeout.truncate(0)
# If the example executed without raising any exceptions,
# then verify its output and report its outcome.
if exception is None:
if self._checker.check_output(example.want, got,
self.optionflags):
if not quiet:
self.report_success(out, test, example, got)
else:
if not quiet:
self.report_failure(out, test, example, got)
failures += 1
# If the example raised an exception, then check if it was
# expected.
else:
exc_info = sys.exc_info()
exc_msg = traceback.format_exception_only(*exc_info[:2])[-1]
# If `example.exc_msg` is None, then we weren't
# expecting an exception.
if example.exc_msg is None:
if not quiet:
self.report_unexpected_exception(out, test, example,
exc_info)
failures += 1
# If `example.exc_msg` matches the actual exception
# message (`exc_msg`), then the example succeeds.
elif (self._checker.check_output(example.exc_msg, exc_msg,
self.optionflags)):
if not quiet:
got += _exception_traceback(exc_info)
self.report_success(out, test, example, got)
# Otherwise, the example fails.
else:
if not quiet:
got += _exception_traceback(exc_info)
self.report_failure(out, test, example, got)
failures += 1
# Restore the option flags (in case they were modified)
self.optionflags = original_optionflags
# Record and return the number of failures and tries.
self.__record_outcome(test, failures, tries)
return failures, tries
def __record_outcome(self, test, f, t):
"""
Record the fact that the given DocTest (`test`) generated `f`
failures out of `t` tried examples.
"""
f2, t2 = self._name2ft.get(test.name, (0,0))
self._name2ft[test.name] = (f+f2, t+t2)
self.failures += f
self.tries += t
__LINECACHE_FILENAME_RE = re.compile(r'<doctest '
r'(?P<name>[\w\.]+)'
r'\[(?P<examplenum>\d+)\]>$')
def __patched_linecache_getlines(self, filename):
m = self.__LINECACHE_FILENAME_RE.match(filename)
if m and m.group('name') == self.test.name:
example = self.test.examples[int(m.group('examplenum'))]
return example.source.splitlines(True)
else:
return self.save_linecache_getlines(filename)
def run(self, test, compileflags=None, out=None, clear_globs=True):
"""
Run the examples in `test`, and display the results using the
writer function `out`.
The examples are run in the namespace `test.globs`. If
`clear_globs` is true (the default), then this namespace will
be cleared after the test runs, to help with garbage
collection. If you would like to examine the namespace after
the test completes, then use `clear_globs=False`.
`compileflags` gives the set of flags that should be used by
the Python compiler when running the examples. If not
specified, then it will default to the set of future-import
flags that apply to `globs`.
The output of each example is checked using
`DocTestRunner.check_output`, and the results are formatted by
the `DocTestRunner.report_*` methods.
"""
self.test = test
if compileflags is None:
compileflags = _extract_future_flags(test.globs)
save_stdout = sys.stdout
if out is None:
out = save_stdout.write
sys.stdout = self._fakeout
# Patch pdb.set_trace to restore sys.stdout during interactive
# debugging (so it's not still redirected to self._fakeout).
# Note that the interactive output will go to *our*
# save_stdout, even if that's not the real sys.stdout; this
# allows us to write test cases for the set_trace behavior.
save_set_trace = pdb.set_trace
self.debugger = _OutputRedirectingPdb(save_stdout)
self.debugger.reset()
pdb.set_trace = self.debugger.set_trace
# Patch linecache.getlines, so we can see the example's source
# when we're inside the debugger.
self.save_linecache_getlines = linecache.getlines
linecache.getlines = self.__patched_linecache_getlines
try:
return self.__run(test, compileflags, out)
finally:
sys.stdout = save_stdout
pdb.set_trace = save_set_trace
linecache.getlines = self.save_linecache_getlines
if clear_globs:
test.globs.clear()
#/////////////////////////////////////////////////////////////////
# Summarization
#/////////////////////////////////////////////////////////////////
def summarize(self, verbose=None):
"""
Print a summary of all the test cases that have been run by
this DocTestRunner, and return a tuple `(f, t)`, where `f` is
the total number of failed examples, and `t` is the total
number of tried examples.
The optional `verbose` argument controls how detailed the
summary is. If the verbosity is not specified, then the
DocTestRunner's verbosity is used.
"""
if verbose is None:
verbose = self._verbose
notests = []
passed = []
failed = []
totalt = totalf = 0
for x in self._name2ft.items():
name, (f, t) = x
assert f <= t
totalt += t
totalf += f
if t == 0:
notests.append(name)
elif f == 0:
passed.append( (name, t) )
else:
failed.append(x)
if verbose:
if notests:
print len(notests), "items had no tests:"
notests.sort()
for thing in notests:
print " ", thing
if passed:
print len(passed), "items passed all tests:"
passed.sort()
for thing, count in passed:
print " %3d tests in %s" % (count, thing)
if failed:
print self.DIVIDER
print len(failed), "items had failures:"
failed.sort()
for thing, (f, t) in failed:
print " %3d of %3d in %s" % (f, t, thing)
if verbose:
print totalt, "tests in", len(self._name2ft), "items."
print totalt - totalf, "passed and", totalf, "failed."
if totalf:
print "***Test Failed***", totalf, "failures."
elif verbose:
print "Test passed."
return totalf, totalt
class OutputChecker:
"""
A class used to check the whether the actual output from a doctest
example matches the expected output. `OutputChecker` defines two
methods: `check_output`, which compares a given pair of outputs,
and returns true if they match; and `output_difference`, which
returns a string describing the differences between two outputs.
"""
def check_output(self, want, got, optionflags):
"""
Return True iff the actual output from an example (`got`)
matches the expected output (`want`). These strings are
always considered to match if they are identical; but
depending on what option flags the test runner is using,
several non-exact match types are also possible. See the
documentation for `TestRunner` for more information about
option flags.
"""
# Handle the common case first, for efficiency:
# if they're string-identical, always return true.
if got == want:
return True
# The values True and False replaced 1 and 0 as the return
# value for boolean comparisons in Python 2.3.
if not (optionflags & DONT_ACCEPT_TRUE_FOR_1):
if (got,want) == ("True\n", "1\n"):
return True
if (got,want) == ("False\n", "0\n"):
return True
# <BLANKLINE> can be used as a special sequence to signify a
# blank line, unless the DONT_ACCEPT_BLANKLINE flag is used.
if not (optionflags & DONT_ACCEPT_BLANKLINE):
# Replace <BLANKLINE> in want with a blank line.
want = re.sub('(?m)^%s\s*?$' % re.escape(BLANKLINE_MARKER),
'', want)
# If a line in got contains only spaces, then remove the
# spaces.
got = re.sub('(?m)^\s*?$', '', got)
if got == want:
return True
# This flag causes doctest to ignore any differences in the
# contents of whitespace strings. Note that this can be used
# in conjunction with the ELLIPSIS flag.
if optionflags & NORMALIZE_WHITESPACE:
got = ' '.join(got.split())
want = ' '.join(want.split())
if got == want:
return True
# The ELLIPSIS flag says to let the sequence "..." in `want`
# match any substring in `got`.
if optionflags & ELLIPSIS:
if _ellipsis_match(want, got):
return True
# We didn't find any match; return false.
return False
# Should we do a fancy diff?
def _do_a_fancy_diff(self, want, got, optionflags):
# Not unless they asked for a fancy diff.
if not optionflags & (REPORT_UDIFF |
REPORT_CDIFF |
REPORT_NDIFF):
return False
# If expected output uses ellipsis, a meaningful fancy diff is
# too hard ... or maybe not. In two real-life failures Tim saw,
# a diff was a major help anyway, so this is commented out.
# [todo] _ellipsis_match() knows which pieces do and don't match,
# and could be the basis for a kick-ass diff in this case.
##if optionflags & ELLIPSIS and ELLIPSIS_MARKER in want:
## return False
# ndiff does intraline difference marking, so can be useful even
# for 1-line differences.
if optionflags & REPORT_NDIFF:
return True
# The other diff types need at least a few lines to be helpful.
return want.count('\n') > 2 and got.count('\n') > 2
def output_difference(self, example, got, optionflags):
"""
Return a string describing the differences between the
expected output for a given example (`example`) and the actual
output (`got`). `optionflags` is the set of option flags used
to compare `want` and `got`.
"""
want = example.want
# If <BLANKLINE>s are being used, then replace blank lines
# with <BLANKLINE> in the actual output string.
if not (optionflags & DONT_ACCEPT_BLANKLINE):
got = re.sub('(?m)^[ ]*(?=\n)', BLANKLINE_MARKER, got)
# Check if we should use diff.
if self._do_a_fancy_diff(want, got, optionflags):
# Split want & got into lines.
want_lines = want.splitlines(True) # True == keep line ends
got_lines = got.splitlines(True)
# Use difflib to find their differences.
if optionflags & REPORT_UDIFF:
diff = difflib.unified_diff(want_lines, got_lines, n=2)
diff = list(diff)[2:] # strip the diff header
kind = 'unified diff with -expected +actual'
elif optionflags & REPORT_CDIFF:
diff = difflib.context_diff(want_lines, got_lines, n=2)
diff = list(diff)[2:] # strip the diff header
kind = 'context diff with expected followed by actual'
elif optionflags & REPORT_NDIFF:
engine = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK)
diff = list(engine.compare(want_lines, got_lines))
kind = 'ndiff with -expected +actual'
else:
assert 0, 'Bad diff option'
# Remove trailing whitespace on diff output.
diff = [line.rstrip() + '\n' for line in diff]
return 'Differences (%s):\n' % kind + _indent(''.join(diff))
# If we're not using diff, then simply list the expected
# output followed by the actual output.
if want and got:
return 'Expected:\n%sGot:\n%s' % (_indent(want), _indent(got))
elif want:
return 'Expected:\n%sGot nothing\n' % _indent(want)
elif got:
return 'Expected nothing\nGot:\n%s' % _indent(got)
else:
return 'Expected nothing\nGot nothing\n'
class DocTestFailure(Exception):
"""A DocTest example has failed in debugging mode.
The exception instance has variables:
- test: the DocTest object being run
- excample: the Example object that failed
- got: the actual output
"""
def __init__(self, test, example, got):
self.test = test
self.example = example
self.got = got
def __str__(self):
return str(self.test)
class UnexpectedException(Exception):
"""A DocTest example has encountered an unexpected exception
The exception instance has variables:
- test: the DocTest object being run
- excample: the Example object that failed
- exc_info: the exception info
"""
def __init__(self, test, example, exc_info):
self.test = test
self.example = example
self.exc_info = exc_info
def __str__(self):
return str(self.test)
class DebugRunner(DocTestRunner):
r"""Run doc tests but raise an exception as soon as there is a failure.
If an unexpected exception occurs, an UnexpectedException is raised.
It contains the test, the example, and the original exception:
>>> runner = DebugRunner(verbose=False)
>>> test = DocTestParser().get_doctest('>>> raise KeyError\n42',
... {}, 'foo', 'foo.py', 0)
>>> try:
... runner.run(test)
... except UnexpectedException, failure:
... pass
>>> failure.test is test
True
>>> failure.example.want
'42\n'
>>> exc_info = failure.exc_info
>>> raise exc_info[0], exc_info[1], exc_info[2]
Traceback (most recent call last):
...
KeyError
We wrap the original exception to give the calling application
access to the test and example information.
If the output doesn't match, then a DocTestFailure is raised:
>>> test = DocTestParser().get_doctest('''
... >>> x = 1
... >>> x
... 2
... ''', {}, 'foo', 'foo.py', 0)
>>> try:
... runner.run(test)
... except DocTestFailure, failure:
... pass
DocTestFailure objects provide access to the test:
>>> failure.test is test
True
As well as to the example:
>>> failure.example.want
'2\n'
and the actual output:
>>> failure.got
'1\n'
If a failure or error occurs, the globals are left intact:
>>> del test.globs['__builtins__']
>>> test.globs
{'x': 1}
>>> test = DocTestParser().get_doctest('''
... >>> x = 2
... >>> raise KeyError
... ''', {}, 'foo', 'foo.py', 0)
>>> runner.run(test)
Traceback (most recent call last):
...
UnexpectedException: <DocTest foo from foo.py:0 (2 examples)>
>>> del test.globs['__builtins__']
>>> test.globs
{'x': 2}
But the globals are cleared if there is no error:
>>> test = DocTestParser().get_doctest('''
... >>> x = 2
... ''', {}, 'foo', 'foo.py', 0)
>>> runner.run(test)
(0, 1)
>>> test.globs
{}
"""
def run(self, test, compileflags=None, out=None, clear_globs=True):
r = DocTestRunner.run(self, test, compileflags, out, False)
if clear_globs:
test.globs.clear()
return r
def report_unexpected_exception(self, out, test, example, exc_info):
raise UnexpectedException(test, example, exc_info)
def report_failure(self, out, test, example, got):
raise DocTestFailure(test, example, got)
######################################################################
## 6. Test Functions
######################################################################
# These should be backwards compatible.
def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
report=True, optionflags=0, extraglobs=None,
raise_on_error=False):
"""m=None, name=None, globs=None, verbose=None, isprivate=None,
report=True, optionflags=0, extraglobs=None
Test examples in docstrings in functions and classes reachable
from module m (or the current module if m is not supplied), starting
with m.__doc__. Unless isprivate is specified, private names
are not skipped.
Also test examples reachable from dict m.__test__ if it exists and is
not None. m.__test__ maps names to functions, classes and strings;
function and class docstrings are tested even if the name is private;
strings are tested directly, as if they were docstrings.
Return (#failures, #tests).
See doctest.__doc__ for an overview.
Optional keyword arg "name" gives the name of the module; by default
use m.__name__.
Optional keyword arg "globs" gives a dict to be used as the globals
when executing examples; by default, use m.__dict__. A copy of this
dict is actually used for each docstring, so that each docstring's
examples start with a clean slate.
Optional keyword arg "extraglobs" gives a dictionary that should be
merged into the globals that are used to execute examples. By
default, no extra globals are used. This is new in 2.4.
Optional keyword arg "verbose" prints lots of stuff if true, prints
only failures if false; by default, it's true iff "-v" is in sys.argv.
Optional keyword arg "report" prints a summary at the end when true,
else prints nothing at the end. In verbose mode, the summary is
detailed, else very brief (in fact, empty if all tests passed).
Optional keyword arg "optionflags" or's together module constants,
and defaults to 0. This is new in 2.3. Possible values (see the
docs for details):
DONT_ACCEPT_TRUE_FOR_1
DONT_ACCEPT_BLANKLINE
NORMALIZE_WHITESPACE
ELLIPSIS
REPORT_UDIFF
REPORT_CDIFF
REPORT_NDIFF
REPORT_ONLY_FIRST_FAILURE
Optional keyword arg "raise_on_error" raises an exception on the
first unexpected exception or failure. This allows failures to be
post-mortem debugged.
Deprecated in Python 2.4:
Optional keyword arg "isprivate" specifies a function used to
determine whether a name is private. The default function is
treat all functions as public. Optionally, "isprivate" can be
set to doctest.is_private to skip over functions marked as private
using the underscore naming convention; see its docs for details.
"""
""" [XX] This is no longer true:
Advanced tomfoolery: testmod runs methods of a local instance of
class doctest.Tester, then merges the results into (or creates)
global Tester instance doctest.master. Methods of doctest.master
can be called directly too, if you want to do something unusual.
Passing report=0 to testmod is especially useful then, to delay
displaying a summary. Invoke doctest.master.summarize(verbose)
when you're done fiddling.
"""
if isprivate is not None:
warnings.warn("the isprivate argument is deprecated; "
"examine DocTestFinder.find() lists instead",
DeprecationWarning)
# If no module was given, then use __main__.
if m is None:
# DWA - m will still be None if this wasn't invoked from the command
# line, in which case the following TypeError is about as good an error
# as we should expect
m = sys.modules.get('__main__')
# Check that we were actually given a module.
if not inspect.ismodule(m):
raise TypeError("testmod: module required; %r" % (m,))
# If no name was given, then use the module's name.
if name is None:
name = m.__name__
# Find, parse, and run all tests in the given module.
finder = DocTestFinder(_namefilter=isprivate)
if raise_on_error:
runner = DebugRunner(verbose=verbose, optionflags=optionflags)
else:
runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
for test in finder.find(m, name, globs=globs, extraglobs=extraglobs):
runner.run(test)
if report:
runner.summarize()
return runner.failures, runner.tries
def run_docstring_examples(f, globs, verbose=False, name="NoName",
compileflags=None, optionflags=0):
"""
Test examples in the given object's docstring (`f`), using `globs`
as globals. Optional argument `name` is used in failure messages.
If the optional argument `verbose` is true, then generate output
even if there are no failures.
`compileflags` gives the set of flags that should be used by the
Python compiler when running the examples. If not specified, then
it will default to the set of future-import flags that apply to
`globs`.
Optional keyword arg `optionflags` specifies options for the
testing and output. See the documentation for `testmod` for more
information.
"""
# Find, parse, and run all tests in the given module.
finder = DocTestFinder(verbose=verbose, recurse=False)
runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
for test in finder.find(f, name, globs=globs):
runner.run(test, compileflags=compileflags)
######################################################################
## 7. Tester
######################################################################
# This is provided only for backwards compatibility. It's not
# actually used in any way.
class Tester:
def __init__(self, mod=None, globs=None, verbose=None,
isprivate=None, optionflags=0):
warnings.warn("class Tester is deprecated; "
"use class doctest.DocTestRunner instead",
DeprecationWarning, stacklevel=2)
if mod is None and globs is None:
raise TypeError("Tester.__init__: must specify mod or globs")
from inspect import ismodule as _ismodule
if mod is not None and not _ismodule(mod):
raise TypeError("Tester.__init__: mod must be a module; %r" %
(mod,))
if globs is None:
globs = mod.__dict__
self.globs = globs
self.verbose = verbose
self.isprivate = isprivate
self.optionflags = optionflags
self.testfinder = DocTestFinder(_namefilter=isprivate)
self.testrunner = DocTestRunner(verbose=verbose,
optionflags=optionflags)
def runstring(self, s, name):
test = DocTestParser().get_doctest(s, self.globs, name, None, None)
if self.verbose:
print "Running string", name
(f,t) = self.testrunner.run(test)
if self.verbose:
print f, "of", t, "examples failed in string", name
return (f,t)
def rundoc(self, object, name=None, module=None):
f = t = 0
tests = self.testfinder.find(object, name, module=module,
globs=self.globs)
for test in tests:
(f2, t2) = self.testrunner.run(test)
(f,t) = (f+f2, t+t2)
return (f,t)
def rundict(self, d, name, module=None):
import new
m = new.module(name)
m.__dict__.update(d)
if module is None:
module = False
return self.rundoc(m, name, module)
def run__test__(self, d, name):
import new
m = new.module(name)
m.__test__ = d
return self.rundoc(m, name, module)
def summarize(self, verbose=None):
return self.testrunner.summarize(verbose)
def merge(self, other):
d = self.testrunner._name2ft
for name, (f, t) in other.testrunner._name2ft.items():
if name in d:
print "*** Tester.merge: '" + name + "' in both" \
" testers; summing outcomes."
f2, t2 = d[name]
f = f + f2
t = t + t2
d[name] = f, t
######################################################################
## 8. Unittest Support
######################################################################
_unittest_reportflags = 0
valid_unittest_reportflags = (
REPORT_CDIFF |
REPORT_UDIFF |
REPORT_NDIFF |
REPORT_ONLY_FIRST_FAILURE
)
def set_unittest_reportflags(flags):
"""Sets the unit test option flags
The old flag is returned so that a runner could restore the old
value if it wished to:
>>> old = _unittest_reportflags
>>> set_unittest_reportflags(REPORT_NDIFF |
... REPORT_ONLY_FIRST_FAILURE) == old
True
>>> import doctest
>>> doctest._unittest_reportflags == (REPORT_NDIFF |
... REPORT_ONLY_FIRST_FAILURE)
True
Only reporting flags can be set:
>>> set_unittest_reportflags(ELLIPSIS)
Traceback (most recent call last):
...
ValueError: ('Invalid flags passed', 8)
>>> set_unittest_reportflags(old) == (REPORT_NDIFF |
... REPORT_ONLY_FIRST_FAILURE)
True
"""
# extract the valid reporting flags:
rflags = flags & valid_unittest_reportflags
# Now remove these flags from the given flags
nrflags = flags ^ rflags
if nrflags:
raise ValueError("Invalid flags passed", flags)
global _unittest_reportflags
old = _unittest_reportflags
_unittest_reportflags = flags
return old
class FakeModule:
"""Fake module created by tests
"""
def __init__(self, dict, name):
self.__dict__ = dict
self.__name__ = name
class DocTestCase(unittest.TestCase):
def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
checker=None):
unittest.TestCase.__init__(self)
self._dt_optionflags = optionflags
self._dt_checker = checker
self._dt_test = test
self._dt_setUp = setUp
self._dt_tearDown = tearDown
def setUp(self):
test = self._dt_test
if self._dt_setUp is not None:
self._dt_setUp(test)
def tearDown(self):
test = self._dt_test
if self._dt_tearDown is not None:
self._dt_tearDown(test)
test.globs.clear()
def runTest(self):
test = self._dt_test
old = sys.stdout
new = StringIO()
optionflags = self._dt_optionflags
if not (optionflags & valid_unittest_reportflags):
# The option flags don't include any reporting flags,
# so add the default reporting flags
optionflags |= _unittest_reportflags
runner = DocTestRunner(optionflags=optionflags,
checker=self._dt_checker, verbose=False)
try:
runner.DIVIDER = "-"*70
failures, tries = runner.run(
test, out=new.write, clear_globs=False)
finally:
sys.stdout = old
if failures:
raise self.failureException(self.format_failure(new.getvalue()))
def format_failure(self, err):
test = self._dt_test
if test.lineno is None:
lineno = 'unknown line number'
else:
lineno = '%s' % test.lineno
lname = '.'.join(test.name.split('.')[-1:])
return ('Failed doctest test for %s\n'
' File "%s", line %s, in %s\n\n%s'
% (test.name, test.filename, lineno, lname, err)
)
def debug(self):
r"""Run the test case without results and without catching exceptions
The unit test framework includes a debug method on test cases
and test suites to support post-mortem debugging. The test code
is run in such a way that errors are not caught. This way a
caller can catch the errors and initiate post-mortem debugging.
The DocTestCase provides a debug method that raises
UnexpectedException errors if there is an unexepcted
exception:
>>> test = DocTestParser().get_doctest('>>> raise KeyError\n42',
... {}, 'foo', 'foo.py', 0)
>>> case = DocTestCase(test)
>>> try:
... case.debug()
... except UnexpectedException, failure:
... pass
The UnexpectedException contains the test, the example, and
the original exception:
>>> failure.test is test
True
>>> failure.example.want
'42\n'
>>> exc_info = failure.exc_info
>>> raise exc_info[0], exc_info[1], exc_info[2]
Traceback (most recent call last):
...
KeyError
If the output doesn't match, then a DocTestFailure is raised:
>>> test = DocTestParser().get_doctest('''
... >>> x = 1
... >>> x
... 2
... ''', {}, 'foo', 'foo.py', 0)
>>> case = DocTestCase(test)
>>> try:
... case.debug()
... except DocTestFailure, failure:
... pass
DocTestFailure objects provide access to the test:
>>> failure.test is test
True
As well as to the example:
>>> failure.example.want
'2\n'
and the actual output:
>>> failure.got
'1\n'
"""
self.setUp()
runner = DebugRunner(optionflags=self._dt_optionflags,
checker=self._dt_checker, verbose=False)
runner.run(self._dt_test)
self.tearDown()
def id(self):
return self._dt_test.name
def __repr__(self):
name = self._dt_test.name.split('.')
return "%s (%s)" % (name[-1], '.'.join(name[:-1]))
__str__ = __repr__
def shortDescription(self):
return "Doctest: " + self._dt_test.name
def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None,
**options):
"""
Convert doctest tests for a module to a unittest test suite.
This converts each documentation string in a module that
contains doctest tests to a unittest test case. If any of the
tests in a doc string fail, then the test case fails. An exception
is raised showing the name of the file containing the test and a
(sometimes approximate) line number.
The `module` argument provides the module to be tested. The argument
can be either a module or a module name.
If no argument is given, the calling module is used.
A number of options may be provided as keyword arguments:
package
The name of a Python package. Text-file paths will be
interpreted relative to the directory containing this package.
The package may be supplied as a package object or as a dotted
package name.
setUp
The name of a set-up function. This is called before running the
tests in each file. The setUp function will be passed a DocTest
object. The setUp function can access the test globals as the
globs attribute of the test passed.
tearDown
The name of a tear-down function. This is called after running the
tests in each file. The tearDown function will be passed a DocTest
object. The tearDown function can access the test globals as the
globs attribute of the test passed.
globs
A dictionary containing initial global variables for the tests.
optionflags
A set of doctest option flags expressed as an integer.
"""
if test_finder is None:
test_finder = DocTestFinder()
module = _normalize_module(module)
tests = test_finder.find(module, globs=globs, extraglobs=extraglobs)
if globs is None:
globs = module.__dict__
if not tests:
# Why do we want to do this? Because it reveals a bug that might
# otherwise be hidden.
raise ValueError(module, "has no tests")
tests.sort()
suite = unittest.TestSuite()
for test in tests:
if len(test.examples) == 0:
continue
if not test.filename:
filename = module.__file__
if filename[-4:] in (".pyc", ".pyo"):
filename = filename[:-1]
test.filename = filename
suite.addTest(DocTestCase(test, **options))
return suite
class DocFileCase(DocTestCase):
def id(self):
return '_'.join(self._dt_test.name.split('.'))
def __repr__(self):
return self._dt_test.filename
__str__ = __repr__
def format_failure(self, err):
return ('Failed doctest test for %s\n File "%s", line 0\n\n%s'
% (self._dt_test.name, self._dt_test.filename, err)
)
def DocFileTest(path, package=None, globs=None, **options):
package = _normalize_module(package)
name = path.split('/')[-1]
dir = os.path.split(package.__file__)[0]
path = os.path.join(dir, *(path.split('/')))
doc = open(path).read()
if globs is None:
globs = {}
test = DocTestParser().get_doctest(doc, globs, name, path, 0)
return DocFileCase(test, **options)
def DocFileSuite(*paths, **kw):
"""Creates a suite of doctest files.
One or more text file paths are given as strings. These should
use "/" characters to separate path segments. Paths are relative
to the directory of the calling module, or relative to the package
passed as a keyword argument.
A number of options may be provided as keyword arguments:
package
The name of a Python package. Text-file paths will be
interpreted relative to the directory containing this package.
The package may be supplied as a package object or as a dotted
package name.
setUp
The name of a set-up function. This is called before running the
tests in each file. The setUp function will be passed a DocTest
object. The setUp function can access the test globals as the
globs attribute of the test passed.
tearDown
The name of a tear-down function. This is called after running the
tests in each file. The tearDown function will be passed a DocTest
object. The tearDown function can access the test globals as the
globs attribute of the test passed.
globs
A dictionary containing initial global variables for the tests.
optionflags
A set of doctest option flags expressed as an integer.
"""
suite = unittest.TestSuite()
# We do this here so that _normalize_module is called at the right
# level. If it were called in DocFileTest, then this function
# would be the caller and we might guess the package incorrectly.
kw['package'] = _normalize_module(kw.get('package'))
for path in paths:
suite.addTest(DocFileTest(path, **kw))
return suite
######################################################################
## 9. Debugging Support
######################################################################
def script_from_examples(s):
r"""Extract script from text with examples.
Converts text with examples to a Python script. Example input is
converted to regular code. Example output and all other words
are converted to comments:
>>> text = '''
... Here are examples of simple math.
...
... Python has super accurate integer addition
...
... >>> 2 + 2
... 5
...
... And very friendly error messages:
...
... >>> 1/0
... To Infinity
... And
... Beyond
...
... You can use logic if you want:
...
... >>> if 0:
... ... blah
... ... blah
... ...
...
... Ho hum
... '''
>>> print script_from_examples(text)
# Here are examples of simple math.
#
# Python has super accurate integer addition
#
2 + 2
# Expected:
## 5
#
# And very friendly error messages:
#
1/0
# Expected:
## To Infinity
## And
## Beyond
#
# You can use logic if you want:
#
if 0:
blah
blah
#
# Ho hum
"""
output = []
for piece in DocTestParser().parse(s):
if isinstance(piece, Example):
# Add the example's source code (strip trailing NL)
output.append(piece.source[:-1])
# Add the expected output:
want = piece.want
if want:
output.append('# Expected:')
output += ['## '+l for l in want.split('\n')[:-1]]
else:
# Add non-example text.
output += [_comment_line(l)
for l in piece.split('\n')[:-1]]
# Trim junk on both ends.
while output and output[-1] == '#':
output.pop()
while output and output[0] == '#':
output.pop(0)
# Combine the output, and return it.
return '\n'.join(output)
def testsource(module, name):
"""Extract the test sources from a doctest docstring as a script.
Provide the module (or dotted name of the module) containing the
test to be debugged and the name (within the module) of the object
with the doc string with tests to be debugged.
"""
module = _normalize_module(module)
tests = DocTestFinder().find(module)
test = [t for t in tests if t.name == name]
if not test:
raise ValueError(name, "not found in tests")
test = test[0]
testsrc = script_from_examples(test.docstring)
return testsrc
def debug_src(src, pm=False, globs=None):
"""Debug a single doctest docstring, in argument `src`'"""
testsrc = script_from_examples(src)
debug_script(testsrc, pm, globs)
def debug_script(src, pm=False, globs=None):
"Debug a test script. `src` is the script, as a string."
import pdb
# Note that tempfile.NameTemporaryFile() cannot be used. As the
# docs say, a file so created cannot be opened by name a second time
# on modern Windows boxes, and execfile() needs to open it.
srcfilename = tempfile.mktemp(".py", "doctestdebug")
f = open(srcfilename, 'w')
f.write(src)
f.close()
try:
if globs:
globs = globs.copy()
else:
globs = {}
if pm:
try:
execfile(srcfilename, globs, globs)
except:
print sys.exc_info()[1]
pdb.post_mortem(sys.exc_info()[2])
else:
# Note that %r is vital here. '%s' instead can, e.g., cause
# backslashes to get treated as metacharacters on Windows.
pdb.run("execfile(%r)" % srcfilename, globs, globs)
finally:
os.remove(srcfilename)
def debug(module, name, pm=False):
"""Debug a single doctest docstring.
Provide the module (or dotted name of the module) containing the
test to be debugged and the name (within the module) of the object
with the docstring with tests to be debugged.
"""
module = _normalize_module(module)
testsrc = testsource(module, name)
debug_script(testsrc, pm, module.__dict__)
######################################################################
## 10. Example Usage
######################################################################
class _TestClass:
"""
A pointless class, for sanity-checking of docstring testing.
Methods:
square()
get()
>>> _TestClass(13).get() + _TestClass(-12).get()
1
>>> hex(_TestClass(13).square().get())
'0xa9'
"""
def __init__(self, val):
"""val -> _TestClass object with associated value val.
>>> t = _TestClass(123)
>>> print t.get()
123
"""
self.val = val
def square(self):
"""square() -> square TestClass's associated value
>>> _TestClass(13).square().get()
169
"""
self.val = self.val ** 2
return self
def get(self):
"""get() -> return TestClass's associated value.
>>> x = _TestClass(-42)
>>> print x.get()
-42
"""
return self.val
__test__ = {"_TestClass": _TestClass,
"string": r"""
Example of a string object, searched as-is.
>>> x = 1; y = 2
>>> x + y, x * y
(3, 2)
""",
"bool-int equivalence": r"""
In 2.2, boolean expressions displayed
0 or 1. By default, we still accept
them. This can be disabled by passing
DONT_ACCEPT_TRUE_FOR_1 to the new
optionflags argument.
>>> 4 == 4
1
>>> 4 == 4
True
>>> 4 > 4
0
>>> 4 > 4
False
""",
"blank lines": r"""
Blank lines can be marked with <BLANKLINE>:
>>> print 'foo\n\nbar\n'
foo
<BLANKLINE>
bar
<BLANKLINE>
""",
"ellipsis": r"""
If the ellipsis flag is used, then '...' can be used to
elide substrings in the desired output:
>>> print range(1000) #doctest: +ELLIPSIS
[0, 1, 2, ..., 999]
""",
"whitespace normalization": r"""
If the whitespace normalization flag is used, then
differences in whitespace are ignored.
>>> print range(30) #doctest: +NORMALIZE_WHITESPACE
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28, 29]
""",
}
def _test():
r = unittest.TextTestRunner()
r.run(DocTestSuite())
if __name__ == "__main__":
_test()
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Extension to use doctest tests as unit tests
This module provides a DocTestSuite contructor for converting doctest
tests to unit tests.
$Id$
"""
from doctest import DocFileSuite, DocTestSuite
from doctest import debug_src, debug
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Support for testing logging code
If you want to test that your code generates proper log output, you
can create and install a handler that collects output:
>>> handler = InstalledHandler('foo.bar')
The handler is installed into loggers for all of the names passed. In
addition, the logger level is set to 1, which means, log
everything. If you want to log less than everything, you can provide a
level keyword argument. The level setting effects only the named
loggers.
Then, any log output is collected in the handler:
>>> logging.getLogger('foo.bar').exception('eek')
>>> logging.getLogger('foo.bar').info('blah blah')
>>> for record in handler.records:
... print record.name, record.levelname
... print ' ', record.getMessage()
foo.bar ERROR
eek
foo.bar INFO
blah blah
A similar effect can be gotten by just printing the handler:
>>> print handler
foo.bar ERROR
eek
foo.bar INFO
blah blah
After checking the log output, you need to uninstall the handler:
>>> handler.uninstall()
At which point, the handler won't get any more log output.
Let's clear the handler:
>>> handler.clear()
>>> handler.records
[]
And then log something:
>>> logging.getLogger('foo.bar').info('blah')
and, sure enough, we still have no output:
>>> handler.records
[]
$Id$
"""
import logging
class Handler(logging.Handler):
def __init__(self, *names, **kw):
logging.Handler.__init__(self)
self.names = names
self.records = []
self.setLoggerLevel(**kw)
def setLoggerLevel(self, level=1):
self.level = level
self.oldlevels = {}
def emit(self, record):
self.records.append(record)
def clear(self):
del self.records[:]
def install(self):
for name in self.names:
logger = logging.getLogger(name)
self.oldlevels[name] = logger.level
logger.setLevel(self.level)
logger.addHandler(self)
def uninstall(self):
for name in self.names:
logger = logging.getLogger(name)
logger.setLevel(self.oldlevels[name])
logger.removeHandler(self)
def __str__(self):
return '\n'.join(
[("%s %s\n %s" %
(record.name, record.levelname,
'\n'.join([line
for line in record.getMessage().split('\n')
if line.strip()])
)
)
for record in self.records]
)
class InstalledHandler(Handler):
def __init__(self, *names):
Handler.__init__(self, *names)
self.install()
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""logging handler for tests that check logging output.
$Id$
"""
import logging
class Handler(logging.Handler):
"""Handler for use with unittest.TestCase objects.
The handler takes a TestCase instance as a constructor argument.
It can be registered with one or more loggers and collects log
records they generate.
The assertLogsMessage() and failIfLogsMessage() methods can be
used to check the logger output and causes the test to fail as
appropriate.
"""
def __init__(self, testcase, propagate=False):
logging.Handler.__init__(self)
self.records = []
# loggers stores (logger, propagate) tuples
self.loggers = []
self.closed = False
self.propagate = propagate
self.testcase = testcase
def close(self):
"""Remove handler from any loggers it was added to."""
if self.closed:
return
for logger, propagate in self.loggers:
logger.removeHandler(self)
logger.propagate = propagate
self.closed = True
def add(self, name):
"""Add handler to logger named name."""
logger = logging.getLogger(name)
old_prop = logger.propagate
logger.addHandler(self)
if self.propagate:
logger.propagate = 1
else:
logger.propagate = 0
self.loggers.append((logger, old_prop))
def emit(self, record):
self.records.append(record)
def assertLogsMessage(self, msg, level=None):
for r in self.records:
if r.getMessage() == msg:
if level is not None and r.levelno == level:
return
msg = "No log message contained %r" % msg
if level is not None:
msg += " at level %d" % level
self.testcase.fail(msg)
def failIfLogsMessage(self, msg):
for r in self.records:
if r.getMessage() == msg:
self.testcase.fail("Found log message %r" % msg)
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Tests for the testing framework.
$Id$
"""
import unittest
from zope.testing.doctestunit import DocTestSuite
def test_suite():
return unittest.TestSuite((
DocTestSuite('zope.testing.loggingsupport'),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment