From e93f66b15cc0ebfe667da0d7e08e1621c931f437 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 27 Sep 2018 07:11:23 -0500 Subject: Unify docs for fields.rst/py back into .py --- docs/api/fields.rst | 352 +---------------------------- docs/conf.py | 7 +- src/zope/configuration/fields.py | 361 ++++++++++++++++++++++++++++-- src/zope/configuration/tests/test_docs.py | 34 ++- 4 files changed, 377 insertions(+), 377 deletions(-) diff --git a/docs/api/fields.rst b/docs/api/fields.rst index c44dcf1..b640a30 100644 --- a/docs/api/fields.rst +++ b/docs/api/fields.rst @@ -1,349 +1,5 @@ -:mod:`zope.configuration.fields` -================================ +=========================== + zope.configuration.fields +=========================== -.. module:: zope.configuration.fields - -.. autoclass:: PythonIdentifier - :members: - :member-order: bysource - - Let's look at an example: - - .. doctest:: - - >>> from zope.configuration.fields import PythonIdentifier - >>> class FauxContext(object): - ... pass - >>> context = FauxContext() - >>> field = PythonIdentifier().bind(context) - - Let's test the fromUnicode method: - - .. doctest:: - - >>> field.fromUnicode(u'foo') - 'foo' - >>> field.fromUnicode(u'foo3') - 'foo3' - >>> field.fromUnicode(u'_foo3') - '_foo3' - - Now let's see whether validation works alright - - .. doctest:: - - >>> for value in (u'foo', u'foo3', u'foo_', u'_foo3', u'foo_3', u'foo3_'): - ... _ = field.fromUnicode(value) - >>> from zope.schema import ValidationError - >>> for value in (u'3foo', u'foo:', u'\\', u''): - ... try: - ... field.fromUnicode(value) - ... except ValidationError: - ... print('Validation Error ' + repr(value)) - Validation Error '3foo' - Validation Error 'foo:' - Validation Error '\\' - Validation Error '' - -.. autoclass:: GlobalObject - :members: - :member-order: bysource - - Let's look at an example: - - .. doctest:: - - >>> d = {'x': 1, 'y': 42, 'z': 'zope'} - >>> class fakeresolver(dict): - ... def resolve(self, n): - ... return self[n] - >>> fake = fakeresolver(d) - - >>> from zope.schema import Int - >>> from zope.configuration.fields import GlobalObject - >>> g = GlobalObject(value_type=Int()) - >>> gg = g.bind(fake) - >>> gg.fromUnicode("x") - 1 - >>> gg.fromUnicode(" x \n ") - 1 - >>> gg.fromUnicode("y") - 42 - >>> gg.fromUnicode("z") - Traceback (most recent call last): - ... - WrongType: ('zope', (, ), '') - - >>> g = GlobalObject(constraint=lambda x: x%2 == 0) - >>> gg = g.bind(fake) - >>> gg.fromUnicode("x") - Traceback (most recent call last): - ... - ConstraintNotSatisfied: 1 - >>> gg.fromUnicode("y") - 42 - >>> g = GlobalObject() - >>> gg = g.bind(fake) - >>> print(gg.fromUnicode('*')) - None - -.. autoclass:: GlobalInterface - :members: - :member-order: bysource - - Example: - - First, we need to set up a stub name resolver: - - .. doctest:: - - >>> from zope.interface import Interface - >>> class IFoo(Interface): - ... pass - >>> class Foo(object): - ... pass - >>> d = {'Foo': Foo, 'IFoo': IFoo} - >>> class fakeresolver(dict): - ... def resolve(self, n): - ... return self[n] - >>> fake = fakeresolver(d) - - Now verify constraints are checked correctly: - - .. doctest:: - - >>> from zope.configuration.fields import GlobalInterface - >>> g = GlobalInterface() - >>> gg = g.bind(fake) - >>> gg.fromUnicode('IFoo') is IFoo - True - >>> gg.fromUnicode(' IFoo ') is IFoo - True - >>> gg.fromUnicode('Foo') - Traceback (most recent call last): - ... - NotAnInterface: (, ... - -.. autoclass:: Tokens - :members: - :member-order: bysource - - Consider GlobalObject tokens: - - First, we need to set up a stub name resolver: - - .. doctest:: - - >>> d = {'x': 1, 'y': 42, 'z': 'zope', 'x.y.x': 'foo'} - >>> class fakeresolver(dict): - ... def resolve(self, n): - ... return self[n] - >>> fake = fakeresolver(d) - - >>> from zope.configuration.fields import Tokens - >>> from zope.configuration.fields import GlobalObject - >>> g = Tokens(value_type=GlobalObject()) - >>> gg = g.bind(fake) - >>> gg.fromUnicode(" \n x y z \n") - [1, 42, 'zope'] - - >>> from zope.schema import Int - >>> g = Tokens(value_type= - ... GlobalObject(value_type= - ... Int(constraint=lambda x: x%2 == 0))) - >>> gg = g.bind(fake) - >>> gg.fromUnicode("x y") - Traceback (most recent call last): - ... - InvalidToken: 1 in x y - - >>> gg.fromUnicode("z y") - Traceback (most recent call last): - ... - InvalidToken: ('zope', (, ), '') in z y - >>> gg.fromUnicode("y y") - [42, 42] - -.. autoclass:: Path - :members: - :member-order: bysource - - Let's look at an example: - - First, we need a "context" for the field that has a path - function for converting relative path to an absolute path. - - We'll be careful to do this in an os-independent fashion. - - .. doctest:: - - >>> from zope.configuration.fields import Path - >>> class FauxContext(object): - ... def path(self, p): - ... return os.path.join(os.sep, 'faux', 'context', p) - >>> context = FauxContext() - >>> field = Path().bind(context) - - Lets try an absolute path first: - - .. doctest:: - - >>> import os - >>> p = os.path.join(os.sep, u'a', u'b') - >>> n = field.fromUnicode(p) - >>> n.split(os.sep) - ['', 'a', 'b'] - - This should also work with extra spaces around the path: - - .. doctest:: - - >>> p = " \n %s \n\n " % p - >>> n = field.fromUnicode(p) - >>> n.split(os.sep) - ['', 'a', 'b'] - - Environment variables are expanded: - - .. doctest:: - - >>> os.environ['path-test'] = '42' - >>> with_env = os.path.join(os.sep, u'a', u'${path-test}') - >>> n = field.fromUnicode(with_env) - >>> n.split(os.sep) - ['', 'a', '42'] - - Now try a relative path: - - .. doctest:: - - >>> p = os.path.join(u'a', u'b') - >>> n = field.fromUnicode(p) - >>> n.split(os.sep) - ['', 'faux', 'context', 'a', 'b'] - - The current user is expanded (these are implicitly relative paths): - - .. doctest:: - - >>> old_home = os.environ.get('HOME') - >>> os.environ['HOME'] = os.path.join(os.sep, 'HOME') - >>> n = field.fromUnicode('~') - >>> n.split(os.sep) - ['', 'HOME'] - >>> if old_home: - ... os.environ['HOME'] = old_home - ... else: - ... del os.environ['HOME'] - - -.. autoclass:: Bool - :members: - :member-order: bysource - - .. doctest:: - - >>> from zope.configuration.fields import Bool - >>> Bool().fromUnicode(u"yes") - True - >>> Bool().fromUnicode(u"y") - True - >>> Bool().fromUnicode(u"true") - True - >>> Bool().fromUnicode(u"no") - False - -.. autoclass:: MessageID - :members: - :member-order: bysource - - .. doctest:: - - >>> from zope.configuration.fields import MessageID - >>> class Info(object): - ... file = 'file location' - ... line = 8 - >>> class FauxContext(object): - ... i18n_strings = {} - ... info = Info() - >>> context = FauxContext() - >>> field = MessageID().bind(context) - - There is a fallback domain when no domain has been specified. - - Exchange the warn function so we can make test whether the warning - has been issued - - .. doctest:: - - >>> warned = None - >>> def fakewarn(*args, **kw): #* syntax highlighting - ... global warned - ... warned = args - - >>> import warnings - >>> realwarn = warnings.warn - >>> warnings.warn = fakewarn - - >>> i = field.fromUnicode(u"Hello world!") - >>> i - 'Hello world!' - >>> i.domain - 'untranslated' - >>> warned - ("You did not specify an i18n translation domain for the '' field in file location",) - - >>> warnings.warn = realwarn - - With the domain specified: - - .. doctest:: - - >>> context.i18n_strings = {} - >>> context.i18n_domain = 'testing' - - We can get a message id: - - .. doctest:: - - >>> i = field.fromUnicode(u"Hello world!") - >>> i - 'Hello world!' - >>> i.domain - 'testing' - - In addition, the string has been registered with the context: - - .. doctest:: - - >>> context.i18n_strings - {'testing': {'Hello world!': [('file location', 8)]}} - - >>> i = field.fromUnicode(u"Foo Bar") - >>> i = field.fromUnicode(u"Hello world!") - >>> from pprint import PrettyPrinter - >>> pprint=PrettyPrinter(width=70).pprint - >>> pprint(context.i18n_strings) - {'testing': {'Foo Bar': [('file location', 8)], - 'Hello world!': [('file location', 8), - ('file location', 8)]}} - - >>> from zope.i18nmessageid import Message - >>> isinstance(list(context.i18n_strings['testing'].keys())[0], Message) - True - - Explicit Message IDs - - .. doctest:: - - >>> i = field.fromUnicode(u'[View-Permission] View') - >>> i - 'View-Permission' - >>> i.default - 'View' - - >>> i = field.fromUnicode(u'[] [Some] text') - >>> i - '[Some] text' - >>> i.default is None - True +.. automodule:: zope.configuration.fields diff --git a/docs/conf.py b/docs/conf.py index 9fffd20..ac65d4b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -257,6 +257,7 @@ intersphinx_mapping = { 'https://zopeschema.readthedocs.io/en/latest': None, } -autodoc_default_flags = ['members', 'show-inheritance'] -autoclass_content = 'both' -autodoc_member_order = 'bysource' +autodoc_default_flags = [ + 'members', + 'show-inheritance', +] diff --git a/src/zope/configuration/fields.py b/src/zope/configuration/fields.py index 668c4a3..85a5ff7 100644 --- a/src/zope/configuration/fields.py +++ b/src/zope/configuration/fields.py @@ -33,13 +33,13 @@ from zope.configuration.exceptions import ConfigurationError from zope.configuration.interfaces import InvalidToken __all__ = [ - 'PythonIdentifier', + 'Bool', 'GlobalObject', 'GlobalInterface', - 'Tokens', - 'Path', - 'Bool', 'MessageID', + 'Path', + 'PythonIdentifier', + 'Tokens', ] @@ -47,6 +47,39 @@ class PythonIdentifier(schema_PythonIdentifier): """ This class is like `zope.schema.PythonIdentifier`. + + Let's look at an example: + + >>> from zope.configuration.fields import PythonIdentifier + >>> class FauxContext(object): + ... pass + >>> context = FauxContext() + >>> field = PythonIdentifier().bind(context) + + Let's test the fromUnicode method: + + >>> field.fromUnicode(u'foo') + 'foo' + >>> field.fromUnicode(u'foo3') + 'foo3' + >>> field.fromUnicode(u'_foo3') + '_foo3' + + Now let's see whether validation works alright + + >>> for value in (u'foo', u'foo3', u'foo_', u'_foo3', u'foo_3', u'foo3_'): + ... _ = field.fromUnicode(value) + >>> from zope.schema import ValidationError + >>> for value in (u'3foo', u'foo:', u'\\\\', u''): + ... try: + ... field.fromUnicode(value) + ... except ValidationError: + ... print('Validation Error ' + repr(value)) + Validation Error '3foo' + Validation Error 'foo:' + Validation Error '\\\\' + Validation Error '' + .. versionchanged:: 4.2.0 Extend `zope.schema.PythonIdentifier`, which implies that `fromUnicode` validates the strings. @@ -79,6 +112,43 @@ class GlobalObject(Field): self.value_type.validate(value) def fromUnicode(self, value): + """ + Find and return the module global at the path *value*. + + >>> d = {'x': 1, 'y': 42, 'z': 'zope'} + >>> class fakeresolver(dict): + ... def resolve(self, n): + ... return self[n] + >>> fake = fakeresolver(d) + + >>> from zope.schema import Int + >>> from zope.configuration.fields import GlobalObject + >>> g = GlobalObject(value_type=Int()) + >>> gg = g.bind(fake) + >>> gg.fromUnicode("x") + 1 + >>> gg.fromUnicode(" x \\n ") + 1 + >>> gg.fromUnicode("y") + 42 + >>> gg.fromUnicode("z") + Traceback (most recent call last): + ... + WrongType: ('zope', (, ), '') + + >>> g = GlobalObject(constraint=lambda x: x%2 == 0) + >>> gg = g.bind(fake) + >>> gg.fromUnicode("x") + Traceback (most recent call last): + ... + ConstraintNotSatisfied: 1 + >>> gg.fromUnicode("y") + 42 + >>> g = GlobalObject() + >>> gg = g.bind(fake) + >>> print(gg.fromUnicode('*')) + None + """ name = str(value.strip()) # special case, mostly for interfaces @@ -108,7 +178,38 @@ class GlobalObject(Field): @implementer(IFromUnicode) class GlobalInterface(GlobalObject): - """An interface that can be accessed from a module. + """ + An interface that can be accessed from a module. + + Example: + + First, we need to set up a stub name resolver: + + >>> from zope.interface import Interface + >>> class IFoo(Interface): + ... pass + >>> class Foo(object): + ... pass + >>> d = {'Foo': Foo, 'IFoo': IFoo} + >>> class fakeresolver(dict): + ... def resolve(self, n): + ... return self[n] + >>> fake = fakeresolver(d) + + Now verify constraints are checked correctly: + + >>> from zope.configuration.fields import GlobalInterface + >>> g = GlobalInterface() + >>> gg = g.bind(fake) + >>> gg.fromUnicode('IFoo') is IFoo + True + >>> gg.fromUnicode(' IFoo ') is IFoo + True + >>> gg.fromUnicode('Foo') + Traceback (most recent call last): + ... + NotAnInterface: (, ... + """ def __init__(self, **kw): super(GlobalInterface, self).__init__(InterfaceField(), **kw) @@ -116,18 +217,57 @@ class GlobalInterface(GlobalObject): @implementer(IFromUnicode) class Tokens(List): - """A list that can be read from a space-separated string. """ - def fromUnicode(self, u): - u = u.strip() - if u: + A list that can be read from a space-separated string. + """ + + def fromUnicode(self, value): + """ + Split the input string and convert it to *value_type*. + + Consider GlobalObject tokens: + + First, we need to set up a stub name resolver: + + >>> d = {'x': 1, 'y': 42, 'z': 'zope', 'x.y.x': 'foo'} + >>> class fakeresolver(dict): + ... def resolve(self, n): + ... return self[n] + >>> fake = fakeresolver(d) + + >>> from zope.configuration.fields import Tokens + >>> from zope.configuration.fields import GlobalObject + >>> g = Tokens(value_type=GlobalObject()) + >>> gg = g.bind(fake) + >>> gg.fromUnicode(" \\n x y z \\n") + [1, 42, 'zope'] + + >>> from zope.schema import Int + >>> g = Tokens(value_type= + ... GlobalObject(value_type= + ... Int(constraint=lambda x: x%2 == 0))) + >>> gg = g.bind(fake) + >>> gg.fromUnicode("x y") + Traceback (most recent call last): + ... + InvalidToken: 1 in x y + + >>> gg.fromUnicode("z y") + Traceback (most recent call last): + ... + InvalidToken: ('zope', (, ), '') in z y + >>> gg.fromUnicode("y y") + [42, 42] + """ + value = value.strip() + if value: vt = self.value_type.bind(self.context) values = [] - for s in u.split(): + for s in value.split(): try: v = vt.fromUnicode(s) - except ValidationError as v: - raise InvalidToken("%s in %s" % (v, u)).with_field_and_value(self, s) + except ValidationError as ex: + raise InvalidToken("%s in %r" % (ex, value)).with_field_and_value(self, s) else: values.append(v) else: @@ -159,15 +299,76 @@ class PathProcessor(object): @implementer(IFromUnicode) class Path(Text): - """A file path name, which may be input as a relative path + """ + A file path name, which may be input as a relative path Input paths are converted to absolute paths and normalized. - - .. versionchanged:: 4.2.0 - Start expanding home directories and environment variables. """ def fromUnicode(self, value): + """ + Convert the input path to a normalized, absolute path. + + Let's look at an example: + + First, we need a "context" for the field that has a path + function for converting relative path to an absolute path. + + We'll be careful to do this in an os-independent fashion. + + >>> from zope.configuration.fields import Path + >>> class FauxContext(object): + ... def path(self, p): + ... return os.path.join(os.sep, 'faux', 'context', p) + >>> context = FauxContext() + >>> field = Path().bind(context) + + Lets try an absolute path first: + + >>> import os + >>> p = os.path.join(os.sep, u'a', u'b') + >>> n = field.fromUnicode(p) + >>> n.split(os.sep) + ['', 'a', 'b'] + + This should also work with extra spaces around the path: + + >>> p = " \\n %s \\n\\n " % p + >>> n = field.fromUnicode(p) + >>> n.split(os.sep) + ['', 'a', 'b'] + + Environment variables are expanded: + + >>> os.environ['path-test'] = '42' + >>> with_env = os.path.join(os.sep, u'a', u'${path-test}') + >>> n = field.fromUnicode(with_env) + >>> n.split(os.sep) + ['', 'a', '42'] + + Now try a relative path: + + >>> p = os.path.join(u'a', u'b') + >>> n = field.fromUnicode(p) + >>> n.split(os.sep) + ['', 'faux', 'context', 'a', 'b'] + + The current user is expanded (these are implicitly relative paths): + + >>> old_home = os.environ.get('HOME') + >>> os.environ['HOME'] = os.path.join(os.sep, 'HOME') + >>> n = field.fromUnicode('~') + >>> n.split(os.sep) + ['', 'HOME'] + >>> if old_home: + ... os.environ['HOME'] = old_home + ... else: + ... del os.environ['HOME'] + + + .. versionchanged:: 4.2.0 + Start expanding home directories and environment variables. + """ filename, needs_processing = PathProcessor.expand(value) if needs_processing: filename = self.context.path(filename) @@ -177,12 +378,46 @@ class Path(Text): @implementer(IFromUnicode) class Bool(schema_Bool): - """A boolean value + """ + A boolean value. Values may be input (in upper or lower case) as any of: - yes, no, y, n, true, false, t, or f. + + - yes / no + - y / n + - true / false + - t / f + + .. caution:: + + Do not confuse this with :class:`zope.schema.Bool`. + That class will only parse ``"True"`` and ``"true"`` as + `True` values. Any other value will silently be accepted as + `False`. This class raises a validation error for unrecognized + input. + """ + def fromUnicode(self, value): + """ + Convert the input string to a boolean. + + Example: + + >>> from zope.configuration.fields import Bool + >>> Bool().fromUnicode(u"yes") + True + >>> Bool().fromUnicode(u"y") + True + >>> Bool().fromUnicode(u"true") + True + >>> Bool().fromUnicode(u"no") + False + >>> Bool().fromUnicode(u"surprise") + Traceback (most recent call last): + ... + zope.schema._bootstrapinterfaces.InvalidValue + """ value = value.lower() if value in ('1', 'true', 'yes', 't', 'y'): return True @@ -195,15 +430,99 @@ class Bool(schema_Bool): @implementer(IFromUnicode) class MessageID(Text): - """Text string that should be translated. + """ + Text string that should be translated. - When a string is converted to a message ID, it is also - recorded in the context. + When a string is converted to a message ID, it is also recorded in + the context. """ __factories = {} def fromUnicode(self, u): + """ + Translate a string to a MessageID. + + >>> from zope.configuration.fields import MessageID + >>> class Info(object): + ... file = 'file location' + ... line = 8 + >>> class FauxContext(object): + ... i18n_strings = {} + ... info = Info() + >>> context = FauxContext() + >>> field = MessageID().bind(context) + + There is a fallback domain when no domain has been specified. + + Exchange the warn function so we can make test whether the warning + has been issued + + >>> warned = None + >>> def fakewarn(*args, **kw): #* syntax highlighting + ... global warned + ... warned = args + + >>> import warnings + >>> realwarn = warnings.warn + >>> warnings.warn = fakewarn + + >>> i = field.fromUnicode(u"Hello world!") + >>> i + 'Hello world!' + >>> i.domain + 'untranslated' + >>> warned + ("You did not specify an i18n translation domain for the '' field in file location",) + + >>> warnings.warn = realwarn + + With the domain specified: + + >>> context.i18n_strings = {} + >>> context.i18n_domain = 'testing' + + We can get a message id: + + >>> i = field.fromUnicode(u"Hello world!") + >>> i + 'Hello world!' + >>> i.domain + 'testing' + + In addition, the string has been registered with the context: + + >>> context.i18n_strings + {'testing': {'Hello world!': [('file location', 8)]}} + + >>> i = field.fromUnicode(u"Foo Bar") + >>> i = field.fromUnicode(u"Hello world!") + >>> from pprint import PrettyPrinter + >>> pprint=PrettyPrinter(width=70).pprint + >>> pprint(context.i18n_strings) + {'testing': {'Foo Bar': [('file location', 8)], + 'Hello world!': [('file location', 8), + ('file location', 8)]}} + + >>> from zope.i18nmessageid import Message + >>> isinstance(list(context.i18n_strings['testing'].keys())[0], Message) + True + + Explicit Message IDs + + >>> i = field.fromUnicode(u'[View-Permission] View') + >>> i + 'View-Permission' + >>> i.default + 'View' + + >>> i = field.fromUnicode(u'[] [Some] text') + >>> i + '[Some] text' + >>> i.default is None + True + + """ context = self.context domain = getattr(context, 'i18n_domain', '') if not domain: diff --git a/src/zope/configuration/tests/test_docs.py b/src/zope/configuration/tests/test_docs.py index 915d2f0..e51a095 100644 --- a/src/zope/configuration/tests/test_docs.py +++ b/src/zope/configuration/tests/test_docs.py @@ -20,6 +20,7 @@ from __future__ import division from __future__ import print_function import re +import sys import os.path import unittest import doctest @@ -41,6 +42,11 @@ checker = renormalizing.RENormalizing([ (re.compile('b(".*?")'), r"\1"), ]) +optionflags = ( + doctest.NORMALIZE_WHITESPACE + | doctest.ELLIPSIS + | doctest.IGNORE_EXCEPTION_DETAIL +) def test_suite(): here = os.path.dirname(os.path.abspath(__file__)) @@ -64,15 +70,22 @@ def test_suite(): 'xmlconfig.rst', ) + # Plain doctest suites + api_to_test = ( + 'config', + 'docutils', + 'fields', + 'interfaces', + 'name', + 'xmlconfig', + 'zopeconfigure', + ) + paths = [os.path.join(docs, f) for f in doc_files_to_test] paths += [os.path.join(api_docs, f) for f in api_files_to_test] m = manuel.ignore.Manuel() - m += manuel.doctest.Manuel(checker=checker, optionflags=( - doctest.NORMALIZE_WHITESPACE - | doctest.ELLIPSIS - | doctest.IGNORE_EXCEPTION_DETAIL - )) + m += manuel.doctest.Manuel(checker=checker, optionflags=optionflags) m += manuel.codeblock.Manuel() m += manuel.capture.Manuel() @@ -84,4 +97,15 @@ def test_suite(): ) ) + for mod_name in api_to_test: + mod_name = 'zope.configuration.' + mod_name + __import__(mod_name) + suite.addTest( + doctest.DocTestSuite( + sys.modules[mod_name], + checker=checker, + optionflags=optionflags + ) + ) + return suite -- cgit v1.2.1 From de407e5f4beb88d9aeedebec6469ab44d88324ac Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 27 Sep 2018 07:14:06 -0500 Subject: Unify docs for docutils.rst/py back into .py --- docs/api/docutils.rst | 29 ++++------------------------- src/zope/configuration/docutils.py | 34 +++++++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/docs/api/docutils.rst b/docs/api/docutils.rst index b75830c..b556c6f 100644 --- a/docs/api/docutils.rst +++ b/docs/api/docutils.rst @@ -1,26 +1,5 @@ -:mod:`zope.configuration.docutils` -================================== +============================= + zope.configuration.docutils +============================= -.. module:: zope.configuration.docutils - -.. autofunction:: wrap - - Examples: - - .. doctest:: - - >>> from zope.configuration.docutils import wrap - >>> print(wrap('foo bar')[:-2]) - foo bar - >>> print(wrap('foo bar', indent=2)[:-2]) - foo bar - >>> print(wrap('foo bar, more foo bar', 10)[:-2]) - foo bar, - more foo - bar - >>> print(wrap('foo bar, more foo bar', 10, 2)[:-2]) - foo bar, - more foo - bar - -.. autofunction:: makeDocStructures +.. automodule:: zope.configuration.docutils diff --git a/src/zope/configuration/docutils.py b/src/zope/configuration/docutils.py index c2386c2..37b0060 100644 --- a/src/zope/configuration/docutils.py +++ b/src/zope/configuration/docutils.py @@ -26,7 +26,24 @@ para_sep = re.compile('\n{2,}') whitespace = re.compile('[ \t\n\r]+') def wrap(text, width=78, indent=0): - """Makes sure that we keep a line length of a certain width. + """ + Makes sure that we keep a line length of a certain width. + + Examples: + + >>> from zope.configuration.docutils import wrap + >>> print(wrap('foo bar')[:-2]) + foo bar + >>> print(wrap('foo bar', indent=2)[:-2]) + foo bar + >>> print(wrap('foo bar, more foo bar', 10)[:-2]) + foo bar, + more foo + bar + >>> print(wrap('foo bar, more foo bar', 10, 2)[:-2]) + foo bar, + more foo + bar """ paras = para_sep.split(text.strip()) @@ -54,15 +71,18 @@ def wrap(text, width=78, indent=0): def makeDocStructures(context): - """Creates two structures that provide a friendly format for + """ + makeDocStructures(context) -> namespaces, subdirs + + Creates two structures that provide a friendly format for documentation. - 'namespaces' is a dictionary that maps namespaces to a directives - dictionary with the key being the name of the directive and the value is a - tuple: (schema, handler, info). + *namespaces* is a dictionary that maps namespaces to a directives + dictionary with the key being the name of the directive and the + value is a tuple: (schema, handler, info). - 'subdirs' maps a (namespace, name) pair to a list of subdirectives that - have the form (namespace, name, schema, info). + *subdirs* maps a (namespace, name) pair to a list of subdirectives + that have the form (namespace, name, schema, info). """ namespaces = {} subdirs = {} -- cgit v1.2.1 From 6114a6022d76b046781ee0434434e696664b84c1 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 27 Sep 2018 07:17:41 -0500 Subject: Unify docs for name.rst/py back into .py. Note that the functions never had any actual documentation and are not used here, so leave a warning that they might get deprecated. --- docs/api/name.rst | 13 ++++--------- src/zope/configuration/name.py | 6 +++++- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/api/name.rst b/docs/api/name.rst index 69a3f56..4471308 100644 --- a/docs/api/name.rst +++ b/docs/api/name.rst @@ -1,10 +1,5 @@ -:mod:`zope.configuration.name` -============================== +========================= + zope.configuration.name +========================= -.. module:: zope.configuration.name - -.. autofunction:: resolve - -.. autofunction:: getNormalizedName - -.. autofunction:: path +.. automodule:: zope.configuration.name diff --git a/src/zope/configuration/name.py b/src/zope/configuration/name.py index a162ea1..97b7d76 100644 --- a/src/zope/configuration/name.py +++ b/src/zope/configuration/name.py @@ -11,7 +11,11 @@ # FOR A PARTICULAR PURPOSE. # ############################################################################## -"""Provide configuration object name resolution +""" +Provide configuration object name resolution. + +.. note:: This module is no longer used by `zope.configuration` and + may be deprecated soon. Its functions are not documented. """ import os -- cgit v1.2.1 From b209bb22b27c27bf97a04fa6e61d8d3979e5c598 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 27 Sep 2018 07:20:05 -0500 Subject: No need to repeat all the docs for zopeconfigure twice. --- docs/api/zopeconfigure.rst | 5 +++-- src/zope/configuration/zopeconfigure.py | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/api/zopeconfigure.rst b/docs/api/zopeconfigure.rst index a84937e..8570e35 100644 --- a/docs/api/zopeconfigure.rst +++ b/docs/api/zopeconfigure.rst @@ -1,4 +1,5 @@ -:mod:`zope.configuration.zopeconfigure` -======================================= +================================== + zope.configuration.zopeconfigure +================================== .. automodule:: zope.configuration.zopeconfigure diff --git a/src/zope/configuration/zopeconfigure.py b/src/zope/configuration/zopeconfigure.py index 0f8077d..266fd46 100644 --- a/src/zope/configuration/zopeconfigure.py +++ b/src/zope/configuration/zopeconfigure.py @@ -123,7 +123,7 @@ class IZopeConfigure(Interface): information collected is used by subdirectives. It may seem that this directive can only be used once per file, but it can - be applied whereever it is convenient. + be applied wherever it is convenient. """ package = GlobalObject( @@ -149,7 +149,9 @@ class IZopeConfigure(Interface): class ZopeConfigure(GroupingContextDecorator): - __doc__ = __doc__ + """ + The implementation of `IZopeConfigure`. + """ def __init__(self, context, **kw): super(ZopeConfigure, self).__init__(context, **kw) -- cgit v1.2.1 From 9c8a74b387d07a7a85669f5813ff249e98818b6e Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 27 Sep 2018 07:21:40 -0500 Subject: A simple automodule:: suffices for .interfaces. --- docs/api/interfaces.rst | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index 6c76434..a3a2624 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -1,16 +1,5 @@ -:mod:`zope.configuration.interfaces` -==================================== +====================================== + zope.configuration.interfaces +====================================== -.. module:: zope.configuration.interfaces - -.. autoclass:: InvalidToken - :members: - :member-order: bysource - -.. autointerface:: IConfigurationContext - :members: - :member-order: bysource - -.. autointerface:: IGroupingContext - :members: - :member-order: bysource +.. automodule:: zope.configuration.interfaces -- cgit v1.2.1 From 2c8cdc48861ef6e42598e1b096e5068cb67d45e1 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 27 Sep 2018 07:25:14 -0500 Subject: Tweak docs for IConfigurationContext.action. --- src/zope/configuration/interfaces.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/zope/configuration/interfaces.py b/src/zope/configuration/interfaces.py index 45e5ac6..ee66358 100644 --- a/src/zope/configuration/interfaces.py +++ b/src/zope/configuration/interfaces.py @@ -98,16 +98,25 @@ class IConfigurationContext(Interface): """Record a configuration action The job of most directives is to compute actions for later - processing. The action method is used to record those - actions. The discriminator is used to to find actions that - conflict. Actions conflict if they have the same - discriminator. The exception to this is the special case of - the discriminator with the value None. An actions with a - discriminator of None never conflicts with other actions. This - is possible to add an order argument to crudely control the - order of execution. 'info' is optional source line information, - 'includepath' is None (the default) or a tuple of include paths for - this action. + processing. The action method is used to record those actions. + + :param callable: The object to call to implement the action. + :param tuple args: Arguments to pass to *callable* + :param dict kw: Keyword arguments to pass to *callable* + + :param object discriminator: Used to to find actions that conflict. + Actions conflict if they have equal discriminators. The + exception to this is the special case of the discriminator + with the value `None`. An action with a discriminator of `None` + never conflicts with other actions. + + :keyword int order: This is possible to add an order argument to crudely control + the order of execution. + + :keyword str info: Optional source line information + + :keyword includepath: is None (the default) or a tuple of + include paths for this action. """ def provideFeature(name): -- cgit v1.2.1 From 1b73d33f04b6434cae6d676f3bd2f522bb75af60 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 27 Sep 2018 07:52:16 -0500 Subject: Unify docs for config.rst/py back into .py --- docs/api/config.rst | 1037 +------------------------------------- src/zope/configuration/config.py | 899 +++++++++++++++++++++++++++++++-- 2 files changed, 867 insertions(+), 1069 deletions(-) diff --git a/docs/api/config.rst b/docs/api/config.rst index c1a5662..df1d205 100644 --- a/docs/api/config.rst +++ b/docs/api/config.rst @@ -1,1034 +1,5 @@ -:mod:`zope.configuration.config` -================================ +=========================== + zope.configuration.config +=========================== -.. module:: zope.configuration.config - -.. autoclass:: ConfigurationContext - - .. automethod:: resolve - - Examples: - - .. doctest:: - - >>> from zope.configuration.config import ConfigurationContext - >>> from zope.configuration.config import ConfigurationError - >>> c = ConfigurationContext() - >>> import zope, zope.interface - >>> c.resolve('zope') is zope - True - >>> c.resolve('zope.interface') is zope.interface - True - >>> c.resolve('zope.configuration.eek') #doctest: +NORMALIZE_WHITESPACE - Traceback (most recent call last): - ... - ConfigurationError: - ImportError: Module zope.configuration has no global eek - - >>> c.resolve('.config.ConfigurationContext') - Traceback (most recent call last): - ... - AttributeError: 'ConfigurationContext' object has no attribute 'package' - >>> import zope.configuration - >>> c.package = zope.configuration - >>> c.resolve('.') is zope.configuration - True - >>> c.resolve('.config.ConfigurationContext') is ConfigurationContext - True - >>> c.resolve('..interface') is zope.interface - True - >>> c.resolve('str') is str - True - - .. automethod:: path - - Examples: - - .. doctest:: - - >>> import os - >>> from zope.configuration.config import ConfigurationContext - >>> c = ConfigurationContext() - >>> c.path("/x/y/z") == os.path.normpath("/x/y/z") - True - >>> c.path("y/z") - Traceback (most recent call last): - ... - AttributeError: 'ConfigurationContext' object has no attribute 'package' - >>> import zope.configuration - >>> c.package = zope.configuration - >>> import os - >>> d = os.path.dirname(zope.configuration.__file__) - >>> c.path("y/z") == d + os.path.normpath("/y/z") - True - >>> c.path("y/./z") == d + os.path.normpath("/y/z") - True - >>> c.path("y/../z") == d + os.path.normpath("/z") - True - - .. automethod:: checkDuplicate - - Examples: - - .. doctest:: - - >>> from zope.configuration.config import ConfigurationContext - >>> from zope.configuration.config import ConfigurationError - >>> c = ConfigurationContext() - >>> c.checkDuplicate('/foo.zcml') - >>> try: - ... c.checkDuplicate('/foo.zcml') - ... except ConfigurationError as e: - ... # On Linux the exact msg has /foo, on Windows \foo. - ... str(e).endswith("foo.zcml' included more than once") - True - - You may use different ways to refer to the same file: - - .. doctest:: - - >>> import zope.configuration - >>> c.package = zope.configuration - >>> import os - >>> d = os.path.dirname(zope.configuration.__file__) - >>> c.checkDuplicate('bar.zcml') - >>> try: - ... c.checkDuplicate(d + os.path.normpath('/bar.zcml')) - ... except ConfigurationError as e: - ... str(e).endswith("bar.zcml' included more than once") - ... - True - - .. automethod:: processFile - - Examples: - - .. doctest:: - - >>> from zope.configuration.config import ConfigurationContext - >>> c = ConfigurationContext() - >>> c.processFile('/foo.zcml') - True - >>> c.processFile('/foo.zcml') - False - - You may use different ways to refer to the same file: - - .. doctest:: - - >>> import zope.configuration - >>> c.package = zope.configuration - >>> import os - >>> d = os.path.dirname(zope.configuration.__file__) - >>> c.processFile('bar.zcml') - True - >>> c.processFile('bar.zcml') - False - - .. automethod:: action - - Examples: - - .. doctest:: - - >>> from zope.configuration.config import ConfigurationContext - >>> c = ConfigurationContext() - - Normally, the context gets actions from subclasses. We'll provide - an actions attribute ourselves: - - .. doctest:: - - >>> c.actions = [] - - We'll use a test callable that has a convenient string representation - - .. doctest:: - - >>> from zope.configuration.tests.directives import f - >>> c.action(1, f, (1, ), {'x': 1}) - >>> from pprint import PrettyPrinter - >>> pprint=PrettyPrinter(width=60).pprint - >>> pprint(c.actions) - [{'args': (1,), - 'callable': f, - 'discriminator': 1, - 'includepath': (), - 'info': '', - 'kw': {'x': 1}, - 'order': 0}] - - >>> c.action(None) - >>> pprint(c.actions) - [{'args': (1,), - 'callable': f, - 'discriminator': 1, - 'includepath': (), - 'info': '', - 'kw': {'x': 1}, - 'order': 0}, - {'args': (), - 'callable': None, - 'discriminator': None, - 'includepath': (), - 'info': '', - 'kw': {}, - 'order': 0}] - - Now set the include path and info: - - .. doctest:: - - >>> c.includepath = ('foo.zcml',) - >>> c.info = "?" - >>> c.action(None) - >>> pprint(c.actions[-1]) - {'args': (), - 'callable': None, - 'discriminator': None, - 'includepath': ('foo.zcml',), - 'info': '?', - 'kw': {}, - 'order': 0} - - We can add an order argument to crudely control the order - of execution: - - .. doctest:: - - >>> c.action(None, order=99999) - >>> pprint(c.actions[-1]) - {'args': (), - 'callable': None, - 'discriminator': None, - 'includepath': ('foo.zcml',), - 'info': '?', - 'kw': {}, - 'order': 99999} - - We can also pass an includepath argument, which will be used as the the - includepath for the action. (if includepath is None, self.includepath - will be used): - - .. doctest:: - - >>> c.action(None, includepath=('abc',)) - >>> pprint(c.actions[-1]) - {'args': (), - 'callable': None, - 'discriminator': None, - 'includepath': ('abc',), - 'info': '?', - 'kw': {}, - 'order': 0} - - We can also pass an info argument, which will be used as the the - source line info for the action. (if info is None, self.info will be - used): - - .. doctest:: - - >>> c.action(None, info='abc') - >>> pprint(c.actions[-1]) - {'args': (), - 'callable': None, - 'discriminator': None, - 'includepath': ('foo.zcml',), - 'info': 'abc', - 'kw': {}, - 'order': 0} - - .. automethod:: hasFeature - - Examples: - - .. doctest:: - - >>> from zope.configuration.config import ConfigurationContext - >>> c = ConfigurationContext() - >>> c.hasFeature('onlinehelp') - False - - You can declare that a feature is provided - - .. doctest:: - - >>> c.provideFeature('onlinehelp') - - and it becomes available - - .. doctest:: - - >>> c.hasFeature('onlinehelp') - True - - .. automethod:: provideFeature - -.. autoclass:: ConfigurationAdapterRegistry - :members: - :member-order: bysource - - Examples: - - .. doctest:: - - >>> from zope.configuration.interfaces import IConfigurationContext - >>> from zope.configuration.config import ConfigurationAdapterRegistry - >>> from zope.configuration.config import ConfigurationError - >>> from zope.configuration.config import ConfigurationMachine - >>> r = ConfigurationAdapterRegistry() - >>> c = ConfigurationMachine() - >>> r.factory(c, ('http://www.zope.com','xxx')) - Traceback (most recent call last): - ... - ConfigurationError: ('Unknown directive', 'http://www.zope.com', 'xxx') - >>> def f(): - ... pass - - >>> r.register(IConfigurationContext, ('http://www.zope.com', 'xxx'), f) - >>> r.factory(c, ('http://www.zope.com','xxx')) is f - True - >>> r.factory(c, ('http://www.zope.com','yyy')) is f - Traceback (most recent call last): - ... - ConfigurationError: ('Unknown directive', 'http://www.zope.com', 'yyy') - >>> r.register(IConfigurationContext, 'yyy', f) - >>> r.factory(c, ('http://www.zope.com','yyy')) is f - True - - Test the documentation feature: - - .. doctest:: - - >>> from zope.configuration.config import IFullInfo - >>> r._docRegistry - [] - >>> r.document(('ns', 'dir'), IFullInfo, IConfigurationContext, None, - ... 'inf', None) - >>> r._docRegistry[0][0] == ('ns', 'dir') - True - >>> r._docRegistry[0][1] is IFullInfo - True - >>> r._docRegistry[0][2] is IConfigurationContext - True - >>> r._docRegistry[0][3] is None - True - >>> r._docRegistry[0][4] == 'inf' - True - >>> r._docRegistry[0][5] is None - True - >>> r.document('all-dir', None, None, None, None) - >>> r._docRegistry[1][0] - ('', 'all-dir') - -.. autoclass:: ConfigurationMachine - - Example: - - .. doctest:: - - >>> from zope.configuration.config import ConfigurationMachine - >>> machine = ConfigurationMachine() - >>> ns = "http://www.zope.org/testing" - - Register a directive: - - .. doctest:: - - >>> from zope.configuration.config import metans - >>> machine((metans, "directive"), - ... namespace=ns, name="simple", - ... schema="zope.configuration.tests.directives.ISimple", - ... handler="zope.configuration.tests.directives.simple") - - and try it out: - - .. doctest:: - - >>> machine((ns, "simple"), a=u"aa", c=u"cc") - >>> from pprint import PrettyPrinter - >>> pprint = PrettyPrinter(width=60).pprint - >>> pprint(machine.actions) - [{'args': ('aa', 'xxx', 'cc'), - 'callable': f, - 'discriminator': ('simple', 'aa', 'xxx', 'cc'), - 'includepath': (), - 'info': None, - 'kw': {}, - 'order': 0}] - - .. automethod:: begin - - .. automethod:: end - - .. automethod:: __call__ - - .. automethod:: getInfo - - .. automethod:: setInfo - - .. automethod:: execute_actions - - For example: - - .. doctest:: - - >>> from zope.configuration.config import ConfigurationMachine - >>> output = [] - >>> def f(*a, **k): #* syntax highlighting - ... output.append(('f', a, k)) - >>> context = ConfigurationMachine() - >>> context.actions = [ - ... (1, f, (1,)), - ... (1, f, (11,), {}, ('x', )), - ... (2, f, (2,)), - ... ] - >>> context.execute_actions() - >>> output - [('f', (1,), {}), ('f', (2,), {})] - - If the action raises an error, we convert it to a - ConfigurationExecutionError. - - .. doctest:: - - >>> from zope.configuration.config import ConfigurationExecutionError - >>> output = [] - >>> def bad(): - ... bad.xxx - >>> context.actions = [ - ... (1, f, (1,)), - ... (1, f, (11,), {}, ('x', )), - ... (2, f, (2,)), - ... (3, bad, (), {}, (), 'oops') - ... ] - >>> try: - ... v = context.execute_actions() - ... except ConfigurationExecutionError as e: - ... v = e - >>> lines = str(v).splitlines() - >>> 'AttributeError' in lines[0] - True - >>> lines[0].endswith("'function' object has no attribute 'xxx'") - True - >>> lines[1:] - [' in:', ' oops'] - - Note that actions executed before the error still have an effect: - - .. doctest:: - - >>> output - [('f', (1,), {}), ('f', (2,), {})] - -.. autoclass:: ConfigurationExecutionError - -.. autointerface:: IStackItem - :members: - :member-order: bysource - -.. autoclass:: SimpleStackItem - :members: - :member-order: bysource - -.. autoclass:: RootStackItem - :members: - :member-order: bysource - -.. autoclass:: GroupingStackItem - :members: - :member-order: bysource - - To see how this works, let's look at an example: - - We need a context. We'll just use a configuration machine - - .. doctest:: - - >>> from zope.configuration.config import GroupingStackItem - >>> from zope.configuration.config import ConfigurationMachine - >>> context = ConfigurationMachine() - - We need a callable to use in configuration actions. We'll use a - convenient one from the tests: - - .. doctest:: - - >>> from zope.configuration.tests.directives import f - - We need a handler for the grouping directive. This is a class - that implements a context decorator. The decorator must also - provide ``before`` and ``after`` methods that are called before - and after any contained directives are processed. We'll typically - subclass ``GroupingContextDecorator``, which provides context - decoration, and default ``before`` and ``after`` methods. - - .. doctest:: - - >>> from zope.configuration.config import GroupingContextDecorator - >>> class SampleGrouping(GroupingContextDecorator): - ... def before(self): - ... self.action(('before', self.x, self.y), f) - ... def after(self): - ... self.action(('after'), f) - - We'll use our decorator to decorate our initial context, providing - keyword arguments x and y: - - .. doctest:: - - >>> dec = SampleGrouping(context, x=1, y=2) - - Note that the keyword arguments are made attributes of the - decorator. - - Now we'll create the stack item. - - .. doctest:: - - >>> item = GroupingStackItem(dec) - - We still haven't called the before action yet, which we can verify - by looking at the context actions: - - .. doctest:: - - >>> context.actions - [] - - Subdirectives will get looked up as adapters of the context. - - We'll create a simple handler: - - .. doctest:: - - >>> def simple(context, data, info): - ... context.action(("simple", context.x, context.y, data), f) - ... return info - - and register it with the context: - - .. doctest:: - - >>> from zope.configuration.interfaces import IConfigurationContext - >>> from zope.configuration.config import testns - >>> context.register(IConfigurationContext, (testns, 'simple'), simple) - - This handler isn't really a propert handler, because it doesn't - return a new context. It will do for this example. - - Now we'll call the contained method on the stack item: - - .. doctest:: - - >>> item.contained((testns, 'simple'), {'z': 'zope'}, "someinfo") - 'someinfo' - - We can verify thet the simple method was called by looking at the - context actions. Note that the before method was called before - handling the contained directive. - - .. doctest:: - - >>> from pprint import PrettyPrinter - >>> pprint = PrettyPrinter(width=60).pprint - - >>> pprint(context.actions) - [{'args': (), - 'callable': f, - 'discriminator': ('before', 1, 2), - 'includepath': (), - 'info': '', - 'kw': {}, - 'order': 0}, - {'args': (), - 'callable': f, - 'discriminator': ('simple', 1, 2, {'z': 'zope'}), - 'includepath': (), - 'info': '', - 'kw': {}, - 'order': 0}] - - Finally, we call finish, which calls the decorator after method: - - .. doctest:: - - >>> item.finish() - - >>> pprint(context.actions) - [{'args': (), - 'callable': f, - 'discriminator': ('before', 1, 2), - 'includepath': (), - 'info': '', - 'kw': {}, - 'order': 0}, - {'args': (), - 'callable': f, - 'discriminator': ('simple', 1, 2, {'z': 'zope'}), - 'includepath': (), - 'info': '', - 'kw': {}, - 'order': 0}, - {'args': (), - 'callable': f, - 'discriminator': 'after', - 'includepath': (), - 'info': '', - 'kw': {}, - 'order': 0}] - - If there were no nested directives: - - .. doctest:: - - >>> context = ConfigurationMachine() - >>> dec = SampleGrouping(context, x=1, y=2) - >>> item = GroupingStackItem(dec) - >>> item.finish() - - Then before will be when we call finish: - - .. doctest:: - - >>> pprint(context.actions) - [{'args': (), - 'callable': f, - 'discriminator': ('before', 1, 2), - 'includepath': (), - 'info': '', - 'kw': {}, - 'order': 0}, - {'args': (), - 'callable': f, - 'discriminator': 'after', - 'includepath': (), - 'info': '', - 'kw': {}, - 'order': 0}] - -.. autoclass:: ComplexStackItem - :members: - :member-order: bysource - - To see how this works, let's look at an example: - - We need a context. We'll just use a configuration machine - - .. doctest:: - - >>> from zope.configuration.config import ConfigurationMachine - >>> context = ConfigurationMachine() - - We need a callable to use in configuration actions. We'll use a - convenient one from the tests: - - .. doctest:: - - >>> from zope.configuration.tests.directives import f - - We need a handler for the complex directive. This is a class - with a method for each subdirective: - - .. doctest:: - - >>> class Handler(object): - ... def __init__(self, context, x, y): - ... self.context, self.x, self.y = context, x, y - ... context.action('init', f) - ... def sub(self, context, a, b): - ... context.action(('sub', a, b), f) - ... def __call__(self): - ... self.context.action(('call', self.x, self.y), f) - - We need a complex directive definition: - - .. doctest:: - - >>> from zope.interface import Interface - >>> from zope.schema import TextLine - >>> from zope.configuration.config import ComplexDirectiveDefinition - >>> class Ixy(Interface): - ... x = TextLine() - ... y = TextLine() - >>> definition = ComplexDirectiveDefinition( - ... context, name="test", schema=Ixy, - ... handler=Handler) - >>> class Iab(Interface): - ... a = TextLine() - ... b = TextLine() - >>> definition['sub'] = Iab, '' - - OK, now that we have the context, handler and definition, we're - ready to use a stack item. - - .. doctest:: - - >>> from zope.configuration.config import ComplexStackItem - >>> item = ComplexStackItem(definition, context, {'x': u'xv', 'y': u'yv'}, - ... 'foo') - - When we created the definition, the handler (factory) was called. - - .. doctest:: - - >>> from pprint import PrettyPrinter - >>> pprint = PrettyPrinter(width=60).pprint - >>> pprint(context.actions) - [{'args': (), - 'callable': f, - 'discriminator': 'init', - 'includepath': (), - 'info': 'foo', - 'kw': {}, - 'order': 0}] - - If a subdirective is provided, the ``contained`` method of the stack item - is called. It will lookup the subdirective schema and call the - corresponding method on the handler instance: - - .. doctest:: - - >>> simple = item.contained(('somenamespace', 'sub'), - ... {'a': u'av', 'b': u'bv'}, 'baz') - >>> simple.finish() - - Note that the name passed to ``contained`` is a 2-part name, consisting of - a namespace and a name within the namespace. - - .. doctest:: - - >>> pprint(context.actions) - [{'args': (), - 'callable': f, - 'discriminator': 'init', - 'includepath': (), - 'info': 'foo', - 'kw': {}, - 'order': 0}, - {'args': (), - 'callable': f, - 'discriminator': ('sub', 'av', 'bv'), - 'includepath': (), - 'info': 'baz', - 'kw': {}, - 'order': 0}] - - The new stack item returned by contained is one that doesn't allow - any more subdirectives, - - When all of the subdirectives have been provided, the ``finish`` - method is called: - - .. doctest:: - - >>> item.finish() - - The stack item will call the handler if it is callable. - - .. doctest:: - - >>> pprint(context.actions) - [{'args': (), - 'callable': f, - 'discriminator': 'init', - 'includepath': (), - 'info': 'foo', - 'kw': {}, - 'order': 0}, - {'args': (), - 'callable': f, - 'discriminator': ('sub', 'av', 'bv'), - 'includepath': (), - 'info': 'baz', - 'kw': {}, - 'order': 0}, - {'args': (), - 'callable': f, - 'discriminator': ('call', 'xv', 'yv'), - 'includepath': (), - 'info': 'foo', - 'kw': {}, - 'order': 0}] - -.. autoclass:: GroupingContextDecorator - :members: - :member-order: bysource - -.. autoclass:: DirectiveSchema - :members: - :member-order: bysource - -.. autointerface:: IDirectivesInfo - :members: - :member-order: bysource - -.. autointerface:: IDirectivesContext - :members: - :member-order: bysource - -.. autoclass:: DirectivesHandler - :members: - :member-order: bysource - -.. autointerface:: IDirectiveInfo - :members: - :member-order: bysource - -.. autointerface:: IFullInfo - :members: - :member-order: bysource - -.. autointerface:: IStandaloneDirectiveInfo - :members: - :member-order: bysource - -.. autofunction:: defineSimpleDirective - - Example: - - .. doctest:: - - >>> from zope.configuration.config import ConfigurationMachine - >>> context = ConfigurationMachine() - >>> from zope.interface import Interface - >>> from zope.schema import TextLine - >>> from zope.configuration.tests.directives import f - >>> class Ixy(Interface): - ... x = TextLine() - ... y = TextLine() - >>> def s(context, x, y): - ... context.action(('s', x, y), f) - - >>> from zope.configuration.config import defineSimpleDirective - >>> defineSimpleDirective(context, 's', Ixy, s, testns) - - >>> context((testns, "s"), x=u"vx", y=u"vy") - >>> from pprint import PrettyPrinter - >>> pprint = PrettyPrinter(width=60).pprint - >>> pprint(context.actions) - [{'args': (), - 'callable': f, - 'discriminator': ('s', 'vx', 'vy'), - 'includepath': (), - 'info': None, - 'kw': {}, - 'order': 0}] - - >>> context(('http://www.zope.com/t1', "s"), x=u"vx", y=u"vy") - Traceback (most recent call last): - ... - ConfigurationError: ('Unknown directive', 'http://www.zope.com/t1', 's') - - >>> context = ConfigurationMachine() - >>> defineSimpleDirective(context, 's', Ixy, s, "*") - - >>> context(('http://www.zope.com/t1', "s"), x=u"vx", y=u"vy") - >>> pprint(context.actions) - [{'args': (), - 'callable': f, - 'discriminator': ('s', 'vx', 'vy'), - 'includepath': (), - 'info': None, - 'kw': {}, - 'order': 0}] - -.. autofunction:: defineGroupingDirective - - Example: - - .. doctest:: - - >>> from zope.configuration.config import ConfigurationMachine - >>> context = ConfigurationMachine() - >>> from zope.interface import Interface - >>> from zope.schema import TextLine - >>> from zope.configuration.tests.directives import f - >>> class Ixy(Interface): - ... x = TextLine() - ... y = TextLine() - - We won't bother creating a special grouping directive class. We'll - just use :class:`GroupingContextDecorator`, which simply sets up a - grouping context that has extra attributes defined by a schema: - - .. doctest:: - - >>> from zope.configuration.config import defineGroupingDirective - >>> from zope.configuration.config import GroupingContextDecorator - >>> defineGroupingDirective(context, 'g', Ixy, - ... GroupingContextDecorator, testns) - - >>> context.begin((testns, "g"), x=u"vx", y=u"vy") - >>> context.stack[-1].context.x - 'vx' - >>> context.stack[-1].context.y - 'vy' - - >>> context(('http://www.zope.com/t1', "g"), x=u"vx", y=u"vy") - Traceback (most recent call last): - ... - ConfigurationError: ('Unknown directive', 'http://www.zope.com/t1', 'g') - - >>> context = ConfigurationMachine() - >>> defineGroupingDirective(context, 'g', Ixy, - ... GroupingContextDecorator, "*") - - >>> context.begin(('http://www.zope.com/t1', "g"), x=u"vx", y=u"vy") - >>> context.stack[-1].context.x - 'vx' - >>> context.stack[-1].context.y - 'vy' - -.. autointerface:: IComplexDirectiveContext - :members: - :member-order: bysource - -.. autoclass:: ComplexDirectiveDefinition - :members: - :member-order: bysource - -.. autofunction:: subdirective - -.. autointerface:: IProvidesDirectiveInfo - :members: - :member-order: bysource - -.. autofunction:: provides - - Example: - - .. doctest:: - - >>> from zope.configuration.config import ConfigurationContext - >>> from zope.configuration.config import provides - >>> c = ConfigurationContext() - >>> provides(c, 'apidoc') - >>> c.hasFeature('apidoc') - True - - Spaces are not allowed in feature names (this is reserved for providing - many features with a single directive in the futute). - - .. doctest:: - - >>> provides(c, 'apidoc onlinehelp') - Traceback (most recent call last): - ... - ValueError: Only one feature name allowed - - >>> c.hasFeature('apidoc onlinehelp') - False - -.. autofunction:: toargs - - Example: - - .. doctest:: - - >>> from zope.configuration.config import toargs - >>> from zope.schema import BytesLine - >>> from zope.schema import Float - >>> from zope.schema import Int - >>> from zope.schema import TextLine - >>> from zope.schema import URI - >>> class schema(Interface): - ... in_ = Int(constraint=lambda v: v > 0) - ... f = Float() - ... n = TextLine(min_length=1, default=u"rob") - ... x = BytesLine(required=False) - ... u = URI() - - >>> context = ConfigurationMachine() - >>> from pprint import PrettyPrinter - >>> pprint=PrettyPrinter(width=50).pprint - - >>> pprint(toargs(context, schema, - ... {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z', - ... 'u': u'http://www.zope.org' })) - {'f': 1.2, - 'in_': 1, - 'n': 'bob', - 'u': 'http://www.zope.org', - 'x': b'x.y.z'} - - If we have extra data, we'll get an error: - - .. doctest:: - - >>> toargs(context, schema, - ... {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z', - ... 'u': u'http://www.zope.org', 'a': u'1'}) - Traceback (most recent call last): - ... - ConfigurationError: ('Unrecognized parameters:', 'a') - - Unless we set a tagged value to say that extra arguments are ok: - - .. doctest:: - - >>> schema.setTaggedValue('keyword_arguments', True) - - >>> pprint(toargs(context, schema, - ... {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z', - ... 'u': u'http://www.zope.org', 'a': u'1'})) - {'a': '1', - 'f': 1.2, - 'in_': 1, - 'n': 'bob', - 'u': 'http://www.zope.org', - 'x': b'x.y.z'} - - If we omit required data we get an error telling us what was omitted: - - .. doctest:: - - >>> pprint(toargs(context, schema, - ... {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z'})) - Traceback (most recent call last): - ... - ConfigurationError: ('Missing parameter:', 'u') - - Although we can omit not-required data: - - .. doctest:: - - >>> pprint(toargs(context, schema, - ... {'in': u'1', 'f': u'1.2', 'n': u'bob', - ... 'u': u'http://www.zope.org', 'a': u'1'})) - {'a': '1', - 'f': 1.2, - 'in_': 1, - 'n': 'bob', - 'u': 'http://www.zope.org'} - - And we can omit required fields if they have valid defaults - (defaults that are valid values): - - .. doctest:: - - >>> pprint(toargs(context, schema, - ... {'in': u'1', 'f': u'1.2', - ... 'u': u'http://www.zope.org', 'a': u'1'})) - {'a': '1', - 'f': 1.2, - 'in_': 1, - 'n': 'rob', - 'u': 'http://www.zope.org'} - - We also get an error if any data was invalid: - - .. doctest:: - - >>> pprint(toargs(context, schema, - ... {'in': u'0', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z', - ... 'u': u'http://www.zope.org', 'a': u'1'})) - Traceback (most recent call last): - ... - ConfigurationError: ('Invalid value for', 'in', '0') - -.. autofunction:: expand_action - -.. autofunction:: resolveConflicts - -.. autoclass:: ConfigurationConflictError +.. automodule:: zope.configuration.config diff --git a/src/zope/configuration/config.py b/src/zope/configuration/config.py index a768021..2089ace 100644 --- a/src/zope/configuration/config.py +++ b/src/zope/configuration/config.py @@ -125,7 +125,39 @@ class ConfigurationContext(object): self._features = set() def resolve(self, dottedname): - """Resolve a dotted name to an object. + """ + Resolve a dotted name to an object. + + Examples: + + >>> from zope.configuration.config import ConfigurationContext + >>> from zope.configuration.config import ConfigurationError + >>> c = ConfigurationContext() + >>> import zope, zope.interface + >>> c.resolve('zope') is zope + True + >>> c.resolve('zope.interface') is zope.interface + True + >>> c.resolve('zope.configuration.eek') #doctest: +NORMALIZE_WHITESPACE + Traceback (most recent call last): + ... + ConfigurationError: + ImportError: Module zope.configuration has no global eek + + >>> c.resolve('.config.ConfigurationContext') + Traceback (most recent call last): + ... + AttributeError: 'ConfigurationContext' object has no attribute 'package' + >>> import zope.configuration + >>> c.package = zope.configuration + >>> c.resolve('.') is zope.configuration + True + >>> c.resolve('.config.ConfigurationContext') is ConfigurationContext + True + >>> c.resolve('..interface') is zope.interface + True + >>> c.resolve('str') is str + True """ name = dottedname.strip() @@ -213,7 +245,31 @@ class ConfigurationContext(object): "ImportError: Module %s has no global %s" % (mname, oname)) def path(self, filename): - """ Compute package-relative paths. + """ + Compute package-relative paths. + + Examples: + + >>> import os + >>> from zope.configuration.config import ConfigurationContext + >>> c = ConfigurationContext() + >>> c.path("/x/y/z") == os.path.normpath("/x/y/z") + True + >>> c.path("y/z") + Traceback (most recent call last): + ... + AttributeError: 'ConfigurationContext' object has no attribute 'package' + >>> import zope.configuration + >>> c.package = zope.configuration + >>> import os + >>> d = os.path.dirname(zope.configuration.__file__) + >>> c.path("y/z") == d + os.path.normpath("/y/z") + True + >>> c.path("y/./z") == d + os.path.normpath("/y/z") + True + >>> c.path("y/../z") == d + os.path.normpath("/z") + True + """ filename, needs_processing = PathProcessor.expand(filename) @@ -239,10 +295,39 @@ class ConfigurationContext(object): return os.path.normpath(os.path.join(basepath, filename)) def checkDuplicate(self, filename): - """Check for duplicate imports of the same file. + """ + Check for duplicate imports of the same file. + + Raises an exception if this file had been processed before. + This is better than an unlimited number of conflict errors. + + Examples: + + >>> from zope.configuration.config import ConfigurationContext + >>> from zope.configuration.config import ConfigurationError + >>> c = ConfigurationContext() + >>> c.checkDuplicate('/foo.zcml') + >>> try: + ... c.checkDuplicate('/foo.zcml') + ... except ConfigurationError as e: + ... # On Linux the exact msg has /foo, on Windows \\foo. + ... str(e).endswith("foo.zcml' included more than once") + True + + You may use different ways to refer to the same file: + + >>> import zope.configuration + >>> c.package = zope.configuration + >>> import os + >>> d = os.path.dirname(zope.configuration.__file__) + >>> c.checkDuplicate('bar.zcml') + >>> try: + ... c.checkDuplicate(d + os.path.normpath('/bar.zcml')) + ... except ConfigurationError as e: + ... str(e).endswith("bar.zcml' included more than once") + ... + True - Raises an exception if this file had been processed before. This - is better than an unlimited number of conflict errors. """ path = self.path(filename) if path in self._seen_files: @@ -250,13 +335,34 @@ class ConfigurationContext(object): self._seen_files.add(path) def processFile(self, filename): - """Check whether a file needs to be processed. + """ + Check whether a file needs to be processed. Return True if processing is needed and False otherwise. If the file needs to be processed, it will be marked as processed, assuming that the caller will procces the file if it needs to be procssed. - """ #' <-- bow to font-lock + + Examples: + + >>> from zope.configuration.config import ConfigurationContext + >>> c = ConfigurationContext() + >>> c.processFile('/foo.zcml') + True + >>> c.processFile('/foo.zcml') + False + + You may use different ways to refer to the same file: + + >>> import zope.configuration + >>> c.package = zope.configuration + >>> import os + >>> d = os.path.dirname(zope.configuration.__file__) + >>> c.processFile('bar.zcml') + True + >>> c.processFile('bar.zcml') + False + """ path = self.path(filename) if path in self._seen_files: return False @@ -265,13 +371,114 @@ class ConfigurationContext(object): def action(self, discriminator, callable=None, args=(), kw=None, order=0, includepath=None, info=None, **extra): - """Add an action with the given discriminator, callable and arguments. - - For testing purposes, the callable and arguments may be omitted. - In that case, a default noop callable is used. + """ + Add an action with the given discriminator, callable and + arguments. + + For testing purposes, the callable and arguments may be + omitted. In that case, a default noop callable is used. + + The discriminator must be given, but it can be None, to + indicate that the action never conflicts. + + + Examples: + + >>> from zope.configuration.config import ConfigurationContext + >>> c = ConfigurationContext() + + Normally, the context gets actions from subclasses. We'll provide + an actions attribute ourselves: + + >>> c.actions = [] + + We'll use a test callable that has a convenient string representation + + >>> from zope.configuration.tests.directives import f + >>> c.action(1, f, (1, ), {'x': 1}) + >>> from pprint import PrettyPrinter + >>> pprint=PrettyPrinter(width=60).pprint + >>> pprint(c.actions) + [{'args': (1,), + 'callable': f, + 'discriminator': 1, + 'includepath': (), + 'info': '', + 'kw': {'x': 1}, + 'order': 0}] + + >>> c.action(None) + >>> pprint(c.actions) + [{'args': (1,), + 'callable': f, + 'discriminator': 1, + 'includepath': (), + 'info': '', + 'kw': {'x': 1}, + 'order': 0}, + {'args': (), + 'callable': None, + 'discriminator': None, + 'includepath': (), + 'info': '', + 'kw': {}, + 'order': 0}] + + Now set the include path and info: + + >>> c.includepath = ('foo.zcml',) + >>> c.info = "?" + >>> c.action(None) + >>> pprint(c.actions[-1]) + {'args': (), + 'callable': None, + 'discriminator': None, + 'includepath': ('foo.zcml',), + 'info': '?', + 'kw': {}, + 'order': 0} + + We can add an order argument to crudely control the order + of execution: + + >>> c.action(None, order=99999) + >>> pprint(c.actions[-1]) + {'args': (), + 'callable': None, + 'discriminator': None, + 'includepath': ('foo.zcml',), + 'info': '?', + 'kw': {}, + 'order': 99999} + + We can also pass an includepath argument, which will be used as the the + includepath for the action. (if includepath is None, self.includepath + will be used): + + >>> c.action(None, includepath=('abc',)) + >>> pprint(c.actions[-1]) + {'args': (), + 'callable': None, + 'discriminator': None, + 'includepath': ('abc',), + 'info': '?', + 'kw': {}, + 'order': 0} + + We can also pass an info argument, which will be used as the the + source line info for the action. (if info is None, self.info will be + used): + + >>> c.action(None, info='abc') + >>> pprint(c.actions[-1]) + {'args': (), + 'callable': None, + 'discriminator': None, + 'includepath': ('foo.zcml',), + 'info': 'abc', + 'kw': {}, + 'order': 0} - The discriminator must be given, but it can be None, to indicate that - the action never conflicts. """ if kw is None: kw = {} @@ -299,14 +506,32 @@ class ConfigurationContext(object): self.actions.append(action) def hasFeature(self, feature): - """Check whether a named feature has been provided. + """ + Check whether a named feature has been provided. + + Initially no features are provided. + + Examples: + + >>> from zope.configuration.config import ConfigurationContext + >>> c = ConfigurationContext() + >>> c.hasFeature('onlinehelp') + False - Initially no features are provided + You can declare that a feature is provided + + >>> c.provideFeature('onlinehelp') + + and it becomes available + + >>> c.hasFeature('onlinehelp') + True """ return feature in self._features def provideFeature(self, feature): - """Declare thata named feature has been provided. + """ + Declare that a named feature has been provided. See :meth:`hasFeature` for examples. """ @@ -314,9 +539,58 @@ class ConfigurationContext(object): class ConfigurationAdapterRegistry(object): - """Simple adapter registry that manages directives as adapters """ - + Simple adapter registry that manages directives as adapters. + + Examples: + + >>> from zope.configuration.interfaces import IConfigurationContext + >>> from zope.configuration.config import ConfigurationAdapterRegistry + >>> from zope.configuration.config import ConfigurationError + >>> from zope.configuration.config import ConfigurationMachine + >>> r = ConfigurationAdapterRegistry() + >>> c = ConfigurationMachine() + >>> r.factory(c, ('http://www.zope.com','xxx')) + Traceback (most recent call last): + ... + ConfigurationError: ('Unknown directive', 'http://www.zope.com', 'xxx') + >>> def f(): + ... pass + + >>> r.register(IConfigurationContext, ('http://www.zope.com', 'xxx'), f) + >>> r.factory(c, ('http://www.zope.com','xxx')) is f + True + >>> r.factory(c, ('http://www.zope.com','yyy')) is f + Traceback (most recent call last): + ... + ConfigurationError: ('Unknown directive', 'http://www.zope.com', 'yyy') + >>> r.register(IConfigurationContext, 'yyy', f) + >>> r.factory(c, ('http://www.zope.com','yyy')) is f + True + + Test the documentation feature: + + >>> from zope.configuration.config import IFullInfo + >>> r._docRegistry + [] + >>> r.document(('ns', 'dir'), IFullInfo, IConfigurationContext, None, + ... 'inf', None) + >>> r._docRegistry[0][0] == ('ns', 'dir') + True + >>> r._docRegistry[0][1] is IFullInfo + True + >>> r._docRegistry[0][2] is IConfigurationContext + True + >>> r._docRegistry[0][3] is None + True + >>> r._docRegistry[0][4] == 'inf' + True + >>> r._docRegistry[0][5] is None + True + >>> r.document('all-dir', None, None, None, None) + >>> r._docRegistry[1][0] + ('', 'all-dir') + """ def __init__(self): super(ConfigurationAdapterRegistry, self).__init__() @@ -355,7 +629,36 @@ class ConfigurationAdapterRegistry(object): @implementer(IConfigurationContext) class ConfigurationMachine(ConfigurationAdapterRegistry, ConfigurationContext): - """Configuration machine + """ + Configuration machine. + + Example: + + >>> from zope.configuration.config import ConfigurationMachine + >>> machine = ConfigurationMachine() + >>> ns = "http://www.zope.org/testing" + + Register a directive: + + >>> from zope.configuration.config import metans + >>> machine((metans, "directive"), + ... namespace=ns, name="simple", + ... schema="zope.configuration.tests.directives.ISimple", + ... handler="zope.configuration.tests.directives.simple") + + and try it out: + + >>> machine((ns, "simple"), a=u"aa", c=u"cc") + >>> from pprint import PrettyPrinter + >>> pprint = PrettyPrinter(width=60).pprint + >>> pprint(machine.actions) + [{'args': ('aa', 'xxx', 'cc'), + 'callable': f, + 'discriminator': ('simple', 'aa', 'xxx', 'cc'), + 'includepath': (), + 'info': None, + 'kw': {}, + 'order': 0}] """ package = None basepath = None @@ -402,9 +705,56 @@ class ConfigurationMachine(ConfigurationAdapterRegistry, ConfigurationContext): self.stack[-1].context.info = info def execute_actions(self, clear=True, testing=False): - """Execute the configuration actions. + """ + Execute the configuration actions. This calls the action callables after resolving conflicts. + + For example: + + >>> from zope.configuration.config import ConfigurationMachine + >>> output = [] + >>> def f(*a, **k): #* syntax highlighting + ... output.append(('f', a, k)) + >>> context = ConfigurationMachine() + >>> context.actions = [ + ... (1, f, (1,)), + ... (1, f, (11,), {}, ('x', )), + ... (2, f, (2,)), + ... ] + >>> context.execute_actions() + >>> output + [('f', (1,), {}), ('f', (2,), {})] + + If the action raises an error, we convert it to a + ConfigurationExecutionError. + + >>> from zope.configuration.config import ConfigurationExecutionError + >>> output = [] + >>> def bad(): + ... bad.xxx + >>> context.actions = [ + ... (1, f, (1,)), + ... (1, f, (11,), {}, ('x', )), + ... (2, f, (2,)), + ... (3, bad, (), {}, (), 'oops') + ... ] + >>> try: + ... v = context.execute_actions() + ... except ConfigurationExecutionError as e: + ... v = e + >>> lines = str(v).splitlines() + >>> 'AttributeError' in lines[0] + True + >>> lines[0].endswith("'function' object has no attribute 'xxx'") + True + >>> lines[1:] + [' in:', ' oops'] + + Note that actions executed before the error still have an effect: + + >>> output + [('f', (1,), {}), ('f', (2,), {})] """ pass_through_exceptions = self.pass_through_exceptions if testing: @@ -434,7 +784,8 @@ class ConfigurationMachine(ConfigurationAdapterRegistry, ConfigurationContext): del self.actions[:] class ConfigurationExecutionError(ConfigurationError): - """An error occurred during execution of a configuration action + """ + An error occurred during execution of a configuration action """ def __init__(self, etype, evalue, info): self.etype, self.evalue, self.info = etype, evalue, info @@ -446,7 +797,8 @@ class ConfigurationExecutionError(ConfigurationError): # Stack items class IStackItem(Interface): - """Configuration machine stack items + """ + Configuration machine stack items Stack items are created when a directive is being processed. @@ -471,7 +823,8 @@ class IStackItem(Interface): @implementer(IStackItem) class SimpleStackItem(object): - """Simple stack item + """ + Simple stack item A simple stack item can't have anything added after it. It can only be removed. It is used for simple directives and @@ -508,6 +861,9 @@ class SimpleStackItem(object): @implementer(IStackItem) class RootStackItem(object): + """ + A root stack item. + """ def __init__(self, context): self.context = context @@ -529,15 +885,158 @@ class RootStackItem(object): @implementer(IStackItem) class GroupingStackItem(RootStackItem): - """Stack item for a grouping directive + """ + Stack item for a grouping directive A grouping stack item is in the stack when a grouping directive is - being processed. Grouping directives group other directives. + being processed. Grouping directives group other directives. Often, they just manage common data, but they may also take actions, either before or after contained directives are executed. A grouping stack item is created with a grouping directive definition, a configuration context, and directive data. + + To see how this works, let's look at an example: + + We need a context. We'll just use a configuration machine + + >>> from zope.configuration.config import GroupingStackItem + >>> from zope.configuration.config import ConfigurationMachine + >>> context = ConfigurationMachine() + + We need a callable to use in configuration actions. We'll use a + convenient one from the tests: + + >>> from zope.configuration.tests.directives import f + + We need a handler for the grouping directive. This is a class that + implements a context decorator. The decorator must also provide + ``before`` and ``after`` methods that are called before and after + any contained directives are processed. We'll typically subclass + ``GroupingContextDecorator``, which provides context decoration, + and default ``before`` and ``after`` methods. + + >>> from zope.configuration.config import GroupingContextDecorator + >>> class SampleGrouping(GroupingContextDecorator): + ... def before(self): + ... self.action(('before', self.x, self.y), f) + ... def after(self): + ... self.action(('after'), f) + + We'll use our decorator to decorate our initial context, providing + keyword arguments x and y: + + >>> dec = SampleGrouping(context, x=1, y=2) + + Note that the keyword arguments are made attributes of the + decorator. + + Now we'll create the stack item. + + >>> item = GroupingStackItem(dec) + + We still haven't called the before action yet, which we can verify + by looking at the context actions: + + >>> context.actions + [] + + Subdirectives will get looked up as adapters of the context. + + We'll create a simple handler: + + >>> def simple(context, data, info): + ... context.action(("simple", context.x, context.y, data), f) + ... return info + + and register it with the context: + + >>> from zope.configuration.interfaces import IConfigurationContext + >>> from zope.configuration.config import testns + >>> context.register(IConfigurationContext, (testns, 'simple'), simple) + + This handler isn't really a propert handler, because it doesn't + return a new context. It will do for this example. + + Now we'll call the contained method on the stack item: + + >>> item.contained((testns, 'simple'), {'z': 'zope'}, "someinfo") + 'someinfo' + + We can verify thet the simple method was called by looking at the + context actions. Note that the before method was called before + handling the contained directive. + + >>> from pprint import PrettyPrinter + >>> pprint = PrettyPrinter(width=60).pprint + + >>> pprint(context.actions) + [{'args': (), + 'callable': f, + 'discriminator': ('before', 1, 2), + 'includepath': (), + 'info': '', + 'kw': {}, + 'order': 0}, + {'args': (), + 'callable': f, + 'discriminator': ('simple', 1, 2, {'z': 'zope'}), + 'includepath': (), + 'info': '', + 'kw': {}, + 'order': 0}] + + Finally, we call finish, which calls the decorator after method: + + >>> item.finish() + + >>> pprint(context.actions) + [{'args': (), + 'callable': f, + 'discriminator': ('before', 1, 2), + 'includepath': (), + 'info': '', + 'kw': {}, + 'order': 0}, + {'args': (), + 'callable': f, + 'discriminator': ('simple', 1, 2, {'z': 'zope'}), + 'includepath': (), + 'info': '', + 'kw': {}, + 'order': 0}, + {'args': (), + 'callable': f, + 'discriminator': 'after', + 'includepath': (), + 'info': '', + 'kw': {}, + 'order': 0}] + + If there were no nested directives: + + >>> context = ConfigurationMachine() + >>> dec = SampleGrouping(context, x=1, y=2) + >>> item = GroupingStackItem(dec) + >>> item.finish() + + Then before will be when we call finish: + + >>> pprint(context.actions) + [{'args': (), + 'callable': f, + 'discriminator': ('before', 1, 2), + 'includepath': (), + 'info': '', + 'kw': {}, + 'order': 0}, + {'args': (), + 'callable': f, + 'discriminator': 'after', + 'includepath': (), + 'info': '', + 'kw': {}, + 'order': 0}] """ def __init__(self, context): @@ -571,14 +1070,135 @@ def noop(): @implementer(IStackItem) class ComplexStackItem(object): - """Complex stack item + """ + Complex stack item A complex stack item is in the stack when a complex directive is - being processed. It only allows subdirectives to be used. + being processed. It only allows subdirectives to be used. A complex stack item is created with a complex directive definition (IComplexDirectiveContext), a configuration context, and directive data. + + To see how this works, let's look at an example: + + We need a context. We'll just use a configuration machine + + >>> from zope.configuration.config import ConfigurationMachine + >>> context = ConfigurationMachine() + + We need a callable to use in configuration actions. We'll use a + convenient one from the tests: + + >>> from zope.configuration.tests.directives import f + + We need a handler for the complex directive. This is a class with + a method for each subdirective: + + >>> class Handler(object): + ... def __init__(self, context, x, y): + ... self.context, self.x, self.y = context, x, y + ... context.action('init', f) + ... def sub(self, context, a, b): + ... context.action(('sub', a, b), f) + ... def __call__(self): + ... self.context.action(('call', self.x, self.y), f) + + We need a complex directive definition: + + >>> from zope.interface import Interface + >>> from zope.schema import TextLine + >>> from zope.configuration.config import ComplexDirectiveDefinition + >>> class Ixy(Interface): + ... x = TextLine() + ... y = TextLine() + >>> definition = ComplexDirectiveDefinition( + ... context, name="test", schema=Ixy, + ... handler=Handler) + >>> class Iab(Interface): + ... a = TextLine() + ... b = TextLine() + >>> definition['sub'] = Iab, '' + + OK, now that we have the context, handler and definition, we're + ready to use a stack item. + + >>> from zope.configuration.config import ComplexStackItem + >>> item = ComplexStackItem(definition, context, {'x': u'xv', 'y': u'yv'}, + ... 'foo') + + When we created the definition, the handler (factory) was called. + + >>> from pprint import PrettyPrinter + >>> pprint = PrettyPrinter(width=60).pprint + >>> pprint(context.actions) + [{'args': (), + 'callable': f, + 'discriminator': 'init', + 'includepath': (), + 'info': 'foo', + 'kw': {}, + 'order': 0}] + + If a subdirective is provided, the ``contained`` method of the + stack item is called. It will lookup the subdirective schema and + call the corresponding method on the handler instance: + + >>> simple = item.contained(('somenamespace', 'sub'), + ... {'a': u'av', 'b': u'bv'}, 'baz') + >>> simple.finish() + + Note that the name passed to ``contained`` is a 2-part name, + consisting of a namespace and a name within the namespace. + + >>> pprint(context.actions) + [{'args': (), + 'callable': f, + 'discriminator': 'init', + 'includepath': (), + 'info': 'foo', + 'kw': {}, + 'order': 0}, + {'args': (), + 'callable': f, + 'discriminator': ('sub', 'av', 'bv'), + 'includepath': (), + 'info': 'baz', + 'kw': {}, + 'order': 0}] + + The new stack item returned by contained is one that doesn't allow + any more subdirectives, + + When all of the subdirectives have been provided, the ``finish`` + method is called: + + >>> item.finish() + + The stack item will call the handler if it is callable. + + >>> pprint(context.actions) + [{'args': (), + 'callable': f, + 'discriminator': 'init', + 'includepath': (), + 'info': 'foo', + 'kw': {}, + 'order': 0}, + {'args': (), + 'callable': f, + 'discriminator': ('sub', 'av', 'bv'), + 'includepath': (), + 'info': 'baz', + 'kw': {}, + 'order': 0}, + {'args': (), + 'callable': f, + 'discriminator': ('call', 'xv', 'yv'), + 'includepath': (), + 'info': 'foo', + 'kw': {}, + 'order': 0}] """ def __init__(self, meta, context, data, info): newcontext = GroupingContextDecorator(context) @@ -724,12 +1344,61 @@ class IStandaloneDirectiveInfo(IDirectivesInfo, IFullInfo): def defineSimpleDirective(context, name, schema, handler, namespace='', usedIn=IConfigurationContext): - """Define a simple directive + """ + Define a simple directive Define and register a factory that invokes the simple directive - and returns a new stack item, which is always the same simple stack item. - - If the namespace is '*', the directive is registered for all namespaces. + and returns a new stack item, which is always the same simple + stack item. + + If the namespace is '*', the directive is registered for all + namespaces. + + Example: + + >>> from zope.configuration.config import ConfigurationMachine + >>> context = ConfigurationMachine() + >>> from zope.interface import Interface + >>> from zope.schema import TextLine + >>> from zope.configuration.tests.directives import f + >>> class Ixy(Interface): + ... x = TextLine() + ... y = TextLine() + >>> def s(context, x, y): + ... context.action(('s', x, y), f) + + >>> from zope.configuration.config import defineSimpleDirective + >>> defineSimpleDirective(context, 's', Ixy, s, testns) + + >>> context((testns, "s"), x=u"vx", y=u"vy") + >>> from pprint import PrettyPrinter + >>> pprint = PrettyPrinter(width=60).pprint + >>> pprint(context.actions) + [{'args': (), + 'callable': f, + 'discriminator': ('s', 'vx', 'vy'), + 'includepath': (), + 'info': None, + 'kw': {}, + 'order': 0}] + + >>> context(('http://www.zope.com/t1', "s"), x=u"vx", y=u"vy") + Traceback (most recent call last): + ... + ConfigurationError: ('Unknown directive', 'http://www.zope.com/t1', 's') + + >>> context = ConfigurationMachine() + >>> defineSimpleDirective(context, 's', Ixy, s, "*") + + >>> context(('http://www.zope.com/t1', "s"), x=u"vx", y=u"vy") + >>> pprint(context.actions) + [{'args': (), + 'callable': f, + 'discriminator': ('s', 'vx', 'vy'), + 'includepath': (), + 'info': None, + 'kw': {}, + 'order': 0}] """ namespace = namespace or context.namespace if namespace != '*': @@ -744,11 +1413,54 @@ def defineSimpleDirective(context, name, schema, handler, def defineGroupingDirective(context, name, schema, handler, namespace='', usedIn=IConfigurationContext): - """Define a grouping directive + """ + Define a grouping directive Define and register a factory that sets up a grouping directive. - If the namespace is '*', the directive is registered for all namespaces. + If the namespace is '*', the directive is registered for all + namespaces. + + Example: + + >>> from zope.configuration.config import ConfigurationMachine + >>> context = ConfigurationMachine() + >>> from zope.interface import Interface + >>> from zope.schema import TextLine + >>> from zope.configuration.tests.directives import f + >>> class Ixy(Interface): + ... x = TextLine() + ... y = TextLine() + + We won't bother creating a special grouping directive class. We'll + just use :class:`GroupingContextDecorator`, which simply sets up a + grouping context that has extra attributes defined by a schema: + + >>> from zope.configuration.config import defineGroupingDirective + >>> from zope.configuration.config import GroupingContextDecorator + >>> defineGroupingDirective(context, 'g', Ixy, + ... GroupingContextDecorator, testns) + + >>> context.begin((testns, "g"), x=u"vx", y=u"vy") + >>> context.stack[-1].context.x + 'vx' + >>> context.stack[-1].context.y + 'vy' + + >>> context(('http://www.zope.com/t1', "g"), x=u"vx", y=u"vy") + Traceback (most recent call last): + ... + ConfigurationError: ('Unknown directive', 'http://www.zope.com/t1', 'g') + + >>> context = ConfigurationMachine() + >>> defineGroupingDirective(context, 'g', Ixy, + ... GroupingContextDecorator, "*") + + >>> context.begin(('http://www.zope.com/t1', "g"), x=u"vx", y=u"vy") + >>> context.stack[-1].context.x + 'vx' + >>> context.stack[-1].context.y + 'vy' """ namespace = namespace or context.namespace if namespace != '*': @@ -806,7 +1518,28 @@ class IProvidesDirectiveInfo(Interface): ) def provides(context, feature): - """Declare that a feature is provided in context. + """ + Declare that a feature is provided in context. + + Example: + + >>> from zope.configuration.config import ConfigurationContext + >>> from zope.configuration.config import provides + >>> c = ConfigurationContext() + >>> provides(c, 'apidoc') + >>> c.hasFeature('apidoc') + True + + Spaces are not allowed in feature names (this is reserved for + providing many features with a single directive in the future). + + >>> provides(c, 'apidoc onlinehelp') + Traceback (most recent call last): + ... + ValueError: Only one feature name allowed + + >>> c.hasFeature('apidoc onlinehelp') + False """ if len(feature.split()) > 1: raise ValueError("Only one feature name allowed") @@ -817,7 +1550,8 @@ def provides(context, feature): # Argument conversion def toargs(context, schema, data): - """Marshal data to an argument dictionary using a schema + """ + Marshal data to an argument dictionary using a schema Names that are python keywords have an underscore added as a suffix in the schema and in the argument list, but are used @@ -828,6 +1562,98 @@ def toargs(context, schema, data): All of the items in the data must have corresponding fields in the schema unless the schema has a true tagged value named 'keyword_arguments'. + + Example: + + >>> from zope.configuration.config import toargs + >>> from zope.schema import BytesLine + >>> from zope.schema import Float + >>> from zope.schema import Int + >>> from zope.schema import TextLine + >>> from zope.schema import URI + >>> class schema(Interface): + ... in_ = Int(constraint=lambda v: v > 0) + ... f = Float() + ... n = TextLine(min_length=1, default=u"rob") + ... x = BytesLine(required=False) + ... u = URI() + + >>> context = ConfigurationMachine() + >>> from pprint import PrettyPrinter + >>> pprint=PrettyPrinter(width=50).pprint + + >>> pprint(toargs(context, schema, + ... {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z', + ... 'u': u'http://www.zope.org' })) + {'f': 1.2, + 'in_': 1, + 'n': 'bob', + 'u': 'http://www.zope.org', + 'x': b'x.y.z'} + + If we have extra data, we'll get an error: + + >>> toargs(context, schema, + ... {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z', + ... 'u': u'http://www.zope.org', 'a': u'1'}) + Traceback (most recent call last): + ... + ConfigurationError: ('Unrecognized parameters:', 'a') + + Unless we set a tagged value to say that extra arguments are ok: + + >>> schema.setTaggedValue('keyword_arguments', True) + + >>> pprint(toargs(context, schema, + ... {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z', + ... 'u': u'http://www.zope.org', 'a': u'1'})) + {'a': '1', + 'f': 1.2, + 'in_': 1, + 'n': 'bob', + 'u': 'http://www.zope.org', + 'x': b'x.y.z'} + + If we omit required data we get an error telling us what was + omitted: + + >>> pprint(toargs(context, schema, + ... {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z'})) + Traceback (most recent call last): + ... + ConfigurationError: ('Missing parameter:', 'u') + + Although we can omit not-required data: + + >>> pprint(toargs(context, schema, + ... {'in': u'1', 'f': u'1.2', 'n': u'bob', + ... 'u': u'http://www.zope.org', 'a': u'1'})) + {'a': '1', + 'f': 1.2, + 'in_': 1, + 'n': 'bob', + 'u': 'http://www.zope.org'} + + And we can omit required fields if they have valid defaults + (defaults that are valid values): + + >>> pprint(toargs(context, schema, + ... {'in': u'1', 'f': u'1.2', + ... 'u': u'http://www.zope.org', 'a': u'1'})) + {'a': '1', + 'f': 1.2, + 'in_': 1, + 'n': 'rob', + 'u': 'http://www.zope.org'} + + We also get an error if any data was invalid: + + >>> pprint(toargs(context, schema, + ... {'in': u'0', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z', + ... 'u': u'http://www.zope.org', 'a': u'1'})) + Traceback (most recent call last): + ... + ConfigurationError: ('Invalid value for', 'in', '0') """ data = dict(data) args = {} @@ -892,7 +1718,8 @@ def expand_action(discriminator, callable=None, args=(), kw=None, return action def resolveConflicts(actions): - """Resolve conflicting actions + """ + Resolve conflicting actions. Given an actions list, identify and try to resolve conflicting actions. Actions conflict if they have the same non-None discriminator. -- cgit v1.2.1 From 8da1212e684d9fa799420d756458ea055da1e384 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 27 Sep 2018 08:03:53 -0500 Subject: Unify docs for xmlconfig.rst/py back into .py --- docs/api/exceptions.rst | 9 +- docs/api/xmlconfig.rst | 214 +----------------------------------- src/zope/configuration/xmlconfig.py | 172 +++++++++++++++++++++++++++-- 3 files changed, 171 insertions(+), 224 deletions(-) diff --git a/docs/api/exceptions.rst b/docs/api/exceptions.rst index 73fb127..dd324db 100644 --- a/docs/api/exceptions.rst +++ b/docs/api/exceptions.rst @@ -1,6 +1,5 @@ -:mod:`zope.configuration.exceptions` -==================================== +=============================== + zope.configuration.exceptions +=============================== -.. module:: zope.configuration.exceptions - -.. autoclass:: ConfigurationError +.. automodule:: zope.configuration.exceptions diff --git a/docs/api/xmlconfig.rst b/docs/api/xmlconfig.rst index 63b94f5..a76b9c8 100644 --- a/docs/api/xmlconfig.rst +++ b/docs/api/xmlconfig.rst @@ -1,211 +1,5 @@ -:mod:`zope.configuration.xmlconfig` -=================================== +============================== + zope.configuration.xmlconfig +============================== -.. module:: zope.configuration.xmlconfig - -.. autoclass:: ZopeXMLConfigurationError - - Example - - .. doctest:: - - >>> from zope.configuration.xmlconfig import ZopeXMLConfigurationError - >>> v = ZopeXMLConfigurationError("blah", AttributeError, "xxx") - >>> print(v) - 'blah' - AttributeError: xxx - -.. autoclass:: ZopeSAXParseException - - Example - - .. doctest:: - - >>> from zope.configuration.xmlconfig import ZopeSAXParseException - >>> v = ZopeSAXParseException("foo.xml:12:3:Not well formed") - >>> print(v) - File "foo.xml", line 12.3, Not well formed - -.. autoclass:: ParserInfo - :members: - :member-order: bysource - - Example - - .. doctest:: - - >>> from zope.configuration.xmlconfig import ParserInfo - >>> info = ParserInfo('tests//sample.zcml', 1, 0) - >>> info - File "tests//sample.zcml", line 1.0 - - >>> print(info) - File "tests//sample.zcml", line 1.0 - - >>> info.characters("blah\\n") - >>> info.characters("blah") - >>> info.text - 'blah\\nblah' - - >>> info.end(7, 0) - >>> info - File "tests//sample.zcml", line 1.0-7.0 - - >>> print(info) - File "tests//sample.zcml", line 1.0-7.0 - - - - - - - -.. autoclass:: ConfigurationHandler - - .. automethod:: evaluateCondition - - The ``have`` and ``not-have`` verbs each take one argument: the name - of a feature: - - .. doctest:: - - >>> from zope.configuration.config import ConfigurationContext - >>> from zope.configuration.xmlconfig import ConfigurationHandler - >>> context = ConfigurationContext() - >>> context.provideFeature('apidoc') - >>> c = ConfigurationHandler(context, testing=True) - >>> c.evaluateCondition("have apidoc") - True - >>> c.evaluateCondition("not-have apidoc") - False - >>> c.evaluateCondition("have onlinehelp") - False - >>> c.evaluateCondition("not-have onlinehelp") - True - - Ill-formed expressions raise an error: - - .. doctest:: - - >>> c.evaluateCondition("want apidoc") - Traceback (most recent call last): - ... - ValueError: Invalid ZCML condition: 'want apidoc' - - >>> c.evaluateCondition("have x y") - Traceback (most recent call last): - ... - ValueError: Only one feature allowed: 'have x y' - - >>> c.evaluateCondition("have") - Traceback (most recent call last): - ... - ValueError: Feature name missing: 'have' - - - The ``installed`` and ``not-installed`` verbs each take one argument: - the dotted name of a pacakge. - - If the pacakge is found, in other words, can be imported, - then the condition will return true / false: - - .. doctest:: - - >>> context = ConfigurationContext() - >>> c = ConfigurationHandler(context, testing=True) - >>> c.evaluateCondition('installed zope.interface') - True - >>> c.evaluateCondition('not-installed zope.interface') - False - >>> c.evaluateCondition('installed zope.foo') - False - >>> c.evaluateCondition('not-installed zope.foo') - True - - Ill-formed expressions raise an error: - - .. doctest:: - - >>> c.evaluateCondition("installed foo bar") - Traceback (most recent call last): - ... - ValueError: Only one package allowed: 'installed foo bar' - - >>> c.evaluateCondition("installed") - Traceback (most recent call last): - ... - ValueError: Package name missing: 'installed' - -.. autofunction:: processxmlfile - -.. autofunction:: openInOrPlain - - For example, the tests/samplepackage dirextory has files: - - - configure.zcml - - configure.zcml.in - - foo.zcml.in - - If we open configure.zcml, we'll get that file: - - .. doctest:: - - >>> import os - >>> from zope.configuration.xmlconfig import __file__ - >>> from zope.configuration.xmlconfig import openInOrPlain - >>> here = os.path.dirname(__file__) - >>> path = os.path.join(here, 'tests', 'samplepackage', 'configure.zcml') - >>> f = openInOrPlain(path) - >>> f.name[-14:] - 'configure.zcml' - >>> f.close() - - But if we open foo.zcml, we'll get foo.zcml.in, since there isn't a - foo.zcml: - - .. doctest:: - - >>> path = os.path.join(here, 'tests', 'samplepackage', 'foo.zcml') - >>> f = openInOrPlain(path) - >>> f.name[-11:] - 'foo.zcml.in' - >>> f.close() - - Make sure other IOErrors are re-raised. We need to do this in a - try-except block because different errors are raised on Windows and - on Linux. - - .. doctest:: - - >>> try: - ... f = openInOrPlain('.') - ... except IOError: - ... print("passed") - ... else: - ... print("failed") - passed - -.. autointerface:: IInclude - :members: - :member-order: bysource - -.. autofunction:: include - -.. autofunction:: exclude - -.. autofunction:: includeOverrides - -.. autofunction:: registerCommonDirectives - -.. autofunction:: file - -.. autofunction:: string - -.. autoclass:: XMLConfig - :members: - :member-order: bysource - -.. autofunction:: xmlconfig - -.. autofunction:: testxmlconfig +.. automodule:: zope.configuration.xmlconfig diff --git a/src/zope/configuration/xmlconfig.py b/src/zope/configuration/xmlconfig.py index 18a2bbe..18f731f 100644 --- a/src/zope/configuration/xmlconfig.py +++ b/src/zope/configuration/xmlconfig.py @@ -71,10 +71,19 @@ ZCML_CONDITION = (ZCML_NAMESPACE, u"condition") class ZopeXMLConfigurationError(ConfigurationError): - """Zope XML Configuration error + """ + Zope XML Configuration error + + These errors are wrappers for other errors. They include + configuration info and the wrapped error type and value. + + Example - These errors are wrappers for other errors. They include configuration - info and the wrapped error type and value. + >>> from zope.configuration.xmlconfig import ZopeXMLConfigurationError + >>> v = ZopeXMLConfigurationError("blah", AttributeError, "xxx") + >>> print(v) + 'blah' + AttributeError: xxx """ def __init__(self, info, etype, evalue): self.info, self.etype, self.evalue = info, etype, evalue @@ -86,7 +95,15 @@ class ZopeXMLConfigurationError(ConfigurationError): repr(self.info), self.etype.__name__, self.evalue) class ZopeSAXParseException(ConfigurationError): - """Sax Parser errors, reformatted in an emacs friendly way + """ + Sax Parser errors, reformatted in an emacs friendly way. + + Example + + >>> from zope.configuration.xmlconfig import ZopeSAXParseException + >>> v = ZopeSAXParseException("foo.xml:12:3:Not well formed") + >>> print(v) + File "foo.xml", line 12.3, Not well formed """ def __init__(self, v): self._v = v @@ -100,10 +117,40 @@ class ZopeSAXParseException(ConfigurationError): return str(v) class ParserInfo(object): - """Information about a directive based on parser data + """ + Information about a directive based on parser data This includes the directive location, as well as text data contained in the directive. + + Example + + >>> from zope.configuration.xmlconfig import ParserInfo + >>> info = ParserInfo('tests//sample.zcml', 1, 0) + >>> info + File "tests//sample.zcml", line 1.0 + + >>> print(info) + File "tests//sample.zcml", line 1.0 + + >>> info.characters("blah\\n") + >>> info.characters("blah") + >>> info.text + 'blah\\nblah' + + >>> info.end(7, 0) + >>> info + File "tests//sample.zcml", line 1.0-7.0 + + >>> print(info) + File "tests//sample.zcml", line 1.0-7.0 + + + + + + """ text = u'' @@ -173,7 +220,8 @@ class ParserInfo(object): class ConfigurationHandler(ContentHandler): - """Interface to the xml parser + """ + Interface to the xml parser Translate parser events into calls into the configuration system. """ @@ -231,12 +279,76 @@ class ConfigurationHandler(ContentHandler): self.context.setInfo(info) def evaluateCondition(self, expression): - """Evaluate a ZCML condition. + """ + Evaluate a ZCML condition. ``expression`` is a string of the form "verb arguments". Currently the supported verbs are ``have``, ``not-have``, ``installed`` and ``not-installed``. + + The ``have`` and ``not-have`` verbs each take one argument: + the name of a feature: + + >>> from zope.configuration.config import ConfigurationContext + >>> from zope.configuration.xmlconfig import ConfigurationHandler + >>> context = ConfigurationContext() + >>> context.provideFeature('apidoc') + >>> c = ConfigurationHandler(context, testing=True) + >>> c.evaluateCondition("have apidoc") + True + >>> c.evaluateCondition("not-have apidoc") + False + >>> c.evaluateCondition("have onlinehelp") + False + >>> c.evaluateCondition("not-have onlinehelp") + True + + Ill-formed expressions raise an error: + + >>> c.evaluateCondition("want apidoc") + Traceback (most recent call last): + ... + ValueError: Invalid ZCML condition: 'want apidoc' + + >>> c.evaluateCondition("have x y") + Traceback (most recent call last): + ... + ValueError: Only one feature allowed: 'have x y' + + >>> c.evaluateCondition("have") + Traceback (most recent call last): + ... + ValueError: Feature name missing: 'have' + + The ``installed`` and ``not-installed`` verbs each take one + argument: the dotted name of a pacakge. + + If the pacakge is found, in other words, can be imported, then + the condition will return true / false: + + >>> context = ConfigurationContext() + >>> c = ConfigurationHandler(context, testing=True) + >>> c.evaluateCondition('installed zope.interface') + True + >>> c.evaluateCondition('not-installed zope.interface') + False + >>> c.evaluateCondition('installed zope.foo') + False + >>> c.evaluateCondition('not-installed zope.foo') + True + + Ill-formed expressions raise an error: + + >>> c.evaluateCondition("installed foo bar") + Traceback (most recent call last): + ... + ValueError: Only one package allowed: 'installed foo bar' + + >>> c.evaluateCondition("installed") + Traceback (most recent call last): + ... + ValueError: Package name missing: 'installed' """ arguments = expression.split(None) verb = arguments.pop(0) @@ -313,11 +425,53 @@ def processxmlfile(file, context, testing=False): def openInOrPlain(filename): - """Open a file, falling back to filename.in. + """ + Open a file, falling back to filename.in. If the requested file does not exist and filename.in does, fall - back to filename.in. If opening the original filename fails for + back to filename.in. If opening the original filename fails for any other reason, allow the failure to propogate. + + For example, the tests/samplepackage dirextory has files: + + - configure.zcml + + - configure.zcml.in + + - foo.zcml.in + + If we open configure.zcml, we'll get that file: + + >>> import os + >>> from zope.configuration.xmlconfig import __file__ + >>> from zope.configuration.xmlconfig import openInOrPlain + >>> here = os.path.dirname(__file__) + >>> path = os.path.join(here, 'tests', 'samplepackage', 'configure.zcml') + >>> f = openInOrPlain(path) + >>> f.name[-14:] + 'configure.zcml' + >>> f.close() + + But if we open foo.zcml, we'll get foo.zcml.in, since there isn't + a foo.zcml: + + >>> path = os.path.join(here, 'tests', 'samplepackage', 'foo.zcml') + >>> f = openInOrPlain(path) + >>> f.name[-11:] + 'foo.zcml.in' + >>> f.close() + + Make sure other IOErrors are re-raised. We need to do this in a + try-except block because different errors are raised on Windows + and on Linux. + + >>> try: + ... f = openInOrPlain('.') + ... except IOError: + ... print("passed") + ... else: + ... print("failed") + passed """ try: return open(filename) -- cgit v1.2.1 From e313ece59efb2bc4c5928377376b170ff4ba3f94 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Fri, 28 Sep 2018 06:41:08 -0500 Subject: Docstring cleanups from review. --- src/zope/configuration/config.py | 16 +++++----- src/zope/configuration/fields.py | 52 +++++++++++++++---------------- src/zope/configuration/tests/test_docs.py | 3 +- src/zope/configuration/xmlconfig.py | 8 ++--- 4 files changed, 39 insertions(+), 40 deletions(-) diff --git a/src/zope/configuration/config.py b/src/zope/configuration/config.py index 2089ace..e7ba289 100644 --- a/src/zope/configuration/config.py +++ b/src/zope/configuration/config.py @@ -360,7 +360,7 @@ class ConfigurationContext(object): >>> d = os.path.dirname(zope.configuration.__file__) >>> c.processFile('bar.zcml') True - >>> c.processFile('bar.zcml') + >>> c.processFile(os.path.join(d, 'bar.zcml')) False """ path = self.path(filename) @@ -397,7 +397,7 @@ class ConfigurationContext(object): >>> from zope.configuration.tests.directives import f >>> c.action(1, f, (1, ), {'x': 1}) >>> from pprint import PrettyPrinter - >>> pprint=PrettyPrinter(width=60).pprint + >>> pprint = PrettyPrinter(width=60).pprint >>> pprint(c.actions) [{'args': (1,), 'callable': f, @@ -550,7 +550,7 @@ class ConfigurationAdapterRegistry(object): >>> from zope.configuration.config import ConfigurationMachine >>> r = ConfigurationAdapterRegistry() >>> c = ConfigurationMachine() - >>> r.factory(c, ('http://www.zope.com','xxx')) + >>> r.factory(c, ('http://www.zope.com', 'xxx')) Traceback (most recent call last): ... ConfigurationError: ('Unknown directive', 'http://www.zope.com', 'xxx') @@ -558,14 +558,14 @@ class ConfigurationAdapterRegistry(object): ... pass >>> r.register(IConfigurationContext, ('http://www.zope.com', 'xxx'), f) - >>> r.factory(c, ('http://www.zope.com','xxx')) is f + >>> r.factory(c, ('http://www.zope.com', 'xxx')) is f True - >>> r.factory(c, ('http://www.zope.com','yyy')) is f + >>> r.factory(c, ('http://www.zope.com', 'yyy')) is f Traceback (most recent call last): ... ConfigurationError: ('Unknown directive', 'http://www.zope.com', 'yyy') >>> r.register(IConfigurationContext, 'yyy', f) - >>> r.factory(c, ('http://www.zope.com','yyy')) is f + >>> r.factory(c, ('http://www.zope.com', 'yyy')) is f True Test the documentation feature: @@ -714,7 +714,7 @@ class ConfigurationMachine(ConfigurationAdapterRegistry, ConfigurationContext): >>> from zope.configuration.config import ConfigurationMachine >>> output = [] - >>> def f(*a, **k): #* syntax highlighting + >>> def f(*a, **k): ... output.append(('f', a, k)) >>> context = ConfigurationMachine() >>> context.actions = [ @@ -1580,7 +1580,7 @@ def toargs(context, schema, data): >>> context = ConfigurationMachine() >>> from pprint import PrettyPrinter - >>> pprint=PrettyPrinter(width=50).pprint + >>> pprint = PrettyPrinter(width=50).pprint >>> pprint(toargs(context, schema, ... {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z', diff --git a/src/zope/configuration/fields.py b/src/zope/configuration/fields.py index 85a5ff7..c4af0ad 100644 --- a/src/zope/configuration/fields.py +++ b/src/zope/configuration/fields.py @@ -44,7 +44,7 @@ __all__ = [ class PythonIdentifier(schema_PythonIdentifier): - """ + r""" This class is like `zope.schema.PythonIdentifier`. @@ -70,14 +70,14 @@ class PythonIdentifier(schema_PythonIdentifier): >>> for value in (u'foo', u'foo3', u'foo_', u'_foo3', u'foo_3', u'foo3_'): ... _ = field.fromUnicode(value) >>> from zope.schema import ValidationError - >>> for value in (u'3foo', u'foo:', u'\\\\', u''): + >>> for value in (u'3foo', u'foo:', u'\\', u''): ... try: ... field.fromUnicode(value) ... except ValidationError: ... print('Validation Error ' + repr(value)) Validation Error '3foo' Validation Error 'foo:' - Validation Error '\\\\' + Validation Error '\\' Validation Error '' .. versionchanged:: 4.2.0 @@ -112,7 +112,7 @@ class GlobalObject(Field): self.value_type.validate(value) def fromUnicode(self, value): - """ + r""" Find and return the module global at the path *value*. >>> d = {'x': 1, 'y': 42, 'z': 'zope'} @@ -127,7 +127,7 @@ class GlobalObject(Field): >>> gg = g.bind(fake) >>> gg.fromUnicode("x") 1 - >>> gg.fromUnicode(" x \\n ") + >>> gg.fromUnicode(" x \n ") 1 >>> gg.fromUnicode("y") 42 @@ -222,7 +222,7 @@ class Tokens(List): """ def fromUnicode(self, value): - """ + r""" Split the input string and convert it to *value_type*. Consider GlobalObject tokens: @@ -239,7 +239,7 @@ class Tokens(List): >>> from zope.configuration.fields import GlobalObject >>> g = Tokens(value_type=GlobalObject()) >>> gg = g.bind(fake) - >>> gg.fromUnicode(" \\n x y z \\n") + >>> gg.fromUnicode(" \n x y z \n") [1, 42, 'zope'] >>> from zope.schema import Int @@ -306,7 +306,7 @@ class Path(Text): """ def fromUnicode(self, value): - """ + r""" Convert the input path to a normalized, absolute path. Let's look at an example: @@ -314,7 +314,7 @@ class Path(Text): First, we need a "context" for the field that has a path function for converting relative path to an absolute path. - We'll be careful to do this in an os-independent fashion. + We'll be careful to do this in an operating system independent fashion. >>> from zope.configuration.fields import Path >>> class FauxContext(object): @@ -333,7 +333,7 @@ class Path(Text): This should also work with extra spaces around the path: - >>> p = " \\n %s \\n\\n " % p + >>> p = " \n %s \n\n " % p >>> n = field.fromUnicode(p) >>> n.split(os.sep) ['', 'a', 'b'] @@ -393,7 +393,7 @@ class Bool(schema_Bool): Do not confuse this with :class:`zope.schema.Bool`. That class will only parse ``"True"`` and ``"true"`` as `True` values. Any other value will silently be accepted as - `False`. This class raises a validation error for unrecognized + `False`. This class raises a validation error for unrecognized input. """ @@ -404,19 +404,19 @@ class Bool(schema_Bool): Example: - >>> from zope.configuration.fields import Bool - >>> Bool().fromUnicode(u"yes") - True - >>> Bool().fromUnicode(u"y") - True - >>> Bool().fromUnicode(u"true") - True - >>> Bool().fromUnicode(u"no") - False - >>> Bool().fromUnicode(u"surprise") - Traceback (most recent call last): - ... - zope.schema._bootstrapinterfaces.InvalidValue + >>> from zope.configuration.fields import Bool + >>> Bool().fromUnicode(u"yes") + True + >>> Bool().fromUnicode(u"y") + True + >>> Bool().fromUnicode(u"true") + True + >>> Bool().fromUnicode(u"no") + False + >>> Bool().fromUnicode(u"surprise") + Traceback (most recent call last): + ... + zope.schema._bootstrapinterfaces.InvalidValue """ value = value.lower() if value in ('1', 'true', 'yes', 't', 'y'): @@ -459,7 +459,7 @@ class MessageID(Text): has been issued >>> warned = None - >>> def fakewarn(*args, **kw): #* syntax highlighting + >>> def fakewarn(*args, **kw): ... global warned ... warned = args @@ -529,7 +529,7 @@ class MessageID(Text): domain = 'untranslated' warnings.warn( "You did not specify an i18n translation domain for the "\ - "'%s' field in %s" % (self.getName(), context.info.file ) + "'%s' field in %s" % (self.getName(), context.info.file) ) if not isinstance(domain, str): # IZopeConfigure specifies i18n_domain as a BytesLine, but that's diff --git a/src/zope/configuration/tests/test_docs.py b/src/zope/configuration/tests/test_docs.py index e51a095..3452e3b 100644 --- a/src/zope/configuration/tests/test_docs.py +++ b/src/zope/configuration/tests/test_docs.py @@ -99,10 +99,9 @@ def test_suite(): for mod_name in api_to_test: mod_name = 'zope.configuration.' + mod_name - __import__(mod_name) suite.addTest( doctest.DocTestSuite( - sys.modules[mod_name], + mod_name, checker=checker, optionflags=optionflags ) diff --git a/src/zope/configuration/xmlconfig.py b/src/zope/configuration/xmlconfig.py index 18f731f..e979e13 100644 --- a/src/zope/configuration/xmlconfig.py +++ b/src/zope/configuration/xmlconfig.py @@ -117,7 +117,7 @@ class ZopeSAXParseException(ConfigurationError): return str(v) class ParserInfo(object): - """ + r""" Information about a directive based on parser data This includes the directive location, as well as text data @@ -133,10 +133,10 @@ class ParserInfo(object): >>> print(info) File "tests//sample.zcml", line 1.0 - >>> info.characters("blah\\n") + >>> info.characters("blah\n") >>> info.characters("blah") >>> info.text - 'blah\\nblah' + 'blah\nblah' >>> info.end(7, 0) >>> info @@ -221,7 +221,7 @@ class ParserInfo(object): class ConfigurationHandler(ContentHandler): """ - Interface to the xml parser + Interface to the XML parser Translate parser events into calls into the configuration system. """ -- cgit v1.2.1