[Grok-dev] Re: implements() works, alsoProvides() breaks

Philipp von Weitershausen philipp at weitershausen.de
Fri Aug 31 04:57:35 EDT 2007


On 31 Aug 2007, at 05:26 , Brandon Craig Rhodes wrote:
> Philipp von Weitershausen <philipp at weitershausen.de> writes:
>
>> Brandon Craig Rhodes wrote:
>>
>>>     zope.component.provideAdapter('index',
>>>                                   adapts=(Plain, IBrowserRequest),
>>>                                   provides=IDefaultViewName)
>>>
>>> I swiped this code from the bootstrap() code of grok._grok, where it
>>> is run twice to provide a default View name of 'index' to grok.Model
>>> and grok.Container.
>>
>> I do recommend doing this declaration in ZCML:
>>
>>   <browser:defaultView for="Plain" name="index" />
>>
>> Doing setup at import time isn't such a good idea. Alternatively,  
>> grok
>> could grow a directive that would allow you to set the default view
>> name for objects.
>
> I would actually prefer that 'index' become the universal default,
> instead of having to configure separate defaults for each class.
> There is no use case I can think of at the moment for some objects
> having something other than 'index' as their default view.

There is a use case. Zope 3 configures 'index.html' as the universal  
default and other packages that you might be wanting to cooperate  
with will most likely to rely on that. Grok can only configure the  
'index' default view for its own little universe.

> But if the use case exists, and a universal default of 'index' is
> therefore a bad idea, then I would love to see a Grok directive for
> this - that would be wonderful!  Grok the caveman must, after all,
> smash ZCML. :-)

We accept patches :).

>> If this is about __parent__, grok should take care of it.  If the
>> object returned by the traverser provides ILocation ... __parent__
>> and __name__ will automatically be assigned to the object.  If not,
>> grok will wrap it in a LocationProxy that adds __parent__ and
>> __name__ without altering the object itself.
>
> The LocationProxy pattern works well when using cascaded Traversers;
> creating an instance named "a" of the following "Example" application
> displays this correct output when you visit "/a/foo/bar/index":
>
>    The URL of this page is http://localhost:8080/a/foo/bar/index
>

[snip]

> So, because the Container has only its "real self" available to offer
> as the parent of the Bar LocationProxy, the chain of __parent__
> relationships winds up broken: the process of following __parent__
> references backwards from the View stops dead when it reaches the Foo
> object, and so "There isn't enough context" is returned.

Correct.

> The solution is to abandon the Proxy pattern for the Container (it's
> still a fine pattern for the final object of the traversal), and make
> the on-demand Container itself capable of being contained.

Unless you have good reasons, it always makes sense to have your  
custom on-the-fly classes implement at least ILocation (if not  
IContained). grok.Traverser will then make sure that __parent__ is  
always set propertly which saves you a lot of hassle. That said, it  
should also work without.

To get down to the specific problem in your code: You have your  
container render a LocationProxy on __getitem__. As you point out,  
'self' isn't proxied hence the parent chain breaks. That's why it  
makes more sense to create the LocationProxy in a custom traverser  
for the container.

However, as I've stated two paragraphs ago, instead of spreading  
LocationProxies through your app with a salt shaker, it's probably  
best to implement at least ILocation (if not IContained) on your  
objects.

> (Actually, I will probably use the line
>
>         return zope.location.location.located(obj, self, key)
>
> instead of manually creating the LocationProxy, on the off chance that
> one of my containers winds up having objects in it that provide
> IContained natively.)

Ah, good to know this exists. It'll let us lose a couple of lines in  
grok.

> Should there be a class with a name like "grok.VirtualContainer" that
> brings these elements together and only requires the user to provide a
> get() method or something?  I guess the above code is pretty dratted
> simple already, so further convenience is really not necessary as long
> as we have documentation showing the users the pattern.

This is actually a very good idea, though I suggest calling it  
grok.CustomContainer or something like that. One should indeed only  
have to write the minimum number of methods:

* get(key, default=None) returns a subobject or default if it can't  
be found

* set(key, obj) adds 'obj' to the container (this will be called by a  
generic
   __setitem__ which sends the event and does the contained magic, etc.)

* delete(key) removes an object from the container (this will be  
called by a generic
   __delitem__ which sends the event and uncontains the object  
automatically, etc.).

* iterkeys() returns an iterator for the keys (generic  
implementations of keys(),
   has_key, __contains__, etc. can all be computed from this  
automatically).


>> From your code:
>>
>>>   zope.interface.alsoProvides(WorkingObject2, IContained)
>>
>> alsoProvides takes an *object* as a first parameter. Not a class.  
>> This
>> is where your problem is.
>
> Actually, the code you are quoting was the code that actually worked!

Well, yes, it executed :). That doesn't mean it did what you intended  
it to do.

> The code that broke was, in fact, the code that passed an instance:
>
>             b = BrokenObject()
>             zope.interface.alsoProvides(b, IContained)
>
> I have no idea why; as I said, the whole example turns out to have
> been a mess.  But thanks for pointing out that I was doing the wrong
> thing here; I will stop using alsoProvides with classes.

It's ok to use alsoProvides with classes when you want the class  
object itself to provide an interface. That's a rare use case though :)





More information about the Grok-dev mailing list