[Grok-dev] changing martian to support class decorators

Martijn Faassen faassen at startifact.com
Mon Mar 21 13:58:37 EDT 2011


Hi there,

Prompted by discussions on zope-dev and a conversation with Souheil I 
looked a bit at Martian to see what would be required to support class 
decorators.

There are two alternatives I can see concerning syntax. One is to turn 
the directives into decorators:

@grok.context(Foo)
@grok.name('bar')
class MyClass(...):
    ...

the other is to use a single decorator to kick things off:

@grok.config(
    grok.context(Foo),
    grok.name('bar'),
    )
class MyClass(...):
     ...

i.e. the directives become arguments of a config method. In this case, 
the config method would turn around and set up the directives on the 
MyClass class like like our frame hack does now.

In both cases we'd use the same code paths as much as possible. The 
@grok.config is a bit more verbose than simply using directives as 
decorators, so I think we should look into the first syntax option first.

For the first syntax, we'd need to come up with a way for directives to 
be callable themselves, generalizing the technique we've used for the 
@grok.subscribe directive. We'd basically need to add a __call__ to the 
directive implementation that takes the class as its argument, and then 
annotates the class with the directive.

We have another issue if we care about Python 3 support: module-level 
directives. These use the same frame hack, but I expect this will fail 
to work for the same reasons. Can we come up with a way to spell module 
level directives differently? Perhaps like this?

module_config = grok.module_config(
    grok.context(Foo),
    )

or maybe this:

@grok.context(Foo)
class ModuleConfig(object):
    pass

if we do the latter, we could even just say people should do 
module-level defaults by simply telling people to mix in the 
ModuleConfig class themselves. A bit verbose and makes it harder to move 
code, though.

The last option leads to interesting possibilities, like using this to 
set up the implicit configuration rules:

@grok.context(Foo)
class ModuleConfig(grok.ImplicitConfig):
    pass

Anyway, all this makes us face an implementation obstacle. The problem 
is that Directive's __init__ checks whether the frame is the expected 
frame (a class in case of a class directive, module for module 
directive, class or module for class or module directive), and then uses 
the frame (the class, or the module) to set the value. This of course 
won't work in the examples above, as the directives are called outside 
of the class.

If we could just throw away that code life would be simple, but we would 
like to manage a transition where some code uses the old style and some 
code uses the new style. We might get somewhere with a slight 
incompatibility to have them take a different code path when used on the 
module level, but many class level directives can also be used on the 
module level (think grok.context), so that won't work correctly.

The require directive in grokcore.security already faces this issue. It 
can only be used in a class context, but in two ways:

class Foo:
    grok.require(...)

class Foo:
    @grok.require(...)
    def bar(...):
       ...

grok.require implements __call__ as I sketched out above. The 
implementation is so subtle and fragile (and uses another frame hack) 
that I doubt it can be straightforwardly adapted, though.

Another approach, which I'd like to avoid if at all possible, would be a 
code conversion tool.

Yet another approach would be to expose *two* sets of directives per 
grokcore package, one old-style, the other new-style. But 
'grok.context()' reads so nicely I don't want to give it up...

Regards,

Martijn



More information about the Grok-dev mailing list