[Checkins] SVN: martian/trunk/ Merged the
jw-philipp-using-ndir-directives branch.
Philipp von Weitershausen
philikon at philikon.de
Sun May 4 10:47:45 EDT 2008
Log message for revision 86375:
Merged the jw-philipp-using-ndir-directives branch.
Changed:
U martian/trunk/CHANGES.txt
U martian/trunk/src/martian/__init__.py
U martian/trunk/src/martian/core.py
A martian/trunk/src/martian/core.txt
D martian/trunk/src/martian/directive.py
A martian/trunk/src/martian/directive.py
D martian/trunk/src/martian/directive.txt
A martian/trunk/src/martian/directive.txt
D martian/trunk/src/martian/ndir.py
D martian/trunk/src/martian/ndir.txt
D martian/trunk/src/martian/tests/directive/
A martian/trunk/src/martian/tests/scan_for_classes.txt
A martian/trunk/src/martian/tests/scanforclasses/
U martian/trunk/src/martian/tests/test_all.py
U martian/trunk/src/martian/util.py
-=-
Modified: martian/trunk/CHANGES.txt
===================================================================
--- martian/trunk/CHANGES.txt 2008-05-04 14:45:00 UTC (rev 86374)
+++ martian/trunk/CHANGES.txt 2008-05-04 14:47:45 UTC (rev 86375)
@@ -4,6 +4,34 @@
0.9.4 (unreleased)
==================
+Features changes
+----------------
+
+* Replaced the various directive base classes with a single
+ ``martian.Directive`` base class:
+
+ - The directive scope is now defined with the ``scope`` class
+ attribute using one of ``martian.CLASS``, ``martian.MODULE``,
+ ``martian.CLASS_OR_MODULE``.
+
+ - The type of storage is defined with the ``store`` class attribute
+ using one of ``martian.ONCE``, ``martian.MULTIPLE``,
+ ``martian.DICT``.
+
+ - Directives have now gained the ability to read the value that they
+ have set on a component or module using a ``get()`` method. The
+ ``class_annotation`` and ``class_annotation_list`` helpers have
+ been removed as a consequence.
+
+* Moved the ``baseclass()`` directive from Grok to Martian.
+
+* Added a ``martian.util.check_provides_one`` helper, in analogy to
+ ``check_implements_one``.
+
+* The ``scan_for_classes`` helper now also accepts an ``interface``
+ argument which allows you to scan for classes based on interface
+ rather than base classes.
+
Bug fixes
---------
Modified: martian/trunk/src/martian/__init__.py
===================================================================
--- martian/trunk/src/martian/__init__.py 2008-05-04 14:45:00 UTC (rev 86374)
+++ martian/trunk/src/martian/__init__.py 2008-05-04 14:47:45 UTC (rev 86375)
@@ -1,4 +1,11 @@
-from core import ModuleGrokker, MultiGrokker, MetaMultiGrokker, grok_dotted_name,\
- grok_package, grok_module
-from components import GlobalGrokker, ClassGrokker, InstanceGrokker
-from util import scan_for_classes
+from martian.core import (
+ ModuleGrokker, MultiGrokker, MetaMultiGrokker, grok_dotted_name,
+ grok_package, grok_module)
+from martian.components import GlobalGrokker, ClassGrokker, InstanceGrokker
+from martian.util import scan_for_classes
+from martian.directive import Directive, MarkerDirective, MultipleTimesDirective
+from martian.directive import ONCE, MULTIPLE, DICT
+from martian.directive import CLASS, CLASS_OR_MODULE, MODULE
+from martian.directive import (
+ validateText, validateInterface, validateInterfaceOrClass)
+from martian.directive import baseclass
Modified: martian/trunk/src/martian/core.py
===================================================================
--- martian/trunk/src/martian/core.py 2008-05-04 14:45:00 UTC (rev 86374)
+++ martian/trunk/src/martian/core.py 2008-05-04 14:47:45 UTC (rev 86375)
@@ -85,7 +85,10 @@
# try to grok everything in module
for name in dir(module):
- if name.startswith('__grok_'):
+ 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.
continue
obj = getattr(module, name)
if not util.defined_locally(obj, module.__name__):
Copied: martian/trunk/src/martian/core.txt (from rev 86374, martian/branches/jw-philipp-using-ndir-directives/src/martian/core.txt)
===================================================================
--- martian/trunk/src/martian/core.txt (rev 0)
+++ martian/trunk/src/martian/core.txt 2008-05-04 14:47:45 UTC (rev 86375)
@@ -0,0 +1,47 @@
+(Edge-case) tests of Martian core components
+============================================
+
+ModuleGrokker ignores values set by directives
+----------------------------------------------
+
+Consider the following module-level directive:
+
+ >>> import martian
+ >>> class store(martian.Directive):
+ ... scope = martian.MODULE
+ ... store = martian.ONCE
+ ...
+ >>> store.__module__ = 'somethingelse' # just so that it isn't __builtin__
+
+Now let's look at a module that contains a simple function and a call
+to the directive defined above:
+
+ >>> class module_with_directive(FakeModule):
+ ... fake_module = True
+ ...
+ ... def some_function():
+ ... return 11
+ ...
+ ... store(some_function)
+ ...
+ >>> module_with_directive = fake_import(module_with_directive)
+
+Now imagine we have the following grokker for functions:
+
+ >>> import types
+ >>> class FunctionGrokker(martian.InstanceGrokker):
+ ... component_class = types.FunctionType
+ ... def grok(self, name, obj, **kw):
+ ... print name, obj()
+ ... return True
+ ...
+ >>> module_grokker = martian.ModuleGrokker()
+ >>> module_grokker.register(FunctionGrokker())
+
+and let it loose on the module, we see that it will only find functions
+set by regular variable assignment, not the ones stored by the
+directive:
+
+ >>> module_grokker.grok('module_with_directive', module_with_directive)
+ some_function 11
+ True
Deleted: martian/trunk/src/martian/directive.py
===================================================================
--- martian/trunk/src/martian/directive.py 2008-05-04 14:45:00 UTC (rev 86374)
+++ martian/trunk/src/martian/directive.py 2008-05-04 14:47:45 UTC (rev 86375)
@@ -1,202 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2006-2007 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Grok directives.
-"""
-
-import sys
-import inspect
-
-from zope import interface
-from zope.interface.interfaces import IInterface
-
-from martian import util
-from martian.error import GrokImportError
-
-class IDirectiveContext(interface.Interface):
- description = interface.Attribute("The correct place in which the "
- "directive can be used.")
-
- def matches(frame):
- """returns whether the given frame is the correct place in
- which the directive can be used.
- """
-
-class ClassDirectiveContext(object):
- interface.implements(IDirectiveContext)
-
- description = "class"
-
- def matches(self, frame):
- return util.frame_is_class(frame)
-
-
-class ModuleDirectiveContext(object):
- interface.implements(IDirectiveContext)
-
- description = "module"
-
- def matches(self, frame):
- return util.frame_is_module(frame)
-
-
-class ClassOrModuleDirectiveContext(object):
- interface.implements(IDirectiveContext)
-
- description = "class or module"
-
- def matches(self, frame):
- return util.frame_is_module(frame) or util.frame_is_class(frame)
-
-
-class Directive(object):
- """
- Directive sets a value into the context's locals as __<name>__
- ('.' in the name are replaced with '_').
- """
-
- def __init__(self, name, directive_context):
- self.name = name
- self.local_name = '__%s__' % name.replace('.', '_')
- self.directive_context = directive_context
-
- def __call__(self, *args, **kw):
- self.check_argument_signature(*args, **kw)
- self.check_arguments(*args, **kw)
-
- frame = sys._getframe(1)
- self.check_directive_context(frame)
-
- value = self.value_factory(*args, **kw)
- return self.store(frame, value)
-
- def check_arguments(self, *args, **kw):
- raise NotImplementedError
-
- # to get a correct error message, we construct a function that has
- # the same signature as check_arguments(), but without "self".
- def check_argument_signature(self, *arguments, **kw):
- args, varargs, varkw, defaults = inspect.getargspec(
- self.check_arguments)
- argspec = inspect.formatargspec(args[1:], varargs, varkw, defaults)
- exec("def signature_checker" + argspec + ": pass")
- try:
- signature_checker(*arguments, **kw)
- except TypeError, e:
- message = e.args[0]
- message = message.replace("signature_checker()", self.name)
- raise TypeError(message)
-
- def check_directive_context(self, frame):
- if not self.directive_context.matches(frame):
- raise GrokImportError("%s can only be used on %s level."
- % (self.name,
- self.directive_context.description))
-
- def value_factory(self, *args, **kw):
- raise NotImplementedError
-
- def store(self, frame, value):
- raise NotImplementedError
-
-
-class OnceDirective(Directive):
- def store(self, frame, value):
- if self.local_name in frame.f_locals:
- raise GrokImportError("%s can only be called once per %s."
- % (self.name,
- self.directive_context.description))
- frame.f_locals[self.local_name] = value
-
-
-class MarkerDirective(OnceDirective):
- """A directive without argument that places a marker.
- """
- def value_factory(self):
- return True
-
- def check_arguments(self):
- pass
-
-class MultipleTimesDirective(Directive):
- def store(self, frame, value):
- values = frame.f_locals.get(self.local_name, [])
- values.append(value)
- frame.f_locals[self.local_name] = values
-
-
-class SingleValue(object):
-
- # Even though the value_factory is called with (*args, **kw),
- # we're safe since check_arguments would have bailed out with a
- # TypeError if the number arguments we were called with was not
- # what we expect here.
- def value_factory(self, value):
- return value
-
-class OptionalValueDirective(object):
- def check_arguments(self, value=None):
- pass
-
- def value_factory(self, value=None):
- if value is None:
- return self.default_value()
- return value
-
- def default_value(self):
- raise NotImplementedError
-
-class BaseTextDirective(object):
- """
- Base directive that only accepts unicode/ASCII values.
- """
-
- def check_arguments(self, value):
- if util.not_unicode_or_ascii(value):
- raise GrokImportError("You can only pass unicode or ASCII to "
- "%s." % self.name)
-
-
-class SingleTextDirective(BaseTextDirective, SingleValue, OnceDirective):
- """
- Directive that accepts a single unicode/ASCII value, only once.
- """
-
-
-class MultipleTextDirective(BaseTextDirective, SingleValue,
- MultipleTimesDirective):
- """
- Directive that accepts a single unicode/ASCII value, multiple times.
- """
-
-
-class InterfaceOrClassDirective(SingleValue, OnceDirective):
- """
- Directive that only accepts classes or interface values.
- """
-
- def check_arguments(self, value):
- if not (IInterface.providedBy(value) or util.isclass(value)):
- raise GrokImportError("You can only pass classes or interfaces to "
- "%s." % self.name)
-
-
-class InterfaceDirective(SingleValue, OnceDirective):
- """
- Directive that only accepts interface values.
- """
-
- def check_arguments(self, value):
- if not (IInterface.providedBy(value)):
- raise GrokImportError("You can only pass interfaces to "
- "%s." % self.name)
Copied: martian/trunk/src/martian/directive.py (from rev 86374, martian/branches/jw-philipp-using-ndir-directives/src/martian/directive.py)
===================================================================
--- martian/trunk/src/martian/directive.py (rev 0)
+++ martian/trunk/src/martian/directive.py 2008-05-04 14:47:45 UTC (rev 86375)
@@ -0,0 +1,220 @@
+import sys
+import inspect
+
+from zope.interface.interfaces import IInterface
+
+from martian import util
+from martian.error import GrokImportError
+
+class StoreOnce(object):
+
+ def set(self, locals_, directive, value):
+ if directive.dotted_name() in locals_:
+ raise GrokImportError(
+ "The '%s' directive can only be called once per %s." %
+ (directive.name, directive.scope.description))
+ locals_[directive.dotted_name()] = value
+
+ def get(self, directive, component, default):
+ return getattr(component, directive.dotted_name(), default)
+
+ def setattr(self, context, directive, value):
+ setattr(context, directive.dotted_name(), value)
+
+ONCE = StoreOnce()
+
+class StoreOnceGetFromThisClassOnly(StoreOnce):
+
+ def get(self, directive, component, default):
+ return component.__dict__.get(directive.dotted_name(), default)
+
+class StoreMultipleTimes(StoreOnce):
+
+ def get(self, directive, component, default):
+ if getattr(component, directive.dotted_name(), default) is default:
+ return default
+
+ if getattr(component, 'mro', None) is None:
+ return getattr(component, directive.dotted_name())
+
+ result = []
+ for base in reversed(component.mro()):
+ list = getattr(base, directive.dotted_name(), default)
+ if list is not default and list not in result:
+ result.append(list)
+
+ result_flattened = []
+ for entry in result:
+ result_flattened.extend(entry)
+ return result_flattened
+
+ def set(self, locals_, directive, value):
+ values = locals_.setdefault(directive.dotted_name(), [])
+ values.append(value)
+
+MULTIPLE = StoreMultipleTimes()
+
+class StoreDict(StoreOnce):
+
+ def get(self, directive, component, default):
+ if getattr(component, directive.dotted_name(), default) is default:
+ return default
+
+ if getattr(component, 'mro', None) is None:
+ return getattr(component, directive.dotted_name())
+
+ result = {}
+ for base in reversed(component.mro()):
+ mapping = getattr(base, directive.dotted_name(), default)
+ if mapping is not default:
+ result.update(mapping)
+ return result
+
+ def set(self, locals_, directive, value):
+ values_dict = locals_.setdefault(directive.dotted_name(), {})
+ try:
+ key, value = value
+ except (TypeError, ValueError):
+ raise GrokImportError(
+ "The factory method for the '%s' directive should return a "
+ "key-value pair." % directive.name)
+ values_dict[key] = value
+
+DICT = StoreDict()
+
+_SENTINEL = object()
+_USE_DEFAULT = object()
+
+class ClassScope(object):
+ description = 'class'
+
+ def check(self, frame):
+ return util.frame_is_class(frame) and not is_fake_module(frame)
+
+CLASS = ClassScope()
+
+class ClassOrModuleScope(object):
+ description = 'class or module'
+
+ def check(self, frame):
+ return util.frame_is_class(frame) or util.frame_is_module(frame)
+
+CLASS_OR_MODULE = ClassOrModuleScope()
+
+class ModuleScope(object):
+ description = 'module'
+
+ def check(self, frame):
+ return util.frame_is_module(frame) or is_fake_module(frame)
+
+MODULE = ModuleScope()
+
+class Directive(object):
+
+ default = None
+
+ def __init__(self, *args, **kw):
+ self.name = self.__class__.__name__
+
+ self.frame = frame = sys._getframe(1)
+ if not self.scope.check(frame):
+ raise GrokImportError("The '%s' directive can only be used on "
+ "%s level." %
+ (self.name, self.scope.description))
+
+ self.check_factory_signature(*args, **kw)
+
+ validate = getattr(self, 'validate', None)
+ if validate is not None:
+ validate(*args, **kw)
+
+ value = self.factory(*args, **kw)
+
+ self.store.set(frame.f_locals, self, value)
+
+ # To get a correct error message, we construct a function that has
+ # the same signature as check_arguments(), but without "self".
+ def check_factory_signature(self, *arguments, **kw):
+ args, varargs, varkw, defaults = inspect.getargspec(
+ self.factory)
+ argspec = inspect.formatargspec(args[1:], varargs, varkw, defaults)
+ exec("def signature_checker" + argspec + ": pass")
+ try:
+ signature_checker(*arguments, **kw)
+ except TypeError, e:
+ message = e.args[0]
+ message = message.replace("signature_checker()", self.name)
+ raise TypeError(message)
+
+ def factory(self, value):
+ return value
+
+ def get_default(self, component):
+ return self.default
+
+ @classmethod
+ def dotted_name(cls):
+ return cls.__module__ + '.' + cls.__name__
+
+ @classmethod
+ def get(cls, component, module=None):
+ # Create an instance of the directive without calling __init__
+ self = cls.__new__(cls)
+
+ value = self.store.get(self, component, _USE_DEFAULT)
+ if value is _USE_DEFAULT and module is not None:
+ value = self.store.get(self, module, _USE_DEFAULT)
+ if value is _USE_DEFAULT:
+ value = self.get_default(component)
+
+ return value
+
+ @classmethod
+ def set(cls, component, value):
+ # Create an instance of the directive without calling __init__
+ self = cls.__new__(cls)
+ cls.store.setattr(component, self, value)
+
+
+class MultipleTimesDirective(Directive):
+ store = MULTIPLE
+ default = []
+
+
+class MarkerDirective(Directive):
+ store = ONCE
+ default = False
+
+ def factory(self):
+ return True
+
+
+class baseclass(MarkerDirective):
+ scope = CLASS
+ store = StoreOnceGetFromThisClassOnly()
+
+
+def validateText(directive, value):
+ if util.not_unicode_or_ascii(value):
+ raise GrokImportError("The '%s' directive can only be called with "
+ "unicode or ASCII." % directive.name)
+
+def validateInterfaceOrClass(directive, value):
+ if not (IInterface.providedBy(value) or util.isclass(value)):
+ raise GrokImportError("The '%s' directive can only be called with "
+ "a class or an interface." % directive.name)
+
+
+def validateInterface(directive, value):
+ if not (IInterface.providedBy(value)):
+ raise GrokImportError("The '%s' directive can only be called with "
+ "an interface." % directive.name)
+
+
+# this here only for testing purposes, which is a bit unfortunate
+# but makes the tests a lot clearer for module-level directives
+# also unfortunate that fake_module needs to be defined directly
+# in the fake module being tested and not in the FakeModule base class;
+# the system cannot find it on the frame if it is in the base class.
+def is_fake_module(frame):
+ return frame.f_locals.has_key('fake_module')
Deleted: martian/trunk/src/martian/directive.txt
===================================================================
--- martian/trunk/src/martian/directive.txt 2008-05-04 14:45:00 UTC (rev 86374)
+++ martian/trunk/src/martian/directive.txt 2008-05-04 14:47:45 UTC (rev 86375)
@@ -1,226 +0,0 @@
-Directives
-==========
-
-A martian directive is a special function call that causes information
-to be set on module or class level. This information is set as a
-Python module is imported, so should do the minimum amount of work (as
-import side-effects are bad). The idea is that this information can
-then be picked up by a martian-based framework during grok time.
-
-Martian has an infrastructure to make it easy to define new
-directives.
-
-Directive contexts
-------------------
-
-If a directive is given ``ModuleDirectiveContext`` it can only work in
-real modules::
-
- >>> from martian.directive import SingleTextDirective, ModuleDirectiveContext
- >>> foo = SingleTextDirective('grok.foo', ModuleDirectiveContext())
-
-We cannot show a working example of the ``foo`` directive in this
-doctest as it is not a real module. It would look like this::
-
- foo('hello world')
-
-We have placed this code in a real module and will import it::
-
- >>> from martian.tests.directive import modulecontext
-
-We expect modulecontext to contain a special attribute ``__grok_foo__``
-with the string ``'hello world'``::
-
- >>> modulecontext.__grok_foo__
- 'hello world'
-
-This directive cannot be used in a class as it's not allowed by its context::
-
- >>> class Test(object):
- ... foo('hello world')
- Traceback (most recent call last):
- ...
- GrokImportError: grok.foo can only be used on module level.
-
-Now let's define a directive that can only work in classes::
-
- >>> from martian.directive import ClassDirectiveContext
- >>> bar = SingleTextDirective('grok.bar', ClassDirectiveContext())
-
-It won't work in a module::
-
- >>> from martian.tests.directive import classcontextbroken
- Traceback (most recent call last):
- ...
- GrokImportError: grok.bar can only be used on class level.
-
-It will work in a class context::
-
- >>> class Test(object):
- ... bar('hello world')
- >>> Test.__grok_bar__
- 'hello world'
-
-Now let's define a directive that can be used both on module-level as
-well as on class-level::
-
- >>> from martian.directive import ClassOrModuleDirectiveContext
- >>> qux = SingleTextDirective('grok.qux', ClassOrModuleDirectiveContext())
-
-It can be used in a class::
-
- >>> class Test(object):
- ... qux('hello world')
- >>> Test.__grok_qux__
- 'hello world'
-
-It can also be used in a module::
-
- >>> from martian.tests.directive import classormodulecontext
- >>> classormodulecontext.__grok_qux__
- 'hello world'
-
-Calling a directive once or multiple times
-------------------------------------------
-
-Directives can either be called once in a particular context, or
-multiple times. Let's define a type of directive that can only be
-called once::
-
- >>> from martian.directive import OnceDirective, SingleValue, BaseTextDirective
- >>> class MyDirective(BaseTextDirective, SingleValue, OnceDirective):
- ... pass
- >>> hoi = MyDirective('hoi', ClassDirectiveContext())
-
-When we try to use it twice, we get an error::
-
- >>> class Test(object):
- ... hoi('once')
- ... hoi('twice')
- Traceback (most recent call last):
- ...
- GrokImportError: hoi can only be called once per class.
-
-This also works for module-level directives::
-
- >>> from martian.tests.directive import onlyoncemodulecontext
- Traceback (most recent call last):
- ...
- GrokImportError: hoi can only be called once per module.
-
-Now let's define a directive that can be called multiple times::
-
- >>> from martian.directive import MultipleTimesDirective
- >>> class MyDirective(BaseTextDirective, SingleValue, MultipleTimesDirective):
- ... pass
- >>> dag = MyDirective('dag', ClassDirectiveContext())
-
-It will allow you to use it multiple times::
-
- >>> class Test(object):
- ... dag('once')
- ... dag('twice')
-
-The underlying annotation will have stored the multiple values::
-
- >>> Test.__dag__
- ['once', 'twice']
-
-Directive values
-----------------
-
-A ``BaseTextDirective`` directive accepts unicode or plain ascii values::
-
- >>> class Test(object):
- ... hoi('hello')
- >>> class Test(object):
- ... hoi(u'è')
-
-It won't accept values in another encoding::
-
- >>> class Test(object):
- ... hoi(u'è'.encode('latin-1'))
- Traceback (most recent call last):
- ...
- GrokImportError: You can only pass unicode or ASCII to hoi.
- >>> class Test(object):
- ... hoi(u'è'.encode('UTF-8'))
- Traceback (most recent call last):
- ...
- GrokImportError: You can only pass unicode or ASCII to hoi.
-
-A ``InterfaceOrClassDirective`` only accepts class or interface objects::
-
- >>> from martian.directive import InterfaceOrClassDirective
- >>> class MyDirective(InterfaceOrClassDirective, SingleValue, OnceDirective):
- ... pass
- >>> hello = MyDirective('hello', ClassDirectiveContext())
- >>> class SomeClass(object):
- ... pass
- >>> class Test(object):
- ... hello(SomeClass)
- >>> class SomeOldStyleClass:
- ... pass
- >>> class Test(object):
- ... hello(SomeOldStyleClass)
- >>> from zope.interface import Interface
- >>> class ISomeInterface(Interface):
- ... pass
- >>> class Test(object):
- ... hello(ISomeInterface)
-
-But not anything else::
-
- >>> class Test(object):
- ... hello(None)
- Traceback (most recent call last):
- ...
- GrokImportError: You can only pass classes or interfaces to hello.
- >>> class Test(object):
- ... hello('foo')
- Traceback (most recent call last):
- ...
- GrokImportError: You can only pass classes or interfaces to hello.
-
-An ``InterfaceDirective`` accepts only interfaces::
-
- >>> from martian.directive import InterfaceDirective
- >>> class MyDirective(InterfaceDirective, SingleValue, OnceDirective):
- ... pass
- >>> hello2 = MyDirective('hello2', ClassDirectiveContext())
- >>> class Test(object):
- ... hello2(ISomeInterface)
-
-But not classes::
-
- >>> class Test(object):
- ... hello2(SomeClass)
- Traceback (most recent call last):
- ...
- GrokImportError: You can only pass interfaces to hello2.
-
-An ``OptionalValueDirective`` accepts a single value, but the value
-can be left out. If so, the value will be calculated by a function we
-pass it::
-
- >>> from martian.directive import OptionalValueDirective
- >>> class MyDirective(OptionalValueDirective, OnceDirective):
- ... def default_value(self):
- ... return 4 # the default if no value is given
- >>> hello3 = MyDirective('hello3', ClassDirectiveContext())
-
-Let's try the directive with a single value::
-
- >>> class Test(object):
- ... hello3(1)
- >>> Test.__hello3__
- 1
-
-We can also try the directive without a value::
-
- >>> class Test(object):
- ... hello3()
- >>> Test.__hello3__
- 4
-
-
Copied: martian/trunk/src/martian/directive.txt (from rev 86374, martian/branches/jw-philipp-using-ndir-directives/src/martian/directive.txt)
===================================================================
--- martian/trunk/src/martian/directive.txt (rev 0)
+++ martian/trunk/src/martian/directive.txt 2008-05-04 14:47:45 UTC (rev 86375)
@@ -0,0 +1,561 @@
+Directives New Style
+====================
+
+When grokking a class, the grokking procedure can be informed by
+directives, on a class, or a module. If a directive is absent, the
+system falls back to a default. Here we introduce a general way to
+define these directives, and how to use them to retrieve information
+for a class for use during the grokking procedure.
+
+A simple directive
+------------------
+
+We define a simple directive that sets a description::
+
+ >>> from martian import Directive, CLASS, ONCE
+ >>> class description(Directive):
+ ... scope = CLASS
+ ... store = ONCE
+ ... default = u''
+
+The name of the directive is ``description``. We specify that the
+directive can only be used in the scope of a class. We also specify it
+can only be used a single time. Finally we define the default in case
+the directive is absent (the empty string).
+
+Now we look at the directive in action::
+
+ >>> class Foo(object):
+ ... description(u"This is a description")
+
+After setting it, we can use the ``get`` method on the directive to
+retrieve it from the class again::
+
+ >>> description.get(Foo)
+ u'This is a description'
+
+Directives in different namespaces get stored differently. We'll
+define a similar directive in another namespace::
+
+ >>> class description2(description):
+ ... pass
+
+ >>> class Foo(object):
+ ... description(u"Description1")
+ ... description2(u"Description2")
+ >>> description.get(Foo)
+ u'Description1'
+ >>> description2.get(Foo)
+ u'Description2'
+
+If we check the value of a class without the directive, we see the
+default value for that directive, this case the empty unicode string::
+
+ >>> class Foo(object):
+ ... pass
+ >>> description.get(Foo)
+ u''
+
+In certain cases we need to set a value on a component as if the directive was
+actually used::
+
+ >>> description.set(Foo, u'value as set')
+ >>> description.get(Foo)
+ u'value as set'
+
+Subclasses of the original class will inherit the properties set by the
+directive:
+
+ >>> class Foo(object):
+ ... description('This is a foo.')
+ ...
+ >>> class Bar(Foo):
+ ... pass
+ ...
+ >>> description.get(Bar)
+ 'This is a foo.'
+
+When we use the directive outside of class scope, we get an error
+message::
+
+ >>> description('Description')
+ Traceback (most recent call last):
+ ...
+ GrokImportError: The 'description' directive can only be used on class level.
+
+In particular, we cannot use it in a module::
+
+ >>> class testmodule(FakeModule):
+ ... fake_module = True
+ ... description("Description")
+ Traceback (most recent call last):
+ ...
+ GrokImportError: The 'description' directive can only be used on class level.
+
+We cannot use the directive twice in the class scope. If we do so, we
+get an error message as well::
+
+ >>> class Foo(object):
+ ... description(u"Description1")
+ ... description(u"Description2")
+ Traceback (most recent call last):
+ ...
+ GrokImportError: The 'description' directive can only be called once per class.
+
+We cannot call the directive with no argument either::
+
+ >>> class Foo(object):
+ ... description()
+ Traceback (most recent call last):
+ ...
+ TypeError: description takes exactly 1 argument (0 given)
+
+Class and module scope
+----------------------
+
+We define a ``layer`` directive that can be used in class and module
+scope both::
+
+ >>> from martian import CLASS_OR_MODULE
+ >>> class layer(Directive):
+ ... scope = CLASS_OR_MODULE
+ ... store = ONCE
+
+By default, the ``default`` property is None which is why we can omit
+specifying it here.
+
+We can use this directive now on a class::
+
+ >>> class Foo(object):
+ ... layer('Test')
+ >>> layer.get(Foo)
+ 'Test'
+
+The defaulting to ``None`` works::
+
+ >>> class Foo(object):
+ ... pass
+ >>> layer.get(Foo) is None
+ True
+
+We can also use it in a module::
+
+ >>> class testmodule(FakeModule):
+ ... layer('Test2')
+ ... class Foo(object):
+ ... pass
+ >>> test_module = fake_import(testmodule)
+
+When we now try to access ``layer`` on ``Foo``, we find the
+module-level default which we just set. We pass the module as the
+second argument to the ``get`` method to have it fall back on this::
+
+ >>> layer.get(testmodule.Foo, testmodule)
+ 'Test2'
+
+Let's look at a module where the directive is not used::
+
+ >>> class testmodule(FakeModule):
+ ... class Foo(object):
+ ... pass
+ >>> testmodule = fake_import(testmodule)
+
+In this case, the value cannot be found so the system falls back on
+the default, ``None``::
+
+ >>> layer.get(testmodule.Foo, testmodule) is None
+ True
+
+Using a directive multiple times
+--------------------------------
+
+A directive can be configured to allow it to be called multiple times
+in the same scope::
+
+ >>> from martian import MultipleTimesDirective
+ >>> class multi(MultipleTimesDirective):
+ ... scope = CLASS
+
+We can now use the directive multiple times without any errors::
+
+ >>> class Foo(object):
+ ... multi(u"Once")
+ ... multi(u"Twice")
+
+We can now retrieve the value and we'll get a list::
+
+ >>> multi.get(Foo)
+ [u'Once', u'Twice']
+
+The default value for a MultipleTimesDirective is an empty list::
+
+ >>> class Bar(object):
+ ... pass
+ >>> multi.get(Bar)
+ []
+
+Whenever the directive is used on a sub class of a component, the values set by
+directives on the base classes are combined::
+
+ >>> class Qux(Foo):
+ ... multi(u'Triple')
+ ...
+ >>> multi.get(Qux)
+ [u'Once', u'Twice', u'Triple']
+
+
+Using a directive multiple times, as a dictionary
+-------------------------------------------------
+
+A directive can be configured to allow it to be called multiple times in the
+same scope. In this case the factory method should be overridden to return a
+key-value pair::
+
+ >>> from martian import DICT
+ >>> class multi(Directive):
+ ... scope = CLASS
+ ... store = DICT
+ ... def factory(self, value):
+ ... return value.lower(), value
+
+We can now use the directive multiple times without any errors::
+
+ >>> class Bar(object):
+ ... multi(u"Once")
+ ... multi(u"Twice")
+
+We can now retrieve the value and we'll get a to the items::
+
+ >>> d = multi.get(Bar)
+ >>> print sorted(d.items())
+ [(u'once', u'Once'), (u'twice', u'Twice')]
+
+When the factory method does not return a key-value pair, an error is raised::
+
+ >>> class wrongmulti(Directive):
+ ... scope = CLASS
+ ... store = DICT
+ ... def factory(self, value):
+ ... return None
+
+ >>> class Baz(object):
+ ... wrongmulti(u"Once")
+ Traceback (most recent call last):
+ ...
+ GrokImportError: The factory method for the 'wrongmulti' directive should
+ return a key-value pair.
+
+ >>> class wrongmulti2(Directive):
+ ... scope = CLASS
+ ... store = DICT
+ ... def factory(self, value):
+ ... return value, value, value
+
+ >>> class Baz(object):
+ ... wrongmulti2(u"Once")
+ Traceback (most recent call last):
+ ...
+ GrokImportError: The factory method for the 'wrongmulti2' directive should
+ return a key-value pair.
+
+Like with MULTIPLE store, values set by directives using the DICT store are
+combined::
+
+ >>> class multi(Directive):
+ ... scope = CLASS
+ ... store = DICT
+ ... def factory(self, value, andanother):
+ ... return value, andanother
+ ...
+ >>> class Frepple(object):
+ ... multi(1, 'AAA')
+ ... multi(2, 'BBB')
+ ...
+ >>> class Fropple(Frepple):
+ ... multi(1, 'CCC')
+ ... multi(3, 'DDD')
+ ... multi(4, 'EEE')
+
+ >>> d = multi.get(Fropple)
+ >>> print sorted(d.items())
+ [(1, 'CCC'), (2, 'BBB'), (3, 'DDD'), (4, 'EEE')]
+
+Using MULTIPLE and DICT can also work on a module level, even though
+inheritance has no meaning there::
+
+ >>> from martian import MODULE
+ >>> class multi(MultipleTimesDirective):
+ ... scope = MODULE
+ ...
+ >>> multi.__module__ = 'somethingelse'
+ >>> class module_with_directive(FakeModule):
+ ... fake_module = True
+ ...
+ ... multi('One')
+ ... multi('Two')
+ ...
+ >>> module_with_directive = fake_import(module_with_directive)
+ >>> print multi.get(module_with_directive)
+ ['One', 'Two']
+
+ >>> from martian import MODULE
+ >>> class multi(Directive):
+ ... scope = MODULE
+ ... store = DICT
+ ... def factory(self, value, andanother):
+ ... return value, andanother
+ ...
+ >>> multi.__module__ = 'somethingelse'
+ >>> class module_with_directive(FakeModule):
+ ... fake_module = True
+ ...
+ ... multi(1, 'One')
+ ... multi(2, 'Two')
+ ...
+ >>> module_with_directive = fake_import(module_with_directive)
+ >>> d = multi.get(module_with_directive)
+ >>> print sorted(d.items())
+ [(1, 'One'), (2, 'Two')]
+
+Calculated defaults
+-------------------
+
+Often instead of just supplying the system with a default, we want to
+calculate the default in some way. We define the ``name`` directive,
+which if not present, will calculate its value from the name of class,
+lower-cased. Instead of passing a default value, we pass a function as the
+default argument::
+
+ >>> class name(Directive):
+ ... scope = CLASS
+ ... store = ONCE
+ ... def get_default(self, component):
+ ... return component.__name__.lower()
+
+ >>> class Foo(object):
+ ... name('bar')
+ >>> name.get(Foo)
+ 'bar'
+
+ >>> class Foo(object):
+ ... pass
+ >>> name.get(Foo)
+ 'foo'
+
+A marker directive
+------------------
+
+Another type of directive is a marker directive. This directive takes
+no arguments at all, but when used it marks the context::
+
+ >>> from martian import MarkerDirective
+ >>> class mark(MarkerDirective):
+ ... scope = CLASS
+
+ >>> class Foo(object):
+ ... mark()
+
+Class ``Foo`` is now marked::
+
+ >>> mark.get(Foo)
+ True
+
+When we have a class that isn't marked, we get the default value, ``False``::
+
+ >>> class Bar(object):
+ ... pass
+ >>> mark.get(Bar)
+ False
+
+If we pass in an argument, we get an error::
+
+ >>> class Bar(object):
+ ... mark("An argument")
+ Traceback (most recent call last):
+ ...
+ TypeError: mark takes no arguments (1 given)
+
+
+Validation
+----------
+
+A directive can be supplied with a validation method. The validation method
+checks whether the value passed in is allowed. It should raise
+``GrokImportError`` if the value cannot be validated, together with a
+description of why not.
+
+First we define our own validation function. A validation function
+takes two arguments:
+
+* the name of the directive we're validating for
+
+* the value we need to validate
+
+The name can be used to format the exception properly.
+
+We'll define a validation method that only expects integer numbers::
+
+ >>> from martian.error import GrokImportError
+ >>> class number(Directive):
+ ... scope = CLASS
+ ... 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)
+
+ >>> class Foo(object):
+ ... number(3)
+
+ >>> class Foo(object):
+ ... number("This shouldn't work")
+ Traceback (most recent call last):
+ ...
+ GrokImportError: The 'number' directive can only be called with an integer.
+
+Some built-in validation functions
+----------------------------------
+
+Let's look at some built-in validation functions.
+
+The ``validateText`` function determines whether a string
+is unicode or plain ascii::
+
+ >>> from martian import validateText
+ >>> class title(Directive):
+ ... scope = CLASS
+ ... store = ONCE
+ ... default = u''
+ ... validate = validateText
+
+When we pass ascii text into the directive, there is no error::
+
+ >>> class Foo(object):
+ ... title('Some ascii text')
+
+We can also pass in a unicode string without error::
+
+ >>> class Foo(object):
+ ... title(u'Some unicode text')
+
+Let's now try it with something that's not text at all, such as a number.
+This fails::
+
+ >>> class Foo(object):
+ ... title(123)
+ Traceback (most recent call last):
+ ...
+ GrokImportError: The 'title' directive can only be called with unicode or ASCII.
+
+It's not allowed to call the direct with a non-ascii encoded string::
+
+ >>> class Foo(object):
+ ... title(u'è'.encode('latin-1'))
+ Traceback (most recent call last):
+ ...
+ GrokImportError: The 'title' directive can only be called with unicode or ASCII.
+
+ >>> class Foo(object):
+ ... title(u'è'.encode('UTF-8'))
+ Traceback (most recent call last):
+ ...
+ GrokImportError: The 'title' directive can only be called with unicode or ASCII.
+
+The ``validateInterfaceOrClass`` function only accepts class or
+interface objects::
+
+ >>> from martian import validateInterfaceOrClass
+ >>> class klass(Directive):
+ ... scope = CLASS
+ ... store = ONCE
+ ... validate = validateInterfaceOrClass
+
+It works with interfaces and classes::
+
+ >>> class Bar(object):
+ ... pass
+ >>> class Foo(object):
+ ... klass(Bar)
+
+ >>> from zope.interface import Interface
+ >>> class IBar(Interface):
+ ... pass
+ >>> class Foo(object):
+ ... klass(IBar)
+
+It won't work with other things::
+
+ >>> class Foo(object):
+ ... klass(Bar())
+ Traceback (most recent call last):
+ ...
+ GrokImportError: The 'klass' directive can only be called with a class or an interface.
+
+ >>> class Foo(object):
+ ... klass(1)
+ Traceback (most recent call last):
+ ...
+ GrokImportError: The 'klass' directive can only be called with a class or an interface.
+
+The ``validateInterface`` validator only accepts an interface::
+
+ >>> from martian import validateInterface
+ >>> class iface(Directive):
+ ... scope = CLASS
+ ... store = ONCE
+ ... validate = validateInterface
+
+Let's try it::
+
+ >>> class Foo(object):
+ ... iface(IBar)
+
+It won't work with classes or other things::
+
+ >>> class Foo(object):
+ ... iface(Bar)
+ Traceback (most recent call last):
+ ...
+ GrokImportError: The 'iface' directive can only be called with an interface.
+
+ >>> class Foo(object):
+ ... iface(1)
+ Traceback (most recent call last):
+ ...
+ GrokImportError: The 'iface' directive can only be called with an interface.
+
+Declaring base classes
+----------------------
+
+There's a special directive called 'baseclass' which lets you declare that a
+certain class is the base class for a series of other components. This
+property should not be inherited by those components. Consider the following
+base class:
+
+ >>> from martian import baseclass
+ >>> class MyBase(object):
+ ... baseclass()
+
+As you would expect, the directive will correctly identify this class as a
+baseclass:
+
+ >>> baseclass.get(MyBase)
+ True
+
+But, if we create a subclass of this base class, the subclass won't inherit
+that property, unlike with a regular directive:
+
+ >>> class SubClass(MyBase):
+ ... pass
+ ...
+ >>> baseclass.get(SubClass)
+ False
+
+Naturally, the directive will also report a false answer if the class doesn't
+inherit from a base class at all and hasn't been marked with the directive:
+
+ >>> class NoBase(object):
+ ... pass
+ ...
+ >>> baseclass.get(NoBase)
+ False
Deleted: martian/trunk/src/martian/ndir.py
===================================================================
--- martian/trunk/src/martian/ndir.py 2008-05-04 14:45:00 UTC (rev 86374)
+++ martian/trunk/src/martian/ndir.py 2008-05-04 14:47:45 UTC (rev 86375)
@@ -1,124 +0,0 @@
-import sys
-
-from zope.interface.interfaces import IInterface
-
-from martian import util
-from martian.error import GrokImportError
-
-# ONCE or MULTIPLE
-ONCE = object()
-MULTIPLE = object()
-
-# arguments
-SINGLE_ARG = object()
-NO_ARG = object()
-OPTIONAL_ARG = object()
-
-_SENTINEL = object()
-_USE_DEFAULT = object()
-
-class ClassScope(object):
- description = 'class'
-
- def check(self, frame):
- return (util.frame_is_class(frame) and
- not is_fake_module(frame))
-
-CLASS = ClassScope()
-
-class ClassOrModuleScope(object):
- description = 'class or module'
-
- def check(self, frame):
- return (util.frame_is_class(frame) or
- util.frame_is_module(frame))
-
-CLASS_OR_MODULE = ClassOrModuleScope()
-
-class Directive(object):
- def __init__(self, namespace, name, scope, times, default,
- validate=None, arg=SINGLE_ARG):
- self.namespace = namespace
- self.name = name
- self.scope = scope
- self.times = times
- self.default = default
- self.validate = validate
- self.arg = arg
-
- def __call__(self, value=_SENTINEL):
- name = self.namespaced_name()
-
- if self.arg is NO_ARG:
- if value is _SENTINEL:
- value = True
- else:
- raise GrokImportError("%s accepts no arguments." % name)
- elif self.arg is SINGLE_ARG:
- if value is _SENTINEL:
- raise GrokImportError("%s requires a single argument." % name)
- elif self.arg is OPTIONAL_ARG:
- if value is _SENTINEL:
- value = _USE_DEFAULT
-
- if self.validate is not None:
- self.validate(name, value)
-
- frame = sys._getframe(1)
- if not self.scope.check(frame):
- raise GrokImportError("%s can only be used on %s level." %
- (name, self.scope.description))
- if self.times is ONCE:
- if name in frame.f_locals:
- raise GrokImportError("%s can only be called once per %s." %
- (name, self.scope.description))
- frame.f_locals[name] = value
- elif self.times is MULTIPLE:
- values = frame.f_locals.get(name, [])
- values.append(value)
- frame.f_locals[name] = values
- else:
- assert False, "Unknown value for times: %" % self.times
-
- def get(self, component, module=None):
- name = self.namespaced_name()
- value = getattr(component, name, _USE_DEFAULT)
- if value is _USE_DEFAULT and module is not None:
- value = getattr(module, name, _USE_DEFAULT)
- if value is _USE_DEFAULT:
- return self.get_default(component)
- return value
-
- def get_default(self, component):
- if callable(self.default):
- return self.default(component)
- return self.default
-
- def namespaced_name(self):
- return self.namespace + '.' + self.name
-
-def validateText(name, value):
- if util.not_unicode_or_ascii(value):
- raise GrokImportError("%s can only be called with unicode or ASCII." %
- name)
-
-def validateInterfaceOrClass(name, value):
- if not (IInterface.providedBy(value) or util.isclass(value)):
- raise GrokImportError("%s can only be called with a class or "
- "interface." %
- name)
-
-
-def validateInterface(name, value):
- if not (IInterface.providedBy(value)):
- raise GrokImportError("%s can only be called with an interface." %
- name)
-
-
-# this here only for testing purposes, which is a bit unfortunate
-# but makes the tests a lot clearer for module-level directives
-# also unfortunate that fake_module needs to be defined directly
-# in the fake module being tested and not in the FakeModule base class;
-# the system cannot find it on the frame if it is in the base class.
-def is_fake_module(frame):
- return frame.f_locals.has_key('fake_module')
Deleted: martian/trunk/src/martian/ndir.txt
===================================================================
--- martian/trunk/src/martian/ndir.txt 2008-05-04 14:45:00 UTC (rev 86374)
+++ martian/trunk/src/martian/ndir.txt 2008-05-04 14:47:45 UTC (rev 86375)
@@ -1,417 +0,0 @@
-Directives New Style
-====================
-
-When grokking a class, the grokking procedure can be informed by
-directives, on a class, or a module. If a directive is absent, the
-system falls back to a default. Here we introduce a general way to
-define these directives, and how to use them to retrieve information
-for a class for use during the grokking procedure.
-
-A simple directive
-------------------
-
-We define a simple directive that sets a description::
-
- >>> from martian.ndir import Directive, CLASS, ONCE
- >>> description = Directive('martian', 'description',
- ... CLASS, ONCE, u'')
-
-This directive is placed in a namespace (in this case, ``martian``);
-this is just a string and should be used to avoid conflicts between
-directives with the same name that could be defined by different
-packages.
-
-The name of the directive is ``description``. We specify that the
-directive can only be used in the scope of a class. We also specify it
-can only be used a single time. Finally we define the default in case
-the directive is absent (the empty string).
-
-Now we look at the directive in action::
-
- >>> class Foo(object):
- ... description(u"This is a description")
-
-After setting it, we can use the ``get`` method on the directive to
-retrieve it from the class again::
-
- >>> description.get(Foo)
- u'This is a description'
-
-Directives in different namespaces get stored differently. We'll
-define a similar directive in another namespace::
-
- >>> description2 = Directive('different', 'description',
- ... CLASS, ONCE, u'')
-
- >>> class Foo(object):
- ... description(u"Description1")
- ... description2(u"Description2")
- >>> description.get(Foo)
- u'Description1'
- >>> description2.get(Foo)
- u'Description2'
-
-If we check the value of a class without the directive, we see the
-default value for that directive, this case the empty unicode string::
-
- >>> class Foo(object):
- ... pass
- >>> description.get(Foo)
- u''
-
-When we use the directive outside of class scope, we get an error
-message::
-
- >>> description('Description')
- Traceback (most recent call last):
- ...
- GrokImportError: martian.description can only be used on class level.
-
-In particular, we cannot use it in a module::
-
- >>> class testmodule(FakeModule):
- ... fake_module = True
- ... description("Description")
- Traceback (most recent call last):
- ...
- GrokImportError: martian.description can only be used on class level.
-
-We cannot use the directive twice in the class scope. If we do so, we
-get an error message as well::
-
- >>> class Foo(object):
- ... description(u"Description1")
- ... description(u"Description2")
- Traceback (most recent call last):
- ...
- GrokImportError: martian.description can only be called once per class.
-
-We cannot call the directive with no argument either::
-
- >>> class Foo(object):
- ... description()
- Traceback (most recent call last):
- ...
- GrokImportError: martian.description requires a single argument.
-
-Class and module scope
-----------------------
-
-We define a ``layer`` directive that can be used in class and module
-scope both::
-
- >>> from martian.ndir import CLASS_OR_MODULE
- >>> layer = Directive('martian', 'layer', CLASS_OR_MODULE, ONCE, None)
-
-We can use it on a class::
-
- >>> class Foo(object):
- ... layer('Test')
- >>> layer.get(Foo)
- 'Test'
-
-The defaulting to ``None`` works::
-
- >>> class Foo(object):
- ... pass
- >>> layer.get(Foo) is None
- True
-
-We can also use it in a module::
-
- >>> class testmodule(FakeModule):
- ... layer('Test2')
- ... class Foo(object):
- ... pass
- >>> test_module = fake_import(testmodule)
-
-When we now try to access ``layer`` on ``Foo``, we find the
-module-level default which we just set. We pass the module as the
-second argument to the ``get`` method to have it fall back on this::
-
- >>> layer.get(testmodule.Foo, testmodule)
- 'Test2'
-
-Let's look at a module where the directive is not used::
-
- >>> class testmodule(FakeModule):
- ... class Foo(object):
- ... pass
- >>> testmodule = fake_import(testmodule)
-
-In this case, the value cannot be found so the system falls back on
-the default, ``None``::
-
- >>> layer.get(testmodule.Foo, testmodule) is None
- True
-
-Using a directive multiple times
---------------------------------
-
-A directive can be configured to allow it to be called multiple times
-in the same scope::
-
- >>> from martian.ndir import MULTIPLE
- >>> multi = Directive('martian', 'multi', CLASS, MULTIPLE, None)
-
-We can now use the directive multiple times without any errors::
-
- >>> class Foo(object):
- ... multi(u"Once")
- ... multi(u"Twice")
-
-We can now retrieve the value and we'll get a list::
-
- >>> multi.get(Foo)
- [u'Once', u'Twice']
-
-Calculated defaults
--------------------
-
-Often instead of just supplying the system with a default, we want to
-calculate the default in some way. We define the ``name`` directive,
-which if not present, will calculate its value from the name of class,
-lower-cased. Instead of passing a default value, we pass a function as the
-default argument::
-
- >>> def default(component):
- ... return component.__name__.lower()
- >>> name = Directive('martian', 'name',
- ... CLASS, ONCE, default)
-
- >>> class Foo(object):
- ... name('bar')
- >>> name.get(Foo)
- 'bar'
-
- >>> class Foo(object):
- ... pass
- >>> name.get(Foo)
- 'foo'
-
-A marker directive
-------------------
-
-Another type of directive is a marker directive. This directive takes
-no arguments at all, but when used it marks the context::
-
- >>> from martian.ndir import NO_ARG
- >>> mark = Directive('martian', 'mark', CLASS, ONCE, False,
- ... arg=NO_ARG)
-
- >>> class Foo(object):
- ... mark()
-
-Class ``Foo`` is now marked::
-
- >>> mark.get(Foo)
- True
-
-When we have a class that isn't marked, we get the default value, ``False``::
-
- >>> class Bar(object):
- ... pass
- >>> mark.get(Bar)
- False
-
-If we pass in an argument, we get an error::
-
- >>> class Bar(object):
- ... mark("An argument")
- Traceback (most recent call last):
- ...
- GrokImportError: martian.mark accepts no arguments.
-
-The marker directive also works with calculated defaults::
-
- >>> def func(component):
- ... return component.__name__
- >>> mark = Directive('martian', 'mark', CLASS, ONCE, func, arg=NO_ARG)
- >>> class Foo(object):
- ... pass
- >>> mark.get(Foo)
- 'Foo'
-
-Optional arguments
-------------------
-
-We can also construct a directive that receives an optional argument::
-
- >>> from martian.ndir import OPTIONAL_ARG
- >>> optional = Directive('martian', 'optional', CLASS, ONCE, 'default',
- ... arg=OPTIONAL_ARG)
-
-We can give it an argument::
-
- >>> class Foo(object):
- ... optional("Hoi")
- >>> optional.get(Foo)
- 'Hoi'
-
-We can also give it no argument. The default will then be used::
-
- >>> class Foo(object):
- ... optional()
- >>> optional.get(Foo)
- 'default'
-
-The default will also be used if the directive isn't used at all::
-
- >>> class Foo(object):
- ... pass
- >>> optional.get(Foo)
- 'default'
-
-The optional value directive also works with calculated defaults::
-
- >>> optional = Directive('martian', 'optional', CLASS, ONCE, func,
- ... arg=OPTIONAL_ARG)
- >>> class Foo(object):
- ... optional()
- >>> optional.get(Foo)
- 'Foo'
- >>> class Foo(object):
- ... pass
- >>> optional.get(Foo)
- 'Foo'
-
-Validation
-----------
-
-A directive can be supplied with a validation function. validation
-function checks whether the value passed in is allowed. The function
-should raise ``GrokImportError`` if the value cannot be validated,
-together with a description of why not.
-
-First we define our own validation function. A validation function
-takes two arguments:
-
-* the name of the directive we're validating for
-
-* the value we need to validate
-
-The name can be used to format the exception properly.
-
-We'll define a validation function that only expects integer numbers::
-
- >>> from martian.error import GrokImportError
- >>> def validateInt(name, value):
- ... if type(value) is not int:
- ... raise GrokImportError("%s can only be called with an integer." %
- ... name)
-
-We use it with a directive::
-
- >>> number = Directive('martian', 'number', CLASS, ONCE, None, validateInt)
- >>> class Foo(object):
- ... number(3)
-
- >>> class Foo(object):
- ... number("This shouldn't work")
- Traceback (most recent call last):
- ...
- GrokImportError: martian.number can only be called with an integer.
-
-Some built-in validation functions
-----------------------------------
-
-Let's look at some built-in validation functions.
-
-The ``validateText`` function determines whether a string
-is unicode or plain ascii::
-
- >>> from martian.ndir import validateText
- >>> title = Directive('martian', 'title', CLASS, ONCE, u'', validateText)
-
-When we pass ascii text into the directive, there is no error::
-
- >>> class Foo(object):
- ... title('Some ascii text')
-
-We can also pass in a unicode string without error::
-
- >>> class Foo(object):
- ... title(u'Some unicode text')
-
-Let's now try it with something that's not text at all, such as a number.
-This fails::
-
- >>> class Foo(object):
- ... title(123)
- Traceback (most recent call last):
- ...
- GrokImportError: martian.title can only be called with unicode or ASCII.
-
-It's not allowed to call the direct with a non-ascii encoded string::
-
- >>> class Foo(object):
- ... title(u'è'.encode('latin-1'))
- Traceback (most recent call last):
- ...
- GrokImportError: martian.title can only be called with unicode or ASCII.
-
- >>> class Foo(object):
- ... title(u'è'.encode('UTF-8'))
- Traceback (most recent call last):
- ...
- GrokImportError: martian.title can only be called with unicode or ASCII.
-
-The ``validateInterfaceOrClass`` function only accepts class or
-interface objects::
-
- >>> from martian.ndir import validateInterfaceOrClass
- >>> klass = Directive('martian', 'klass', CLASS, ONCE, None,
- ... validateInterfaceOrClass)
-
-It works with interfaces and classes::
-
- >>> class Bar(object):
- ... pass
- >>> class Foo(object):
- ... klass(Bar)
-
- >>> from zope.interface import Interface
- >>> class IBar(Interface):
- ... pass
- >>> class Foo(object):
- ... klass(IBar)
-
-It won't work with other things::
-
- >>> class Foo(object):
- ... klass(Bar())
- Traceback (most recent call last):
- ...
- GrokImportError: martian.klass can only be called with a class or interface.
-
- >>> class Foo(object):
- ... klass(1)
- Traceback (most recent call last):
- ...
- GrokImportError: martian.klass can only be called with a class or interface.
-
-The ``validateInterface`` validator only accepts an interface::
-
- >>> from martian.ndir import validateInterface
- >>> iface = Directive('martian', 'iface', CLASS, ONCE, None,
- ... validateInterface)
-
-Let's try it::
-
- >>> class Foo(object):
- ... iface(IBar)
-
-It won't work with classes or other things::
-
- >>> class Foo(object):
- ... iface(Bar)
- Traceback (most recent call last):
- ...
- GrokImportError: martian.iface can only be called with an interface.
-
- >>> class Foo(object):
- ... iface(1)
- Traceback (most recent call last):
- ...
- GrokImportError: martian.iface can only be called with an interface.
-
Copied: martian/trunk/src/martian/tests/scan_for_classes.txt (from rev 86374, martian/branches/jw-philipp-using-ndir-directives/src/martian/tests/scan_for_classes.txt)
===================================================================
--- martian/trunk/src/martian/tests/scan_for_classes.txt (rev 0)
+++ martian/trunk/src/martian/tests/scan_for_classes.txt 2008-05-04 14:47:45 UTC (rev 86375)
@@ -0,0 +1,35 @@
+Scanning for the context object
+-------------------------------
+
+Let's import a module that contains no ``Context`` subclass, nor classes
+that implement ``IContext``::
+
+ >>> from martian.tests.scanforclasses import IContext
+
+We shouldn't see any classes that are contexts::
+
+ >>> from martian.util import scan_for_classes
+ >>> from martian.tests.scanforclasses import test1
+ >>> list(scan_for_classes(test1, interface=IContext))
+ []
+
+Now we look at a module with a single ``Context`` subclass::
+
+ >>> from martian.tests.scanforclasses import test2
+ >>> list(scan_for_classes(test2, interface=IContext))
+ [<class 'martian.tests.scanforclasses.test2.MyContext'>]
+
+Now we'll look at a module with a single class that implements ``IContext``::
+
+ >>> from martian.tests.scanforclasses import test3
+ >>> list(scan_for_classes(test3, interface=IContext))
+ [<class 'martian.tests.scanforclasses.test3.MyContext'>]
+
+Let's finish by looking at a module which defines multiple contexts::
+
+ >>> from martian.tests.scanforclasses import test4
+ >>> len(list(scan_for_classes(test4, interface=IContext)))
+ 4
+
+
+
Copied: martian/trunk/src/martian/tests/scanforclasses (from rev 86374, martian/branches/jw-philipp-using-ndir-directives/src/martian/tests/scanforclasses)
Modified: martian/trunk/src/martian/tests/test_all.py
===================================================================
--- martian/trunk/src/martian/tests/test_all.py 2008-05-04 14:45:00 UTC (rev 86374)
+++ martian/trunk/src/martian/tests/test_all.py 2008-05-04 14:47:45 UTC (rev 86375)
@@ -70,10 +70,14 @@
optionflags=optionflags),
doctest.DocFileSuite('directive.txt',
package='martian',
+ globs=globs,
optionflags=optionflags),
- doctest.DocFileSuite('ndir.txt',
+ doctest.DocFileSuite('core.txt',
package='martian',
globs=globs,
optionflags=optionflags),
+ doctest.DocFileSuite('scan_for_classes.txt',
+ package='martian.tests',
+ optionflags=optionflags),
])
return suite
Modified: martian/trunk/src/martian/util.py
===================================================================
--- martian/trunk/src/martian/util.py 2008-05-04 14:45:00 UTC (rev 86374)
+++ martian/trunk/src/martian/util.py 2008-05-04 14:47:45 UTC (rev 86375)
@@ -21,6 +21,7 @@
from zope import interface
+import martian
from martian.error import GrokError, GrokImportError
def not_unicode_or_ascii(value):
@@ -48,37 +49,8 @@
return sys._getframe(2).f_globals['__name__']
def is_baseclass(name, component):
- return (type(component) is type and
- class_annotation_nobase(component, 'grok.baseclass', False))
+ return (isclass(component) and martian.baseclass.get(component))
-def class_annotation(obj, name, default):
- return getattr(obj, '__%s__' % name.replace('.', '_'), default)
-
-def class_annotation_nobase(obj, name, default):
- """This will only look in the given class obj for the annotation.
-
- It will not look in the inheritance chain.
- """
- return obj.__dict__.get('__%s__' % name.replace('.', '_'), default)
-
-def class_annotation_list(obj, name, default):
- """This will process annotations that are lists correctly in the face of
- inheritance.
- """
- if class_annotation(obj, name, default) is default:
- return default
-
- result = []
- for base in reversed(obj.mro()):
- list = class_annotation(base, name, [])
- if list not in result:
- result.append(list)
-
- result_flattened = []
- for entry in result:
- result_flattened.extend(entry)
- return result_flattened
-
def defined_locally(obj, dotted_name):
obj_module = getattr(obj, '__grok_module__', None)
if obj_module is None:
@@ -100,22 +72,38 @@
"(use grok.provides to specify which one to use)."
% class_, class_)
+def check_provides_one(obj):
+ provides = list(interface.providedBy(obj))
+ if len(provides) < 1:
+ raise GrokError("%r must provide at least one interface "
+ "(use zope.interface.classProvides to specify)."
+ % obj, obj)
+ if len(provides) > 1:
+ raise GrokError("%r provides more than one interface "
+ "(use grok.provides to specify which one to use)."
+ % obj, obj)
-def scan_for_classes(module, classes):
+def scan_for_classes(module, classes=None, interface=None):
"""Given a module, scan for classes.
"""
- result = set()
for name in dir(module):
- if name.startswith('__grok_'):
+ 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.
continue
obj = getattr(module, name)
- if not defined_locally(obj, module.__name__):
+ if not defined_locally(obj, module.__name__) or not isclass(obj):
continue
- for class_ in classes:
- if check_subclass(obj, class_):
- result.add(obj)
- return list(result)
+ if classes is not None:
+ for class_ in classes:
+ if check_subclass(obj, class_):
+ yield obj
+
+ if interface is not None and interface.implementedBy(obj):
+ yield obj
+
def methods_from_class(class_):
# XXX Problem with zope.interface here that makes us special-case
# __provides__.
@@ -128,4 +116,4 @@
return frame.f_locals is frame.f_globals
def frame_is_class(frame):
- return '__module__' in frame.f_locals
+ return '__module__' in frame.f_locals
More information about the Checkins
mailing list