[Checkins] SVN: martian/trunk/ the computed default can now be defined in the directive class using a get_default() classmethod
Jan-Wijbrand Kolman
janwijbrand at gmail.com
Wed Nov 3 07:48:19 EDT 2010
Log message for revision 118145:
the computed default can now be defined in the directive class using a get_default() classmethod
Changed:
U martian/trunk/CHANGES.txt
U martian/trunk/src/martian/directive.py
U martian/trunk/src/martian/directive.txt
U martian/trunk/src/martian/util.py
-=-
Modified: martian/trunk/CHANGES.txt
===================================================================
--- martian/trunk/CHANGES.txt 2010-11-03 11:33:43 UTC (rev 118144)
+++ martian/trunk/CHANGES.txt 2010-11-03 11:48:19 UTC (rev 118145)
@@ -4,9 +4,33 @@
0.14 (unreleased)
=================
-- Nothing changed yet.
+Feature changes
+---------------
+* The computation of the default value for a directive can now be defined inside
+ the directive class definition. Whenever there is a ``get_default``
+ classmethod, it is used for computing the default::
+ class name(Directive):
+ scope = CLASS
+ store = ONCE
+
+ @classmethod
+ def get_default(cls, component, module=None, **data):
+ return component.__name__.lower()
+
+ When binding the directive, the default-default behaviour can still be
+ overriden by passing a ``get_default`` function::
+
+ def another_default(component, module=None, **data):
+ return component.__name__.lower()
+
+ name.bind(get_default=another_default).get(some_component)
+
+ Making the default behaviour intrinsic to the directive, prevents having to
+ pass the ``get_default`` function over and over when getting values, for
+ example in the grokkers.
+
0.13 (2010-11-01)
=================
Modified: martian/trunk/src/martian/directive.py
===================================================================
--- martian/trunk/src/martian/directive.py 2010-11-03 11:33:43 UTC (rev 118144)
+++ martian/trunk/src/martian/directive.py 2010-11-03 11:48:19 UTC (rev 118145)
@@ -68,7 +68,7 @@
MULTIPLE_NOBASE = StoreMultipleTimesNoBase()
class StoreDict(StoreOnce):
-
+
def get(self, directive, component, default):
if getattr(component, directive.dotted_name(), default) is default:
return default
@@ -196,7 +196,7 @@
return result
# look up default rule for this class or its bases
return _default(mro, get_default)
-
+
CLASS_OR_MODULE = ClassOrModuleScope()
class ModuleScope(object):
@@ -213,8 +213,11 @@
MODULE = ModuleScope()
+_unused = object()
+
class Directive(object):
+ # The BoundDirective will fallback to the directive-level default value.
default = None
def __init__(self, *args, **kw):
@@ -239,8 +242,7 @@
# To get a correct error message, we construct a function that has
# the same signature as factory(), but without "self".
def check_factory_signature(self, *arguments, **kw):
- args, varargs, varkw, defaults = inspect.getargspec(
- self.factory)
+ args, varargs, varkw, defaults = inspect.getargspec(self.factory)
argspec = inspect.formatargspec(args[1:], varargs, varkw, defaults)
exec("def signature_checker" + argspec + ": pass")
try:
@@ -262,24 +264,30 @@
cls.store.setattr(component, cls, value)
@classmethod
- def bind(cls, default=None, get_default=None, name=None):
+ def bind(cls, default=_unused, get_default=None, name=None):
return BoundDirective(cls, default, get_default, name)
-
class BoundDirective(object):
- def __init__(self, directive, default=None, get_default=None, name=None):
+ def __init__(self, directive, default=_unused, get_default=None, name=None):
self.directive = directive
self.default = default
if name is None:
name = directive.__name__
self.name = name
+ # Whenever the requester provides its own get_default function,
+ # it'll override the default get_default.
if get_default is not None:
self.get_default = get_default
def get_default(self, component, module=None, **data):
- if self.default is not None:
+ if self.default is not _unused:
return self.default
+ # Fallback to the directive-level default value. Call the
+ # ``get_default`` classmethod when it is available, else use the
+ # ``default`` attribute.
+ if hasattr(self.directive, 'get_default'):
+ return self.directive.get_default(component, module, **data)
return self.directive.default
def get(self, component=None, module=None, **data):
Modified: martian/trunk/src/martian/directive.txt
===================================================================
--- martian/trunk/src/martian/directive.txt 2010-11-03 11:33:43 UTC (rev 118144)
+++ martian/trunk/src/martian/directive.txt 2010-11-03 11:48:19 UTC (rev 118145)
@@ -226,7 +226,7 @@
--------------------------------------------
Let's look at how our layer directive can inherit in combination with
-directive use on module scope.
+directive use on module scope.
First we define a module which defines a class that gets the ``Test``
layer through the use of the layer directive on module scope::
@@ -255,7 +255,7 @@
... class Baz(inheritmodule2.Bar):
... pass
>>> from martiantest.fake import inheritmodule3
-
+
The layer should still be inherited::
>>> layer.bind().get(inheritmodule3.Baz, inheritmodule3)
@@ -471,7 +471,7 @@
--------------------------
When you use ``zope.interface.Interface`` to define a new interface using
-the class statement, in fact a special interface instance is created, not a
+the class statement, in fact a special interface instance is created, not a
class. To let the directive store a value on an interface, we need to
use a special storage (``martian.ONCE_IFACE``)::
@@ -479,7 +479,7 @@
>>> class skin(Directive):
... scope = CLASS
... store = ONCE_IFACE
-
+
Note that we still indicate the ``CLASS`` scope for this kind of
directive. At some point we may introduce a special scope for
directives on interfaces.
@@ -501,20 +501,43 @@
-----------------
Often instead of just supplying the system with a default, we want to
-compute the default in some way. We define the ``name`` directive,
-which if not present, will compute its value from the name of class,
-lower-cased. Instead of passing a default value, we pass a function as the
-default argument::
+compute the default in some way. The computation of the default can be defined
+in the directive class, by the ``get_default`` classmethod.
+We define the ``name`` directive, which if not present, will compute its value
+from the name of class, lower-cased.
+
>>> class name(Directive):
... scope = CLASS
... store = ONCE
...
- >>> def default_name_lowercase(component, module, **data):
- ... return component.__name__.lower()
+ ... @classmethod
+ ... def get_default(cls, component, module=None, **data):
+ ... return component.__name__.lower()
...
- >>> bound_name = name.bind(get_default=default_name_lowercase)
+ >>> bound_name = name.bind()
+ >>>
+ >>> class Foo(object):
+ ... name('bar')
+ ...
+ >>> bound_name.get(Foo)
+ 'bar'
+ >>> class Foo2(object):
+ ... pass
+ ...
+ >>> bound_name.get(Foo2)
+ 'foo2'
+
+To override the default-default behaviour you can pass a function when binding
+the directive. In this case, we do not want the default behaviour of using the
+lower cased class name, but have it upper cased::
+
+ >>> def default_name_uppercase(component, module, **data):
+ ... return component.__name__.upper()
+ ...
+ >>> bound_name = name.bind(get_default=default_name_uppercase)
+
>>> class Foo(object):
... name('bar')
@@ -524,7 +547,7 @@
>>> class Foo2(object):
... pass
>>> bound_name.get(Foo2)
- 'foo2'
+ 'FOO2'
Let's test this with a deeper inheritance hierarchy. Explicit
directives should always trump computed defaults::
@@ -537,10 +560,7 @@
Now let's look at a hierarchy in which the explicit rule should
apply::
- >>> def default_name_lowercase2(component, module, **data):
- ... return component.__name__.lower()
- ...
- >>> bound_name2 = name.bind(get_default=default_name_lowercase2)
+ >>> bound_name2 = name.bind()
>>> class Alpha(object):
... pass
>>> class Beta(Alpha):
@@ -580,7 +600,7 @@
>>> bound_name3.get(testmodule.Foo, testmodule) is martian.UNKNOWN
True
-Now we define a module which does have ``1`` in it, so the rule should
+Now we define a module which does have ``1`` in it, so the rule should
be triggered::
>>> class testmodule1(FakeModule):
@@ -624,7 +644,7 @@
We don't expect the default rule to kick in as we can find an
explicitly set value::
- >>> layer.bind(get_default=immediate_get_default).get(inheritmodule2.Bar,
+ >>> layer.bind(get_default=immediate_get_default).get(inheritmodule2.Bar,
... inheritmodule2)
'Test'
@@ -690,28 +710,32 @@
a ``GrokError`` if the class name doesn't start with an upper case
letter::
+ >>> from martian.error import GrokError
>>> class name(Directive):
... scope = CLASS
... store = ONCE
+ ...
+ ... @classmethod
+ ... def get_default(cls, component, module, **data):
+ ... if component.__name__[0].isupper():
+ ... return component.__name__
+ ... raise GrokError(
+ ... "Component %r has a name that doesn't start with upper "
+ ... "case letter." % component, component)
- >>> from martian.error import GrokError
- >>> def name_get_default(component, module, **data):
- ... if component.__name__[0].isupper():
- ... return component.__name__
- ... raise GrokError("Component %r has a name that doesn't start with upper case letter." % component, component)
-
Let's test it::
>>> class A(object):
... pass
>>> class b(object):
... pass
- >>> name.bind(get_default=name_get_default).get(A)
+ >>> name.bind().get(A)
'A'
- >>> name.bind(get_default=name_get_default).get(b)
+ >>> name.bind().get(b)
Traceback (most recent call last):
...
- GrokError: Component <class 'b'> has a name that doesn't start with upper case letter.
+ GrokError: Component <class 'b'> has a name that doesn't start with upper
+ case letter.
Instead of raising ``GrokError`` we can also raise ``UnknownError`` in
a computed default. This has the same meaning as returning
@@ -730,16 +754,23 @@
if there is no ``foo`` attribute on the class::
>>> from martian import UnknownError
- >>> def foo_get_default(component, module, **data):
- ... if not component.__dict__.has_key('foo'):
- ... raise UnknownError("Foo cannot be found for class %s" % component.__name__, component)
- ... return "Found for class %s" % component.__name__
+ >>> class name(Directive):
+ ... scope = CLASS
+ ... store = ONCE
+ ...
+ ... @classmethod
+ ... def get_default(cls, component, module, **data):
+ ... if not component.__dict__.has_key('foo'):
+ ... raise UnknownError(
+ ... "Foo cannot be found for class %s" % component.__name__,
+ ... component)
+ ... return "Found for class %s" % component.__name__
Let's try it on a simple class first::
>>> class Test:
... pass
- >>> name.bind(get_default=foo_get_default).get(Test)
+ >>> name.bind().get(Test)
Traceback (most recent call last):
...
GrokError: Foo cannot be found for class Test
@@ -748,7 +779,7 @@
>>> class Test(object):
... pass
- >>> name.bind(get_default=foo_get_default).get(Test)
+ >>> name.bind().get(Test)
Traceback (most recent call last):
...
GrokError: Foo cannot be found for class Test
@@ -759,7 +790,7 @@
... pass
>>> class Test2(Test1):
... pass
- >>> name.bind(get_default=foo_get_default).get(Test2)
+ >>> name.bind().get(Test2)
Traceback (most recent call last):
...
GrokError: Foo cannot be found for class Test2
@@ -776,7 +807,7 @@
... foo = 1
>>> class Test2(Test1):
... pass
- >>> name.bind(get_default=foo_get_default).get(Test2)
+ >>> name.bind().get(Test2)
'Found for class Test1'
Computed defaults for instances
@@ -805,7 +836,7 @@
If no directive was used on the class, we will get ``None``, the
default default value::
- >>> (name.bind().get(instancemodule.no_directive_on_this, instancemodule) is
+ >>> (name.bind().get(instancemodule.no_directive_on_this, instancemodule) is
... None)
True
@@ -826,7 +857,12 @@
>>> class layer2(Directive):
... scope = CLASS
... store = ONCE
-
+ ... @classmethod
+ ... def get_default(cls, component, module=None, **data):
+ ... if '1' in module.__name__:
+ ... return 'we found it'
+ ... return martian.UNKNOWN
+ ...
>>> class oldstyle1(FakeModule):
... class Base:
... pass
@@ -835,12 +871,7 @@
... class Sub(oldstyle1.Base):
... pass
>>> from martiantest.fake import oldstyle2
-
- >>> def get_default(component, module, **data):
- ... if '1' in module.__name__:
- ... return 'we found it'
- ... return martian.UNKNOWN
- >>> layer2.bind(get_default=get_default).get(oldstyle2.Sub)
+ >>> layer2.bind().get(oldstyle2.Sub)
'we found it'
And let's try it with a ``CLASS_OR_MODULE`` scope directive too::
@@ -848,7 +879,12 @@
>>> class layer3(Directive):
... scope = CLASS_OR_MODULE
... store = ONCE
- >>> layer3.bind(get_default=get_default).get(oldstyle2.Sub)
+ ... @classmethod
+ ... def get_default(cls, component, module=None, **data):
+ ... if '1' in module.__name__:
+ ... return 'we found it'
+ ... return martian.UNKNOWN
+ >>> layer3.bind().get(oldstyle2.Sub)
'we found it'
A marker directive
@@ -910,8 +946,9 @@
... store = ONCE
... def validate(self, value):
... if type(value) is not int:
- ... raise GrokImportError("The '%s' directive can only be called with an integer." %
- ... self.name)
+ ... raise GrokImportError(
+ ... "The '%s' directive can only be called with an integer." %
+ ... self.name)
>>> class Foo(object):
... number(3)
@@ -1104,11 +1141,12 @@
>>> class info(Directive):
... scope = CLASS
... store = ONCE
- >>> def get_default(component, module, **data):
- ... context = getattr(module, 'Context', None)
- ... if context is None:
- ... return martian.UNKNOWN
- ... return context.value
+ ... @classmethod
+ ... def get_default(cls, component, module, **data):
+ ... context = getattr(module, 'Context', None)
+ ... if context is None:
+ ... return martian.UNKNOWN
+ ... return context.value
Let use this rule with an example where no baseclass is declared
first::
@@ -1123,7 +1161,7 @@
... class B(basemodule.A):
... pass
>>> from martiantest.fake import submodule
- >>> info.bind(get_default=get_default).get(submodule.B)
+ >>> info.bind().get(submodule.B)
1
Now let's apply the rule where ``A`` is declared to be a
@@ -1140,7 +1178,7 @@
... class B(basemodule2.A):
... pass
>>> from martiantest.fake import submodule2
- >>> info.bind(get_default=get_default).get(submodule2.B) is martian.UNKNOWN
+ >>> info.bind().get(submodule2.B) is martian.UNKNOWN
True
If we change the default rule so we use ``UnknownError`` we see the same
@@ -1159,10 +1197,8 @@
1
But we will get a ``GrokError`` when a baseclass is in play::
-
+
>>> info.bind(get_default=get_default).get(submodule2.B)
Traceback (most recent call last):
...
GrokError: No Context object found!
-
-
Modified: martian/trunk/src/martian/util.py
===================================================================
--- martian/trunk/src/martian/util.py 2010-11-03 11:33:43 UTC (rev 118144)
+++ martian/trunk/src/martian/util.py 2010-11-03 11:48:19 UTC (rev 118145)
@@ -94,8 +94,8 @@
for name in dir(module):
if '.' in name:
# This must be a module-level variable that couldn't have
- # been set by the developer. It must have been a
- # module-level directive.
+ # been set by the developer. It must have been a module-level
+ # directive.
continue
obj = getattr(module, name)
if not defined_locally(obj, module.__name__) or not isclass(obj):
More information about the checkins
mailing list