Merge lp:~salgado/launchpad/apidoc into lp:launchpad

Proposed by Guilherme Salgado
Status: Merged
Approved by: Guilherme Salgado
Approved revision: no longer in the source branch.
Merged at revision: 11220
Proposed branch: lp:~salgado/launchpad/apidoc
Merge into: lp:launchpad
Diff against target: 593 lines (+217/-52)
20 files modified
configs/development/apidoc-configure-normal.zcml (+84/-39)
configs/development/launchpad-lazr.conf (+3/-0)
lib/canonical/config/schema-lazr.conf (+2/-0)
lib/canonical/launchpad/browser/launchpad.py (+2/-2)
lib/canonical/launchpad/doc/webapp-publication.txt (+5/-0)
lib/canonical/launchpad/layers.py (+4/-0)
lib/canonical/launchpad/permissions.zcml (+5/-0)
lib/canonical/launchpad/security.py (+14/-0)
lib/canonical/launchpad/systemhomes.py (+11/-2)
lib/canonical/launchpad/webapp/authentication.py (+21/-1)
lib/canonical/launchpad/webapp/authorization.py (+6/-2)
lib/canonical/launchpad/webapp/configure.zcml (+1/-1)
lib/canonical/launchpad/webapp/interfaces.py (+4/-0)
lib/canonical/launchpad/webapp/publisher.py (+0/-3)
lib/canonical/launchpad/webapp/servers.py (+15/-1)
lib/lp/app/stories/apidoc.txt (+25/-0)
setup.py (+5/-0)
site.zcml (+1/-0)
versions.cfg (+7/-1)
zopeapp.zcml (+2/-0)
To merge this branch: bzr merge lp:~salgado/launchpad/apidoc
Reviewer Review Type Date Requested Status
Gary Poster (community) Approve
Review via email: mp+30520@code.launchpad.net

Commit message

Add a new vhost (apidoc.launchpad.dev), enabled only when devmode is on, for Launchpad's apidoc using zope.app.apidoc.

Description of the change

Make zope.app.apidoc available on the new apidoc.lp.dev vhost.

To post a comment you must log in.
Revision history for this message
Gary Poster (gary) wrote :
Download full text (4.4 KiB)

Hi Salgado. Very cool. Thank you. Comments below.

Requested:

- Let's put the changes from lib/canonical/launchpad/webapp/configure.zcml into apidoc-configure-normal.zcml.

Questions and comments:

- Why is this necessary, do you know? I realize it is an existing code that you moved around, but neither the comment nor the "provides" made much sense to me, though Google did show me some hints that it might be standard practice for enabling the apidoc for some reason. Did you try removing it?
+ <!-- Turn on devmode for the following includes principal=work -->
+ <meta:provides feature="devmode" />

- I'm concerned that the tests are going to be run with even more registrations that will not be part of production. I wish there were a light, easy way to start up another LP instance for the apidoc smoke test that then would not pollute the rest of the tests with the apidoc registrations in the zcml file. However, I don't know of one, and unless you do, I won't insist that we address this concern. It's certainly just slightly enlarging an existing problem, rather than making a new one.

- Did you audit zope.login to make sure it's registrations don't do something we need to be worried about from a security perspective? If not, one of us should. I'd feel somewhat happier if ``<include package="zope.login" />`` were part of the apidoc zcml, but then we're exacerbating the previous problem (that is, production registrations are more and more different from test registrations).

Suggested:

- I suggest renaming LaunchpadPrincipalAnnotations to TemporaryPrincipalAnnotations: the behavior (and the fact that the implementation is effectively a stub) is something worth highlighting. Calling it "Launchpad" doesn't buy us anything.

- FWIW, LaunchpadPrincipalAnnotations could subclass UserDict.UserDict and be shortened to the __init__ plus a call to the shared constructor and the interface declarations. Do it if you want.

