diff options
| author | Tres Seaver <tseaver@palladion.com> | 2012-03-26 20:56:58 +0000 |
|---|---|---|
| committer | Tres Seaver <tseaver@palladion.com> | 2012-03-26 20:56:58 +0000 |
| commit | bc4d8bc1569c9d4dc28c62272a5acbaebc04cdfe (patch) | |
| tree | 605f4701a12fc03dc084e6115d6f07ef1a152029 /docs | |
| parent | 2fd652d0d095b79e2e6365b00c739f589bf631a5 (diff) | |
| download | zope-interface-bc4d8bc1569c9d4dc28c62272a5acbaebc04cdfe.tar.gz | |
Merge from LP branch.
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/Makefile | 153 | ||||
| -rw-r--r-- | docs/README.rst | 829 | ||||
| -rw-r--r-- | docs/README.ru.rst | 803 | ||||
| -rw-r--r-- | docs/adapter.rst | 543 | ||||
| -rw-r--r-- | docs/adapter.ru.rst | 540 | ||||
| -rw-r--r-- | docs/conf.py | 246 | ||||
| -rw-r--r-- | docs/foodforthought.rst | 61 | ||||
| -rw-r--r-- | docs/human.rst | 152 | ||||
| -rw-r--r-- | docs/human.ru.rst | 156 | ||||
| -rw-r--r-- | docs/index.rst | 38 | ||||
| -rw-r--r-- | docs/make.bat | 190 | ||||
| -rw-r--r-- | docs/verify.rst | 127 |
12 files changed, 3838 insertions, 0 deletions
diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d34ea74 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make <target>' where <target> is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/zopeinterface.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/zopeinterface.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/zopeinterface" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/zopeinterface" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/README.rst b/docs/README.rst new file mode 100644 index 0000000..253d1ed --- /dev/null +++ b/docs/README.rst @@ -0,0 +1,829 @@ +========== +Interfaces +========== + +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 interface's documentation:: + + >>> IFoo.__doc__ + 'Foo blah blah' + +and its name:: + + >>> IFoo.__name__ + 'IFoo' + +and even its 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 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 they implement. + + We can generalize this to factories. For any callable object we + can declare that it produces objects that provide some interfaces + by saying that the factory implements the interfaces. + +Now that we've defined these terms, we can talk about the API for +declaring interfaces. + +Declaring implemented 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-callable +object:: + + >>> IFoo.implementedBy(foo) + Traceback (most recent call last): + ... + TypeError: ('ImplementedBy called for non-factory', Foo(None)) + + >>> list(zope.interface.implementedBy(foo)) + Traceback (most recent call last): + ... + TypeError: ('ImplementedBy called for non-factory', 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 implemented by other factories (besides +classes). We do this using a Python-2.4-style decorator named +`implementer`. In versions of Python before 2.4, this looks like:: + + >>> def yfoo(y): + ... foo = Foo() + ... foo.y = y + ... return foo + >>> yfoo = zope.interface.implementer(IFoo)(yfoo) + + >>> list(zope.interface.implementedBy(yfoo)) + [<InterfaceClass __main__.IFoo>] + +Note that the implementer decorator may modify it's argument. Callers +should not assume that a new object is created. + +Using implementer also works on callable objects. This is used by +zope.formlib, as an example. + + >>> class yfactory: + ... def __call__(self, y): + ... foo = Foo() + ... foo.y = y + ... return foo + >>> yfoo = yfactory() + >>> yfoo = zope.interface.implementer(IFoo)(yfoo) + + >>> list(zope.interface.implementedBy(yfoo)) + [<InterfaceClass __main__.IFoo>] + +XXX: Double check and update these version numbers: + +In zope.interface 3.5.2 and lower, the implementer decorator can not +be used for classes, but in 3.6.0 and higher it can: + + >>> Foo = zope.interface.implementer(IFoo)(Foo) + >>> list(zope.interface.providedBy(Foo())) + [<InterfaceClass __main__.IFoo>] + +Note that class decorators using the @implementer(IFoo) syntax are only +supported in Python 2.6 and later. + + +Declaring provided interfaces +----------------------------- + +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 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 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 of attribute specifications +--------------------------------------- + +An interface may override attribute definitions from 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 inheritance applies to both +declarations and specifications. Declarations actually extend the +interfaces that they declare:: + + >>> class Baz(object): + ... zope.interface.implements(IBaz) + + >>> baz_implements = zope.interface.implementedBy(Baz) + >>> baz_implements.__bases__ + (<InterfaceClass __main__.IBaz>, <implementedBy ...object>) + + >>> 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>, + <implementedBy ...object>) + + +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> + +Tagged values can also be defined from within an interface definition:: + + >>> class IWithTaggedValues(zope.interface.Interface): + ... zope.interface.taggedValue('squish', 'squash') + >>> IWithTaggedValues.getTaggedValue('squish') + 'squash' + +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:: + + >>> from zope.interface.exceptions import Invalid + >>> errors = [] + >>> try: + ... IRange.validateInvariants(Range(2,1), errors) + ... except Invalid, e: + ... str(e) + '[RangeError(Range(2, 1))]' + +And the list will be filled with the individual exceptions:: + + >>> errors + [RangeError(Range(2, 1))] + + + >>> del errors[:] + +Adaptation +========== + +Interfaces can be called to perform adaptation. + +The semantics are based on those of the PEP 246 adapt function. + +If an object cannot be adapted, then a TypeError is raised:: + + >>> class I(zope.interface.Interface): + ... pass + + >>> I(0) + Traceback (most recent call last): + ... + TypeError: ('Could not adapt', 0, <InterfaceClass __main__.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 __main__.I>) + +__adapt__ +--------- + + >>> class I(zope.interface.Interface): + ... pass + +Interfaces implement the PEP 246 __adapt__ method. + +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:: + + >>> 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) + + +.. [#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. + but maybe it should do some checks. diff --git a/docs/README.ru.rst b/docs/README.ru.rst new file mode 100644 index 0000000..a284c9a --- /dev/null +++ b/docs/README.ru.rst @@ -0,0 +1,803 @@ +========== +Интерфейсы +========== + +.. contents:: + +Интерфейсы - это объекты специфицирующие (документирующие) внешнее поведение +объектов которые их "предоставляют". Интерфейсы определяют поведение через +следующие составляющие: + +- Неформальную документацию в строках документации + +- Определения атрибутов + +- Инварианты - условия, которые должны соблюдаться для объектов предоставляющих + интерфейс + +Определения атрибутов описывают конкретные атрибуты. Они определяют +имя атрибута и предоставляют документацию и ограничения для значений +атрибута. Определения атрибутов могут быть заданы несколькими путями +как мы увидим ниже. + +Определение интерфейсов +======================= + +Интерфейсы определяются с использованием ключевого слова class:: + + >>> 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""" + +В примере выше мы создали интерфейс `IFoo`. Мы наследуем его от +класса `zope.interface.Interface`, который является родительским интерфейсом +для всех интерфейсов, как `object` - это родительский класс для всех новых +классов [#create]_. Данный интерфейс не является классом, а является +Интерфейсом, экземпляром `InterfaceClass`:: + + >>> type(IFoo) + <class 'zope.interface.interface.InterfaceClass'> + +Мы можем запросить у интерфейса его документацию:: + + >>> IFoo.__doc__ + 'Foo blah blah' + +и его имя:: + + >>> IFoo.__name__ + 'IFoo' + +и даже модуль в котором он определен:: + + >>> IFoo.__module__ + '__main__' + +Наш интерфейс определяет два атрибута: + +`x` + Это простейшая форма определения атрибутов. Определяются имя + и строка документации. Формально здесь не определяется ничего более. + +`bar` + Это метод. Методы определяются как обычные функции. Метод - это просто + атрибут который должен быть вызываемым с указанием сигнатуры, + предоставляемой определением функции. + + Надо отметить, что аргумент `self` не указывается для `bar`. Интерфейс + документирует как объект *используется*. Когда методы экземпляров классов + вызываются мы не передаем аргумент `self`, таким образом аргумент `self` + не включается и в сигнатуру интерфейса. Аргумент `self` в методах + экземпляров классов на самом деле деталь реализации экземпляров классов + в Python. Другие объекты кроме экземпляров классов могут предоставлять + интерфейсы и их методы могут не быть методами экземпляров классов. Для + примера модули могут предоставлять интерфейсы и их методы обычно просто + функции. Даже экземпляры могут иметь методы не являющиеся методами + экземпляров класса. + +Мы можем получить доступ к атрибутам определенным интерфейсом используя +синтаксис доступа к элементам массива:: + + >>> 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') + +Можно использовать `in` для определения содержит ли интерфейс +определенное имя:: + + >>> 'x' in IFoo + True + +Мы можем использовать итератор для интерфейсов что бы получить все имена +которые интерфейсы определяют:: + + >>> names = list(IFoo) + >>> names.sort() + >>> names + ['bar', 'x'] + +Надо помнить, что интерфейсы не являются классами. Мы не можем получить +доступ к определениям атрибутов через доступ к атрибутам интерфейсов:: + + >>> IFoo.x + Traceback (most recent call last): + File "<stdin>", line 1, in ? + AttributeError: 'InterfaceClass' object has no attribute 'x' + +Методы также предоставляют доступ к сигнатуре метода:: + + >>> bar = IFoo['bar'] + >>> bar.getSignatureString() + '(q, r=None)' + +Объявление интерфейсов +====================== + +Определив интерфейс мы можем теперь *объявить*, что объекты предоставляют их. +Перед описанием деталей определим некоторые термины: + +*предоставлять* + Мы говорим, что объекты *предоставляют* интерфейсы. Если объект + предоставляет интерфейс, тогда интерфейс специфицирует поведение объекта. + Другими словами, интерфейсы специфицируют поведение объектов которые + предоставляют их. + +*реализовать* + Мы обычно говорим что классы *реализуют* интерфейсы. Если класс + реализует интерфейс, тогда экземпляры этого класса предоставляют + данный интерфейс. Объекты предоставляют интерфейсы которые их классы + реализуют [#factory]_. (Объекты также могут предоставлять интерфейсы напрямую + плюс к тем которые реализуют их классы.) + + Важно помнить, что классы обычно не предоставляют интерфейсы которые + они реализуют. + + Мы можем обобщить это до фабрик. Для любого вызываемого объекта мы можем + объявить что он производит объекты которые предоставляют какие-либо + интерфейсы сказав, что фабрика реализует данные интерфейсы. + +Теперь после того как мы определили эти термины мы можем поговорить об +API для объявления интерфейсов. + +Объявление реализуемых интерфейсов +---------------------------------- + +Наиболее часто используемый путь для объявления интерфейсов - это использование +функции implements в определении класса:: + + >>> 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 + +В этом примере мы объявили, что `Foo` реализует `IFoo`. Это значит, что +экземпляры `Foo` предоставляют `IFoo`. После данного объявления есть +несколько путей для анализа объявлений. Во-первых мы можем спросить +что интерфейс реализован классом:: + + >>> IFoo.implementedBy(Foo) + True + +Также мы можем спросить если интерфейс предоставляется объектами класса:: + + >>> foo = Foo() + >>> IFoo.providedBy(foo) + True + +Конечно `Foo` не предоставляет `IFoo`, он реализует его:: + + >>> IFoo.providedBy(Foo) + False + +Мы можем также узнать какие интерфейсы реализуются объектами:: + + >>> list(zope.interface.implementedBy(Foo)) + [<InterfaceClass __main__.IFoo>] + +Это ошибка спрашивать про интерфейсы реализуемые не вызываемым объектом:: + + >>> IFoo.implementedBy(foo) + Traceback (most recent call last): + ... + TypeError: ('ImplementedBy called for non-factory', Foo(None)) + + >>> list(zope.interface.implementedBy(foo)) + Traceback (most recent call last): + ... + TypeError: ('ImplementedBy called for non-factory', Foo(None)) + +Также можно узнать какие интерфейсы предоставляются объектами:: + + >>> list(zope.interface.providedBy(foo)) + [<InterfaceClass __main__.IFoo>] + >>> list(zope.interface.providedBy(Foo)) + [] + +Мы можем объявить интерфейсы реализуемые другими фабриками (кроме классов). +Это можно сделать используя декоратор `implementer` (в стиле Python 2.4). +Для версий Python ниже 2.4 это будет выглядеть следующим образом:: + + >>> def yfoo(y): + ... foo = Foo() + ... foo.y = y + ... return foo + >>> yfoo = zope.interface.implementer(IFoo)(yfoo) + + >>> list(zope.interface.implementedBy(yfoo)) + [<InterfaceClass __main__.IFoo>] + +Надо заметить, что декоратор implementer может модифицировать свои аргументы. +Вызывающая сторона не должна предполагать, что всегда будет создаваться +новый объект. + +XXX: Double check and update these version numbers, and translate to russian: + +In zope.interface 3.5.1 and lower, the implementer decorator can not +be used for classes, but in 3.5.2 and higher it can: + + >>> Foo = zope.interface.implementer(IFoo)(Foo) + >>> list(zope.interface.providedBy(Foo())) + [<InterfaceClass __main__.IFoo>] + +Note that class decorators using the @implementer(IFoo) syntax are only +supported in Python 2.6 and later. + + +Объявление предоставляемых интерфейсов +-------------------------------------- + +Мы можем объявлять интерфейсы напрямую предоставляемые объектами. Предположим +что мы хотим документировать что делает метод `__init__` класса `Foo`. Это +*точно* не часть `IFoo`. Обычно мы не должны напрямую вызывать метод `__init__` +для экземпляров Foo. Скорее метод `__init__` является частью метода `__call__` +класса `Foo`:: + + >>> class IFooFactory(zope.interface.Interface): + ... """Create foos""" + ... + ... def __call__(x=None): + ... """Create a foo + ... + ... The argument provides the initial value for x ... + ... """ + +У нас есть класс предоставляющий данный интерфейс, таким образом мы можем +объявить интерфейс класса:: + + >>> zope.interface.directlyProvides(Foo, IFooFactory) + +Теперь мы видим, что Foo уже предоставляет интерфейсы:: + + >>> list(zope.interface.providedBy(Foo)) + [<InterfaceClass __main__.IFooFactory>] + >>> IFooFactory.providedBy(Foo) + True + +Объявление интерфейсов класса достаточно частая операция и для нее есть +специальная функция объявления `classProvides`, которая позволяет объявлять +интерфейсы при определении класса:: + + >>> 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 + +Похожая функция `moduleProvides` поддерживает объявление интерфейсов при +определении модуля. Для примера смотрите использование вызова +`moduleProvides` в `zope.interface.__init__`, который объявляет, что +пакет `zope.interface` предоставляет `IInterfaceDeclaration`. + +Иногда мы хотим объявить интерфейсы экземпляров, даже если эти экземпляры +уже берут интерфейсы от своих классов. Предположим, что мы создаем новый +интерфейс `ISpecial`:: + + >>> class ISpecial(zope.interface.Interface): + ... reason = zope.interface.Attribute("Reason why we're special") + ... def brag(): + ... "Brag about being special" + +Мы можем сделать созданный экземпляр foo специальным предоставив атрибуты +`reason` и `brag`:: + + >>> foo.reason = 'I just am' + >>> def brag(): + ... return "I'm special!" + >>> foo.brag = brag + >>> foo.reason + 'I just am' + >>> foo.brag() + "I'm special!" + +и объявив интерфейс:: + + >>> zope.interface.directlyProvides(foo, ISpecial) + +таким образом новый интерфейс включается в список предоставляемых интерфейсов:: + + >>> ISpecial.providedBy(foo) + True + >>> list(zope.interface.providedBy(foo)) + [<InterfaceClass __main__.ISpecial>, <InterfaceClass __main__.IFoo>] + +Мы также можем определить, что интерфейсы напрямую предоставляются +объектами:: + + >>> list(zope.interface.directlyProvidedBy(foo)) + [<InterfaceClass __main__.ISpecial>] + + >>> newfoo = Foo() + >>> list(zope.interface.directlyProvidedBy(newfoo)) + [] + +Наследуемые объявления +---------------------- + +Обычно объявления наследуются:: + + >>> 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>] + +Иногда мы не хотим наследовать объявления. В этом случае мы можем +использовать `implementsOnly` вместо `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>] + +Внешние объявления +------------------ + +Обычно мы создаем объявления реализации как часть объявления класса. Иногда +мы можем захотеть создать объявления вне объявления класса. Для примера, +мы можем хотеть объявить интерфейсы для классов которые писали не мы. +Для этого может использоваться функция `classImplements`:: + + >>> class C: + ... pass + + >>> zope.interface.classImplements(C, IFoo) + >>> list(zope.interface.implementedBy(C)) + [<InterfaceClass __main__.IFoo>] + +Мы можем использовать `classImplementsOnly` для исключения наследуемых +интерфейсов:: + + >>> class C(Foo): + ... pass + + >>> zope.interface.classImplementsOnly(C, ISpecial) + >>> list(zope.interface.implementedBy(C)) + [<InterfaceClass __main__.ISpecial>] + +Объекты объявлений +------------------ + +Когда мы объявляем интерфейсы мы создаем объект *объявления*. Когда мы +запрашиваем объявления возвращается объект объявления:: + + >>> type(zope.interface.implementedBy(Special)) + <class 'zope.interface.declarations.Implements'> + +Объекты объявления и объекты интерфейсов во многом похожи друг на друга. +На самом деле они даже имеют общий базовый класс. Важно понять, что они могут +использоваться там где в объявлениях ожидаются интерфейсы. Вот простой +пример:: + + >>> 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 + +Объявление здесь практически такое же как +``zope.interface.implements(ISpecial)``, отличие только в порядке +интерфейсов в итоговом объявления:: + + >>> list(zope.interface.implementedBy(Special2)) + [<InterfaceClass __main__.IFoo>, <InterfaceClass __main__.ISpecial>] + +Наследование интерфейсов +======================== + +Интерфейсы могут расширять другие интерфейсы. Они делают это просто +показывая эти интерфейсы как базовые:: + + >>> 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'] + +Заметим, что `IBaz` переопределяет eek:: + + >>> IBlat['eek'].__doc__ + 'eek blah blah' + >>> IBaz['eek'].__doc__ + 'eek in baz blah' + +Мы были осторожны переопределяя eek совместимым путем. Когда интерфейс +расширяется, расширенный интерфейс должен быть совместимым [#compat]_ с +расширяемыми интерфейсами. + +Мы можем запросить расширяет ли один из интерфейсов другой:: + + >>> IBaz.extends(IFoo) + True + >>> IBlat.extends(IFoo) + False + +Заметим, что интерфейсы не расширяют сами себя:: + + >>> IBaz.extends(IBaz) + False + +Иногда мы можем хотеть что бы они расширяли сами себя, но вместо этого +мы можем использовать `isOrExtends`:: + + >>> IBaz.isOrExtends(IBaz) + True + >>> IBaz.isOrExtends(IFoo) + True + >>> IFoo.isOrExtends(IBaz) + False + +Когда мы применяем итерацию к интерфейсу мы получаем все имена которые он +определяет включая имена определенные для базовых интерфейсов. Иногда +мы хотим получить *только* имена определенные интерфейсом напрямую. +Для этого мы используем метод `names`:: + + >>> list(IBaz.names()) + ['eek'] + +Наследование в случае определения атрибутов +-------------------------------------------- + +Интерфейс может переопределять определения атрибутов из базовых интерфейсов. +Если два базовых интерфейса определяют один и тот же атрибут атрибут +наследуется от более специфичного интерфейса. Для примера:: + + >>> 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 для foo будет из IBase2 т.к. IBase2 более специфичен для +IBase:: + + >>> ISub['foo'].__doc__ + 'base2 foo doc' + +Заметим, что это отличается от поиска в глубину. + +Иногда полезно узнать, что интерфейс определяет атрибут напрямую. Мы можем +использовать метод direct для получения напрямую определенных атрибутов:: + + >>> IBase.direct('foo').__doc__ + 'base foo doc' + + >>> ISub.direct('foo') + +Спецификации +------------ + +Интерфейсы и объявления - это специальные случаи спецификаций. Описание +выше для наследования интерфейсов можно применить и к объявлениям и +к спецификациям. Объявления фактически расширяют интерфейсы которые они +объявляют:: + + >>> class Baz(object): + ... zope.interface.implements(IBaz) + + >>> baz_implements = zope.interface.implementedBy(Baz) + >>> baz_implements.__bases__ + (<InterfaceClass __main__.IBaz>, <implementedBy ...object>) + + >>> baz_implements.extends(IFoo) + True + + >>> baz_implements.isOrExtends(IFoo) + True + >>> baz_implements.isOrExtends(baz_implements) + True + +Спецификации (интерфейсы и объявления) предоставляют атрибут `__sro__` +который описывает спецификацию и всех ее предков:: + + >>> baz_implements.__sro__ + (<implementedBy __main__.Baz>, + <InterfaceClass __main__.IBaz>, + <InterfaceClass __main__.IFoo>, + <InterfaceClass __main__.IBlat>, + <InterfaceClass zope.interface.Interface>, + <implementedBy ...object>) + +Помеченные значения +=================== + +Интерфейсы и описания атрибутов поддерживают механизм расширения +заимствованный из UML и называемый "помеченные значения" который позволяет +сохранять дополнительные данные:: + + >>> 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'] + +Атрибуты функций конвертируются в помеченные значения когда создаются +определения атрибутов метода:: + + >>> class IBazFactory(zope.interface.Interface): + ... def __call__(): + ... "create one" + ... __call__.return_type = IBaz + + >>> IBazFactory['__call__'].getTaggedValue('return_type') + <InterfaceClass __main__.IBaz> + +Помеченные значения также могут быть определены внутри определения +интерфейса:: + + >>> class IWithTaggedValues(zope.interface.Interface): + ... zope.interface.taggedValue('squish', 'squash') + >>> IWithTaggedValues.getTaggedValue('squish') + 'squash' + +Инварианты +========== + +Интерфейсы могут описывать условия которые должны быть соблюдены для объектов +которые их предоставляют. Эти условия описываются используя один или более +инвариантов. Инварианты - это вызываемые объекты которые будут вызваны +с объектом предоставляющим интерфейс в качестве параметра. Инвариант +должен выкинуть исключение `Invalid` если условие не соблюдено. Например:: + + >>> 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) + +Определив этот инвариант мы можем использовать его в определении интерфейсов:: + + >>> class IRange(zope.interface.Interface): + ... min = zope.interface.Attribute("Lower bound") + ... max = zope.interface.Attribute("Upper bound") + ... + ... zope.interface.invariant(range_invariant) + +Интерфейсы имеют метод для проверки своих инвариантов:: + + >>> 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) + +В случае нескольких инвариантов мы можем захотеть остановить проверку после +первой ошибки. Если мы передадим в `validateInvariants` пустой список тогда +будет выкинуто единственное исключение `Invalid` со списком исключений +как аргументом:: + + >>> from zope.interface.exceptions import Invalid + >>> errors = [] + >>> try: + ... IRange.validateInvariants(Range(2,1), errors) + ... except Invalid, e: + ... str(e) + '[RangeError(Range(2, 1))]' + +И список будет заполнен индивидуальными исключениями:: + + >>> errors + [RangeError(Range(2, 1))] + + >>> del errors[:] + +Адаптация +========= + +Интерфейсы могут быть вызваны для осуществления адаптации. Эта семантика +основана на функции adapt из PEP 246. Если объект не может быть адаптирован +будет выкинут TypeError:: + + >>> class I(zope.interface.Interface): + ... pass + + >>> I(0) + Traceback (most recent call last): + ... + TypeError: ('Could not adapt', 0, <InterfaceClass __main__.I>) + +только если альтернативное значение не передано как второй аргумент:: + + >>> I(0, 'bob') + 'bob' + +Если объект уже реализует нужный интерфейс он будет возвращен:: + + >>> class C(object): + ... zope.interface.implements(I) + + >>> obj = C() + >>> I(obj) is obj + True + +Если объект реализует __conform__, тогда она будет использована:: + + >>> class C(object): + ... zope.interface.implements(I) + ... def __conform__(self, proto): + ... return 0 + + >>> I(C()) + 0 + +Также если присутствуют функции для вызова адаптации (см. __adapt__) они будут +использованы:: + + >>> 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 __main__.I>) + + +__adapt__ +--------- + + >>> class I(zope.interface.Interface): + ... pass + +Интерфейсы реализуют метод __adapt__ из PEP 246. Этот метод обычно не +вызывается напрямую. Он вызывается архитектурой адаптации из PEP 246 и методом +__call__ интерфейсов. Метод адаптации отвечает за адаптацию объекта к +получателю. Версия по умолчанию возвращает None:: + + >>> I.__adapt__(0) + +если только переданный объект не предоставляет нужный интерфейс:: + + >>> class C(object): + ... zope.interface.implements(I) + + >>> obj = C() + >>> I.__adapt__(obj) is obj + True + +Функции для вызова адаптации могут быть добавлены (или удалены) для +предоставления адаптации "на заказ". Мы установим глупую функцию которая +адаптирует 0 к 42. Мы устанавливаем функцию просто добавляя ее к списку +adapter_hooks:: + + >>> 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 + +Функции должны возвращать либо адаптер, либо None если адаптер не найден. +Функции могут быть удалены удалением их из списка:: + + >>> adapter_hooks.remove(adapt_0_to_42) + >>> I.__adapt__(0) + + +.. [#create] Основная причина по которой мы наследуемся от `Interface` - это + что бы быть уверенными в том, что ключевое слово class будет + создавать интерфейс, а не класс. + + Есть возможность создать интерфейсы вызвав специальный + класс интерфейса напрямую. Делая это, возможно (и в редких + случаях полезно) создать интерфейсы которые не наследуются + от `Interface`. Однако использование этой техники выходит + за рамки данного документа. + +.. [#factory] Классы - это фабрики. Они могут быть вызваны для создания + своих экземпляров. Мы ожидаем что в итоге мы расширим + концепцию реализации на другие типы фабрик, таким образом + мы сможем объявлять интерфейсы предоставляемые созданными + фабриками объектами. + +.. [#compat] Цель - заменяемость. Объект который предоставляет расширенный + интерфейс должен быть заменяем в качестве объектов которые + предоставляют расширяемый интерфейс. В нашем примере объект + который предоставляет IBaz должен быть используемым и + в случае если ожидается объект который предоставляет IBlat. + + Реализация интерфейса не требует этого. Но возможно в дальнейшем + она должна будет делать какие-либо проверки. diff --git a/docs/adapter.rst b/docs/adapter.rst new file mode 100644 index 0000000..298a862 --- /dev/null +++ b/docs/adapter.rst @@ -0,0 +1,543 @@ +================ +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 + +Finding out what, if anything, is registered +-------------------------------------------- + +We can ask if there is an adapter registered for a collection of +interfaces. This is different than lookup, because it looks for an +exact match. + + >>> print registry.registered([IR1], IP1) + 11 + + >>> print registry.registered([IR1], IP2) + 12 + + >>> print registry.registered([IR1], IP2, 'bob') + Bob's 12 + + + >>> print registry.registered([IR2], IP1) + 21 + + >>> print registry.registered([IR2], IP2) + None + +In the last example, None was returned because nothing was registered +exactly for the given interfaces. + +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' + +Dict adapters +------------- + +At some point it was impossible to register dictionary-based adapters due a +bug. Let's make sure this works now: + + >>> adapter = {} + >>> registry.register((), IQ, '', adapter) + >>> registry.lookup((), IQ, '') is adapter + True + +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() + >>> assert 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() + >>> assert adapters == [(u'', '1q22'), (u'bob', '1q2 for bob')] + +And even null adapters:: + + >>> registry.register([], IP2, 'bob', 3) + >>> adapters = list(registry.lookupAll([], IP1)) + >>> adapters.sort() + >>> assert 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. + +You can have multiple subscribers for the same specification:: + + >>> registry.subscribe([IR1], IP2, 'sub12 2') + >>> registry.subscriptions([IR1], IP2) + ['sub12 1', 'sub12 2'] + +If subscribers are registered for the same required interfaces, they +are returned in the order of definition. + +You can register subscribers for all specifications using None:: + + >>> registry.subscribe([None], IP1, 'sub_1') + >>> registry.subscriptions([IR2], IP1) + ['sub_1', 'sub12 1', 'sub12 2'] + +Note that the new subscriber is returned first. Subscribers defined +for less general required interfaces are returned before subscribers +for more general interfaces. + +Subscriptions may be combined over multiple compatible specifications:: + + >>> registry.subscriptions([IR2], IP1) + ['sub_1', 'sub12 1', 'sub12 2'] + >>> registry.subscribe([IR1], IP1, 'sub11') + >>> registry.subscriptions([IR2], IP1) + ['sub_1', 'sub12 1', 'sub12 2', 'sub11'] + >>> registry.subscribe([IR2], IP2, 'sub22') + >>> registry.subscriptions([IR2], IP1) + ['sub_1', 'sub12 1', 'sub12 2', 'sub11', 'sub22'] + >>> registry.subscriptions([IR2], IP2) + ['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'] + >>> registry.subscriptions([IR1, IQ], IP2) + ['sub_q2', 'sub1q2'] + +You can have subscriptions that are indepenent of any specifications:: + + >>> list(registry.subscriptions([], IP1)) + [] + + >>> registry.subscribe([], IP2, 'sub2') + >>> registry.subscriptions([], IP1) + ['sub2'] + >>> registry.subscribe([], IP1, 'sub1') + >>> registry.subscriptions([], IP1) + ['sub2', 'sub1'] + >>> registry.subscriptions([], IP2) + ['sub2'] + +Unregistering subscribers +------------------------- + +We can unregister subscribers. When unregistering a subscriber, we +can unregister a specific subscriber:: + + >>> registry.unsubscribe([IR1], IP1, 'sub11') + >>> registry.subscriptions([IR1], IP1) + ['sub_1', 'sub12 1', 'sub12 2'] + +If we don't specify a value, then all subscribers matching the given +interfaces will be unsubscribed: + + >>> registry.unsubscribe([IR1], IP2) + >>> registry.subscriptions([IR1], IP1) + ['sub_1'] + + +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] + +adapter factory subcribers can't return None values:: + + >>> def M3(x, y): + ... return None + + >>> registry.subscribe([IR, IQ], IM, M3) + >>> subscribers = registry.subscribers((x, q), IM) + >>> len(subscribers) + 2 + +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 its 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 diff --git a/docs/adapter.ru.rst b/docs/adapter.ru.rst new file mode 100644 index 0000000..30c2782 --- /dev/null +++ b/docs/adapter.ru.rst @@ -0,0 +1,540 @@ +================ +Реестр адаптеров +================ + +.. contents:: + +Реестры адаптеров предоставляют возможность для регистрации объектов которые +зависят от одной, или нескольких спецификаций интерфейсов и предоставляют +(возможно не напрямую) какой-либо интерфейс. В дополнение, регистрации имеют +имена. (Можно думать об именах как о спецификаторах предоставляемого +интерфейса.) + +Термин "спецификация интерфейса" ссылается и на интерфейсы и на определения +интерфейсов, такие как определения интерфейсов реализованных некоторым классом. + +Одиночные адаптеры +================== + +Давайте рассмотрим простой пример использующий единственную требуемую +спецификацию:: + + >>> 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() + +Мы зарегистрируем объект который зависит от IR1 и "предоставляет" IP2:: + + >>> registry.register([IR1], IP2, '', 12) + +После регистрации мы можем запросить объект снова:: + + >>> registry.lookup([IR1], IP2, '') + 12 + +Заметьте, что мы используем целое в этом примере. В реальных приложениях вы +можете использовать объекты которые на самом деле зависят или предоставляют +интерфейсы. Реестр не заботиться о том, что регистрируется и таким образом мы +можем использовать целые, или строки что бы упростить наши примеры. Здесь есть +одно исключение. Регистрация значения None удаляет регистрацию для любого +зарегистрированного прежде значения. + +Если объект зависит от спецификации он может быть запрошен с помощью +спецификации которая расширяет спецификацию от которой он зависит:: + + >>> class IR2(IR1): + ... pass + >>> registry.lookup([IR2], IP2, '') + 12 + +Мы можем использовать класс реализующий спецификацию для запроса объекта:: + + >>> class C2: + ... zope.interface.implements(IR2) + + >>> registry.lookup([zope.interface.implementedBy(C2)], IP2, '') + 12 + +и объект может быть запрошен для интерфейсов которые предоставляемый объектом +интерфейс расширяет:: + + >>> registry.lookup([IR1], IP1, '') + 12 + >>> registry.lookup([IR2], IP1, '') + 12 + +Но если вы требуете спецификацию которая не расширяет спецификацию от которой +зависит объект, вы не получите ничего:: + + >>> registry.lookup([zope.interface.Interface], IP1, '') + +Между прочим, вы можете передать значение по умолчанию при запросе:: + + >>> registry.lookup([zope.interface.Interface], IP1, '', 42) + 42 + +Если вы пробуете получить интерфейс который объект не предоставляет вы также +не получите ничего:: + + >>> class IP3(IP2): + ... pass + >>> registry.lookup([IR1], IP3, '') + +Вы также не получите ничего если вы используете неверное имя:: + + >>> registry.lookup([IR1], IP1, 'bob') + >>> registry.register([IR1], IP2, 'bob', "Bob's 12") + >>> registry.lookup([IR1], IP1, 'bob') + "Bob's 12" + +Вы можете не использовать имя при запросе:: + + >>> registry.lookup([IR1], IP1) + 12 + +Если мы регистрируем объект который предоставляет IP1:: + + >>> registry.register([IR1], IP1, '', 11) + +тогда этот объект будет иметь преимущество перед O(12):: + + >>> registry.lookup([IR1], IP1, '') + 11 + +Также, если мы регистрируем объект для IR2 тогда он будет иметь преимущество +когда используется IR2:: + + >>> registry.register([IR2], IP1, '', 21) + >>> registry.lookup([IR2], IP1, '') + 21 + +Поиск того, что (если вообще что-то) зарегистрировано +----------------------------------------------------- + +Мы можем спросить есть-ли адаптер зарегистрированный для набора интерфейсов. +Это отличается от обычного запроса так как здесь мы ищем точное совпадение:: + + >>> print registry.registered([IR1], IP1) + 11 + + >>> print registry.registered([IR1], IP2) + 12 + + >>> print registry.registered([IR1], IP2, 'bob') + Bob's 12 + + + >>> print registry.registered([IR2], IP1) + 21 + + >>> print registry.registered([IR2], IP2) + None + +В последнем примере, None был возвращен потому, что для данного интерфейса +ничего не было зарегистрировано. + +lookup1 +------- + +Запрос одиночного адаптера - это наиболее частая операция и для нее есть +специализированная версия запроса которая получает на вход единственный +требуемый интерфейс:: + + >>> registry.lookup1(IR2, IP1, '') + 21 + >>> registry.lookup1(IR2, IP1) + 21 + +Адаптация на практике +--------------------- + +Реестр адаптеров предназначен для поддержки адаптации когда один объект +реализующий интерфейс адаптируется к другому объекту который поддерживает +другой интерфейс. Реестр адаптеров также поддерживает вычисление адаптеров. В +этом случае мы должны регистрировать фабрики для адаптеров:: + + >>> 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) + +В этом случае мы регистрируем класс как фабрику. Теперь мы можем вызвать +`queryAdapter` для получения адаптированного объекта:: + + >>> x = X() + >>> y = registry.queryAdapter(x, IP1) + >>> y.__class__.__name__ + 'Y' + >>> y.context is x + True + +Мы также можем регистрировать и запрашивать по имени:: + + >>> 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 + +Когда фабрика для адаптера возвращает `None` - это рассматривается как если бы +адаптер не был найден. Это позволяет нам избежать адаптации (по желанию) и дает +возможность фабрике адаптера определить возможна ли адаптация основываясь на +состоянии объекта который адаптируется:: + + >>> 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' + +Альтернативный метод для предоставления такой же функциональности как и +`queryAdapter()` - это `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 + +`adapter_hook()` просто меняет порядок аргументов для объекта и интерфейса. Это +используется для встраивания в механизм вызовов интерфейсов. + +Адаптеры по умолчанию +--------------------- + +Иногда вы можете захотеть предоставить адаптер который не будет ничего +адаптировать. Для этого нужно передать None как требуемый интерфейс:: + + >>> registry.register([None], IP1, '', 1) + +после этого вы можете использовать этот адаптер для интерфейсов для которых у +вас нет конкретного адаптера:: + + >>> class IQ(zope.interface.Interface): + ... pass + >>> registry.lookup([IQ], IP1, '') + 1 + +Конечно, конкретные адаптеры все еще используются когда необходимо:: + + >>> registry.lookup([IR2], IP1, '') + 21 + +Адаптеры классов +---------------- + +Вы можете регистрировать адаптеры для определений классов, что будет похоже на +регистрацию их для классов:: + + >>> registry.register([zope.interface.implementedBy(C2)], IP1, '', 'C21') + >>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '') + 'C21' + +Адаптеры для словарей +--------------------- + +В какой-то момент было невозможно регистрировать адаптеры основанные на +словарях из-за ошибки. Давайте удостоверимся что это теперь работает:: + + >>> adapter = {} + >>> registry.register((), IQ, '', adapter) + >>> registry.lookup((), IQ, '') is adapter + True + +Удаление регистрации +-------------------- + +Вы можете удалить регистрацию регистрируя None вместо объекта:: + + >>> registry.register([zope.interface.implementedBy(C2)], IP1, '', None) + >>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '') + 21 + +Конечно это значит, что None не может быть зарегистрирован. Это исключение к +утверждению выше о том, что реестр не заботиться о том, что регистрируется. + +Мульти-адаптеры +=============== + +Вы можете адаптировать несколько спецификаций:: + + >>> 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' + +Мульти-адаптация +---------------- + +Вы можете адаптировать несколько объектов:: + + >>> class Q: + ... zope.interface.implements(IQ) + +Как и с одиночными адаптерами, мы регистрируем фабрику которая возвращает +класс:: + + >>> 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) + +И затем мы можем вызвать `queryMultiAdapter` для вычисления адаптера:: + + >>> q = Q() + >>> m = registry.queryMultiAdapter((x, q), IM) + >>> m.__class__.__name__ + 'M' + >>> m.x is x and m.q is q + True + +и, конечно, мы можем использовать имена:: + + >>> 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 + +Адаптеры по умолчанию +--------------------- + +Как и для одиночных адаптеров вы можете определить адаптер по умолчанию передав +None вместо *первой* спецификации:: + + >>> registry.register([None, IQ], IP2, '', 'q2') + >>> registry.lookup([IS, IQ], IP2, '') + 'q2' + +Нулевые адаптеры +================ + +Вы можете также адаптировать без спецификации:: + + >>> registry.register([], IP2, '', 2) + >>> registry.lookup([], IP2, '') + 2 + >>> registry.lookup([], IP1, '') + 2 + +Перечисление именованных адаптеров +---------------------------------- + +Адаптеры имеют имена. Иногда это полезно для получения всех именованных +адаптеров для заданного интерфейса:: + + >>> adapters = list(registry.lookupAll([IR1], IP1)) + >>> adapters.sort() + >>> assert adapters == [(u'', 11), (u'bob', "Bob's 12")] + +Это работает также и для мульти-адаптеров:: + + >>> registry.register([IR1, IQ2], IP2, 'bob', '1q2 for bob') + >>> adapters = list(registry.lookupAll([IR2, IQ2], IP1)) + >>> adapters.sort() + >>> assert adapters == [(u'', '1q22'), (u'bob', '1q2 for bob')] + +И даже для нулевых адаптеров:: + + >>> registry.register([], IP2, 'bob', 3) + >>> adapters = list(registry.lookupAll([], IP1)) + >>> adapters.sort() + >>> assert adapters == [(u'', 2), (u'bob', 3)] + +Подписки +======== + +Обычно мы хотим запросить объект который наиболее близко соответствует +спецификации. Иногда мы хотим получить все объекты которые соответствуют +какой-либо спецификации. Мы используем подписки для этого. Мы подписываем +объекты для спецификаций и затем позже находим все подписанные объекты:: + + >>> registry.subscribe([IR1], IP2, 'sub12 1') + >>> registry.subscriptions([IR1], IP2) + ['sub12 1'] + +Заметьте, что в отличие от обычных адаптеров подписки не имеют имен. + +Вы можете иметь несколько подписчиков для одной спецификации:: + + >>> registry.subscribe([IR1], IP2, 'sub12 2') + >>> registry.subscriptions([IR1], IP2) + ['sub12 1', 'sub12 2'] + +Если подписчики зарегистрированы для одних и тех же требуемых интерфейсов, они +возвращаются в порядке определения. + +Вы можете зарегистрировать подписчики для всех спецификаций используя None:: + + >>> registry.subscribe([None], IP1, 'sub_1') + >>> registry.subscriptions([IR2], IP1) + ['sub_1', 'sub12 1', 'sub12 2'] + +Заметьте, что новый подписчик возвращается первым. Подписчики определенные +для менее общих требуемых интерфейсов возвращаются перед подписчиками +для более общих интерфейсов. + +Подписки могут смешиваться между несколькими совместимыми спецификациями:: + + >>> registry.subscriptions([IR2], IP1) + ['sub_1', 'sub12 1', 'sub12 2'] + >>> registry.subscribe([IR1], IP1, 'sub11') + >>> registry.subscriptions([IR2], IP1) + ['sub_1', 'sub12 1', 'sub12 2', 'sub11'] + >>> registry.subscribe([IR2], IP2, 'sub22') + >>> registry.subscriptions([IR2], IP1) + ['sub_1', 'sub12 1', 'sub12 2', 'sub11', 'sub22'] + >>> registry.subscriptions([IR2], IP2) + ['sub12 1', 'sub12 2', 'sub22'] + +Подписки могут существовать для нескольких спецификаций:: + + >>> registry.subscribe([IR1, IQ], IP2, 'sub1q2') + >>> registry.subscriptions([IR1, IQ], IP2) + ['sub1q2'] + +Как и с одиночными подписчиками и адаптерами без подписок, вы можете определить +None для первого требуемого интерфейса, что бы задать значение по умолчанию:: + + >>> registry.subscribe([None, IQ], IP2, 'sub_q2') + >>> registry.subscriptions([IS, IQ], IP2) + ['sub_q2'] + >>> registry.subscriptions([IR1, IQ], IP2) + ['sub_q2', 'sub1q2'] + +Вы можете создать подписки которые независимы от любых спецификаций:: + + >>> list(registry.subscriptions([], IP1)) + [] + + >>> registry.subscribe([], IP2, 'sub2') + >>> registry.subscriptions([], IP1) + ['sub2'] + >>> registry.subscribe([], IP1, 'sub1') + >>> registry.subscriptions([], IP1) + ['sub2', 'sub1'] + >>> registry.subscriptions([], IP2) + ['sub2'] + +Удаление регистрации подписчиков +-------------------------------- + +Мы можем удалять регистрацию подписчиков. При удалении регистрации подписчика +мы можем удалить регистрацию заданного адаптера:: + + >>> registry.unsubscribe([IR1], IP1, 'sub11') + >>> registry.subscriptions([IR1], IP1) + ['sub_1', 'sub12 1', 'sub12 2'] + +Если мы не задаем никакого значения тогда подписки будут удалены для всех +подписчиков совпадающих с заданным интерфейсом:: + + >>> registry.unsubscribe([IR1], IP2) + >>> registry.subscriptions([IR1], IP1) + ['sub_1'] + +Адаптеры подписки +----------------- + +Обычно мы регистрируем фабрики для адаптеров которые затем позволяют нам +вычислять адаптеры, но с подписками мы получаем несколько адаптеров. Это пример +подписчика для нескольких объектов:: + + >>> 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] + +подписчики фабрик адаптеров не могут возвращать None:: + + >>> def M3(x, y): + ... return None + + >>> registry.subscribe([IR, IQ], IM, M3) + >>> subscribers = registry.subscribers((x, q), IM) + >>> len(subscribers) + 2 + +Обработчики +----------- + +Обработчик - это подписанная фабрика которая не возвращает нормального +значения. Она возвращает None. Обработчик отличается от адаптеров тем, что он +делает всю работу когда вызывается фабрика. + +Для регистрации обработчика надо просто передать None как предоставляемый +интерфейс:: + + >>> def handler(event): + ... print 'handler', event + + >>> registry.subscribe([IR1], None, handler) + >>> registry.subscriptions([IR1], None) == [handler] + True diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..581a428 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,246 @@ +# -*- coding: utf-8 -*- +# +# zope.interface documentation build configuration file, created by +# sphinx-quickstart on Mon Mar 26 16:31:31 2012. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'zope.interface' +copyright = u'2012, Zope Foundation contributors' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '3.8' +# The full version, including alpha/beta/rc tags. +release = '3.8.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'zopeinterfacedoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'zopeinterface.tex', u'zope.interface Documentation', + u'Zope Foundation contributors', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'zopeinterface', u'zope.interface Documentation', + [u'Zope Foundation contributors'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'zopeinterface', u'zope.interface Documentation', + u'Zope Foundation contributors', 'zopeinterface', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/docs/foodforthought.rst b/docs/foodforthought.rst new file mode 100644 index 0000000..45d961b --- /dev/null +++ b/docs/foodforthought.rst @@ -0,0 +1,61 @@ +================================ +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'] diff --git a/docs/human.rst b/docs/human.rst new file mode 100644 index 0000000..749b87d --- /dev/null +++ b/docs/human.rst @@ -0,0 +1,152 @@ +========================== +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 is 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! diff --git a/docs/human.ru.rst b/docs/human.ru.rst new file mode 100644 index 0000000..a149072 --- /dev/null +++ b/docs/human.ru.rst @@ -0,0 +1,156 @@ +=============================== +Использование реестра адаптеров +=============================== + +Данный документ содержит небольшую демонстрацию пакета ``zope.interface`` и его +реестра адаптеров. Документ рассчитывался как конкретный, но более узкий пример +того как использовать интерфейсы и адаптеры вне Zope 3. + +Сначала нам необходимо импортировать пакет для работы с интерфейсами:: + + >>> import zope.interface + +Теперь мы разработаем интерфейс для нашего объекта - простого файла. Наш файл +будет содержать всего один атрибут - body, в котором фактически будет сохранено +содержимое файла:: + + >>> class IFile(zope.interface.Interface): + ... + ... body = zope.interface.Attribute(u'Содержимое файла.') + ... + +Для статистики нам часто необходимо знать размер файла. Но было бы несколько +топорно реализовывать определение размера прямо для объекта файла, т.к. размер +больше относится к мета-данным. Таким образом мы создаем еще один интерфейс для +представления размера какого-либо объекта:: + + >>> class ISize(zope.interface.Interface): + ... + ... def getSize(): + ... 'Return the size of an object.' + ... + +Теперь мы должны создать класс реализующий наш файл. Необходимо что бы наш +объект хранил информацию о том, что он реализует интерфейс `IFile`. Мы также +создаем атрибут с содержимым файла по умолчанию (для упрощения нашего +примера):: + + >>> class File(object): + ... + ... zope.interface.implements(IFile) + ... body = 'foo bar' + ... + +Дальше мы создаем адаптер, который будет предоставлять интерфейс `ISize` +получая любой объект предоставляющий интерфейс `IFile`. По соглашению мы +используем атрибут `__used_for__` для указания интерфейса который как мы +ожидаем предоставляет адаптируемый объект, `IFile` в нашем случае. На самом +деле этот атрибут используется только для документирования. В случае если +адаптер используется для нескольких интерфейсов можно указать их все в виде +кортежа. + +Опять же по соглашению конструктор адаптера получает один аргумент - context +(контекст). В нашем случае контекст - это экземпляр `IFile` (объект, +предоставляющий `IFile`) который используется для получения из него размера. +Так же по соглашению контекст сохраняется а адаптере в атрибуте с именем +`context`. Twisted комьюнити ссылается на контекст как на объект `original`. +Таким образом можно также дать аргументу любое подходящее имя, например +`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) + ... + +Теперь когда мы написали наш адаптер мы должны зарегистрировать его в реестре +адаптеров, что бы его можно было запросить когда он понадобится. Здесь нет +какого-либо глобального реестра адаптеров, таким образом мы должны +самостоятельно создать для нашего примера реестр:: + + >>> from zope.interface.adapter import AdapterRegistry + >>> registry = AdapterRegistry() + +Реестр содержит отображение того, что адаптер реализует на основе другого +интерфейса который предоставляет объект. Поэтому дальше мы регистрируем адаптер +который адаптирует интерфейс `IFile` к интерфейсу `ISize`. Первый аргумент к +методу `register()` реестра - это список адаптируемых интерфейсов. В нашем +случае мы имеем только один адаптируемый интерфейс - `IFile`. Список +интерфейсов имеет смысл для использования концепции мульти-адаптеров, которые +требуют нескольких оригинальных объектов для адаптации к новому интерфейсу. В +этой ситуации конструктор адаптера будет требовать новый аргумент для каждого +оригинального интерфейса. + +Второй аргумент метода `register()` - это интерфейс который предоставляет +адаптер, в нашем случае `ISize`. Третий аргумент - имя адаптера. Сейчас нам не +важно имя адаптера и мы передаем его как пустую строку. Обычно имена полезны +если используются адаптеры для одинакового набора интерфейсов, но в различных +ситуациях. Последний аргумент - это класс адаптера:: + + >>> registry.register([IFile], ISize, '', FileSize) + +Теперь мы можем использовать реестр для запроса адаптера:: + + >>> registry.lookup1(IFile, ISize, '') + <class '__main__.FileSize'> + +Попробуем более практичный пример. Создадим экземпляр `File` и создадим адаптер +использующий запрос реестра. Затем мы увидим возвращает ли адаптер корректный +размер при вызове `getSize()`:: + + >>> file = File() + >>> size = registry.lookup1(IFile, ISize, '')(file) + >>> size.getSize() + 7 + +На самом деле это не очень практично, т.к. нам нужно самим передавать все +аргументы методу запроса. Существует некоторый синтаксический леденец который +позволяет нам получить экземпляр адаптера просто вызвав `ISize(file)`. Что бы +использовать эту функциональность нам понадобится добавить наш реестр к списку +adapter_hooks, который находится в модуле с адаптерами. Этот список хранит +коллекцию вызываемых объектов которые вызываются автоматически когда вызывается +IFoo(obj); их предназначение - найти адаптеры которые реализуют интерфейс для +определенного экземпляра контекста. + +Необходимо реализовать свою собственную функцию для поиска адаптера; данный +пример описывает одну из простейших функций для использования с реестром, но +также можно реализовать поисковые функции которые, например, используют +кэширование, или адаптеры сохраняемые в базе. Функция поиска должна принимать +желаемый на выходе интерфейс (в нашем случае `ISize`) как первый аргумент и +контекст для адаптации (`file`) как второй. Функция должна вернуть адаптер, +т.е. экземпляр `FileSize`:: + + >>> def hook(provided, object): + ... adapter = registry.lookup1(zope.interface.providedBy(object), + ... provided, '') + ... return adapter(object) + ... + +Теперь мы просто добавляем нашу функцию к списку `adapter_hooks`:: + + >>> from zope.interface.interface import adapter_hooks + >>> adapter_hooks.append(hook) + +Как только функция зарегистрирована мы можем использовать желаемый синтаксис:: + + >>> size = ISize(file) + >>> size.getSize() + 7 + +После нам нужно прибраться за собой, что бы другие получили чистый список +`adaper_hooks` после нас:: + + >>> adapter_hooks.remove(hook) + +Это все. Здесь намеренно отложена дискуссия об именованных и мульти-адаптерах, +т.к. данный текст рассчитан как практическое и простое введение в интерфейсы и +адаптеры Zope 3. Для более подробной информации имеет смысл прочитать +`adapter.txt` из пакета `zope.interface`, что бы получить более формальное, +справочное и полное трактование пакета. Внимание: многие жаловались, что +`adapter.txt` приводит их мозг к расплавленному состоянию! diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..a44105d --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,38 @@ +.. zope.interface documentation master file, created by + sphinx-quickstart on Mon Mar 26 16:31:31 2012. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to zope.interface's documentation! +========================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + README + adapter + human + verify + foodforthought + +По-русски +========= + +.. toctree:: + :maxdepth: 2 + + README.ru + adapter.ru + human.ru + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..f59869c --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,190 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^<target^>` where ^<target^> is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\zopeinterface.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\zopeinterface.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/docs/verify.rst b/docs/verify.rst new file mode 100644 index 0000000..7eec6d2 --- /dev/null +++ b/docs/verify.rst @@ -0,0 +1,127 @@ +=================================== +Verifying interface implementations +=================================== + +The ``zope.interface.verify`` module provides functions that test whether a +given interface is implemented by a class or provided by an object, resp. + + +Verifying classes +================= + +This is covered by unit tests defined in ``zope.interface.tests.test_verify``. + + +Verifying objects +================= + +An object provides an interface if + +- either its class declares that it implements the interfaces, or the object + declares that it directly provides the interface + +- the object defines all the methods required by the interface + +- all the methods have the correct signature + +- the object defines all non-method attributes required by the interface + +This doctest currently covers only the latter item. + +Testing for attributes +---------------------- + +Attributes of the object, be they defined by its class or added by its +``__init__`` method, will be recognized: + +>>> from zope.interface import Interface, Attribute, implements +>>> from zope.interface.exceptions import BrokenImplementation +>>> class IFoo(Interface): +... x = Attribute("The X attribute") +... y = Attribute("The Y attribute") + +>>> class Foo(object): +... implements(IFoo) +... x = 1 +... def __init__(self): +... self.y = 2 + +>>> from zope.interface.verify import verifyObject +>>> verifyObject(IFoo, Foo()) +True + +If either attribute is missing, verification will fail: + +>>> class Foo(object): +... implements(IFoo) +... x = 1 + +>>> try: #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS +... verifyObject(IFoo, Foo()) +... except BrokenImplementation, e: +... print str(e) +An object has failed to implement interface <InterfaceClass ...IFoo> +<BLANKLINE> + The y attribute was not provided. +<BLANKLINE> + +>>> class Foo(object): +... implements(IFoo) +... def __init__(self): +... self.y = 2 + +>>> try: #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS +... verifyObject(IFoo, Foo()) +... except BrokenImplementation, e: +... print str(e) +An object has failed to implement interface <InterfaceClass ...IFoo> +<BLANKLINE> + The x attribute was not provided. +<BLANKLINE> + +If an attribute is implemented as a property that raises an AttributeError +when trying to get its value, the attribute is considered missing: + +>>> class IFoo(Interface): +... x = Attribute('The X attribute') + +>>> class Foo(object): +... implements(IFoo) +... @property +... def x(self): +... raise AttributeError + +>>> try: #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS +... verifyObject(IFoo, Foo()) +... except BrokenImplementation, e: +... print str(e) +An object has failed to implement interface <InterfaceClass ...IFoo> +<BLANKLINE> + The x attribute was not provided. +<BLANKLINE> + +Any other exception raised by a property will propagate to the caller of +``verifyObject``: + +>>> class Foo(object): +... implements(IFoo) +... @property +... def x(self): +... raise Exception + +>>> verifyObject(IFoo, Foo()) +Traceback (most recent call last): +Exception + +Of course, broken properties that are not required by the interface don't do +any harm: + +>>> class Foo(object): +... implements(IFoo) +... x = 1 +... @property +... def y(self): +... raise Exception + +>>> verifyObject(IFoo, Foo()) +True |
