Zope Subversion Repository |
|
Zope: martian/trunk/src/martian/ndir.txt
File:
[Zope] /
martian /
trunk /
src /
martian / ndir.txt
(
download)
Revision:
83322,
Wed Jan 30 20:25:18 2008 UTC (5 years, 3 months ago) by
faassen
File size: 10091 byte(s)
Expand the options for directives. Note that the argument checking
is too limited to support some multi-argument directives, so this code
will need to be revised.
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']
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.
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'
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.