[Grok-dev] solving the grok.template() bug - part A of the solution
Martijn Faassen
faassen at startifact.com
Mon Jan 12 11:38:47 EST 2009
Hey,
Brandon Craig Rhodes wrote:
> My worry is that we are creating something a bit more complicated than
> it really has to be. In particular, I note that we are creating a kind
> of scoping that, unless several minutes of thinking have failed to
> reveal it to me, has *no* parallel anywhere in Python itself.
Thanks for bringing this up. Let me try to give some examples of
more-or-less parallels in Python.
Class directive inheritance is simple to do in straight Python. First
the Grok example (look at grok.templatedir for now, not grok.context):
module x:
import grok
class Index(grok.View):
grok.templatedir('foo')
grok.context(object)
module y:
import x
class Something(x.Index):
pass
In straight Python:
module x:
class Index(object):
templatedir = 'foo'
module y:
import x
class Something(x.Index)
pass
We'd expect inheritance to work.
Now let's consider this example of using templatedir as a module-level
directive:
module x:
import grok
grok.templatedir('foo')
class Index(grok.View):
grok.context(object)
module y:
import x
class Something(x.Index):
pass
Here we'd like 'Something' to have the templatedir of Index. This is
what currently isn't working right with inheritance.
In straight Python, you could write something like this:
module x:
templatedir = 'foo'
class Index(object):
def render(self):
return os.join(templatedir, self.__class__.__name__.lower())
module y:
import x
class Something(x.Index):
pass
You'd expect that to work when you call 'render()' on an instance of
Something (if I didn't write any bugs in the above :). In Grok it
currently doesn't.
Of course the parallels with Python aren't complete. This for instance
should work with Grok:
module x:
import grok
grok.templatedir('foo')
class Index(grok.View):
grok.context(object)
module y:
import x
grok.templatedir('bar')
class Something(x.Index):
pass
Now 'Something' would look at 'bar' for its template, not 'foo'. This is
because module-level directives are equivalent to specifying these
directives explicitly in all components in the module that don't have
those directives at class-level.
In order for that to be done with Python constructs, you'd need to
override the subclass with a custom method.
I happen to like that Martian rule though. And after all, if we wanted
to do exactly what's available with Python out-of-the-box we'd not have
no need for directives or the Martian library in the first place.
> That is, if someone wants to understand how directives work - so that they can
> look at code and predict which directive will get used with a class that
> doesn't define the directive explicitly - then we can't say "Oh, it
> works like <such-and-such a normal Python concept>..."; we're creating
> something new with its own rules.
I think the one extra rule people would need to remember is the rule
above: when a component doesn't have an explicit directive in the class
scope, look at the module scope to see whether the directive is there.
The module-level directive is interpreted as being exactly equivalent as
a class-level directive.
> * Grok first looks for a ``grok.context()`` right on the class.
>
> * Failing that, Grok does an *inheritance* search, looking through the
> MRO of the class to see whether one of its parent classes declared a
> ``grok.context()`` anywhere.
>
> * Failing that, Grok then tries to do a *lexically scoped* search,
> looking at enclosing scopes - well, okay, it actually just looks at
> the module, like old-fashioned Python without true lexical scoping,
> skipping the levels in between - and seeing if a ``grok.context()``
> directive exists up there.
>
> * Failing that, Grok looks in the enclosing module for one, and exactly
> one, class that implements ``IContext``, and uses that if one is
> found.
JW already answered this one talking from the perspective of
implementation. Thinking about it more I get confused again and I'm not
sure that the implementation JW proposed is enough to cover the rule
I'll write next. So, I'll rewrite what you have above to what I think
the conceptual rule should be:
1) Grok first looks for a ``grok.context()`` right on the component,
explicitly defined there.
2) Failing that, Grok then looks for a ``grok.context()`` in the module
that the component is defined in.
3) Failing that, Grok looks in the enclosing module for one, and
exactly one class that implements IContext. Note that this rule
makes ``grok.context`` special from other directives that
have class or module scope. The rest of the rule is the same.
4) Failing that, Grok does an *inheritance* search, looking through the
MRO of the class to see whether one of its parent classes has a
context. That context can be gained automatically through rule
1), 2) and 3).
5) Failing that, Grok reports an error during grokking.
You can consider 2) and definitely 3) weird things that Grok does. The
others follow normal python inheritance and override rules.
Our primary goal in this exercise is to make 4) work correctly. 4) is
not working correctly primarily because of rule 2), and in case of
grok.context in particular, because of rule 3). If we dropped both those
rules 4) would work correctly thanks to Python inheritance (as we stuff
the explicit directive value into the class).
A good question to ask is why is rule 3) not below rule 4), instead of
after 2)? I think conceptually the automatic association to a IContext
should be read as implicitly defining a module-level grok.context. The
result of this is that a subclass of a view in a module with just one
model will automatically associate with *that* model. This is preferred
to associating with the model that the base-view was associated with.
Note that quite frequently grok.baseclass() will be used to mark such
base views. In that case, grok directives should still be interpreted
but the view should not actually be grokked and registered and rule 5)
therefore doesn't apply. This allows someone to write a context-less
view. If they do put in a model in the same module as the baseclass
view, the baseclass will however associate with that model still.
> Therefore I advocate that we either drop the inheritance-search, and
> have ``grok.context()`` searched for like the ``x`` in ``x.y``, or drop
> the module-level default, and have it searched for like the ``y``. Our
> rules are already going to be more complicated because we have the
> fallback to looking for ``IContext``; I don't think we need to be adding
> a third layer.
Here you propose either dropping rule 4) or rule 2).
Dropping 4) means no more inheritance of directives. I think this breaks
fundamental expectations a Python programmer has. It's also a rule
zope.interface.implements() (aka grok.implements()) follows. Think about
grok.implements and tell me what you expect in case of *that* directive.
I think dropping rule 4) is therefore out of the question.
Dropping 2) means no more module-level directives. This would work in
any case besides that of ``grok.context()``, as we'd also need to drop
3). (which you don't propose).
If we want to make all rules work, we'd need the directive value lookup
procedure to do the following:
* check whether the value is defined on this class (and not on any base
classes, check only dict of this class). If so, we're done.
* Check whether the value is defined in the module of this class. If so,
we're done. In case of grok.context we extend this rule and also look
for a non-ambiguous IContext in the module of the class.
* Check whether the value is defined on the class, including any base
classes (normal getattr on the class for the directive value). If
so, we're done.
* Check whether the value is defined on the module of the module of
the baseclass of this class (for each baseclass, following MRO). If
so, we're done. In case of grok.context we also look for IContexts
again.
I hope I got the rule right this time, whenever JW and I talk about it
we both get confused. :) Note that the rule above suggests some way to
hook in the grok.context behavior into the process, which is nice.
Now if we dropped the grok.context auto-association rule and we dropped
module-level directives, it'd become much simpler to implement everything:
* just get the value from the class.
I think that's too big a change to drop on people though. Besides, I do
use auto-association and module-level directives quite frequently in my
code and they're quite nice to have and I don't think usually make
things much harder to read either.
Regards,
Martijn
More information about the Grok-dev
mailing list