forked from baijum/zcadoc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathizca.txt
5033 lines (3636 loc) · 139 KB
/
izca.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
====================================================
A Comprehensive Guide to Zope Component Architecture
====================================================
:Author: Baiju M
:Version: 0.5.8
:Printed Book: `http://www.lulu.com/content/1561045
<http://www.lulu.com/content/1561045>`_
:Online PDF: `http://www.muthukadan.net/docs/zca.pdf
<http://www.muthukadan.net/docs/zca.pdf>`_
Copyright (C) 2007,2008,2009 Baiju M <baiju.m.mail AT gmail.com>.
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3 or
any later version published by the Free Software Foundation.
The source code in this document is subject to the provisions of the
Zope Public License, Version 2.1 (ZPL).
THE SOURCE CODE IN THIS DOCUMENT AND THE DOCUMENT ITSELF IS PROVIDED
"AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE,
MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR
PURPOSE.
.. sidebar:: Acknowledgements
Many people have helped me to write this book. The initial draft
was reviewed by my colleague Brad Allen. When I announced this
book through my blog, I received many encouraging comments to
proceed with this work. Kent Tenney edited most parts of the book,
he also rewrote the example application. Many others sent me fixes
and comments including, Lorenzo Gil Sanchez, Leonardo Caballero,
Michael Haubenwallner, Nando Quintana, Stephane Klein, Tim Cook,
Kamal Gill and Thomas Herve. Lorenzo and Leonardo translated this work
to Spanish and Stephane translated it to French. Thanks to all !
.. contents::
.. .. sectnum::
Getting started
---------------
Introduction
~~~~~~~~~~~~
Developing a large software system is always very complicated. An
object-oriented approach to analysis, design and programming has been
shown to be well-suited for dealing with large systems. Component-
based design and programming is becoming very
popular these days. A component-based approach helps you to write and
maintain easily unit-testable software systems. There are many
frameworks for supporting component-based design in different
languages; some are even language neutral. Examples of these are
Microsoft's COM and Mozilla's XPCOM.
**Zope Component Architecture (ZCA)** is a Python framework for
supporting component-based design and programming. It is very well-
suited to developing large Python software systems. The ZCA is not
specific to the Zope web application server; it can be used for
developing any Python application. Maybe it should be called
`Python Component Architecture`.
The ZCA is all about using Python objects effectively. Components
are reusable objects with introspectable interfaces. An interface is
an object that describes how you work with a particular component.
In other words, a component provides an interface implemented in a
class, or any other callable object. It doesn't matter how the
component is implemented; the important part is that it complies with
its interface contracts. Using ZCA, you can spread the complexity of
systems over multiple cooperating components. It helps you to create
two basic kinds of components: `adapter` and `utility`.
There are three core packages related to the ZCA:
- ``zope.interface`` is used to define the interface of a
component.
- ``zope.event`` provides a simple event system.
- ``zope.component`` deals with creation, registration and
retrieval of components.
Remember, the ZCA is not the components themselves, rather it is
about creating, registering, and retrieving components. Remember
also, an `adapter` is a normal Python class (or a factory in general)
and `utility` is a normal Python callable object.
The ZCA framework is developed as part of the Zope 3 project. As
noted earlier, it is a pure Python framework, so it can be used in
any kind of Python application. Currently Zope 3, Zope 2 and Grok
projects use this framework extensively. There are many other
projects including non-web applications using it [#projects]_.
.. [#projects] http://wiki.zope.org/zope3/ComponentArchitecture
A brief history
~~~~~~~~~~~~~~~
The ZCA framework project began in 2001 as part of Zope 3 project. It
grew out of lessons learned while developing large software systems
using Zope 2. Jim Fulton was the project leader of this project.
Many people contributed to the design and implementation, including
but not limited to, Stephan Richter, Philipp von Weitershausen, Guido
van Rossum (*aka. Python BDFL*), Tres Seaver, Phillip J Eby and
Martijn Faassen.
Initially ZCA defined additional components; `services` and `views`,
but the developers came to realize that utility can replace `service`
and multi-adapter can replace `view`. Now ZCA has a very small
number of core component types: `utilities`, `adapters`,
`subscribers` and `handlers`. In fact, `subscribers` and `handlers`
are two special types of adapters.
During the Zope 3.2 release cycle, Jim Fulton proposed a major
simplification of ZCA [#proposal]_. With this simplification, a new
single interface (`IComponentRegistry`) for registration of both
global and local component was created.
.. [#proposal] http://wiki.zope.org/zope3/LocalComponentManagementSimplification
The ``zope.component`` package had a long list of dependencies, many
of which were not required for a non Zope 3 application. During PyCon
2007, Jim Fulton added setuptools' `extras_require` feature to allow
separating out core ZCA functionality from add-on features [#extras]_.
.. [#extras] http://peak.telecommunity.com/DevCenter/setuptools#declaring-dependencies
In March 2009, Tres Seaver removed dependencies of
``zope.deferredimport`` and ``zope.proxy``.
Now, The ZCA project is an independent project with it's own release
cycle and Subversion repository. This project is coming as part the
bigger the Zope framework project [#framework]_. However, issues and
bugs are still tracked as part of the Zope 3 project [#bugs]_, and
the main zope-dev list is used for development discussions
[#discussions]_. There is also another general user list for Zope 3
(`zope3-users`) which can be used for any queries about the ZCA
[#z3users]_.
.. [#framework] http://docs.zope.org/zopeframework/
.. [#bugs] https://bugs.launchpad.net/zope3
.. [#discussions] http://mail.zope.org/mailman/listinfo/zope-dev
.. [#z3users] http://mail.zope.org/mailman/listinfo/zope3-users
Installation
~~~~~~~~~~~~
The ``zope.component``, package together with the ``zope.interface``
and ``zope.event`` packages are the core of Zope Component
Architecture. They provide facilities for defining, registering and
looking up components. The ``zope.component`` package and its
dependencies are available in egg format from the Python Package
Index (PyPI) [#pypi]_.
.. [#pypi] Repository of Python packages: http://pypi.python.org/pypi
You can install ``zope.component`` and it's dependencies using
`easy_install` [#easyinstall]_ ::
$ easy_install zope.component
.. [#easyinstall] http://peak.telecommunity.com/DevCenter/EasyInstall
This command will download ``zope.component`` and its dependencies
from PyPI and install it in your Python path.
Alternately, you can download ``zope.component`` and its dependencies
from PyPI and then install them. Install packages in the order given
below. On Windows, you may need binary packages of ``zope.interface``.
1. ``zope.interface``
2. ``zope.event``
3. ``zope.component``
To install these packages, after downloading them, you can use
``easy_install`` command with argument as the eggs. (You may also
give all these eggs in the same line.)::
$ easy_install /path/to/zope.interface-3.x.x.tar.gz
$ easy_install /path/to/zope.event-3.x.x.tar.gz
$ easy_install /path/to/zope.component-3.x.x.tar.gz
You can also install these packages after extracting each one
separately. For example::
$ tar zxvf /path/to/zope.interface-3.x.x.tar.gz
$ cd zope.interface-3.x.x
$ python setup.py build
$ python setup.py install
These methods will install the ZCA to the `system Python`, in the
``site-packages`` directory, which can cause problems. In a Zope 3
mailing list post, Jim Fulton recommends against using the system
Python [#systempython]_. You can use ``virtualenv`` and/or
``zc.buildout`` for playing with any Python packages, also good for
deployments.
.. [#systempython] http://article.gmane.org/gmane.comp.web.zope.zope3/21045
Experimenting with code
~~~~~~~~~~~~~~~~~~~~~~~
There are two popular approaches in Python for setting up isolated
working environments for developing applications. The most popular
one is ``virtualenv`` package created by Ian Biking and
``zc.buildout`` created by Jim Fulton. You can also use these
packages together. These packages helps you to install
``zope.component`` and other dependencies into an isolated working
environment. It is a good practice to use these packages for
experimenting with any Python code. Familiarity with these tools
will be beneficial when developing and deploying any Python
application.
**virtualenv**
You can install ``virtualenv`` using ``easy_install``::
$ easy_install virtualenv
Then create a new environment like this::
$ virtualenv --no-site-packages myve
This will create a new virtual environment in the ``myve`` directory.
Now, from inside the ``myve`` directory, you can install
``zope.component`` and dependencies using ``easy_install`` inside
``myve/bin`` directory::
$ cd myve
$ ./bin/easy_install zope.component
Now you can import ``zope.interface`` and ``zope.component`` from the
new ``python`` interpreter inside ``myve/bin`` directory::
$ ./bin/python
This command will give you a Python prompt which you can use to run
the code in this book.
**zc.buildout**
Using ``zc.buildout`` with ``zc.recipe.egg`` recipe you can create
Python interpreter with specified Python eggs. First, install
``zc.buildout`` using ``easy_install`` command. (You may also do it
inside virtual environment). To create new buildout to experiment
with Python eggs, first create a directory and initialize it using
``buildout init`` command::
$ mkdir mybuildout
$ cd mybuildout
$ buildout init
Now the new ``mybuildout`` directory is a buildout. The default
configuration file for buildout is `buildout.cfg` . After
initializing, it will be having this content::
[buildout]
parts =
You can change it like this::
[buildout]
parts = py
[py]
recipe = zc.recipe.egg
interpreter = python
eggs = zope.component
Now run ``buildout`` command available inside ``mybuildout/bin``
directory without any argument. This will create a new Python
interpreter inside ``mybuildout/bin`` directory::
$ ./bin/buildout
$ ./bin/python
This command will give you a Python prompt which you can use to run
the code in this book.
An example
----------
Introduction
~~~~~~~~~~~~
Consider a business application for registering guests staying in a
hotel. Python can implement this in a number of ways. We will start
with a brief look at a procedural implementation, and then move to a
basic object oriented approach. As we examine the object oriented
approach, we will see how we can benefit from the classic design
patterns, `adapter` and `interface`. This will bring us into the
world of the Zope Component Architecture.
Procedural approach
~~~~~~~~~~~~~~~~~~~
In any business application, data storage is very critical. For
simplicity, this example use a Python dictionary as the storage. We
will generate unique IDs for the dictionary, the associated value
will be a dictionary of details about the booking.
>>> bookings_db = {} #key: unique ID, value: details in a dictionary
A minimal implementation requires a function which we pass the
details of the booking, and a supporting function which provides the
the unique ID for the storage dictionary key.
We can get the unique ID like this::
>>> def get_next_id():
... db_keys = bookings_db.keys()
... if db_keys == []:
... next_id = 1
... else:
... next_id = max(db_keys) + 1
... return next_id
As you can see, the `get_next_id` function implementation is very
simple. The function gets a list of keys and checks for an empty
list. If the list is empty, this is our first booking, so we return
`1`. If the list is not empty, we add `1` to the maximum value in
the list and return it.
Now we will use the above function to create entries in the
bookings_db dictionary::
>>> def book_room(name, place):
... next_id = get_next_id()
... bookings_db[next_id] = {
... 'name': name,
... 'room': place
... }
The requirements of a hotel booking management application require
considering additional data:
- phone numbers
- room options
- payment methods
- ...
And code to manage the data:
- cancel a reservation
- update a reservation
- pay for a room
- persist the data
- insure security of the data
- ...
Were we to continue with the procedural example, we would create many
functions, passing data back and forth between them. As requirements
change and are added, the code becomes harder to maintain and bugs
become harder to find and fix.
We will end our discussion of the procedural approach here. It will
be much easier to provide data persistence, design flexibility and
code testability using objects.
Object oriented approach
~~~~~~~~~~~~~~~~~~~~~~~~
.. ??? should this paragraph talk about "creating an object for
handling registration" or "creating a class to handle registration"?
Our discussion of object oriented design will introduce the `class` which
serves to encapsulate the data, and the code to manage it.
Our main class will be the ``FrontDesk``. ``FrontDesk``, or other
classes it delegates to, will know how to manage the data for the
hotel. We will create `instances` of ``FrontDesk`` to apply this
knowledge to the business of running a hotel.
Experience has shown that by consolidating the code and data
requirements via objects, we will end up with a design which is
easier to understand, test, and change.
Lets look at the implementation details of a ``FrontDesk`` class::
>>> class FrontDesk(object):
...
... def book_room(self, name, place):
... next_id = get_next_id()
... bookings_db[next_id] = {
... 'name': name,
... 'place': place
... }
In this implementation, the `frontdesk` object (an instance of
`FrontDesk` class) is able to handle the bookings. We can
use it like this::
>>> frontdesk = FrontDesk()
>>> frontdesk.book_room("Jack", "Bangalore")
Any real project will involve changing requirements. In this
case management has decided that each guest must provide a phone
number, so we must change the code.
We can achieve this requirement by adding one argument to the
`book_room` method which will be added to the dictionary of values::
>>> class FrontDesk(object):
...
... def book_room(self, name, place, phone):
... next_id = get_next_id()
... bookings_db[next_id] = {
... 'name': name,
... 'place': place,
... 'phone': phone
... }
In addition to migrating the data to new schema, we now have to
change all calls to ``FrontDesk``. If we abstract the details of
guest into an object and use it for registration, the code changes
can be minimized. We now can make changes to the details of the
guest object and the calls to ``FrontDesk`` won't need to change.
Now we have::
>>> class FrontDesk(object):
...
... def book_room(self, guest):
... next_id = get_next_id()
... bookings_db[next_id] = {
... 'name': guest.name,
... 'place': guest.place,
... 'phone': guest.phone
... }
We still will have to change code to respond to changing requirements.
This is unavoidable, however, our goal is to minimize those changes,
thereby increasing maintainability.
.. note::
When coding, it is important to feel free to make changes without
fear of breaking the application. The way to get the immediate
feedback required is via automated testing. With well written
tests (and good version control) you can make changes large or
small with impunity. A good source of information about this
programming philosophy is the book `Extreme Programming Explained`
by Kent Beck.
By introducing the guest object, you saved some typing. More
importantly, the abstraction provided by the guest object made the
system simpler and more understandable. As a result, the code is
easier to restructure and maintain.
The adapter pattern
~~~~~~~~~~~~~~~~~~~
In a real application, the frontdesk object will need to handle
functionalities such as cancellations and updates. In the current
design, we will need to pass the guest object to frontdesk every time
we call methods such as `cancel_booking` and `update_booking`.
We can avoid this requirement if we pass the guest object to
FrontDesk.__init__(), making it an attribute of the instance.
>>> class FrontDeskNG(object):
...
... def __init__(self, guest):
... self.guest = guest
...
... def book_room(self):
... guest = self.guest
... next_id = get_next_id()
... bookings_db[next_id] = {
... 'name': guest.name,
... 'place': guest.place,
... 'phone': guest.phone
... }
...
... def cancel_booking(self):
... guest = self.guest
... #code for cancellations goes here ...
...
... def update_booking(self):
... guest = self.guest
... #code for updatiion goes here ...
.. include this bit at the front of the `Adapters` section when I get
the equivalent quote from the Patterns book to start the
`Interfaces` section
The solution we have reached is a common design pattern called,
`Adapter`. The `Gang of Four` [#patternbook]_ give this as the
*intent* of Adapter::
"Convert the interface of a class into another interface clients
expect. Adapter lets classes work together that couldn't otherwise
because of incompatible interfaces."
The solution we have reached is a well known pattern, the *adapter*.
In general, an adapter *contains* an *adaptee*::
>>> class Adapter(object):
...
... def __init__(self, adaptee):
... self.adaptee = adaptee
This pattern will be useful in dealing with implementation details
which depend on considerations such as:
- changing customer requirements
- storage requirements (ZODB, RDBM, XML ...)
- output requirements (HTML, PDF, plain text ...)
- markup rendering (ReST, Markdown, Textile ...)
ZCA uses adapters and a *component registry* to provide the capability
to change implementation details of code via *configuration*.
As we will see in the section on ZCA adapters, the ability to
configure implementation details provides useful capability:
- the ability to switch between implementations
- the ability to add implementations as needed
- increased re-use of both legacy and ZCA code
These capabilities lead to code that is flexible, scalable and
re-usable. There is a cost however, maintaining the component registry
adds a level of complexity to the application. If an application will
never require these features, ZCA is unnecessary.
We are now ready to begin our study of the Zope Component
Architecture, beginning with interfaces.
Interfaces
----------
Introduction
~~~~~~~~~~~~
The README.txt [#readmes]_ in path/to/zope/interface defines
interfaces like this::
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
The classic software engineering book `Design Patterns` [#patternbook]_
by the `Gang of Four` recommends that you "Program to an interface,
not an implementation". Defining a formal interface is helpful in
understanding a system. Moreover, interfaces bring to you all the
benefits of ZCA.
.. [#readmes] The Zope code tree is full of README.txt files which
offer wonderful documentation.
.. [#patternbook] http://en.wikipedia.org/wiki/Design_Patterns
An interface specifies the characteristics of an object, it's
behaviour, it's capabilities. The interface describes *what* an
object can do, to learn *how*, you must look at the implementation.
Commonly used metaphors for interfaces are `contract` or `blueprint`,
the legal and architectural terms for a set of specifications.
In some modern programming languages: Java, C#, VB.NET etc, interfaces
are an explicit aspect of the language. Since Python lacks
interfaces, ZCA implements them as a meta-class to inherit from.
Here is a classic *hello world* style example::
>>> class Host(object):
...
... def goodmorning(self, name):
... """Say good morning to guests"""
...
... return "Good morning, %s!" % name
In the above class, you defined a `goodmorning` method. If you call
the `goodmorning` method from an object created using this class, it
will return `Good morning, ...!` ::
>>> host = Host()
>>> host.goodmorning('Jack')
'Good morning, Jack!'
Here ``host`` is the actual object your code uses. If you want to
examine implementation details you need to access the class ``Host``,
either via the source code or an API [#api]_ documentation tool.
.. [#api] http://en.wikipedia.org/wiki/Application_programming_interface
Now we will begin to use the ZCA interfaces. For the class given
above you can specify the interface like this::
>>> from zope.interface import Interface
>>> class IHost(Interface):
...
... def goodmorning(guest):
... """Say good morning to guest"""
As you can see, the interface inherits from zope.interface.Interface.
This use (abuse?) of Python's class statement is how ZCA defines an
interface. The ``I`` prefix for the interface name is a useful
convention.
Declaring interfaces
~~~~~~~~~~~~~~~~~~~~
You have already seen how to declare an interface using
``zope.interface`` in previous section. This section will explain the
concepts in detail.
Consider this example interface::
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> class IHost(Interface):
... """A host object"""
...
... name = Attribute("""Name of host""")
...
... def goodmorning(guest):
... """Say good morning to guest"""
The interface, ``IHost`` has two attributes, ``name`` and
``goodmorning``. Recall that, at least in Python, methods are also
attributes of classes. The ``name`` attribute is defined using
``zope.interface.Attribute`` class. When you add the attribute
``name`` to the ``IHost`` interface, you don't set an initial value.
The purpose of defining the attribute ``name`` here is merely to
indicate that any implementation of this interface will feature an
attribute named ``name``. In this case, you don't even say what type
of attribute it has to be!. You can pass a documentation string as a
first argument to ``Attribute``.
The other attribute, ``goodmorning`` is a method defined using a
function definition. Note that `self` is not required in interfaces,
because `self` is an implementation detail of class. For example, a
module can implement this interface. If a module implement this
interface, there will be a ``name`` attribute and ``goodmorning``
function defined. And the ``goodmorning`` function will accept one
argument.
Now you will see how to connect `interface-class-object`. So object
is the real living thing, objects are instances of classes. And
interface is the actual definition of the object, so classes are just
the implementation details. This is why you should program to an
interface and not to an implementation.
Now you should familiarize two more terms to understand other
concepts. First one is `provide` and the other one is `implement`.
Object provides interfaces and classes implement interfaces. In other
words, objects provide interfaces that their classes implement. In
the above example ``host`` (object) provides ``IHost`` (interface) and
``Host`` (class) implement ``IHost`` (interface). One object can
provide more than one interface also one class can implement more than
one interface. Objects can also provide interfaces directly, in
addition to what their classes implement.
.. note::
Classes are the implementation details of objects. In Python,
classes are callable objects, so why other callable objects can't
implement an interface. Yes, it is possible. For any `callable
object` you can declare that it produces objects that provide some
interfaces by saying that the `callable object` implements the
interfaces. The `callable objects` are generally called as
`factories`. Since functions are callable objects, a function can
be an `implementer` of an interface.
Implementing interfaces
~~~~~~~~~~~~~~~~~~~~~~~
To declare a class implements a particular interface, use the function
``zope.interface.implements`` in the class statement.
Consider this example, here ``Host`` implements ``IHost``::
>>> from zope.interface import implements
>>> class Host(object):
...
... implements(IHost)
...
... name = u''
...
... def goodmorning(self, guest):
... """Say good morning to guest"""
...
... return "Good morning, %s!" % guest
.. note::
If you wonder how ``implements`` function works, refer the blog post
by James Henstridge
(http://blogs.gnome.org/jamesh/2005/09/08/python-class-advisors/) .
In the adapter section, you will see an ``adapts`` function, it is
also working similarly.
Since ``Host`` implements ``IHost``, instances of ``Host`` provides
``IHost``. There are some utility methods to introspect the
declarations. The declaration can write outside the class also. If
you don't write ``interface.implements(IHost)`` in the above example,
then after defining the class statement, you can write like this::
>>> from zope.interface import classImplements
>>> classImplements(Host, IHost)
Example revisited
~~~~~~~~~~~~~~~~~
Now, return to the example application. Here you will see how to
define the interface of the frontdesk object::
>>> from zope.interface import Interface
>>> class IDesk(Interface):
... """A frontdesk will register object's details"""
...
... def register():
... """Register object's details"""
...
Here, first you imported ``Interface`` class from ``zope.interface``
module. If you define a subclass of this ``Interface`` class, it
will be an interface from Zope Component Architecture point of view.
An interface can be implemented, as you have already seen, in a class
or any other callable object.
The frontdesk interface defined here is ``IDesk``. The documentation
string for interface gives an idea about the object. By defining a
method in the interface, you made a contract for the component, that
there will be a method with same name available. For the method
definition interface, the first argument should not be `self`,
because an interface will never be instantiated nor will its methods
ever be called. Instead, the interface class merely documents what
methods and attributes should appear in any normal class that claims
to implement it, and the `self` parameter is an implementation detail
which doesn't need to be documented.
As you know, an interface can also specify normal attributes::
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> class IGuest(Interface):
...
... name = Attribute("Name of guest")
... place = Attribute("Place of guest")
In this interface, guest object has two attributes specified with
documentation. An interface can also specify both attributes and
methods together. An interface can be implemented in a class, module
or any other objects. For example a function can dynamically create
the component and return, in this case the function is an implementer
for the interface.
Now you know what is an interface and how to define and use it. In
the next chapter you can see how an interface is used to define an
adapter component.
Marker interfaces
~~~~~~~~~~~~~~~~~
An interface can be used to declare that a particular object belongs
to a special type. An interface without any attribute or method is
called `marker interface`.
Here is a `marker interface`::
>>> from zope.interface import Interface
>>> class ISpecialGuest(Interface):
... """A special guest"""
This interface can be used to declare an object is a special guest.
Invariants
~~~~~~~~~~
Sometimes you will be required to use some rule for your component
which involve one or more normal attributes. These kind of rule is
called `invariants`. You can use ``zope.interface.invariant`` for
setting `invariants` for your objects in their interface.
Consider a simple example, there is a `person` object. A person
object has `name`, `email` and `phone` attributes. How do you
implement a validation rule that says either email or phone have to
exist, but not necessarily both.
First you have to make a callable object, either a simple function or
callable instance of a class like this::
>>> def contacts_invariant(obj):
...
... if not (obj.email or obj.phone):
... raise Exception(
... "At least one contact info is required")
Then define the `person` object's interface like this. Use the
``zope.interface.invariant`` function to set the invariant::
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> from zope.interface import invariant
>>> class IPerson(Interface):
...
... name = Attribute("Name")
... email = Attribute("Email Address")
... phone = Attribute("Phone Number")
...
... invariant(contacts_invariant)
Now use `validateInvariants` method of the interface to validate::
>>> from zope.interface import implements
>>> class Person(object):
... implements(IPerson)
...
... name = None
... email = None
... phone = None
>>> jack = Person()
>>> jack.email = u"jack@some.address.com"
>>> IPerson.validateInvariants(jack)
>>> jill = Person()
>>> IPerson.validateInvariants(jill)
Traceback (most recent call last):
...
Exception: At least one contact info is required
As you can see `jack` object validated without raising any exception.
But `jill` object didn't validated the invariant constraint, so it
raised exception.
Adapters
--------
Implementation
~~~~~~~~~~~~~~
This section will describe adapters in detail. Zope Component
Architecture, as you noted, helps to effectively use Python objects.
Adapter components are one of the basic components used by Zope
Component Architecture for effectively using Python objects. Adapter
components are Python objects, but with well defined interface.
To declare a class is an adapter use `adapts` function defined in
``zope.component`` package. Here is a new `FrontDeskNG` adapter
with explicit interface declaration::
>>> from zope.interface import implements
>>> from zope.component import adapts
>>> class FrontDeskNG(object):
...
... implements(IDesk)
... adapts(IGuest)
...
... def __init__(self, guest):
... self.guest = guest
...
... def register(self):
... guest = self.guest
... next_id = get_next_id()
... bookings_db[next_id] = {
... 'name': guest.name,
... 'place': guest.place,
... 'phone': guest.phone
... }
What you defined here is an `adapter` for `IDesk`, which adapts
`IGuest` object. The `IDesk` interface is implemented by
`FrontDeskNG` class. So, an instance of this class will provide
`IDesk` interface.
::
>>> class Guest(object):
...
... implements(IGuest)
...
... def __init__(self, name, place):
... self.name = name
... self.place = place
>>> jack = Guest("Jack", "Bangalore")
>>> jack_frontdesk = FrontDeskNG(jack)
>>> IDesk.providedBy(jack_frontdesk)
True
The `FrontDeskNG` is just one adapter you created, you can also
create other adapters which handles guest registration differently.
Registration
~~~~~~~~~~~~
To use this adapter component, you have to register this in a
component registry also known as site manager. A site manager
normally resides in a site. A site and site manager will be more
important when developing a Zope 3 application. For now you only
required to bother about global site and global site manager ( or
component registry). A global site manager will be in memory, but a
local site manager is persistent.
To register your component, first get the global site manager::
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> gsm.registerAdapter(FrontDeskNG,
... (IGuest,), IDesk, 'ng')
To get the global site manager, you have to call
``getGlobalSiteManager`` function available in ``zope.component``
package. In fact, the global site manager is available as an
attribute (``globalSiteManager``) of ``zope.component`` package. So,
you can directly use ``zope.component.globalSiteManager`` attribute.
To register the adapter in component, as you can see above, use
``registerAdapter`` method of component registry. The first argument
should be your adapter class/factory. The second argument is a tuple
of `adaptee` objects, i.e, the object which you are adapting. In this
example, you are adapting only `IGuest` object. The third argument is
the interface implemented by the adapter component. The fourth
argument is optional, that is the name of the particular adapter.
Since you gave a name for this adapter, this is a `named adapter`. If
name is not given, it will default to an empty string ('').
In the above registration, you have given the adaptee interface and
interface to be provided by the adapter. Since you have already given
these details in adapter implementation, it is not required to specify
again. In fact, you could have done the registration like this::
>>> gsm.registerAdapter(FrontDeskNG, name='ng')
There are some old API to do the registration, which you should avoid.
The old API functions starts with `provide`, eg: ``provideAdapter``,
``provideUtility`` etc. While developing a Zope 3 application you can
use Zope configuration markup language (ZCML) for registration of
components. In Zope 3, local components (persistent components) can
be registered from Zope Management Interface (ZMI) or you can do it
programmatically also.
You registered `FrontDeskNG` with a name `ng`. Similarly you can
register other adapters with different names. If a component is
registered without name, it will default to an empty string.
.. note::
Local components are persistent components but global components are
in memory. Global components will be registered based on the
configuration of application. Local components are taken to memory
from database while starting the application.
Querying adapter
~~~~~~~~~~~~~~~~
Retrieving registered components from component registry is achieved
through two functions available in ``zope.component`` package. One of
them is ``getAdapter`` and the other is ``queryAdapter`` . Both
functions accepts same arguments. The ``getAdapter`` will raise
``ComponentLookupError`` if component lookup fails on the other hand
``queryAdapter`` will return `None`.
You can import the methods like this::