- For this comment:
+ <!-- XXX: Copied from zope.browserresource as apidoc breaks if we make
+ IAPIDocRoot implement ISite so that the original registration works
+ for us. -->
I suggest that this is not an XXX. To that point, this is not "Copied" but "Modified" (the "for" is different, and that's the key).

- For this comment:
+ <!-- XXX Probably make this in overrides for ISimpleReadContainer. Safer:
+ register for all apidoc containers, one way or another -->
You mentioned that these comments came from me. I think we can just remove the comment entirely. As I said on IRC, for the first half, I think I expected that this was going to have to go in overrides. Since it works without being in overrides, this is a "never mind...". For the second half ("Safer:..."), I think we're probably fine. It would be nice if we could easily register this only for the bots we care about, but, eh. It's all right, I think.

- I have said that I was fine with the change to authorization.py, but actually I had another idea. I'm not 100% convinced the new idea is better, but it is simpler, at least. The idea is to revert all current changes from that file, and then add ``from zope.proxy import removeAllProx...

Read more...

Revision history for this message
Gary Poster (gary) :
review: Needs Information
Revision history for this message
Guilherme Salgado (salgado) wrote :
Download full text (6.0 KiB)

On Wed, 2010-07-21 at 17:08 +0000, Gary Poster wrote:
> Hi Salgado. Very cool. Thank you. Comments below.
>
> Requested:
>
> - Let's put the changes from
> lib/canonical/launchpad/webapp/configure.zcml into
> apidoc-configure-normal.zcml.

Good catch. Done.

>
> Questions and comments:
>
> - Why is this necessary, do you know? I realize it is an existing
> code that you moved around, but neither the comment nor the "provides"
> made much sense to me, though Google did show me some hints that it
> might be standard practice for enabling the apidoc for some reason.
> Did you try removing it?
> + <!-- Turn on devmode for the following includes principal=work
> -->
> + <meta:provides feature="devmode" />

For some reason it became necessary when I moved the zcml to
apidoc-configure-normal.zcml. I've moved the principal= part.

>
> - I'm concerned that the tests are going to be run with even more
> registrations that will not be part of production. I wish there were
> a light, easy way to start up another LP instance for the apidoc smoke
> test that then would not pollute the rest of the tests with the apidoc
> registrations in the zcml file. However, I don't know of one, and
> unless you do, I won't insist that we address this concern. It's
> certainly just slightly enlarging an existing problem, rather than
> making a new one.

I hadn't thought about that, but I'll see if I can find a way to not
make this situation any worse.

>
> - Did you audit zope.login to make sure it's registrations don't do
> something we need to be worried about from a security perspective? If
> not, one of us should. I'd feel somewhat happier if ``<include
> package="zope.login" />`` were part of the apidoc zcml, but then we're
> exacerbating the previous problem (that is, production registrations
> are more and more different from test registrations).

zope.login is now needed because the ILoginPassword adapter has been
moved there from zope.publisher, but you have a good point that it
should be audited.

I've just checked and it just registers the ILoginPassword adapters for
both IHTTPCredentials and IFTPCredentials as zope.publisher did until
version 3.10.

I'm now wondering if I should do the same for all the eggs that I've
updated as part of this -- they could also register new things that
we're not expecting.

>
> Suggested:
>
> - I suggest renaming LaunchpadPrincipalAnnotations to
> TemporaryPrincipalAnnotations: the behavior (and the fact that the
> implementation is effectively a stub) is something worth highlighting.
> Calling it "Launchpad" doesn't buy us anything.

I like your suggestion; renamed.

>
> - FWIW, LaunchpadPrincipalAnnotations could subclass UserDict.UserDict
> and be shortened to the __init__ plus a call to the shared constructor
> and the interface declarations. Do it if you want.

I'm all for it!

Also, I thought I could make apidoc.lp.dev work for anonymous users by
just registering an adapter like the above for
IUnauthenticatedPrincipal, but then I got
http://paste.ubuntu.com/467401/

I'm thinking that it may be better to actually require a logged-in user
to use apidoc.lp.dev and ask them to login in case they're no...

Read more...

Revision history for this message
Guilherme Salgado (salgado) wrote :

Another alternative to deal with the fact that apidoc.lp.dev doesn't seem to work for anonymous users would be to somehow pretend all requests on apidoc.lp.dev are authenticated. I did that by making APIDocBrowserPublication.getPrincipal() return a fixed principal, but now I always get that same LocationError traceback: http://paste.ubuntu.com/467401/

Revision history for this message
Gary Poster (gary) wrote :
Download full text (5.9 KiB)

Hey Salgado. Thank you. Below, I've deleted sections for which I thought no further discussion was necessary.

On Jul 22, 2010, at 5:15 AM, Guilherme Salgado wrote:

> On Wed, 2010-07-21 at 17:08 +0000, Gary Poster wrote:
>> Hi Salgado. Very cool. Thank you. Comments below.
>>
>> Requested:
>>
>> - Let's put the changes from
>> lib/canonical/launchpad/webapp/configure.zcml into
>> apidoc-configure-normal.zcml.
>
> Good catch. Done.

I don't understand why you didn't also move this:
+ <utility
+ component="canonical.launchpad.systemhomes.apidocroot"
+ provides="canonical.launchpad.webapp.interfaces.IAPIDocRoot" />
Was it an oversight, or is there a reason to keep it where it is?

...

>> - I'm concerned that the tests are going to be run with even more
>> registrations that will not be part of production. I wish there were
>> a light, easy way to start up another LP instance for the apidoc smoke
>> test that then would not pollute the rest of the tests with the apidoc
>> registrations in the zcml file. However, I don't know of one, and
>> unless you do, I won't insist that we address this concern. It's
>> certainly just slightly enlarging an existing problem, rather than
>> making a new one.
>
> I hadn't thought about that, but I'll see if I can find a way to not
> make this situation any worse.

Ack, thanks.

>> - Did you audit zope.login to make sure it's registrations don't do
>> something we need to be worried about from a security perspective? If
>> not, one of us should. I'd feel somewhat happier if ``<include
>> package="zope.login" />`` were part of the apidoc zcml, but then we're
>> exacerbating the previous problem (that is, production registrations
>> are more and more different from test registrations).
>
> zope.login is now needed because the ILoginPassword adapter has been
> moved there from zope.publisher, but you have a good point that it
> should be audited.
>
> I've just checked and it just registers the ILoginPassword adapters for
> both IHTTPCredentials and IFTPCredentials as zope.publisher did until
> version 3.10.

Cool.

> I'm now wondering if I should do the same for all the eggs that I've
> updated as part of this -- they could also register new things that
> we're not expecting.

The reason why I singled in on this one is that it is registered in zopeapp.zcml, so it will affect production. The rest will only affect tests and development, so I'm less concerned in this regard.

zope.publisher's update I guess is worth a look, actually. But that's the only other one I see.

> - FWIW, LaunchpadPrincipalAnnotations could subclass UserDict.UserDict
>> and be shortened to the __init__ plus a call to the shared constructor
>> and the interface declarations. Do it if you want.
>
> I'm all for it!
>
> Also, I thought I could make apidoc.lp.dev work for anonymous users by
> just registering an adapter like the above for
> IUnauthenticatedPrincipal, but then I got
> http://paste.ubuntu.com/467401/

Meh, digging into it just a bit, I see that preference group thing is doing somewhat unusual security dances. I wouldn't be all that surprised if that LocationError were actually hiding a Forbidd...

Read more...

Revision history for this message
Gary Poster (gary) :
review: Approve
Revision history for this message
Guilherme Salgado (salgado) wrote :
Download full text (5.5 KiB)

On Fri, 2010-07-23 at 12:42 +0000, Gary Poster wrote:
> Hey Salgado. Thank you. Below, I've deleted sections for which I thought no further discussion was necessary.
>
> On Jul 22, 2010, at 5:15 AM, Guilherme Salgado wrote:
>
> > On Wed, 2010-07-21 at 17:08 +0000, Gary Poster wrote:
> >> Hi Salgado. Very cool. Thank you. Comments below.
> >>
> >> Requested:
> >>
> >> - Let's put the changes from
> >> lib/canonical/launchpad/webapp/configure.zcml into
> >> apidoc-configure-normal.zcml.
> >
> > Good catch. Done.
>
> I don't understand why you didn't also move this:
> + <utility
> + component="canonical.launchpad.systemhomes.apidocroot"
> + provides="canonical.launchpad.webapp.interfaces.IAPIDocRoot" />
> Was it an oversight, or is there a reason to keep it where it is?

Yep, oversight. Moved.

>
> > I'm now wondering if I should do the same for all the eggs that I've
> > updated as part of this -- they could also register new things that
> > we're not expecting.
>
> The reason why I singled in on this one is that it is registered in
> zopeapp.zcml, so it will affect production. The rest will only affect
> tests and development, so I'm less concerned in this regard.
>
> zope.publisher's update I guess is worth a look, actually. But that's
> the only other one I see.
>

http://paste.ubuntu.com/467986/ is the diff between the previous
configure.zcml and the current version, so nothing to worry about, I
think?

> > - FWIW, LaunchpadPrincipalAnnotations could subclass UserDict.UserDict
> >> and be shortened to the __init__ plus a call to the shared constructor
> >> and the interface declarations. Do it if you want.
> >
> > I'm all for it!
> >
> > Also, I thought I could make apidoc.lp.dev work for anonymous users by
> > just registering an adapter like the above for
> > IUnauthenticatedPrincipal, but then I got
> > http://paste.ubuntu.com/467401/
>
> Meh, digging into it just a bit, I see that preference group thing is
> doing somewhat unusual security dances. I wouldn't be all that
> surprised if that LocationError were actually hiding a
> ForbiddenAttribute on a value that should be there, but maybe I'm
> wrong.
>
> > I'm thinking that it may be better to actually require a logged-in user
> > to use apidoc.lp.dev and ask them to login in case they're not, just
> > like we do on other launchpad.AnyPerson pages. What do you think?
>
> Since this is only for devel instances, in the abstract there's no
> reason not to expose this for anonymous users that I see. That will
> also make it easier to make a static version of the apidoc, which I
> think is part of the plan.

Agreed. All I wanted was to make sure we require people to login as
early as possible so that they don't see these obscure errors that one
has no idea can be worked around just by logging in.

>
> If you want me to look into the traceback further just ask.

I'd really appreciate as I wouldn't know where to start. Just some
pointers so I can get started would be great.

>
> ...
>
> >> - I have said that I was fine with the change to authorization.py, but
> >> actually I had another idea. I'm not 100% convinced the new idea is
> >> better, ...

Read more...

Revision history for this message
Gary Poster (gary) wrote :
Download full text (3.6 KiB)

On Jul 23, 2010, at 9:12 AM, Guilherme Salgado wrote:

> On Fri, 2010-07-23 at 12:42 +0000, Gary Poster wrote:
>> Hey Salgado. Thank you. Below, I've deleted sections for which I thought no further discussion was necessary.

Doing that again.

>>
>> On Jul 22, 2010, at 5:15 AM, Guilherme Salgado wrote:
>>
>>> On Wed, 2010-07-21 at 17:08 +0000, Gary Poster wrote:
>
...

>>
>>> I'm now wondering if I should do the same for all the eggs that I've
>>> updated as part of this -- they could also register new things that
>>> we're not expecting.
>>
>> The reason why I singled in on this one is that it is registered in
>> zopeapp.zcml, so it will affect production. The rest will only affect
>> tests and development, so I'm less concerned in this regard.
>>
>> zope.publisher's update I guess is worth a look, actually. But that's
>> the only other one I see.
>>
>
> http://paste.ubuntu.com/467986/ is the diff between the previous
> configure.zcml and the current version, so nothing to worry about, I
> think?

Agreed, thanks.

>
>
>>> - FWIW, LaunchpadPrincipalAnnotations could subclass UserDict.UserDict
>>>> and be shortened to the __init__ plus a call to the shared constructor
>>>> and the interface declarations. Do it if you want.
>>>
>>> I'm all for it!
>>>
>>> Also, I thought I could make apidoc.lp.dev work for anonymous users by
>>> just registering an adapter like the above for
>>> IUnauthenticatedPrincipal, but then I got
>>> http://paste.ubuntu.com/467401/
>>
>> Meh, digging into it just a bit, I see that preference group thing is
>> doing somewhat unusual security dances. I wouldn't be all that
>> surprised if that LocationError were actually hiding a
>> ForbiddenAttribute on a value that should be there, but maybe I'm
>> wrong.
>>
>>> I'm thinking that it may be better to actually require a logged-in user
>>> to use apidoc.lp.dev and ask them to login in case they're not, just
>>> like we do on other launchpad.AnyPerson pages. What do you think?
>>
>> Since this is only for devel instances, in the abstract there's no
>> reason not to expose this for anonymous users that I see. That will
>> also make it easier to make a static version of the apidoc, which I
>> think is part of the plan.
>
> Agreed. All I wanted was to make sure we require people to login as
> early as possible so that they don't see these obscure errors that one
> has no idea can be worked around just by logging in.
>
>>
>> If you want me to look into the traceback further just ask.
>
> I'd really appreciate as I wouldn't know where to start. Just some
> pointers so I can get started would be great.

I was skeptical of the way you were using object and UserDict together--that doesn't make UserDict a new-style class, so it doesn't play the "super" game. This fixes the traceback for unauthenticated users, and is generally more correct:

=== modified file 'lib/canonical/launchpad/webapp/authentication.py'
--- lib/canonical/launchpad/webapp/authentication.py 2010-07-22 09:12:46 +0000
+++ lib/canonical/launchpad/webapp/authentication.py 2010-07-23 14:54:07 +0000
@@ -326,12 +326,12 @@

 # zope.app.apidoc expects our principals to be adaptable into IAnno...

Read more...

Revision history for this message
Gary Poster (gary) wrote :

One more thing, but it's really important.

I think LP devs might get a lot more excited about this if you just add this little bit.

=== modified file 'configs/development/apidoc-configure-normal.zcml'
--- configs/development/apidoc-configure-normal.zcml 2010-07-22 09:12:46 +0000
+++ configs/development/apidoc-configure-normal.zcml 2010-07-23 18:25:06 +0000
@@ -3,6 +3,7 @@
     xmlns:browser="http://namespaces.zope.org/browser"
     xmlns:meta="http://namespaces.zope.org/meta"
     xmlns:i18n="http://namespaces.zope.org/i18n"
+ xmlns:apidoc="http://namespaces.zope.org/apidoc"
     i18n_domain="canonical">

     <!-- These packages/declarations are required by apidoc. If they are
@@ -48,7 +49,7 @@
     <!-- apidoc.lp.dev breaks if we make IAPIDocRoot subclass ISite, so we
       need to register this view here. -->
     <view
- for="canonical.launchpad.webapp.interfaces.IAPIDocRoot"
+ for="canonical.launchpad.webapp.interfaces.IAPIDocRoot"
         type="zope.publisher.interfaces.browser.IDefaultBrowserLayer"
         name=""
         factory="zope.browserresource.resources.Resources"
@@ -74,5 +75,15 @@
     <include package="zope.app.tree" />
     <include package="zope.app.apidoc" />

+ <apidoc:rootModule module="canonical" />
+ <apidoc:rootModule module="lp" />
+ <apidoc:rootModule module="lazr" />
+ <apidoc:rootModule module="zc" />
+ <apidoc:rootModule module="wadllib" />
+ <apidoc:rootModule module="martian" />
+ <apidoc:rootModule module="manuel" />
+ <apidoc:rootModule module="chameleon" />
+ <apidoc:rootModule module="bzrlib" />
+ <apidoc:rootModule module="storm" />
+
 </configure>
-

With this, all of that code is browsable and searchable, and the work you've done shows what we expect in the apidoc. It's pretty cool, I think.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== renamed file 'configs/development/apidoc-configure-normal.zcml.OFF' => 'configs/development/apidoc-configure-normal.zcml'
--- configs/development/apidoc-configure-normal.zcml.OFF 2008-09-02 16:03:35 +0000
+++ configs/development/apidoc-configure-normal.zcml 2010-07-23 19:21:07 +0000
@@ -1,48 +1,93 @@
1<configure1<configure
2 xmlns="http://namespaces.zope.org/zope"2 xmlns="http://namespaces.zope.org/zope"
3 xmlns:meta="http://namespaces.zope.org/meta">3 xmlns:browser="http://namespaces.zope.org/browser"
4 <!-- These packages are required by apidoc. If they are deemed4 xmlns:meta="http://namespaces.zope.org/meta"
5 generally useful, move them to zopeapp.zcml5 xmlns:i18n="http://namespaces.zope.org/i18n"
6 -->6 xmlns:apidoc="http://namespaces.zope.org/apidoc"
7 <!--7 i18n_domain="canonical">
8 How frustrating. This is needed for just one page registration8
9 in introspector.zcml.9 <!-- These packages/declarations are required by apidoc. If they are
10 -->10 deemed generally useful, move them to zopeapp.zcml -->
11 <include package="zope.app" file="menus.zcml" />11
12 <include package="zope.app.tree.browser" />12 <browser:menu
13 <include package="zope.app.tree" /> 13 id="zmi_views"
14 <include package="zope.app.renderer" file="meta.zcml" />14 title="Views"
15 description="Menu for displaying alternate representations of an object"
16 />
17 <browser:menu
18 id="zmi_actions"
19 title="Actions"
20 description="Menu for displaying actions to be performed"
21 />
22
23 <!-- Use the default IAbsoluteURL adapter for requests on the apidoc
24 vhost. -->
25 <adapter
26 for="zope.interface.Interface
27 canonical.launchpad.webapp.servers.APIDocBrowserRequest"
28 provides="zope.traversing.browser.interfaces.IAbsoluteURL"
29 factory="zope.traversing.browser.AbsoluteURL"
30 />
31
32 <view
33 for="zope.container.interfaces.IReadContainer"
34 type="zope.publisher.interfaces.http.IHTTPRequest"
35 provides="zope.publisher.interfaces.IPublishTraverse"
36 factory="zope.container.traversal.ContainerTraverser"
37 permission="zope.Public"
38 allowed_interface="zope.publisher.interfaces.IPublishTraverse"
39 />
40
41 <utility
42 component="canonical.launchpad.systemhomes.apidocroot"
43 provides="canonical.launchpad.webapp.interfaces.IAPIDocRoot" />
44
45 <adapter factory="canonical.launchpad.webapp.authentication.TemporaryPrincipalAnnotations" />
46 <adapter
47 factory="canonical.launchpad.webapp.authentication.TemporaryUnauthenticatedPrincipalAnnotations" />
48
49 <class class="canonical.launchpad.webapp.servers.LaunchpadBrowserRequest">
50 <implements interface="zope.app.apidoc.browser.skin.APIDOC" />
51 </class>
52
53 <!-- apidoc.lp.dev breaks if we make IAPIDocRoot subclass ISite, so we
54 need to register this view here. -->
55 <view
56 for="canonical.launchpad.webapp.interfaces.IAPIDocRoot"
57 type="zope.publisher.interfaces.browser.IDefaultBrowserLayer"
58 name=""
59 factory="zope.browserresource.resources.Resources"
60 permission="zope.Public"
61 allowed_interface="zope.publisher.interfaces.browser.IBrowserPublisher"
62 />
63
64 <browser:defaultView
65 for="canonical.launchpad.webapp.interfaces.IAPIDocRoot"
66 name="++apidoc++"
67 />
68
69 <!-- Turn on devmode for the following includes to work -->
70 <meta:provides feature="devmode" />
71
72 <include package="zope.location" />
73 <include package="zope.app.applicationcontrol" />
15 <include package="zope.app.renderer" />74 <include package="zope.app.renderer" />
16
17 <!-- XXX: StuartBishop 2005-03-13 bug=39834:
18 We also need the Z3 preference junk, whatever that is.
19 Unfortunately, this depends on annotatable principals so will not
20 currently work with Launchpad. We can still get some apidoc functionality,
21 such as the ZCML browser, but the bulk of it is not functional.
22 -->
23 <include package="zope.app.preference" file="meta.zcml" />75 <include package="zope.app.preference" file="meta.zcml" />
24 <!--76 <include package="zope.app.apidoc.codemodule" file="meta.zcml" />
77 <include package="zope.app.apidoc.bookmodule" file="meta.zcml" />
25 <include package="zope.app.preference" />78 <include package="zope.app.preference" />
26 -->79 <include package="zope.app.tree" />
27
28
29 <!-- Turn on devmode for the following includes principal=work -->
30 <meta:provides feature="devmode" />
31
32 <include package="zope.app.apidoc" file="meta.zcml" />
33 <include package="zope.app.apidoc" />80 <include package="zope.app.apidoc" />
3481
35 <meta:redefinePermission82 <apidoc:rootModule module="canonical" />
36 from="zope.app.apidoc.UseAPIDoc" to="zope.Public"83 <apidoc:rootModule module="lp" />
37 />84 <apidoc:rootModule module="lazr" />
3885 <apidoc:rootModule module="zc" />
39 <!-- Override a strange permission in apidoc -->86 <apidoc:rootModule module="wadllib" />
40 <class class="zope.app.apidoc.apidoc.APIDocumentation">87 <apidoc:rootModule module="martian" />
41 <require88 <apidoc:rootModule module="manuel" />
42 interface="zope.app.container.interfaces.IReadContainer"89 <apidoc:rootModule module="chameleon" />
43 permission="zope.Public"90 <apidoc:rootModule module="bzrlib" />
44 />91 <apidoc:rootModule module="storm" />
45 </class>
4692
47</configure>93</configure>
48
4994
=== modified file 'configs/development/launchpad-lazr.conf'
--- configs/development/launchpad-lazr.conf 2010-07-15 15:57:40 +0000
+++ configs/development/launchpad-lazr.conf 2010-07-23 19:21:07 +0000
@@ -300,6 +300,9 @@
300[vhost.openid]300[vhost.openid]
301hostname: openid.launchpad.dev301hostname: openid.launchpad.dev
302302
303[vhost.apidoc]
304hostname: apidoc.launchpad.dev
305
303[vhost.testopenid]306[vhost.testopenid]
304hostname: testopenid.dev307hostname: testopenid.dev
305308
306309
=== added symlink 'configs/testrunner/apidoc-configure-normal.zcml'
=== target is u'../development/apidoc-configure-normal.zcml'
=== modified file 'lib/canonical/config/schema-lazr.conf'
--- lib/canonical/config/schema-lazr.conf 2010-07-16 20:55:50 +0000
+++ lib/canonical/config/schema-lazr.conf 2010-07-23 19:21:07 +0000
@@ -1977,6 +1977,8 @@
19771977
1978[vhost.testopenid]1978[vhost.testopenid]
19791979
1980[vhost.apidoc]
1981
1980[vhost.ubuntu_openid]1982[vhost.ubuntu_openid]
19811983
1982[vhost.shipitubuntu]1984[vhost.shipitubuntu]
19831985
=== modified file 'lib/canonical/launchpad/browser/launchpad.py'
--- lib/canonical/launchpad/browser/launchpad.py 2010-07-14 13:54:20 +0000
+++ lib/canonical/launchpad/browser/launchpad.py 2010-07-23 19:21:07 +0000
@@ -62,8 +62,8 @@
62 stepto)62 stepto)
63from canonical.launchpad.webapp.breadcrumb import Breadcrumb63from canonical.launchpad.webapp.breadcrumb import Breadcrumb
64from canonical.launchpad.webapp.interfaces import (64from canonical.launchpad.webapp.interfaces import (
65 GoneError, IBreadcrumb, ILaunchBag, ILaunchpadRoot, INavigationMenu,65 GoneError, IBreadcrumb, ILaunchBag, ILaunchpadRoot,
66 NotFoundError, POSTToNonCanonicalURL)66 INavigationMenu, NotFoundError, POSTToNonCanonicalURL)
67from canonical.launchpad.webapp.publisher import RedirectionView67from canonical.launchpad.webapp.publisher import RedirectionView
68from canonical.launchpad.webapp.authorization import check_permission68from canonical.launchpad.webapp.authorization import check_permission
69from canonical.launchpad.webapp.tales import PageTemplateContextsAPI69from canonical.launchpad.webapp.tales import PageTemplateContextsAPI
7070
=== modified file 'lib/canonical/launchpad/doc/webapp-publication.txt'
--- lib/canonical/launchpad/doc/webapp-publication.txt 2010-07-21 00:34:38 +0000
+++ lib/canonical/launchpad/doc/webapp-publication.txt 2010-07-23 19:21:07 +0000
@@ -41,6 +41,10 @@
41 rooturl: http://api.launchpad.dev/41 rooturl: http://api.launchpad.dev/
42 althosts:42 althosts:
43 ----43 ----
44 apidoc @ apidoc.launchpad.dev
45 rooturl: http://apidoc.launchpad.dev/
46 althosts:
47 ----
44 blueprints @ blueprints.launchpad.dev48 blueprints @ blueprints.launchpad.dev
45 rooturl: http://blueprints.launchpad.dev/49 rooturl: http://blueprints.launchpad.dev/
46 althosts:50 althosts:
@@ -106,6 +110,7 @@
106 ... print hostname110 ... print hostname
107 answers.launchpad.dev111 answers.launchpad.dev
108 api.launchpad.dev112 api.launchpad.dev
113 apidoc.launchpad.dev
109 blueprints.launchpad.dev114 blueprints.launchpad.dev
110 bugs.launchpad.dev115 bugs.launchpad.dev
111 code.launchpad.dev116 code.launchpad.dev
112117
=== modified file 'lib/canonical/launchpad/layers.py'
--- lib/canonical/launchpad/layers.py 2010-07-21 22:09:39 +0000
+++ lib/canonical/launchpad/layers.py 2010-07-23 19:21:07 +0000
@@ -36,6 +36,10 @@
36 """36 """
3737
3838
39class APIDocLayer(IBrowserRequest, IDefaultBrowserLayer):
40 """The `APIDocLayer` layer."""
41
42
39class TestOpenIDLayer(LaunchpadLayer):43class TestOpenIDLayer(LaunchpadLayer):
40 """The `TestOpenIDLayer` layer."""44 """The `TestOpenIDLayer` layer."""
4145
4246
=== modified file 'lib/canonical/launchpad/permissions.zcml'
--- lib/canonical/launchpad/permissions.zcml 2009-11-15 01:05:49 +0000
+++ lib/canonical/launchpad/permissions.zcml 2010-07-23 19:21:07 +0000
@@ -85,5 +85,10 @@
85 title="Review Launchpad projects"85 title="Review Launchpad projects"
86 access_level="write" />86 access_level="write" />
8787
88 <!-- This permission only exists to please apidoc.launchpad.dev and
89 shouldn't be used anywhere else. -->
90 <permission
91 id="zope.ManageApplication" title="Needed by zope.app.apidoc"
92 access_level="read" />
8893
89</configure>94</configure>
9095
=== modified file 'lib/canonical/launchpad/security.py'
--- lib/canonical/launchpad/security.py 2010-07-20 12:33:43 +0000
+++ lib/canonical/launchpad/security.py 2010-07-23 19:21:07 +0000
@@ -1080,13 +1080,27 @@
10801080
10811081
1082class UseApiDoc(AuthorizationBase):1082class UseApiDoc(AuthorizationBase):
1083 """This is just to please apidoc.launchpad.dev."""
1083 permission = 'zope.app.apidoc.UseAPIDoc'1084 permission = 'zope.app.apidoc.UseAPIDoc'
1084 usedfor = Interface1085 usedfor = Interface
10851086
1087 def checkUnauthenticated(self):
1088 return True
1089
1086 def checkAuthenticated(self, user):1090 def checkAuthenticated(self, user):
1087 return True1091 return True
10881092
10891093
1094class ManageApplicationForEverybody(UseApiDoc):
1095 """This is just to please apidoc.launchpad.dev.
1096
1097 We do this because zope.app.apidoc uses that permission, but nothing else
1098 should be using it.
1099 """
1100 permission = 'zope.ManageApplication'
1101 usedfor = Interface
1102
1103
1090class OnlyBazaarExpertsAndAdmins(AuthorizationBase):1104class OnlyBazaarExpertsAndAdmins(AuthorizationBase):
1091 """Base class that allows only the Launchpad admins and Bazaar1105 """Base class that allows only the Launchpad admins and Bazaar
1092 experts."""1106 experts."""
10931107
=== modified file 'lib/canonical/launchpad/systemhomes.py'
--- lib/canonical/launchpad/systemhomes.py 2010-04-19 06:35:23 +0000
+++ lib/canonical/launchpad/systemhomes.py 2010-07-23 19:21:07 +0000
@@ -6,7 +6,7 @@
6__all__ = [6__all__ = [
7 'AuthServerApplication',7 'AuthServerApplication',
8 'BazaarApplication',8 'BazaarApplication',
9 'CodeImportScheduler',9 'CodeImportSchedulerApplication',
10 'FeedsApplication',10 'FeedsApplication',
11 'MailingListApplication',11 'MailingListApplication',
12 'MaloneApplication',12 'MaloneApplication',
@@ -39,7 +39,8 @@
39from lp.hardwaredb.interfaces.hwdb import (39from lp.hardwaredb.interfaces.hwdb import (
40 IHWDeviceSet, IHWDriverSet, IHWSubmissionDeviceSet, IHWSubmissionSet,40 IHWDeviceSet, IHWDriverSet, IHWSubmissionDeviceSet, IHWSubmissionSet,
41 IHWVendorIDSet, ParameterError)41 IHWVendorIDSet, ParameterError)
42from canonical.launchpad.webapp.interfaces import ICanonicalUrlData42from canonical.launchpad.webapp.interfaces import (
43 IAPIDocRoot, ICanonicalUrlData)
43from lp.bugs.interfaces.bug import (44from lp.bugs.interfaces.bug import (
44 CreateBugParams, IBugSet, InvalidBugTargetType)45 CreateBugParams, IBugSet, InvalidBugTargetType)
45from lp.code.interfaces.codehosting import ICodehostingApplication46from lp.code.interfaces.codehosting import ICodehostingApplication
@@ -388,3 +389,11 @@
388389
389class TestOpenIDApplication:390class TestOpenIDApplication:
390 implements(ITestOpenIDApplication)391 implements(ITestOpenIDApplication)
392
393
394class APIDocRoot:
395 implements(IAPIDocRoot)
396 __parent__ = None
397 __name__ = None
398
399apidocroot = APIDocRoot()
391400
=== modified file 'lib/canonical/launchpad/webapp/authentication.py'
--- lib/canonical/launchpad/webapp/authentication.py 2010-02-16 14:25:51 +0000
+++ lib/canonical/launchpad/webapp/authentication.py 2010-07-23 19:21:07 +0000
@@ -16,12 +16,16 @@
16import binascii16import binascii
17import hashlib17import hashlib
18import random18import random
19from UserDict import UserDict
1920
20from contrib.oauth import OAuthRequest21from contrib.oauth import OAuthRequest
2122
23from zope.annotation.interfaces import IAnnotations
24from zope.authentication.interfaces import IUnauthenticatedPrincipal
22from zope.interface import implements25from zope.interface import implements
23from zope.component import getUtility26from zope.component import adapts, getUtility
24from zope.event import notify27from zope.event import notify
28from zope.preference.interfaces import IPreferenceGroup
2529
26from zope.security.proxy import removeSecurityProxy30from zope.security.proxy import removeSecurityProxy
2731
@@ -320,6 +324,22 @@
320 return encryptor.validate(pw1, pw2)324 return encryptor.validate(pw1, pw2)
321325
322326
327# zope.app.apidoc expects our principals to be adaptable into IAnnotations, so
328# we use these dummy adapters here just to make that code not OOPS.
329class TemporaryPrincipalAnnotations(UserDict):
330 implements(IAnnotations)
331 adapts(ILaunchpadPrincipal, IPreferenceGroup)
332
333 def __init__(self, principal, pref_group):
334 UserDict.__init__(self)
335
336
337class TemporaryUnauthenticatedPrincipalAnnotations(
338 TemporaryPrincipalAnnotations):
339 implements(IAnnotations)
340 adapts(IUnauthenticatedPrincipal, IPreferenceGroup)
341
342
323def get_oauth_authorization(request):343def get_oauth_authorization(request):
324 """Retrieve OAuth authorization information from a request.344 """Retrieve OAuth authorization information from a request.
325345
326346
=== modified file 'lib/canonical/launchpad/webapp/authorization.py'
--- lib/canonical/launchpad/webapp/authorization.py 2010-01-14 13:25:34 +0000
+++ lib/canonical/launchpad/webapp/authorization.py 2010-07-23 19:21:07 +0000
@@ -10,6 +10,7 @@
10from zope.component import getUtility, queryAdapter10from zope.component import getUtility, queryAdapter
11from zope.browser.interfaces import IView11from zope.browser.interfaces import IView
1212
13from zope.proxy import removeAllProxies
13from zope.publisher.interfaces import IApplicationRequest14from zope.publisher.interfaces import IApplicationRequest
14from zope.security.interfaces import ISecurityPolicy15from zope.security.interfaces import ISecurityPolicy
15from zope.security.checker import CheckerPublic16from zope.security.checker import CheckerPublic
@@ -138,8 +139,11 @@
138 # We will not be able to lookup an adapter for this, so we can139 # We will not be able to lookup an adapter for this, so we can
139 # return False already.140 # return False already.
140 return False141 return False
141 # Remove security proxies from object to authorize.142 # Remove all proxies from object to authorize. The security proxy is
142 objecttoauthorize = removeSecurityProxy(objecttoauthorize)143 # removed for obvious reasons but we also need to remove the location
144 # proxy (which is used on apidoc.lp.dev) because otherwise we can't
145 # create a weak reference to our object in our security policy cache.
146 objecttoauthorize = removeAllProxies(objecttoauthorize)
143147
144 participations = [participation148 participations = [participation
145 for participation in self.participations149 for participation in self.participations
146150
=== modified file 'lib/canonical/launchpad/webapp/configure.zcml'
--- lib/canonical/launchpad/webapp/configure.zcml 2010-07-15 15:57:40 +0000
+++ lib/canonical/launchpad/webapp/configure.zcml 2010-07-23 19:21:07 +0000
@@ -672,7 +672,7 @@
672 permission="zope.Public"672 permission="zope.Public"
673 class="canonical.launchpad.webapp.launchbag.LaunchBagView"673 class="canonical.launchpad.webapp.launchbag.LaunchBagView"
674 />674 />
675675
676 <!-- Resource unnamed view, allowing Z3 preferred spelling676 <!-- Resource unnamed view, allowing Z3 preferred spelling
677 /@@/launchpad-icon-small to access the images directory -->677 /@@/launchpad-icon-small to access the images directory -->
678 <browser:page678 <browser:page
679679
=== modified file 'lib/canonical/launchpad/webapp/interfaces.py'
--- lib/canonical/launchpad/webapp/interfaces.py 2010-07-15 15:59:24 +0000
+++ lib/canonical/launchpad/webapp/interfaces.py 2010-07-23 19:21:07 +0000
@@ -65,6 +65,10 @@
65 """65 """
6666
6767
68class IAPIDocRoot(IContainmentRoot):
69 """Marker interface for the root object of the apidoc vhost."""
70
71
68class ILaunchpadContainer(Interface):72class ILaunchpadContainer(Interface):
69 """Marker interface for objects used as the context of something."""73 """Marker interface for objects used as the context of something."""
7074
7175
=== modified file 'lib/canonical/launchpad/webapp/publisher.py'
--- lib/canonical/launchpad/webapp/publisher.py 2010-03-15 17:17:05 +0000
+++ lib/canonical/launchpad/webapp/publisher.py 2010-07-23 19:21:07 +0000
@@ -544,9 +544,6 @@
544544
545class RootObject:545class RootObject:
546 implements(ILaunchpadApplication, ILaunchpadRoot)546 implements(ILaunchpadApplication, ILaunchpadRoot)
547 # These next two needed by the Z3 API browser
548 __parent__ = None
549 __name__ = 'Launchpad'
550547
551548
552rootObject = ProxyFactory(RootObject(), NamesChecker(["__class__"]))549rootObject = ProxyFactory(RootObject(), NamesChecker(["__class__"]))
553550
=== modified file 'lib/canonical/launchpad/webapp/servers.py'
--- lib/canonical/launchpad/webapp/servers.py 2010-07-23 18:54:44 +0000
+++ lib/canonical/launchpad/webapp/servers.py 2010-07-23 19:21:07 +0000
@@ -61,7 +61,7 @@
61from canonical.launchpad.webapp.notifications import (61from canonical.launchpad.webapp.notifications import (
62 NotificationRequest, NotificationResponse, NotificationList)62 NotificationRequest, NotificationResponse, NotificationList)
63from canonical.launchpad.webapp.interfaces import (63from canonical.launchpad.webapp.interfaces import (
64 IBasicLaunchpadRequest, IBrowserFormNG,64 IAPIDocRoot, IBasicLaunchpadRequest, IBrowserFormNG,
65 ILaunchpadBrowserApplicationRequest, ILaunchpadProtocolError,65 ILaunchpadBrowserApplicationRequest, ILaunchpadProtocolError,
66 INotificationRequest, INotificationResponse, IPlacelessAuthUtility,66 INotificationRequest, INotificationResponse, IPlacelessAuthUtility,
67 IPlacelessLoginSource, OAuthPermission, UnexpectedFormData)67 IPlacelessLoginSource, OAuthPermission, UnexpectedFormData)
@@ -1077,6 +1077,16 @@
1077 implements(canonical.launchpad.layers.FeedsLayer)1077 implements(canonical.launchpad.layers.FeedsLayer)
10781078
10791079
1080# ---- apidoc
1081
1082class APIDocBrowserRequest(LaunchpadBrowserRequest):
1083 implements(canonical.launchpad.layers.APIDocLayer)
1084
1085
1086class APIDocBrowserPublication(LaunchpadBrowserPublication):
1087 root_object_interface = IAPIDocRoot
1088
1089
1080# ---- testopenid1090# ---- testopenid
10811091
1082class TestOpenIDBrowserRequest(LaunchpadBrowserRequest):1092class TestOpenIDBrowserRequest(LaunchpadBrowserRequest):
@@ -1442,6 +1452,10 @@
1442 factories.append(VHRP('testopenid', TestOpenIDBrowserRequest,1452 factories.append(VHRP('testopenid', TestOpenIDBrowserRequest,
1443 TestOpenIDBrowserPublication))1453 TestOpenIDBrowserPublication))
14441454
1455 if config.devmode:
1456 factories.append(
1457 VHRP('apidoc', APIDocBrowserRequest, APIDocBrowserPublication))
1458
1445 # We may also have a private XML-RPC server.1459 # We may also have a private XML-RPC server.
1446 private_port = None1460 private_port = None
1447 for server in config.servers:1461 for server in config.servers:
14481462
=== added file 'lib/lp/app/stories/apidoc.txt'
--- lib/lp/app/stories/apidoc.txt 1970-01-01 00:00:00 +0000
+++ lib/lp/app/stories/apidoc.txt 2010-07-23 19:21:07 +0000
@@ -0,0 +1,25 @@
1Launchpad's API docs
2====================
3
4It is generated by the zope.app.apidoc package, and can be seen on
5apidoc.launchpad.dev. (Note that this is just a smoke test)
6
7 >>> browser.open('http://apidoc.launchpad.dev')
8 >>> print extract_text(browser.contents)
9 Zope 3 API Documentation
10
11It uses frames, so here we open the URL of the main frame manually.
12
13 >>> browser.open('http://apidoc.launchpad.dev/++apidoc++/contents.html')
14 >>> print extract_text(browser.contents)
15 Zope 3 apidoc...
16 Welcome to the Zope 3 API documentation tool...
17 Code Browser...
18 Interfaces...
19
20And here we open the page of one of our interfaces, just to make sure they
21render fine.
22
23 >>> browser.open('http://apidoc.launchpad.dev/++apidoc++/Interface/'
24 ... 'lp.code.interfaces.branchcollection.IAllBranches/'
25 ... 'index.html')
026
=== modified file 'setup.py'
--- setup.py 2010-07-21 18:51:37 +0000
+++ setup.py 2010-07-23 19:21:07 +0000
@@ -71,6 +71,7 @@
71 'z3c.pt',71 'z3c.pt',
72 'z3c.ptcompat',72 'z3c.ptcompat',
73 'zc.zservertracelog',73 'zc.zservertracelog',
74 'zope.app.apidoc',
74 'zope.app.appsetup',75 'zope.app.appsetup',
75 'zope.app.component',76 'zope.app.component',
76 'zope.app.dav', # ./package-includes/dav-configure.zcml77 'zope.app.dav', # ./package-includes/dav-configure.zcml
@@ -78,7 +79,9 @@
78 'zope.app.exception',79 'zope.app.exception',
79 'zope.app.file',80 'zope.app.file',
80 'zope.app.form',81 'zope.app.form',
82 'zope.app.onlinehelp',
81 'zope.app.pagetemplate',83 'zope.app.pagetemplate',
84 'zope.app.preference',
82 'zope.app.publication',85 'zope.app.publication',
83 'zope.app.publisher',86 'zope.app.publisher',
84 'zope.app.security',87 'zope.app.security',
@@ -86,6 +89,7 @@
86 'zope.app.server',89 'zope.app.server',
87 'zope.app.session',90 'zope.app.session',
88 'zope.app.testing',91 'zope.app.testing',
92 'zope.app.tree',
89 'zope.app.zcmlfiles',93 'zope.app.zcmlfiles',
90 'zope.app.wsgi',94 'zope.app.wsgi',
91 'zope.app.zapi',95 'zope.app.zapi',
@@ -102,6 +106,7 @@
102 'zope.hookable', # indirect, via zope.app.component106 'zope.hookable', # indirect, via zope.app.component
103 'zope.lifecycleevent',107 'zope.lifecycleevent',
104 'zope.location',108 'zope.location',
109 'zope.login',
105 'zope.pagetemplate',110 'zope.pagetemplate',
106 'zope.publisher',111 'zope.publisher',
107 'zope.proxy',112 'zope.proxy',
108113
=== modified file 'site.zcml'
--- site.zcml 2009-07-13 18:15:02 +0000
+++ site.zcml 2010-07-23 19:21:07 +0000
@@ -38,4 +38,5 @@
38 <include file="summarizerequests.zcml" />38 <include file="summarizerequests.zcml" />
3939
40 <include package="zc.zservertracelog" />40 <include package="zc.zservertracelog" />
41
41</configure>42</configure>
4243
=== modified file 'versions.cfg'
--- versions.cfg 2010-07-21 18:51:37 +0000
+++ versions.cfg 2010-07-23 19:21:07 +0000
@@ -124,6 +124,7 @@
124ZODB3 = 3.9.2124ZODB3 = 3.9.2
125zodbcode = 3.4.0125zodbcode = 3.4.0
126zope.annotation = 3.5.0126zope.annotation = 3.5.0
127zope.app.apidoc = 3.7.3
127zope.app.applicationcontrol = 3.5.1128zope.app.applicationcontrol = 3.5.1
128zope.app.appsetup = 3.12.0129zope.app.appsetup = 3.12.0
129zope.app.authentication = 3.6.1130zope.app.authentication = 3.6.1
@@ -146,7 +147,10 @@
146zope.app.interface = 3.5.0147zope.app.interface = 3.5.0
147zope.app.locales = 3.5.1148zope.app.locales = 3.5.1
148zope.app.localpermission = 3.7.0149zope.app.localpermission = 3.7.0
150zope.app.onlinehelp = 3.5.2
149zope.app.pagetemplate = 3.7.1151zope.app.pagetemplate = 3.7.1
152zope.app.preference = 3.8.1
153zope.preference = 3.8.0
150zope.app.principalannotation = 3.6.1154zope.app.principalannotation = 3.6.1
151zope.app.publication = 3.9.0155zope.app.publication = 3.9.0
152zope.app.publisher = 3.10.0156zope.app.publisher = 3.10.0
@@ -159,6 +163,7 @@
159# not in ZTK163# not in ZTK
160zope.app.session = 3.6.0164zope.app.session = 3.6.0
161zope.app.testing = 3.7.5165zope.app.testing = 3.7.5
166zope.app.tree = 3.6.0
162zope.app.wsgi = 3.6.0167zope.app.wsgi = 3.6.0
163# not in ZTK168# not in ZTK
164zope.app.zapi = 3.4.1169zope.app.zapi = 3.4.1
@@ -198,6 +203,7 @@
198zope.interface = 3.5.2203zope.interface = 3.5.2
199zope.lifecycleevent = 3.5.2204zope.lifecycleevent = 3.5.2
200zope.location = 3.7.0205zope.location = 3.7.0
206zope.login = 1.0.0
201zope.minmax = 1.1.1207zope.minmax = 1.1.1
202zope.modulealias = 3.4.0208zope.modulealias = 3.4.0
203zope.pagetemplate = 3.5.0209zope.pagetemplate = 3.5.0
@@ -207,7 +213,7 @@
207zope.processlifetime = 1.0213zope.processlifetime = 1.0
208zope.proxy = 3.5.0214zope.proxy = 3.5.0
209zope.ptresource = 3.9.0215zope.ptresource = 3.9.0
210zope.publisher = 3.10.0216zope.publisher = 3.12.0
211zope.schema = 3.5.4217zope.schema = 3.5.4
212zope.security = 3.7.1218zope.security = 3.7.1
213zope.securitypolicy = 3.6.1219zope.securitypolicy = 3.6.1
214220
=== modified file 'zopeapp.zcml'
--- zopeapp.zcml 2009-07-13 18:15:02 +0000
+++ zopeapp.zcml 2010-07-23 19:21:07 +0000
@@ -25,6 +25,8 @@
25 <include package="zope.app.interface" />25 <include package="zope.app.interface" />
26 <include package="zope.app.security" />26 <include package="zope.app.security" />
2727
28 <include package="zope.login" />
29
28 <!--30 <!--
29 <include package="zope.app.observable" />31 <include package="zope.app.observable" />
30 <include package="zope.app.annotation" />32 <include package="zope.app.annotation" />