[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