Download as pdf or txt
Download as pdf or txt
You are on page 1of 156

AnIntroduction totheZope3Component Architecture

Brandon Craig Rhodes Georgia Tech NOLA Plone Symposium 2008

Thistalk,onthewhole,is dividedintofourparts

1.Enhancingobjects

1.Enhancingobjects 2.Adaptingobjects

1.Enhancingobjects 2.Adaptingobjects 3.Componentframework

1.Enhancingobjects 2.Adaptingobjects 3.Componentframework 4.Adaptingfortheweb

Let'sgo!

Manyprogramming languagesusestatic typing

floathalf(intn) { returnn/2.0; }

floathalf(intn) { returnn/2.0; }

Pythontypingisdynamic

defhalf(n): returnn/2.0

Youdon'tworryabout whetheranobjectisof therighttype

Yousimplytryusingit

DuckTyping (AlexMartelli)

DuckTyping Walkslikeaduck? Quackslikeaduck? It'saduck!

defhalf(n): returnn/2.0

defhalf(n): returnn/2.0 (Isnwillingtobedividedbytwo? Thenit'snumberishenoughforus!)

Now,imagine...

Imagineawonderful duckprocessinglibraryto whichyouwanttopass anobject

But... Theobjectyouwantto passisn'taduck?

Whatifitdoesn't alreadyquack?

Whatifitbears nottheleastresemblance toaduck!?

Example!

YouhaveaMessage objectfromthePython emailmodule

>>> from email import message_from_file >>> e = message_from_file(open('msg.txt')) >>> print e <email.message.Message instance at ...> >>> e.is_multipart() True >>> for part in e.get_payload(): ... print part.get_content_type() text/plain text/html

multipart/mixed

Messages canbe recursive

text/plain multipart/alternative text/plain text/html image/jpeg

Imaginethatweare writingaGUIemailclient

Andwewanttoshowthe partsinaTreeWidget

TheTreewidgetneeds:
method name() - returns name under which this tree node should be displayed method children() - returns list of child nodes in the tree method __len__() - returns number of child nodes beneath this one

Howcanweaddthese behaviorstoour Message?

(Howcanwemakean objectwhichisnotaduck behavelikeaduck?)

1.Subclassing

CreateaTreeMessage classthatinheritsfrom theMessageclass...

class TreeMessage(Message): def name(self): return self.get_content_type() def children(self): if not self.is_multipart(): return [] return [ TreeMessage(part) for part in self.get_payload() ] def __len__(self): return len(self.children())

Whatwillthetestsuite looklike?

Remember: Untestedcode isbrokencode


PhilippvonWeitershausen, MartinAspeli

Yourtestsuite mustinstantiatea TreeMessageandverify itstreelikebehavior...

txt = From: persephone@gmail.com To: brandon@rhodesmill.org Subject: what an article! Did you read Arts & Letters Daily today? m = message_from_string(txt, TreeMessage) assert m.name() == 'text/plain' assert m.children == [] assert m.__len__() == 0

Wewerelucky!

Ourtestcancheaply instantiateMessages.

txt = From: persephone@gmail.com To: brandon@rhodesmill.org Subject: what an article! Did you read Arts & Letters Daily today? m = message_from_string(txt, TreeMessage) assert m.name() == 'text/plain' assert m.children == [] assert m.__len__() == 0

Whatifwewere subclassinganLDAP connector?! We'dneedanLDAPserver justtorununittests!

Wewerelucky(#2)!

The message_from_string() methodletusspecifyan alternatefactory!

txt = From: persephone@gmail.com To: brandon@rhodesmill.org Subject: what an article! Did you read Arts & Letters Daily today? m = message_from_string(txt, TreeMessage) assert m.name() == 'text/plain' assert m.children == [] assert m.__len__() == 0

Finalnote:wehavejust brokentheMessage class'sbehavior!

Pythonlibrarymanual 7.1.1definesMessage:
__len__(): Returnthetotalnumberofheaders, includingduplicates.

>>> t = From: persephone@gmail.com To: brandon@rhodesmill.org Subject: what an article! Did >>> >>> 3 >>> >>> 0 you read Arts & Letters Daily today? m = message_from_file(t, Message) print len(m) m = message_from_file(t, TreeMessage) print len(m)

Sohowdoes subclassing score?

Noharmtobaseclass

Noharmtobaseclass Cannottestinisolation

Noharmtobaseclass Cannottestinisolation Needcontroloffactory

Noharmtobaseclass Cannottestinisolation Needcontroloffactory Breaksifnamescollide

Noharmtobaseclass Cannottestinisolation Needcontroloffactory Breaksifnamescollide Subclassing:D

2.Usingamixin

CreateaTreeMessage classthatinheritsfrom bothMessageanda Mixin...

class Mixin(object): def name(self): return self.get_content_type() def children(self): if not self.is_multipart(): return [] return [ TreeMessage(part) for part in self.get_payload() ] def __len__(self): return len(self.children()) class TreeMessage(Message, Mixin): pass

Yourtestsuitecanthen inheritfromamockedup message...

class FakeMessage(Mixin): def get_content_type(self): return 'text/plain' def is_multipart(self): return False def get_payload(self): return '' m = FakeMessage() assert m.name() == 'text/plain' assert m.children() == [] assert m.__len__() == 0

Howdoes amixinrate?

Noharmtobaseclass

Noharmtobaseclass Cantestmixinbyitself

Noharmtobaseclass Cantestmixinbyitself Needcontroloffactory

Noharmtobaseclass Cantestmixinbyitself Needcontroloffactory Breaksifnamescollide

Noharmtobaseclass Cantestmixinbyitself Needcontroloffactory Breaksifnamescollide Mixin:C

3.Monkeypatching

Tomonkeypatcha class,youaddorchange itsmethodsdynamically...

def name(self): return self.get_content_type() def children(self): if not self.is_multipart(): return [] return [ Message(part) for part in self.get_payload() ] def __len__(self): return len(self.children()) Message.name = name Message.children = children Message.__len__ = __len__

Isthisdesirable?

Don'tcareaboutfactory

Don'tcareaboutfactory Changesclassitself

Don'tcareaboutfactory Changesclassitself Brokenbycollisions

Don'tcareaboutfactory Changesclassitself Brokenbycollisions Patchesfighteachother

Don'tcareaboutfactory Changesclassitself Brokenbycollisions Patchesfighteachother Rubypeopledothis

Don'tcareaboutfactory Changesclassitself Brokenbycollisions Patchesfighteachother Rubypeopledothis Monkeypatching:F

4.Adapter

Toutedin theGangof Fourbook (1994)

Idea:provideTree functionsthroughan entirelyseparateclass


Message get_content_type() is_multipart() call get_payload() MessageTreeAdapter name() children() __len__()

class MessageTreeAdapter(object): def __init__(self, message): self.m = message def name(self): return self.m.get_content_type() def children(self): if not self.m.is_multipart(): return [] return [ TreeMessageAdapter(part) for part in self.m.get_payload() ] def __len__(self): return len(self.children())

Howdoeswrappinglook inyourcode?

IMAP library (or whatever) Messageobject tw = TreeWidget(MessageTreeAdapter(msg)) Adaptedobject TreeWidget

Testsuitecantryadapting amockupobject

class FakeMessage(object): def get_content_type(self): return 'text/plain' def is_multipart(self): return True def get_payload(self): return [] m = MessageTreeAdapter(FakeMessage()) assert m.name() == 'text/plain' assert m.children == [] assert m.__len__() == 0

HowdoestheAdapter designpatternstackup?

Noharmtobaseclass

Noharmtobaseclass Cantestwithmockup

Noharmtobaseclass Cantestwithmockup Don'tneedfactories

Noharmtobaseclass Cantestwithmockup Don'tneedfactories Nocollisionworries

Noharmtobaseclass Cantestwithmockup Don'tneedfactories Nocollisionworries Wrappingisannoying

Noharmtobaseclass Cantestwithmockup Don'tneedfactories Nocollisionworries Wrappingisannoying Adapter:B

Q:Whycallwrapping annoying?

Theexamplemakes itlooksoeasy!

IMAP library (or whatever) Messageobject tw = TreeWidget(TreeMessageAdapter(msg)) Adaptedobject TreeWidget

A:Theexamplelooks easybecauseitonlydoes adaptationonce!

Butinarealapplication, ithappensallthrough yourcode...

Adapters A B C

Yourapplication 3rdparty Producers IMAP Genealogy DB email msg C(msg) 3rdparty Consumers Web Widget GUI

objects A(famtree) objects B(msg) C(msg)

Howcanyouavoid repeatingyourself,and scatteringinformation aboutadaptersand consumerseverywhere?

IMAP library (or whatever) Messageobject tw = TreeWidget(TreeMessageAdapter(msg)) Adaptedobject TreeWidget

tw = TreeWidget(TreeMessageAdapter(msg))

tw = TreeWidget(TreeMessageAdapter(msg))

Thekeyisseeingthatthis codeconflatestwoissues!

tw = TreeWidget(TreeMessageAdapter(msg))

Whydoesthislinework?

tw = TreeWidget(TreeMessageAdapter(msg))

Itworksbecausea TreeWidgetneedswhat ouradapterprovides.

tw = TreeWidget(TreeMessageAdapter(msg))

Butifwecalltheadapter thentheneed=wantis hiddeninsideofourhead!

Weneedtodefinewhat theTreeWidgetneedsthat ouradapterprovides!

Aninterface ishowwe specifyaset ofbehaviors

(1988)

(1995)

Aninterface ishowwe specifyaset ofbehaviors

Forthemoment,forget Zopethewebframework

Instead,lookatZopethe componentframework: zope.interface zope.component

Withthreesimplesteps, Zopewillridyourcodeof manualadaptation

1.Defineaninterface 2.Registerouradapter 3.Requestadaptation

Define
from zope.interface import Interface class def def def ITree(Interface): name(): Return this tree node's name. children(): Return this node's children. __len__(): Return how many children.

Register
from zope.component import provideAdapter provideAdapter(MessageTreeAdapter, adapts=Message, provides=ITree)

Request
from your_interfaces import ITree class TreeWidget(...): def __init__(self, arg): tree = ITree(arg) ...

Request
from your_interfaces import ITree class TreeWidget(...): def __init__(self, arg): tree = ITree(arg) ... Zope will: 1. Recognize need 2. Find the registered adapter 3. Wrap and return the Message

Request
from your_interfaces import ITree class TreeWidget(...): def __init__(self, arg): tree = ITree(arg) ... (Look! Zope is Pythonic!) i = int(32.1) l = list('abc') f = float(1024)

Andthat'sit!

Andthat'sit! Defineaninterface Registerouradapter Requestadaptation

Noharmtobaseclass Cantestwithmockup Don'tneedfactories Nocollisionworries Adaptersnowdynamic! Registeredadapter:A

Adapters A B C

Yourapplication 3rdparty Producers IMAP Genealogy DB email msg C(msg) 3rdparty Consumers Web Widget GUI

objects A(famtree) objects B(msg) C(msg)

What adapters provide

What consumers need

msg IMAP Genealogy DB email

C(msg) Web Widget GUI

A(famtree) B(msg) C(msg)

IMAP Genealogy DB email

Web Widget GUI

Thefinale AdaptingfortheWeb

dum...dum...dum... DAHDUM!

Grok

Webframework builtatopZope3 componentarchitecture

Grokmakes Zope3simpletouse (andtopresent!)

ImagineaPersonclass

ThePersonclasswas writtenbysomeoneelse

ThePersonclassisfullof businesslogic,andstores instancesinadatabase

Wewanttobrowse PersonobjectsontheWeb

WhatmighttheWeb needtheobjecttodo?

http://person_app/Joe

1.What'sataURL Person 3.WhatisitsURL

2.HTMLdocument
<HTML> <HEAD> <TITLE>Person JOE </TITLE> </HEAD> <BODY> This page presents the basic data we have regarding Joe. ...

http://person_app/Joe

1.

What'satthisURL?

What'satthisURL?
http://person_app/Joe

# how Zope processes this URL: r = root j = ITraverser(r).traverse('person_app') k = ITraverser(j).traverse('Joe') return k

What'satthisURL?
http://person_app/Joe # what we write: class PersonTraverser(grok.Traverser): grok.context(PersonApp) def traverse(self, name): if person_exists(name): return get_person(name) return None

What'satthisURL?
http://person_app/Joe # what we write: class PersonTraverser(grok.Traverser): grok.context(PersonApp) def traverse(self, name): if person_exists(name): return get_person(name) return None

What'satthisURL?
http://person_app/Joe # what we write: class PersonTraverser(grok.Traverser): grok.context(PersonApp) def traverse(self, name): if person_exists(name): return get_person(name)

2.

HowdoesaPersonrender?

HowdoesaPersonrender?
app.py class PersonIndex(grok.View): grok.context(Person) grok.name('index') app_templates/personindex.pt <html><head><title>All about <tal tal:replace= context/name /> </title></head>...

3.

Whatisaperson'sURL?

Whatisaperson'sURL?
class PersonURL(grok.MultiAdapter): grok.adapts(Person, IHTTPRequest) grok.implements(IAbsoluteURL) def __init__(self, person, req): self.person, self.req = person, req def __call__(self): base = grok.url(grok.getSite()) return base + '/' + self.person.name

5+3+8=16lines

http://person_app/Joe PersonTraverser 1.What'sataURL Person 3.WhatisitsURL PersonURL http://person_app/Joe 2.HTMLDocument PersonIndex


<HTML> <HEAD> <TITLE>Person JOE </TITLE> </HEAD> <BODY> This page presents the basic data we have regarding Joe. ...

OtherZopeadapteruses

OtherZopeadapteruses
IndexingIndex,Query,Search,... DataschemasSchema,Vocabulary,DublinCore... FormgenerationAddForm,EditForm,... SecuritySecurityPolicy,Proxy,Checker,... AuthenticationLogin,Logout,Allow,Require,... CopyandpasteObjectMover,ObjectCopier,... I18nTranslationDomain,Translator,... AppearanceSkins,macros,viewlets,... Much,muchmore!

Adapterscanbelocal!
http://person_app/Joe http:// Globaladapters person_app Joe

Localadapters

ComingAttraction

five.grok

five.grok
LennartRegebro

Thankyou!
http://zope.org/Products/Zope3 http://grok.zope.org/ http://rhodesmill.org/brandon/adapters http://regebro.wordpress.com/ zopedev@zope.orgmailinglist grokdev@zope.orgmailinglist WebComponentDevelopmentwithZope3byPvW

You might also like