diff --git a/.travis.yml b/.travis.yml index c5d1671..bddb890 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,9 +15,11 @@ matrix: install: - pip install -U pip setuptools - pip install -U coverage coveralls + - pip install -U flake8 - pip install -U -e .[test] script: - coverage run -m zope.testrunner --test-path=. + - find setup.py ZConfig -name '*.py' | xargs flake8 --isolated notifications: email: false after_success: diff --git a/ZConfig/__init__.py b/ZConfig/__init__.py index deba14e..19634d4 100644 --- a/ZConfig/__init__.py +++ b/ZConfig/__init__.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2002, 2003 Zope Foundation and Contributors. +# Copyright (c) 2002, 2003, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -33,13 +33,17 @@ """ __docformat__ = "reStructuredText" -version_info = (3, 0) -__version__ = ".".join([str(n) for n in version_info]) +from ZConfig._compat import TextIO +import ZConfig.loader -from ZConfig.loader import loadConfig, loadConfigFile -from ZConfig.loader import loadSchema, loadSchemaFile +loadConfigFile = ZConfig.loader.loadConfigFile +loadSchemaFile = ZConfig.loader.loadSchemaFile +loadConfig = ZConfig.loader.loadConfig +loadSchema = ZConfig.loader.loadSchema -from ZConfig._compat import TextIO + +version_info = (3, 0) +__version__ = ".".join([str(n) for n in version_info]) class ConfigurationError(Exception): @@ -48,8 +52,8 @@ class ConfigurationError(Exception): All instances provide a ``message`` attribute that describes the specific error, and a ``url`` attribute that gives the URL of the resource the error was located in, or ``None``. - """ + """ # The 'message' attribute was deprecated for BaseException with # Python 2.6; here we create descriptor properties to continue using it @@ -214,12 +218,14 @@ def __init__(self, source, name, url=None, lineno=None): def configureLoggers(text): """Configure one or more loggers from configuration text.""" - schema = loadSchemaFile(TextIO(""" + + schema = ZConfig.loader.loadSchemaFile(TextIO(""" """)) - for factory in loadConfigFile(schema, TextIO(text))[0].loggers: + config, _ = ZConfig.loader.loadConfigFile(schema, TextIO(text)) + for factory in config.loggers: factory() diff --git a/ZConfig/_compat.py b/ZConfig/_compat.py index c0068fd..c377e35 100644 --- a/ZConfig/_compat.py +++ b/ZConfig/_compat.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2016 Zope Foundation and Contributors. +# Copyright (c) 2016, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -12,28 +12,36 @@ # ############################################################################## +from io import StringIO +from io import BytesIO +import abc import sys + PY3 = sys.version_info[0] >= 3 # Native string object IO if str is not bytes: from io import StringIO as NStringIO string_types = str + text_type = str + have_unicode = False else: # Python 2 from io import BytesIO as NStringIO - string_types = str, unicode + string_types = str, unicode # noqa: F821 + text_type = string_types[1] # avoid direct reference! + have_unicode = True + NStringIO = NStringIO -from io import StringIO -from io import BytesIO def TextIO(text): - "Return StringIO or BytesIO as appropriate" + """Return StringIO or BytesIO as appropriate""" return BytesIO(text) if isinstance(text, bytes) else StringIO(text) + try: import urllib2 except ImportError: @@ -58,24 +66,23 @@ def TextIO(text): urlparse = urlparse -if PY3: # pragma: no cover + +if PY3: # pragma: no cover import builtins exec_ = getattr(builtins, "exec") - text_type = str binary_type = bytes maxsize = sys.maxsize - def reraise(tp, value, tb=None): #pragma NO COVER + def reraise(tp, value, tb=None): # pragma NO COVER if value.__traceback__ is not tb: raise value.with_traceback(tb) raise value -else: # pragma: no cover - text_type = unicode +else: # pragma: no cover binary_type = bytes maxsize = sys.maxint - def exec_(code, globs=None, locs=None): #pragma NO COVER + def exec_(code, globs=None, locs=None): # pragma NO COVER """Execute code in a namespace.""" if globs is None: frame = sys._getframe(1) @@ -93,9 +100,9 @@ def exec_(code, globs=None, locs=None): #pragma NO COVER def raise_with_same_tb(exception): - "Raise an exception having the current traceback (if there is one)" - reraise(type(exception), exception, sys.exc_info()[2]) + """Raise an exception having the current traceback (if there is one)""" + reraise(type(exception), exception, sys.exc_info()[2]) + -import abc # workaround the metaclass diff in Py2/Py3 AbstractBaseClass = abc.ABCMeta('AbstractBaseClass', (object,), {}) diff --git a/ZConfig/_schema_utils.py b/ZConfig/_schema_utils.py index ec3b707..d134614 100755 --- a/ZConfig/_schema_utils.py +++ b/ZConfig/_schema_utils.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2017 Zope Corporation and Contributors. +# Copyright (c) 2017, 2018 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -39,6 +39,9 @@ from ZConfig.info import AbstractType +MARKER = object() + + class _VisitorBuilder(object): def __init__(self): @@ -50,7 +53,6 @@ def dec(func): return func return dec -MARKER = object() class AbstractSchemaFormatter(AbstractBaseClass): @@ -134,27 +136,30 @@ def body(self): class AbstractSchemaPrinter(AbstractBaseClass): - - def __init__(self, schema, stream=None, allowed_names=(), excluded_names=()): + def __init__(self, schema, stream=None, + allowed_names=(), excluded_names=()): self.schema = schema stream = stream or sys.stdout self._explained = set() self._seen_typenames = set() self.fmt = self._schema_formatter(schema, stream) - def _make_predicate(names): names = {x.lower() for x in names} + def predicate(name_info): name, _ = name_info return name and name.lower() in names + return predicate def _make_filter(names, filt): iter_all = self._iter_schema_items pred = _make_predicate(names) + def it(): return filt(pred, iter_all()) + return it if allowed_names: @@ -162,7 +167,8 @@ def it(): if excluded_names: excluded_names = {x.lower() for x in excluded_names} - self._iter_schema_items = _make_filter(excluded_names, ifilterfalse) + self._iter_schema_items = _make_filter(excluded_names, + ifilterfalse) self._included = lambda st: st.name not in excluded_names @abstractmethod @@ -173,7 +179,7 @@ def _included(self, st): return True def _explain(self, st): - if st.name in self._explained: # pragma: no cover + if st.name in self._explained: # pragma: no cover return self._explained.add(st.name) @@ -192,14 +198,14 @@ def _iter_schema_items(self): def everything(): return itertools.chain(self.schema.itertypes(), self.schema) - # The abstract types tend to be the most important. Since - # we only document a concrete type the first time we find it, - # and we can find extensions of abstract types beneath - # the abstract type which is itself buried under a concrete section, - # all the different permutations would be only documented once under - # that section. By exposing these first, they get documented at the top-level, - # and each concrete section that uses the abstract type gets a reference - # to it. + # The abstract types tend to be the most important. Since we + # only document a concrete type the first time we find it, and + # we can find extensions of abstract types beneath the abstract + # type which is itself buried under a concrete section, all the + # different permutations would be only documented once under + # that section. By exposing these first, they get documented at + # the top-level, and each concrete section that uses the + # abstract type gets a reference to it. def abstract_sections(base): for name, info in base: @@ -219,7 +225,7 @@ def printSchema(self): self.buildSchema() def buildSchema(self): - seen = set() # prevent duplicates at the top-level + seen = set() # prevent duplicates at the top-level # as we find multiple abstract types with self.fmt.body(): with self.fmt.item_list(): @@ -258,14 +264,14 @@ def _visit_SectionType(self, name, info): with self.fmt.item_list(): for sub in info: - self.visit(*sub) # pragma: no cover - + self.visit(*sub) # pragma: no cover @TypeVisitor(SectionInfo) def _visit_SectionInfo(self, name, info): st = info.sectiontype if st.isabstract(): - with self.fmt.describing(info.description, lambda: self._explain(st)): + with self.fmt.describing(info.description, + lambda: self._explain(st)): self.fmt.abstract_name(st.name) self.fmt.concrete_name(info.name) @@ -283,7 +289,8 @@ def _visit_SectionInfo(self, name, info): @TypeVisitor(AbstractType) def _visit_AbstractType(self, name, info): - with self.fmt.describing(info.description, lambda: self._explain(info)): + with self.fmt.describing(info.description, + lambda: self._explain(info)): self.fmt.abstract_name(info.name) def _visit_default(self, name, info): @@ -305,8 +312,9 @@ def load_schema(schema, package, package_file): if not package: schema_reader = argparse.FileType('r')(schema) else: - schema_template = "" % ( - schema, package_file or 'component.xml') + schema_template = ( + "" + % (schema, package_file or 'component.xml')) from ZConfig._compat import TextIO schema_reader = TextIO(schema_template) diff --git a/ZConfig/cfgparser.py b/ZConfig/cfgparser.py index d9337f7..d20e141 100644 --- a/ZConfig/cfgparser.py +++ b/ZConfig/cfgparser.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2002, 2003 Zope Foundation and Contributors. +# Copyright (c) 2002, 2003, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -13,12 +13,26 @@ ############################################################################## """Configuration parser.""" +import re + import ZConfig import ZConfig.url from ZConfig.substitution import isname, substitute from ZConfig._compat import raise_with_same_tb + +# _name_re does not allow "(" or ")" for historical reasons. Though +# the restriction could be lifted, there seems no need to do so. +_name_re = r"[^\s()]+" +_keyvalue_rx = re.compile(r"(?P%s)\s*(?P[^\s].*)?$" + % _name_re) +_section_start_rx = re.compile(r"(?P%s)" + r"(?:\s+(?P%s))?" + r"$" + % (_name_re, _name_re)) + + class ZConfigParser(object): __slots__ = ('resource', 'context', 'lineno', @@ -172,21 +186,7 @@ def error(self, message): ZConfig.ConfigurationSyntaxError( message, self.url, self.lineno)) - def _normalize_case(self, string): # This method is factored out solely to allow subclasses to modify # the behavior of the parser. return string.lower() - - -import re -# _name_re does not allow "(" or ")" for historical reasons. Though -# the restriction could be lifted, there seems no need to do so. -_name_re = r"[^\s()]+" -_keyvalue_rx = re.compile(r"(?P%s)\s*(?P[^\s].*)?$" - % _name_re) -_section_start_rx = re.compile(r"(?P%s)" - r"(?:\s+(?P%s))?" - r"$" - % (_name_re, _name_re)) -del re diff --git a/ZConfig/cmdline.py b/ZConfig/cmdline.py index 084f613..74c0a48 100644 --- a/ZConfig/cmdline.py +++ b/ZConfig/cmdline.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2003 Zope Foundation and Contributors. +# Copyright (c) 2003, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -22,6 +22,7 @@ class from the :mod:`ZConfig.loader` module. This provides support for Each setting is given by a value specifier string, as described by :meth:`ExtendedConfigLoader.addOption`. + """ import ZConfig @@ -30,6 +31,7 @@ class from the :mod:`ZConfig.loader` module. This provides support for from ZConfig._compat import raise_with_same_tb + class ExtendedConfigLoader(ZConfig.loader.ConfigLoader): """A :class:`~.ConfigLoader` subclass that adds support for command-line overrides. @@ -153,7 +155,7 @@ def get_section_info(self, type_, name): bk = self.basic_key(s, pos) if name and self._normalize_case(s) == name: L.append((optpath[1:], val, pos)) - elif bk == type_: # pragma: no cover + elif bk == type_: # pragma: no cover L.append((optpath[1:], val, pos)) else: R.append(item) @@ -206,6 +208,7 @@ def finish(self): self.finish_optionbag() return ZConfig.matcher.SectionMatcher.finish(self) + class ExtendedSchemaMatcher(MatcherMixin, ZConfig.matcher.SchemaMatcher): def finish(self): self.finish_optionbag() diff --git a/ZConfig/components/basic/mapping.py b/ZConfig/components/basic/mapping.py index e30c289..6122406 100644 --- a/ZConfig/components/basic/mapping.py +++ b/ZConfig/components/basic/mapping.py @@ -14,5 +14,6 @@ """Python datatype for the ZConfig.components.basic.mapping section type.""" + def mapping(section): return section.mapping diff --git a/ZConfig/components/basic/tests/test_mapping.py b/ZConfig/components/basic/tests/test_mapping.py index 85e0890..63bf27c 100644 --- a/ZConfig/components/basic/tests/test_mapping.py +++ b/ZConfig/components/basic/tests/test_mapping.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2003 Zope Foundation and Contributors. +# Copyright (c) 2003, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -13,7 +13,8 @@ ############################################################################## """Tests of the 'basic' section types provided as part of -ZConfig.components.basic.""" +ZConfig.components.basic. +""" import ZConfig.tests.support import unittest @@ -42,8 +43,8 @@ ''' -class BasicSectionTypeTestCase( - ZConfig.tests.support.TestHelper, unittest.TestCase): +class BasicSectionTypeTestCase(ZConfig.tests.support.TestHelper, + unittest.TestCase): schema = None @@ -87,5 +88,6 @@ def test_derived_dict(self): def test_suite(): return unittest.defaultTestLoader.loadTestsFromName(__name__) + if __name__ == '__main__': unittest.main(defaultTest='test_suite') diff --git a/ZConfig/components/logger/datatypes.py b/ZConfig/components/logger/datatypes.py index 8f7ff37..d521430 100644 --- a/ZConfig/components/logger/datatypes.py +++ b/ZConfig/components/logger/datatypes.py @@ -28,6 +28,7 @@ "notset": 0, } + def logging_level(value): s = str(value).lower() if s in _logging_levels: diff --git a/ZConfig/components/logger/factory.py b/ZConfig/components/logger/factory.py index dfe57b5..ce4b813 100644 --- a/ZConfig/components/logger/factory.py +++ b/ZConfig/components/logger/factory.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2002 Zope Foundation and Contributors. +# Copyright (c) 2002, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -12,12 +12,14 @@ # ############################################################################## -_marker = object() - from abc import abstractmethod from ZConfig._compat import AbstractBaseClass + +_marker = object() + + class Factory(AbstractBaseClass): """Generic wrapper for instance construction. @@ -27,7 +29,9 @@ class Factory(AbstractBaseClass): The instance is created using the factory's create() method, which must be overriden by subclasses. + """ + def __init__(self): self.instance = _marker diff --git a/ZConfig/components/logger/handlers.py b/ZConfig/components/logger/handlers.py index 1c05f70..4b9c2e8 100644 --- a/ZConfig/components/logger/handlers.py +++ b/ZConfig/components/logger/handlers.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2003 Zope Foundation and Contributors. +# Copyright (c) 2003, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -20,6 +20,7 @@ from ZConfig.components.logger.factory import Factory + _log_format_variables = { 'name': '', 'levelno': '3', @@ -37,6 +38,7 @@ 'process': 1, } + def log_format(value): value = ctrl_char_insert(value) try: @@ -48,14 +50,17 @@ def log_format(value): raise ValueError('Invalid log format string %s' % value) return value + _control_char_rewrites = {r'\n': '\n', r'\t': '\t', r'\b': '\b', r'\f': '\f', r'\r': '\r'}.items() + def ctrl_char_insert(value): for pattern, replacement in _control_char_rewrites: value = value.replace(pattern, replacement) return value + def resolve(name): """Given a dotted name, returns an object imported from a Python module.""" name = name.split('.') @@ -70,7 +75,9 @@ def resolve(name): found = getattr(found, n) return found + class HandlerFactory(Factory): + def __init__(self, section): Factory.__init__(self) self.section = section @@ -90,10 +97,12 @@ def create(self): logger.setLevel(self.section.level) return logger - def getLevel(self): # pragma: no cover Is this used? + def getLevel(self): # pragma: no cover Is this used? return self.section.level + class FileHandlerFactory(HandlerFactory): + def create_loghandler(self): from ZConfig.components.logger import loghandler path = self.section.path @@ -130,6 +139,7 @@ def create_loghandler(self): handler = loghandler.FileHandler(path) return handler + _syslog_facilities = { "auth": 1, "authpriv": 1, @@ -153,6 +163,7 @@ def create_loghandler(self): "local7": 1, } + def syslog_facility(value): value = value.lower() if value not in _syslog_facilities: @@ -160,17 +171,22 @@ def syslog_facility(value): raise ValueError("Syslog facility must be one of " + ", ".join(L)) return value + class SyslogHandlerFactory(HandlerFactory): + def create_loghandler(self): from ZConfig.components.logger import loghandler return loghandler.SysLogHandler(self.section.address.address, self.section.facility) + class Win32EventLogFactory(HandlerFactory): + def create_loghandler(self): from ZConfig.components.logger import loghandler return loghandler.Win32EventLogHandler(self.section.appname) + def http_handler_url(value): scheme, netloc, path, param, query, fragment = urlparse.urlparse(value) if scheme != 'http': @@ -191,6 +207,7 @@ def http_handler_url(value): q.append(fragment) return (netloc, path + ''.join(q)) + def get_or_post(value): value = value.upper() if value not in ('GET', 'POST'): @@ -198,13 +215,17 @@ def get_or_post(value): + repr(value)) return value + class HTTPHandlerFactory(HandlerFactory): + def create_loghandler(self): from ZConfig.components.logger import loghandler host, selector = self.section.url return loghandler.HTTPHandler(host, selector, self.section.method) + class SMTPHandlerFactory(HandlerFactory): + def create_loghandler(self): from ZConfig.components.logger import loghandler host, port = self.section.smtp_server diff --git a/ZConfig/components/logger/logger.py b/ZConfig/components/logger/logger.py index e786692..9cb2a12 100644 --- a/ZConfig/components/logger/logger.py +++ b/ZConfig/components/logger/logger.py @@ -25,6 +25,7 @@ class LoggerFactoryBase(Factory): to allow the app time to set an effective user). An instance of this wrapper is a callable which, when called, returns a logger object. + """ def __init__(self, section): @@ -55,6 +56,7 @@ def getLowestHandlerLevel(self): If all handlers and the logger itself have level==NOTSET, this returns NOTSET. + """ import logging lowest = self.level @@ -74,6 +76,7 @@ def reopen(self): factory directly; handlers for child loggers are not affected. (This can be considered a bug, but is sufficient at the moment.) + """ logger = self() for handler in logger.handlers: diff --git a/ZConfig/components/logger/loghandler.py b/ZConfig/components/logger/loghandler.py index 2d9c6bd..2652b28 100644 --- a/ZConfig/components/logger/loghandler.py +++ b/ZConfig/components/logger/loghandler.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2001 Zope Foundation and Contributors. +# Copyright (c) 2001, 2018 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. @@ -13,29 +13,25 @@ """Handlers which can plug into a PEP 282 logger.""" +import logging +import logging.handlers import os - import weakref -from logging import Handler, StreamHandler -from logging.handlers import RotatingFileHandler as _RotatingFileHandler -from logging.handlers import TimedRotatingFileHandler \ - as _TimedRotatingFileHandler -from logging.handlers import SysLogHandler, BufferingHandler -from logging.handlers import HTTPHandler, SMTPHandler -from logging.handlers import NTEventLogHandler as Win32EventLogHandler +from ZConfig._compat import maxsize -# Export these, they're used in handlers.py -SysLogHandler = SysLogHandler -HTTPHandler = HTTPHandler -SMTPHandler = SMTPHandler -Win32EventLogHandler = Win32EventLogHandler -from ZConfig._compat import maxsize +# Export these, they're used in handlers.py +StreamHandler = logging.StreamHandler +SysLogHandler = logging.handlers.SysLogHandler +HTTPHandler = logging.handlers.HTTPHandler +SMTPHandler = logging.handlers.SMTPHandler +Win32EventLogHandler = logging.handlers.NTEventLogHandler _reopenable_handlers = [] + def closeFiles(): """Reopen all logfiles managed by ZConfig configuration.""" while _reopenable_handlers: @@ -44,6 +40,7 @@ def closeFiles(): if h is not None: h.close() + def reopenFiles(): """Reopen all logfiles managed by ZConfig configuration.""" for wr in _reopenable_handlers[:]: @@ -56,6 +53,7 @@ def reopenFiles(): else: h.reopen() + def _remove_from_reopenable(wr): try: _reopenable_handlers.remove(wr) @@ -63,7 +61,7 @@ def _remove_from_reopenable(wr): pass -class FileHandler(StreamHandler): +class FileHandler(logging.StreamHandler): """File handler which supports reopening of logs. Re-opening should be used instead of the 'rollover' feature of @@ -72,7 +70,7 @@ class FileHandler(StreamHandler): def __init__(self, filename, mode="a"): filename = os.path.abspath(filename) - StreamHandler.__init__(self, open(filename, mode)) + logging.StreamHandler.__init__(self, open(filename, mode)) self.baseFilename = filename self.mode = mode self._wr = weakref.ref(self, _remove_from_reopenable) @@ -85,8 +83,8 @@ def close(self): # StreamHandler.close() isn't called. This seems the best # compromise. :-( try: - StreamHandler.close(self) - except KeyError: # pragma: no cover + logging.StreamHandler.close(self) + except KeyError: # pragma: no cover pass _remove_from_reopenable(self._wr) @@ -103,11 +101,12 @@ class Win32FileHandler(FileHandler): """File-based log handler for Windows that supports an additional 'rotate' method. reopen() is generally useless since Windows cannot do a move on an open file. + """ + def rotate(self, rotateFilename=None): if not rotateFilename: rotateFilename = self.baseFilename + ".last" - error = None self.close() try: os.rename(self.baseFilename, rotateFilename) @@ -116,42 +115,44 @@ def rotate(self, rotateFilename=None): self.stream = open(self.baseFilename, self.mode) + if os.name == "nt": # Make it the default for Windows - we install a 'reopen' handler that # tries to rotate the logfile. FileHandler = Win32FileHandler -class RotatingFileHandler(_RotatingFileHandler): +class RotatingFileHandler(logging.handlers.RotatingFileHandler): def __init__(self, *args, **kw): - _RotatingFileHandler.__init__(self, *args, **kw) + logging.handlers.RotatingFileHandler.__init__(self, *args, **kw) self._wr = weakref.ref(self, _remove_from_reopenable) _reopenable_handlers.append(self._wr) def close(self): - _RotatingFileHandler.close(self) + logging.handlers.RotatingFileHandler.close(self) _remove_from_reopenable(self._wr) def reopen(self): self.doRollover() -class TimedRotatingFileHandler(_TimedRotatingFileHandler): + +class TimedRotatingFileHandler(logging.handlers.TimedRotatingFileHandler): def __init__(self, *args, **kw): - _TimedRotatingFileHandler.__init__(self, *args, **kw) + logging.handlers.TimedRotatingFileHandler.__init__(self, *args, **kw) self._wr = weakref.ref(self, _remove_from_reopenable) _reopenable_handlers.append(self._wr) def close(self): - _TimedRotatingFileHandler.close(self) + logging.handlers.TimedRotatingFileHandler.close(self) _remove_from_reopenable(self._wr) def reopen(self): self.doRollover() -class NullHandler(Handler): +class NullHandler(logging.Handler): """Handler that does nothing.""" def emit(self, record): @@ -161,7 +162,7 @@ def handle(self, record): pass -class StartupHandler(BufferingHandler): +class StartupHandler(logging.handlers.BufferingHandler): """Handler which stores messages in a buffer until later. This is useful at startup before we can know that we can safely @@ -169,7 +170,7 @@ class StartupHandler(BufferingHandler): """ def __init__(self): - BufferingHandler.__init__(self, maxsize) + logging.handlers.BufferingHandler.__init__(self, maxsize) def shouldFlush(self, record): return False diff --git a/ZConfig/components/logger/tests/test_logger.py b/ZConfig/components/logger/tests/test_logger.py index b90c110..ced3b66 100644 --- a/ZConfig/components/logger/tests/test_logger.py +++ b/ZConfig/components/logger/tests/test_logger.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2002 Zope Foundation and Contributors. +# Copyright (c) 2002, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -76,7 +76,7 @@ def tearDown(self): for h in self._old_logger.handlers: self._old_logger.removeHandler(h) for h in self._old_handlers: - self._old_logger.addHandler(h) # pragma: no cover + self._old_logger.addHandler(h) # pragma: no cover self._old_logger.setLevel(self._old_level) while self._created: @@ -212,7 +212,7 @@ def test_with_timed_rotating_logfile(self): self.assertEqual(logfile.level, logging.DEBUG) self.assertEqual(logfile.backupCount, 11) self.assertEqual(logfile.interval, 86400*3) - self.assertTrue(isinstance(logfile, loghandler.TimedRotatingFileHandler)) + self.assertIsInstance(logfile, loghandler.TimedRotatingFileHandler) logger.removeHandler(logfile) logfile.close() @@ -258,7 +258,6 @@ def test_with_timed_rotating_logfile_and_size_should_fail(self): " \n" "" % fn) - def test_with_rotating_logfile_and_STD_should_fail(self): for path in ('STDERR', 'STDOUT'): for param in ('old-files 10', 'max-size 5mb'): @@ -274,7 +273,6 @@ def test_with_rotating_logfile_and_STD_should_fail(self): " \n" "" % (path, param)) - def check_standard_stream(self, name): old_stream = getattr(sys, name) conf = self.get_config(""" @@ -333,10 +331,10 @@ def test_with_syslog(self): syslog = logger.handlers[0] self.assertEqual(syslog.level, logging.ERROR) self.assertTrue(isinstance(syslog, loghandler.SysLogHandler)) - syslog.close() # avoid ResourceWarning + syslog.close() # avoid ResourceWarning try: - syslog.socket.close() # ResourceWarning under 3.2 - except socket.SocketError: # pragma: no cover + syslog.socket.close() # ResourceWarning under 3.2 + except socket.SocketError: # pragma: no cover pass def test_with_http_logger_localhost(self): @@ -686,6 +684,7 @@ def test_filehandler_reopen_thread_safety(self): self.assertEqual(calls, ["acquire", "release"]) + class TestFunctions(TestHelper, unittest.TestCase): def test_log_format_bad(self): @@ -727,9 +726,12 @@ def test_http_handler_url(self): def test_close_files(self): class F(object): closed = 0 + def close(self): self.closed += 1 + f = F() + def wr(): return f @@ -788,8 +790,10 @@ def test_buffer(self): self.assertEqual(maxsize, handler.capacity) records = [] + def handle(record): records.append(record) + handle.handle = handle handler.flushBufferTo(handle) @@ -801,6 +805,7 @@ def handle(record): del handle.handle + def test_logger_convenience_function_and_ommiting_name_to_get_root_logger(): """ @@ -853,11 +858,13 @@ def test_logger_convenience_function_and_ommiting_name_to_get_root_logger(): """ + def test_suite(): return unittest.TestSuite([ unittest.defaultTestLoader.loadTestsFromName(__name__), doctest.DocTestSuite() ]) + if __name__ == '__main__': unittest.main(defaultTest="test_suite") diff --git a/ZConfig/datatypes.py b/ZConfig/datatypes.py index aa55766..1d3c051 100644 --- a/ZConfig/datatypes.py +++ b/ZConfig/datatypes.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2002, 2003 Zope Foundation and Contributors. +# Copyright (c) 2002, 2003, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -27,6 +27,7 @@ input value is not acceptable. :exc:`ValueError` is the preferred exception for disallowed inputs, but any other exception will be properly propagated. + """ import os @@ -34,14 +35,10 @@ import sys import datetime -try: - unicode -except NameError: - # Python 3 - have_unicode = False +from ZConfig._compat import have_unicode, text_type, PY3 + +if PY3: from functools import reduce -else: - have_unicode = True class MemoizedConversion(object): @@ -51,6 +48,7 @@ class MemoizedConversion(object): at a later time; failed conversions are not cached in any way, since it is difficult to raise a meaningful exception providing information about the specific failure. + """ def __init__(self, conversion): @@ -75,6 +73,7 @@ class RangeCheckedConversion(object): not ``None``, are the inclusive endpoints of the allowed range. Values returned by *conversion* which lay outside the range described by *min* and *max* cause :exc:`ValueError` to be raised. + """ def __init__(self, conversion, min=None, max=None): @@ -132,6 +131,7 @@ def check_locale(value): class BasicKeyConversion(RegularExpressionConversion): + def __init__(self): RegularExpressionConversion.__init__(self, "[a-zA-Z][-._a-zA-Z0-9]*") @@ -141,15 +141,17 @@ def __call__(self, value): class ASCIIConversion(RegularExpressionConversion): + def __call__(self, value): value = RegularExpressionConversion.__call__(self, value) - if have_unicode and isinstance(value, unicode): + if have_unicode and isinstance(value, text_type): value = value.encode("ascii") return value _ident_re = "[_a-zA-Z][_a-zA-Z0-9]*" + class IdentifierConversion(ASCIIConversion): reason = "not a valid Python identifier" @@ -220,7 +222,7 @@ def __call__(self, s): # last part is not the port number host = s p = None - if p: # else leave port at None + if p: # else leave port at None port = port_number(p) host = host.lower() else: @@ -244,6 +246,7 @@ def __call__(self, s): inet_connection_address = InetAddress("127.0.0.1") inet_binding_address = InetAddress("") + class SocketAddress(object): # Parsing results in family and address # Family can be AF_UNIX (for addresses that are path names) @@ -253,6 +256,7 @@ class SocketAddress(object): # Notice that no DNS lookup is performed, so if the host # is a DNS name, DNS lookup may end up with either IPv4 or # IPv6 addresses, or both + def __init__(self, s): import socket if "/" in s or s.find(os.sep) >= 0: @@ -267,11 +271,13 @@ def __init__(self, s): def _parse_address(self, s): return inet_address(s) + class SocketBindingAddress(SocketAddress): def _parse_address(self, s): return inet_binding_address(s) + class SocketConnectionAddress(SocketAddress): def _parse_address(self, s): @@ -288,13 +294,13 @@ def __init__(self): # We allow underscores in hostnames although this is considered # illegal according to RFC1034. # Addition: IPv6 addresses are now also accepted - expr = (r"(^(\d|[01]?\d\d|2[0-4]\d|25[0-5])\." #ipaddr - r"(\d|[01]?\d\d|2[0-4]\d|25[0-5])\." #ipaddr cont'd - r"(\d|[01]?\d\d|2[0-4]\d|25[0-5])\." #ipaddr cont'd - r"(\d|[01]?\d\d|2[0-4]\d|25[0-5])$)" #ipaddr cont'd - r"|([A-Za-z_][-A-Za-z0-9_.]*[-A-Za-z0-9_])" # or hostname - r"|([0-9A-Fa-f:.]+:[0-9A-Fa-f:.]*)" # or superset of IPv6 addresses - # (requiring at least one colon) + expr = (r"(^(\d|[01]?\d\d|2[0-4]\d|25[0-5])\." # ipaddr + r"(\d|[01]?\d\d|2[0-4]\d|25[0-5])\." # ipaddr cont'd + r"(\d|[01]?\d\d|2[0-4]\d|25[0-5])\." # ipaddr cont'd + r"(\d|[01]?\d\d|2[0-4]\d|25[0-5])$)" # ipaddr cont'd + r"|([A-Za-z_][-A-Za-z0-9_.]*[-A-Za-z0-9_])" # or hostname + # or superset of IPv6 addresses (requiring at least one colon) + r"|([0-9A-Fa-f:.]+:[0-9A-Fa-f:.]*)" ) RegularExpressionConversion.__init__(self, expr) @@ -310,24 +316,28 @@ def __call__(self, value): raise ValueError('%r is not a valid IPv6 address' % value) return result + def existing_directory(v): nv = os.path.expanduser(v) if os.path.isdir(nv): return nv raise ValueError('%s is not an existing directory' % v) + def existing_path(v): nv = os.path.expanduser(v) if os.path.exists(nv): return nv raise ValueError('%s is not an existing path' % v) + def existing_file(v): nv = os.path.expanduser(v) if os.path.exists(nv): return nv raise ValueError('%s is not an existing file' % v) + def existing_dirpath(v): nv = os.path.expanduser(v) dirname = os.path.dirname(nv) @@ -348,10 +358,12 @@ def __init__(self, d, default=1): self._d = d self._default = default # all keys must be the same size + def check(a, b): if len(a) != len(b): raise ValueError("suffix length mismatch") return a + self._keysz = len(reduce(check, d)) def __call__(self, v): @@ -418,8 +430,8 @@ def timedelta(s): "socket-address": SocketAddress, "socket-binding-address": SocketBindingAddress, "socket-connection-address": SocketConnectionAddress, - "ipaddr-or-hostname":IpaddrOrHostname(), - "existing-directory":existing_directory, + "ipaddr-or-hostname": IpaddrOrHostname(), + "existing-directory": existing_directory, "existing-path": existing_path, "existing-file": existing_file, "existing-dirpath": existing_dirpath, @@ -442,7 +454,9 @@ class Registry(object): If given, *stock* should be a mapping which defines the "built-in" data types for the registry; if omitted or ``None``, the standard set of data types is used (see :ref:`standard-datatypes`). + """ + def __init__(self, stock=None): if stock is None: stock = stock_datatypes.copy() @@ -459,7 +473,7 @@ def find_name(self, conversion): return k # If they followed the rules, we shouldn't get here. - return str(conversion) # pragma: no cover + return str(conversion) # pragma: no cover def get(self, name): """Return the type conversion routine for *name*. @@ -470,6 +484,7 @@ def get(self, name): registered, this method uses the :meth:`search` method to load the conversion function. This is the only method the rest of :mod:`ZConfig` requires. + """ if '.' not in name: if self._basic_key is None: @@ -493,6 +508,7 @@ def register(self, name, conversion): If *name* is already registered or provided as a stock data type, :exc:`ValueError` is raised (this includes the case when *name* was found using the :meth:`search` method). + """ if name in self._stock: raise ValueError("datatype name conflicts with built-in type: " @@ -509,8 +525,9 @@ def search(self, name): for the name by dynamically importing the containing module and extracting the value of the name. The name must refer to a usable conversion function. + """ - if not "." in name: + if "." not in name: raise ValueError("unloadable datatype name: " + repr(name)) components = name.split('.') start = components[0] diff --git a/ZConfig/info.py b/ZConfig/info.py index 26962a5..525d12b 100644 --- a/ZConfig/info.py +++ b/ZConfig/info.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2002, 2003 Zope Foundation and Contributors. +# Copyright (c) 2002, 2003, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -22,6 +22,7 @@ from ZConfig._compat import AbstractBaseClass + @total_ordering class UnboundedThing(object): __slots__ = () @@ -34,9 +35,10 @@ def __gt__(self, other): def __eq__(self, other): return isinstance(other, self.__class__) - def __repr__(self): # pragma: no cover + def __repr__(self): # pragma: no cover return "" + Unbounded = UnboundedThing() @@ -64,8 +66,10 @@ class BaseInfo(object): def __init__(self, name, datatype, minOccurs, maxOccurs, handler, attribute): - assert maxOccurs is not None, "Use Unbounded for an upper bound, not None" - assert minOccurs is not None, "Use 0 for a lower bound, not None" + assert maxOccurs is not None, ( + "Use Unbounded for an upper bound, not None") + assert minOccurs is not None, ( + "Use 0 for a lower bound, not None") if maxOccurs < 1: raise ZConfig.SchemaError( @@ -403,7 +407,7 @@ def getsectioninfo(self, type_, name): if st.isabstract(): try: st = st.getsubtype(type_) - except ZConfig.ConfigurationError: # pragma: no cover + except ZConfig.ConfigurationError: # pragma: no cover raise ZConfig.ConfigurationError( "section type %s not allowed for name %s" % (repr(type_), repr(key))) @@ -420,19 +424,19 @@ def getsectioninfo(self, type_, name): return info elif info.sectiontype.isabstract(): st = info.sectiontype - if st.name == type_: # pragma: no cover + if st.name == type_: # pragma: no cover raise ZConfig.ConfigurationError( "cannot define section with an abstract type") try: st = st.getsubtype(type_) - except ZConfig.ConfigurationError: # pragma: no cover + except ZConfig.ConfigurationError: # pragma: no cover # not this one; maybe a different one pass else: return info raise ZConfig.ConfigurationError( - "no matching section defined for type='%s', name='%s'" % ( - type_, name)) + "no matching section defined for type='%s', name='%s'" + % (type_, name)) def isabstract(self): return False @@ -470,7 +474,7 @@ def getunusedtypes(self): alltypes.remove(n) if self.name and self.name in alltypes: # Not clear we can get here - alltypes.remove(self.name) # pragma: no cover. + alltypes.remove(self.name) # pragma: no cover. return alltypes def createSectionType(self, name, keytype, valuetype, datatype): diff --git a/ZConfig/loader.py b/ZConfig/loader.py index dfa2bbc..aa371bc 100644 --- a/ZConfig/loader.py +++ b/ZConfig/loader.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2002, 2003 Zope Foundation and Contributors. +# Copyright (c) 2002, 2003, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -214,7 +214,7 @@ def openResource(self, url): except urllib2.URLError as e: # urllib2.URLError has a particularly hostile str(), so we # generally don't want to pass it along to the user. - self._raise_open_error(url, e.reason) # pragma: no cover + self._raise_open_error(url, e.reason) # pragma: no cover except (IOError, OSError) as e: # Python 2.1 raises a different error from Python 2.2+, # so we catch both to make sure we detect the situation. @@ -371,7 +371,7 @@ def loadResource(self, resource): def schemaComponentSource(self, package, filename): parts = package.split(".") - if not parts: # pragma: no cover. can we even get here? + if not parts: # pragma: no cover. can we even get here? raise ZConfig.SchemaError( "illegal schema component name: " + repr(package)) if "" in parts: @@ -401,8 +401,8 @@ class ConfigLoader(BaseLoader): conform to the schema *schema*. The ``load*()`` methods return a tuple consisting of the configuration object and a composite handler. - """ + """ def __init__(self, schema): if schema.isabstract(): diff --git a/ZConfig/matcher.py b/ZConfig/matcher.py index 2482478..ab4ee09 100644 --- a/ZConfig/matcher.py +++ b/ZConfig/matcher.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2002, 2003 Zope Foundation and Contributors. +# Copyright (c) 2002, 2003, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -55,7 +55,7 @@ def addSection(self, type_, name, sectvalue): v.append(sectvalue) elif v is None: self._values[attr] = sectvalue - else: # pragma: no cover + else: # pragma: no cover raise ZConfig.ConfigurationError( "too many instances of %s section" % repr(ci.sectiontype.name)) @@ -76,7 +76,7 @@ def addValue(self, key, value, position): raise ZConfig.ConfigurationError( repr(key) + " is not a known key name") k, ci = arbkey_info - if ci.issection(): # pragma: no cover + if ci.issection(): # pragma: no cover if ci.name: extra = " in %s sections" % repr(self.type.name) else: @@ -90,15 +90,15 @@ def addValue(self, key, value, position): v = self._values[attr] if v is None: if k == '+': - v = {} # pragma: no cover + v = {} # pragma: no cover elif ismulti: - v = [] # pragma: no cover + v = [] # pragma: no cover self._values[attr] = v elif not ismulti: if k != '+': raise ZConfig.ConfigurationError( repr(key) + " does not support multiple values") - elif len(v) == ci.maxOccurs: # pragma: no cover + elif len(v) == ci.maxOccurs: # pragma: no cover # This code may be impossible to hit. Previously it would # have raised a NameError because it used an unbound # local. @@ -113,7 +113,7 @@ def addValue(self, key, value, position): else: v[realkey] = [value] else: - if realkey in v: # pragma: no cover + if realkey in v: # pragma: no cover raise ZConfig.ConfigurationError( "too many values for " + repr(key)) v[realkey] = value @@ -155,7 +155,7 @@ def finish(self): raise ZConfig.ConfigurationError( "no values for %s; %s required" % (key, ci.minOccurs)) else: - v = values[attr] = default[:] # pragma: no cover + v = values[attr] = default[:] # pragma: no cover if ci.ismulti(): if not v: default = ci.getdefault() @@ -169,7 +169,7 @@ def finish(self): % (key, len(v), ci.minOccurs)) if v is None and not ci.issection(): if ci.ismulti(): - v = ci.getdefault()[:] # pragma: no cover + v = ci.getdefault()[:] # pragma: no cover else: v = ci.getdefault() values[attr] = v @@ -282,12 +282,12 @@ def __repr__(self): return "<%s for %s %s>" % (clsname, self._matcher.type.name, name) def __str__(self): - l = [] + lst = [] attrnames = sorted([s for s in self.__dict__ if s[0] != "_"]) for k in attrnames: v = getattr(self, k) - l.append('%-40s: %s' % (k, v)) - return '\n'.join(l) + lst.append('%-40s: %s' % (k, v)) + return '\n'.join(lst) def getSectionName(self): return self._name diff --git a/ZConfig/schema.py b/ZConfig/schema.py index 3a6b2e9..03e40d6 100644 --- a/ZConfig/schema.py +++ b/ZConfig/schema.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2002, 2003 Zope Foundation and Contributors. +# Copyright (c) 2002, 2003, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -26,6 +26,7 @@ BLANK = u'' + def parseResource(resource, loader): parser = SchemaParser(loader, resource.url) xml.sax.parse(resource.file, parser) @@ -54,7 +55,8 @@ class BaseParser(xml.sax.ContentHandler): "description": ["key", "section", "multikey", "multisection", "sectiontype", "abstracttype", "schema", "component"], - "example": ["schema", "sectiontype", "key", "multikey", "section", "multisection"], + "example": ["schema", "sectiontype", "key", "multikey", + "section", "multisection"], "metadefault": ["key", "section", "multikey", "multisection"], "default": ["key", "multikey"], "import": ["schema", "component"], @@ -101,17 +103,17 @@ def startElement(self, name, attrs): # self._schema is assigned to in self.start_<_top_level>, so # most of the checks for it being None are just extra precaution. if name == self._top_level: - if self._schema is not None: # pragma: no cover + if self._schema is not None: # pragma: no cover self.error("schema element improperly nested") getattr(self, "start_" + name)(attrs) elif name in self._handled_tags: - if self._schema is None: # pragma: no cover + if self._schema is None: # pragma: no cover self.error(name + " element outside of schema") getattr(self, "start_" + name)(attrs) elif name in self._cdata_tags: - if self._schema is None: # pragma: no cover + if self._schema is None: # pragma: no cover self.error(name + " element outside of schema") - if self._cdata is not None: # pragma: no cover + if self._cdata is not None: # pragma: no cover # this should be handled by the earlier nesting check self.error(name + " element improperly nested") self._cdata = [] @@ -137,7 +139,7 @@ def endElement(self, name): getattr(self, "characters_" + name)(data) def endDocument(self): - if self._schema is None: # pragma: no cover + if self._schema is None: # pragma: no cover # this would have to be a broken subclass self.error("no %s found" % self._top_level) @@ -148,7 +150,7 @@ def get_position(self): return (self._locator.getLineNumber(), self._locator.getColumnNumber(), (self._locator.getSystemId() or self._url)) - return None, None, self._url # pragma: no cover + return None, None, self._url # pragma: no cover def get_handler(self, attrs): v = attrs.get("handler") @@ -235,7 +237,7 @@ def get_key_info(self, attrs, element): any_name, name, attribute = self.get_name_info(attrs, element) if any_name == '*': self.error(element + " may not specify '*' for name") - if not name and any_name != '+': # pragma: no cover + if not name and any_name != '+': # pragma: no cover # Can we even get here? self.error(element + " name may not be omitted or empty") datatype = self.get_datatype(attrs, "datatype", "string") @@ -365,7 +367,7 @@ def start_section(self, attrs): handler = self.get_handler(attrs) minOccurs = 1 if self.get_required(attrs) else 0 any_name, name, attribute = self.get_name_info(attrs, "section", "*") - if any_name and not attribute: # pragma: no cover + if any_name and not attribute: # pragma: no cover # It seems like this is handled by get_name_info. self.error( "attribute must be specified if section name is '*' or '+'") @@ -380,7 +382,8 @@ def end_section(self): def start_multisection(self, attrs): sectiontype = self.get_sectiontype(attrs) minOccurs, maxOccurs = self.get_ordinality(attrs) - any_name, name, attribute = self.get_name_info(attrs, "multisection", "*") + any_name, name, attribute = self.get_name_info( + attrs, "multisection", "*") if any_name not in ("*", "+"): self.error("multisection must specify '*' or '+' for the name") handler = self.get_handler(attrs) @@ -431,7 +434,8 @@ def start_multikey(self, attrs): name, datatype, handler, attribute = self.get_key_info(attrs, "multikey") minOccurs, maxOccurs = self.get_ordinality(attrs) - key = info.MultiKeyInfo(name, datatype, minOccurs, maxOccurs, handler, attribute) + key = info.MultiKeyInfo(name, datatype, minOccurs, maxOccurs, + handler, attribute) self._stack[-1].addkey(key) self._stack.append(key) @@ -598,7 +602,7 @@ def end_component(self): self.pop_prefix() def _check_not_toplevel(self, what): - if not self._stack: # pragma: no cover + if not self._stack: # pragma: no cover # we can't get here because the elements that call # this function have specified _allowed_parents that are # checked first diff --git a/ZConfig/schema2html.py b/ZConfig/schema2html.py index 99eb8e1..07ef522 100755 --- a/ZConfig/schema2html.py +++ b/ZConfig/schema2html.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2003 Zope Corporation and Contributors. +# Copyright (c) 2003, 2018 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -11,7 +11,6 @@ # FOR A PARTICULAR PURPOSE. # ############################################################################## -from __future__ import print_function import argparse from contextlib import contextmanager @@ -28,6 +27,7 @@ from ZConfig._schema_utils import load_schema from ZConfig.sphinx import RstSchemaPrinter + class HtmlSchemaFormatter(AbstractSchemaFormatter): def esc(self, x): @@ -85,10 +85,12 @@ def body(self): yield self.write('') + class HtmlSchemaPrinter(AbstractSchemaPrinter): _schema_formatter = HtmlSchemaFormatter + def main(argv=None): argv = argv if argv is not None else sys.argv[1:] @@ -97,39 +99,45 @@ def main(argv=None): argparser.add_argument( "schema", metavar='[SCHEMA-OR-PACKAGE]', - help="The schema to print. By default, a file. Optionally, a Python package." - " If not given, defaults to reading a schema file from stdin", - default="-" + help=("The schema to print. By default, a file." + " Optionally, a Python package." + " If not given, defaults to reading a schema file from stdin"), + default="-", ) argparser.add_argument( "--out", "-o", help="Write the schema to this file; if not given, write to stdout", - type=argparse.FileType('w')) + type=argparse.FileType('w'), + ) argparser.add_argument( "--package", action='store_true', default=False, - help="The SCHEMA-OR-PACKAGE argument indicates a Python package instead of a file." - " The component.xml (by default) from the package will be read.") + help=("The SCHEMA-OR-PACKAGE argument indicates a Python package" + " instead of a file. The component.xml (by default) from the" + " package will be read."), + ) argparser.add_argument( "--package-file", action="store", default="component.xml", - help="When PACKAGE is given, this can specify the file inside it to load.") - + help=("When PACKAGE is given, this can specify the file inside it" + " to load."), + ) argparser.add_argument( "--members", action="store", nargs="*", - help="Only output sections and types in this list (and reachable from it)") - + help=("Only output sections and types in this list (and reachable" + " from it)."), + ) if RstSchemaPrinter: argparser.add_argument( "--format", action="store", - choices=('html', 'xml'), # XXX Can we get actual valid RST out? + choices=('html', 'xml'), # XXX Can we get actual valid RST out? default="HTML", - help="What output format to produce" + help="The output format to produce." ) args = argparser.parse_args(argv) @@ -142,11 +150,10 @@ def main(argv=None): if hasattr(args, 'format') and args.format == 'xml': printer_factory = RstSchemaPrinter - printer_factory(schema, out, allowed_names=args.members).printSchema() - return 0 + if __name__ == '__main__': main() diff --git a/ZConfig/sphinx.py b/ZConfig/sphinx.py index 3686bbb..7efe091 100755 --- a/ZConfig/sphinx.py +++ b/ZConfig/sphinx.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2017 Zope Corporation and Contributors. +# Copyright (c) 2017, 2018 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -22,7 +22,7 @@ import docutils.frontend import docutils.parsers.rst from docutils.parsers.rst import Directive -except ImportError: # pragma: no cover +except ImportError: # pragma: no cover RstSchemaPrinter = None RstSchemaFormatter = None else: @@ -53,7 +53,6 @@ def _parsed(self, text, name='Schema'): name, settings=self.settings) - parser = docutils.parsers.rst.Parser() parser.parse(text, document) return document.children @@ -88,7 +87,6 @@ def item_list(self): yield self._current_node = old_node - @contextmanager def describing(self, description=MARKER, after=None): dl = self._current_node @@ -108,7 +106,6 @@ def describing(self, description=MARKER, after=None): self._describing(description, after) - @contextmanager def described_as(self): item = self._current_node @@ -150,7 +147,6 @@ def printSchema(self): super(RstSchemaPrinter, self).printSchema() print(self.fmt.document.pformat(), file=self.fmt.stream) - class SchemaToRstDirective(Directive): required_arguments = 1 optional_arguments = 2 @@ -159,6 +155,7 @@ class SchemaToRstDirective(Directive): 'members': str, 'excluded-members': str, } + def run(self): schema = load_schema(self.arguments[0], True, self.options.get('file')) @@ -171,13 +168,14 @@ def run(self): if 'excluded-members' in self.options: excluded_members = self.options['excluded-members'].split() - printer = RstSchemaPrinter(schema, allowed_names=members, excluded_names=excluded_members) + printer = RstSchemaPrinter(schema, allowed_names=members, + excluded_names=excluded_members) printer.fmt.settings = self.state.document.settings printer.buildSchema() return printer.fmt.document.children - def setup(app): # pragma: no cover + def setup(app): # pragma: no cover "Sphinx extension entry point to add the zconfig directive." app.add_directive("zconfig", SchemaToRstDirective) diff --git a/ZConfig/substitution.py b/ZConfig/substitution.py index 50c3529..f05441e 100644 --- a/ZConfig/substitution.py +++ b/ZConfig/substitution.py @@ -14,9 +14,14 @@ """Shell-style string substitution helper.""" import os +import re + import ZConfig +_name_match = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*").match + + def substitute(s, mapping): """Substitute values from *mapping* into *s*. @@ -108,8 +113,3 @@ def _split(s): return prefix, name.lower(), name, s[i:], vtype else: return s, None, None, None, None - - -import re -_name_match = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*").match -del re diff --git a/ZConfig/tests/support.py b/ZConfig/tests/support.py index 8e42965..6264272 100644 --- a/ZConfig/tests/support.py +++ b/ZConfig/tests/support.py @@ -30,11 +30,14 @@ INPUT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "input")) CONFIG_BASE = "file://%s/" % pathname2url(INPUT_DIR) + def input_file(fname): return os.path.abspath(os.path.join(INPUT_DIR, fname)) + def with_stdin_from_input_file(fname): input_fname = input_file(fname) + @contextlib.contextmanager def stdin_replaced(): old_stdin = sys.stdin diff --git a/ZConfig/tests/test_cfgimports.py b/ZConfig/tests/test_cfgimports.py index 6622bab..a8459a8 100644 --- a/ZConfig/tests/test_cfgimports.py +++ b/ZConfig/tests/test_cfgimports.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2003 Zope Foundation and Contributors. +# Copyright (c) 2003, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -13,6 +13,7 @@ ############################################################################## """Tests of the %import mechanism. """ + import unittest import ZConfig @@ -21,8 +22,8 @@ from ZConfig._compat import NStringIO as StringIO -class TestImportFromConfiguration( - ZConfig.tests.support.TestHelper, unittest.TestCase): +class TestImportFromConfiguration(ZConfig.tests.support.TestHelper, + unittest.TestCase): def test_simple_import(self): schema = self.load_schema_text("") @@ -53,5 +54,6 @@ def test_missing_import(self): def test_suite(): return unittest.defaultTestLoader.loadTestsFromName(__name__) + if __name__ == '__main__': unittest.main(defaultTest='test_suite') diff --git a/ZConfig/tests/test_cmdline.py b/ZConfig/tests/test_cmdline.py index e36b3e8..9d04f99 100644 --- a/ZConfig/tests/test_cmdline.py +++ b/ZConfig/tests/test_cmdline.py @@ -106,7 +106,6 @@ def test_named_sections(self): matcher.addValue, 'invalid name', 'value', (1, 1, '')) - simple_schema = None def get_simple_schema(self): diff --git a/ZConfig/tests/test_config.py b/ZConfig/tests/test_config.py index 1a4bc74..763d8cf 100644 --- a/ZConfig/tests/test_config.py +++ b/ZConfig/tests/test_config.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2002, 2003 Zope Foundation and Contributors. +# Copyright (c) 2002, 2003, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -23,6 +23,7 @@ from ZConfig._compat import NStringIO as StringIO + class ConfigurationTestCase(TestHelper, unittest.TestCase): schema = None @@ -37,10 +38,8 @@ def load(self, relurl, context=None): url = CONFIG_BASE + relurl self.conf, self.handlers = ZConfig.loadConfig(self.get_schema(), url) conf = self.conf - #self.assertEqual(conf.url, url) self.assertTrue(conf.getSectionName() is None) self.assertTrue(conf.getSectionType() is None) - #self.assertTrue(conf.delegate is None) return conf def loadtext(self, text): @@ -100,14 +99,14 @@ def test_simple_sections(self): self.assertEqual(conf.var, "foo") # check each interleaved position between sections for c in "0123456": - self.assertEqual(getattr(conf, "var_" +c), "foo-" + c) - sect = [sect for sect in conf.sections - if sect.getSectionName() == "name"][0] + self.assertEqual(getattr(conf, "var_" + c), "foo-" + c) + sect = list(sect for sect in conf.sections + if sect.getSectionName() == "name")[0] self.assertEqual(sect.var, "bar") self.assertEqual(sect.var_one, "splat") self.assertTrue(sect.var_three is None) - sect = [sect for sect in conf.sections - if sect.getSectionName() == "delegate"][0] + sect = list(sect for sect in conf.sections + if sect.getSectionName() == "delegate")[0] self.assertEqual(sect.var, "spam") self.assertEqual(sect.var_two, "stuff") self.assertTrue(sect.var_three is None) @@ -211,8 +210,8 @@ def test_bad_section(self): 'malformed section start', self.loadtext, ' Unbounded) self.assertEqual(Unbounded, Unbounded) + class InfoMixin(TestHelper): Class = None default_kwargs = {'name': '', 'datatype': None, 'handler': None, - 'minOccurs': 0, 'maxOccurs': Unbounded, 'attribute': None} + 'minOccurs': 0, 'maxOccurs': Unbounded, + 'attribute': None} def make_one(self, **kwargs): args = self.default_kwargs.copy() @@ -70,6 +72,7 @@ def test_repr(self): # just doesn't raise repr(self.make_one()) + class BaseKeyInfoTestCase(InfoMixin, unittest.TestCase): class Class(BaseKeyInfo): @@ -95,6 +98,7 @@ def test_adddefaultc(self): info.adddefault, None, None, key='key') + class KeyInfoTestCase(InfoMixin, unittest.TestCase): Class = KeyInfo @@ -109,12 +113,14 @@ def test_add_with_default(self): info.adddefault, 'value', None) + class SectionInfoTestCase(InfoMixin, unittest.TestCase): Class = SectionInfo class MockSectionType(object): name = None + @classmethod def isabstract(cls): return True @@ -139,6 +145,7 @@ def test_misc(self): self.assertFalse(info.isAllowedName('*')) self.assertFalse(info.isAllowedName('+')) + class AbstractTypeTestCase(unittest.TestCase): def test_subtypes(self): @@ -151,6 +158,7 @@ def test_subtypes(self): t.addsubtype(self) self.assertTrue(t.hassubtype('foo')) + class SectionTypeTestCase(TestHelper, unittest.TestCase): def make_one(self, name='', keytype=None, valuetype=None, @@ -169,10 +177,13 @@ def test_required_types_with_name(self): self.assertEqual(['foo'], info.getrequiredtypes()) def test_getsectioninfo(self): + class MockChild(object): _issection = False + def issection(self): return self._issection + child = MockChild() info = self.make_one() @@ -189,6 +200,7 @@ def issection(self): info.getsectioninfo, None, 'baz') + class SchemaTypeTestCase(TestHelper, unittest.TestCase): def test_various(self): diff --git a/ZConfig/tests/test_loader.py b/ZConfig/tests/test_loader.py index cd3db2d..1904613 100644 --- a/ZConfig/tests/test_loader.py +++ b/ZConfig/tests/test_loader.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2002, 2003 Zope Foundation and Contributors. +# Copyright (c) 2002, 2003, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -77,10 +77,13 @@ def test_schema_loader_source_errors(self): 'foo..bar', None) def test_config_loader_abstract_schema(self): + class MockSchema(object): _abstract = True + def isabstract(self): return self._abstract + def gettype(self, _t): return self @@ -223,7 +226,7 @@ def test_import_component_twice_2(self): def test_urlsplit_urlunsplit(self): # Extracted from Python's test.test_urlparse module: - for url, parsed, split in [ + samples = [ ('http://www.python.org', ('http', 'www.python.org', '', '', '', ''), ('http', 'www.python.org', '', '', '')), @@ -239,7 +242,8 @@ def test_urlsplit_urlunsplit(self): ('file:///tmp/junk.txt', ('file', '', '/tmp/junk.txt', '', '', ''), ('file', '', '/tmp/junk.txt', '', '')), - ]: + ] + for url, parsed, split in samples: result = ZConfig.url.urlsplit(url) self.assertEqual(result, split) result2 = ZConfig.url.urlunsplit(result) @@ -387,6 +391,7 @@ def test_zip_import_component_from_config(self): ZConfig.loadConfigFile(schema, sio, overrides=["sample/data=othervalue"]) + class TestOpenPackageResource(TestHelper, unittest.TestCase): magic_name = 'not a valid import name' diff --git a/ZConfig/tests/test_matcher.py b/ZConfig/tests/test_matcher.py index 53d4805..b2ed374 100644 --- a/ZConfig/tests/test_matcher.py +++ b/ZConfig/tests/test_matcher.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2017 Zope Foundation and Contributors. +# Copyright (c) 2017, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -27,6 +27,7 @@ class SectionValueTestCase(unittest.TestCase): def test_repr(self): + class MockMatcher(object): type = None @@ -49,22 +50,28 @@ def test_str(self): 'k : v', str(sv)) + class SectionMatcherTestCase(TestHelper, unittest.TestCase): def test_constructor_error(self): + class Mock(object): name = 'name' + def allowUnnamed(self): return False + mock = Mock() self.assertRaisesRegex(ConfigurationError, "sections may not be unnamed", SectionMatcher, mock, mock, None, None) + class BaseMatcherTestCase(TestHelper, unittest.TestCase): def test_repr(self): + class Mock(dict): name = 'name' @@ -72,6 +79,7 @@ class Mock(dict): repr(matcher) def test_duplicate_section_names(self): + class Mock(dict): name = 'name' @@ -84,6 +92,7 @@ class Mock(dict): None, 'foo', None) def test_construct_errors(self): + class MockType(object): attribute = 'attr' @@ -98,7 +107,7 @@ def issection(self): type_ = [] matcher = BaseMatcher(None, type_, None) - type_.append( ('key', MockType() ) ) + type_.append(('key', MockType())) class MockSection(object): def getSectionDefinition(self): @@ -117,7 +126,6 @@ def datatype(self, _s): with self.assertRaises(DataConversionError): matcher.constuct() - def test_create_child_bad_name(self): class MockType(list): diff --git a/ZConfig/tests/test_readme.py b/ZConfig/tests/test_readme.py index f9613aa..1cc86c2 100644 --- a/ZConfig/tests/test_readme.py +++ b/ZConfig/tests/test_readme.py @@ -25,16 +25,19 @@ old = {} + def setUp(test): logger = logging.getLogger() old['level'] = logger.level old['handlers'] = logger.handlers[:] + def tearDown(test): logger = logging.getLogger() logger.level = old['level'] logger.handlers = old['handlers'] + def findRoot(): here = os.path.dirname(os.path.abspath(__file__)) while not os.path.exists(os.path.join(here, 'setup.py')): @@ -44,6 +47,7 @@ def findRoot(): raise AssertionError('could not find my setup.py') return here + def docSetUp(test): # Python 2 makes __path__ and __file__ relative in some # cases (such as when we're executing with the 'ZConfig' @@ -60,11 +64,13 @@ def docSetUp(test): os.chdir(doc_path) setUp(test) + def docTearDown(test): os.chdir(old['pwd']) tearDown(test) old.clear() + def test_suite(): root = findRoot() plugins = manuel.doctest.Manuel(optionflags=options) @@ -83,5 +89,6 @@ def test_suite(): ), ]) + if __name__ == '__main__': unittest.main(defaultTest='test_suite') diff --git a/ZConfig/tests/test_schema.py b/ZConfig/tests/test_schema.py index 09550dc..dfeb370 100644 --- a/ZConfig/tests/test_schema.py +++ b/ZConfig/tests/test_schema.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2002, 2003 Zope Foundation and Contributors. +# Copyright (c) 2002, 2003, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -23,13 +23,17 @@ def uppercase(value): return str(value).upper() + def appsection(value): return MySection(value) + def get_foo(section): return section.foo + class MySection: + def __init__(self, value): self.conf = value @@ -42,7 +46,6 @@ def get_section_attributes(section): class SchemaTestCase(TestHelper, unittest.TestCase): """Tests of the basic schema support itself.""" - def test_minimal_schema(self): schema = self.load_schema_text("") self.assertEqual(len(schema), 0) @@ -55,7 +58,7 @@ def test_simple(self): schema, conf = self.load_both("simple.xml", "simple.conf") self._verifySimpleConf(conf) - def _verifySimpleConf(self,conf): + def _verifySimpleConf(self, conf): eq = self.assertEqual eq(conf.var1, 'abc') eq(conf.int_var, 12) @@ -320,7 +323,7 @@ def test_duplicate_section_names(self): """) - conf = self.load_config_text(schema, """\ + self.load_config_text(schema, """\ @@ -587,7 +590,6 @@ def test_getunusedtypes(self): def test_section_value_mutation(self): schema, conf = self.load_both("simple.xml", "simple.conf") - orig = conf.empty new = [] conf.empty = new self.assertTrue(conf.empty is new) @@ -645,7 +647,7 @@ def test_nested_abstract_sectiontype(self):
""") - conf = self.load_config_text(schema, """\ + self.load_config_text(schema, """\ @@ -663,7 +665,7 @@ def test_nested_abstract_sectiontype_without_name(self):
""") - conf = self.load_config_text(schema, """\ + self.load_config_text(schema, """\ @@ -676,6 +678,7 @@ def test_reserved_attribute_prefix(self): %s """ + def check(thing, self=self, template=template): text = template % thing self.assertRaises(ZConfig.SchemaError, @@ -999,8 +1002,8 @@ def test_simple_extends(self): self._verifySimpleConf(self.load_config(schema, "simple.conf")) def test_extends_fragment_failure(self): - self.assertRaises(ZConfig.SchemaError, - self.load_schema_text, + self.assertRaises( + ZConfig.SchemaError, self.load_schema_text, "" % CONFIG_BASE) def test_extends_description_override(self): @@ -1097,7 +1100,8 @@ def test_key_example(self): """) - self.assertEqual(schema.gettype('abc').getinfo('def').example, 'This is an example') + self.assertEqual(schema.gettype('abc').getinfo('def').example, + 'This is an example') def test_multikey_example(self): schema = self.load_schema_text("""\ @@ -1109,7 +1113,8 @@ def test_multikey_example(self): """) - self.assertEqual(schema.gettype('abc').getinfo('def').example, 'This is an example') + self.assertEqual(schema.gettype('abc').getinfo('def').example, + 'This is an example') def test_sectiontype_example(self): schema = self.load_schema_text("""\ @@ -1141,7 +1146,8 @@ def test_section_example(self):
""") - self.assertEqual(schema.getinfo('def').sectiontype.example, 'This is a sectiontype example') + self.assertEqual(schema.getinfo('def').sectiontype.example, + 'This is a sectiontype example') self.assertEqual(schema.getinfo('def').example, 'This is an example') def test_multisection_example(self): @@ -1155,7 +1161,8 @@ def test_multisection_example(self): """) - self.assertEqual(schema[0][1].sectiontype.example, 'This is a sectiontype example') + self.assertEqual(schema[0][1].sectiontype.example, + 'This is a sectiontype example') self.assertEqual(schema[0][1].example, 'This is an example') def checkErrorText(self, schema, error_text): @@ -1174,12 +1181,13 @@ def test_error_extra_cdata(self): self.checkErrorText("text", "non-blank character data") - def test_error_subclass(self): import ZConfig.schema import ZConfig.datatypes + class MockLoader(object): registry = ZConfig.datatypes.Registry() + parser = ZConfig.schema.SchemaParser(MockLoader(), 'url') parser.push_prefix({'prefix': __name__}) parser.push_prefix({'prefix': '.' + __name__}) @@ -1231,7 +1239,8 @@ def test_error_multikey(self): self.checkErrorText( """ - + """, "default values for multikey must be given") @@ -1331,5 +1340,6 @@ def test_error_component_section(self): def test_suite(): return unittest.defaultTestLoader.loadTestsFromName(__name__) + if __name__ == '__main__': unittest.main(defaultTest='test_suite') diff --git a/ZConfig/tests/test_schema2html.py b/ZConfig/tests/test_schema2html.py index 3ab1fe3..e489fc9 100644 --- a/ZConfig/tests/test_schema2html.py +++ b/ZConfig/tests/test_schema2html.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2017 Zope Foundation and Contributors. +# Copyright (c) 2017, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -25,7 +25,6 @@ import docutils.parsers.rst.directives - try: # Note that we're purposely using the old # StringIO object on Python 2 because it auto-converts @@ -39,13 +38,16 @@ from ZConfig import schema2html from ZConfig.sphinx import SchemaToRstDirective -docutils.parsers.rst.directives.register_directive("zconfig", SchemaToRstDirective) from ZConfig.sphinx import RstSchemaFormatter from .support import input_file from .support import with_stdin_from_input_file +docutils.parsers.rst.directives.register_directive("zconfig", + SchemaToRstDirective) + + @contextlib.contextmanager def stdout_replaced(buf): old_stdout = sys.stdout @@ -62,7 +64,8 @@ def run_transform(*args): with stdout_replaced(buf): schema2html.main(args) return buf - return schema2html.main(args) # pragma: no cover + return schema2html.main(args) # pragma: no cover + if schema2html.RstSchemaPrinter: def run_transform_rst(*args): @@ -72,6 +75,7 @@ def run_transform_rst(*args): def run_transform_rst(*args): pass + class TestSchema2HTML(unittest.TestCase): def test_schema_only(self): @@ -115,14 +119,15 @@ def test_cover_logging_components(self): self.assertIn('eventlog', res.getvalue()) run_transform_rst('--package', 'ZConfig.components.logger') + class TestRst(unittest.TestCase): def _parse(self, text): document = docutils.utils.new_document( "Schema", settings=docutils.frontend.OptionParser( - components=(docutils.parsers.rst.Parser,) - ).get_default_values()) + components=(docutils.parsers.rst.Parser,) + ).get_default_values()) parser = docutils.parsers.rst.Parser() text = textwrap.dedent(text) @@ -197,31 +202,37 @@ def test_parse_package_excluded_names(self): self.assertNotIn("SyslogHandlerFactory", doc_text) self.assertNotIn("FileHandlerFactory", doc_text) - def test_description_dedent(self): text = """No leading whitespace on this line. But this line has whitespace. As does this one. """ written = [] + class FUT(RstSchemaFormatter): + def __init__(self): pass + def _parsed(self, text, _): return text + def write(self, *texts): written.extend(texts) + fut = FUT() fut.description(text) - dedented = ("""No leading whitespace on this line.\n""" - """But this line has whitespace.\n""" - """As does this one.\n""") + dedented = ("No leading whitespace on this line.\n" + "But this line has whitespace.\n" + "As does this one.\n") self.assertEqual(written[0], dedented) + def test_suite(): return unittest.defaultTestLoader.loadTestsFromName(__name__) + if __name__ == '__main__': unittest.main(defaultTest='test_suite') diff --git a/ZConfig/tests/test_schemaless.py b/ZConfig/tests/test_schemaless.py index ebc0114..3a6ab9a 100644 --- a/ZConfig/tests/test_schemaless.py +++ b/ZConfig/tests/test_schemaless.py @@ -23,6 +23,7 @@ from ZConfig.schemaless import Section + class TestSection(unittest.TestCase): def test_init_with_data(self): @@ -38,5 +39,6 @@ def test_suite(): '../schemaless.txt'), ]) + if __name__ == '__main__': unittest.main(defaultTest='test_suite') diff --git a/ZConfig/tests/test_subst.py b/ZConfig/tests/test_subst.py index 33339e9..8f90ed7 100644 --- a/ZConfig/tests/test_subst.py +++ b/ZConfig/tests/test_subst.py @@ -29,8 +29,10 @@ def test_simple_names(self): "name1": "abc", "name_": "def", "_123": "ghi"} + def check(s, v): self.assertEqual(substitute(s, d), v) + check("$name", "value") check(" $name ", " value ") check("${name}", "value") @@ -65,9 +67,11 @@ def test_undefined_names(self): def test_syntax_errors(self): d = {"name": "${next"} + def check(s): self.assertRaises(SubstitutionSyntaxError, substitute, s, d) + check("${") check("${name") check("${1name}") @@ -105,5 +109,6 @@ def test_isname(self): def test_suite(): return unittest.defaultTestLoader.loadTestsFromName(__name__) + if __name__ == '__main__': unittest.main(defaultTest='test_suite') diff --git a/ZConfig/tests/test_validator.py b/ZConfig/tests/test_validator.py index fa1e4e1..364b4ad 100644 --- a/ZConfig/tests/test_validator.py +++ b/ZConfig/tests/test_validator.py @@ -20,9 +20,11 @@ from .support import input_file from .support import with_stdin_from_input_file + def run_validator(*args): return validator.main(args) + class TestValidator(unittest.TestCase): def test_no_schema(self): @@ -53,5 +55,6 @@ def test_bad_config(self): def test_suite(): return unittest.defaultTestLoader.loadTestsFromName(__name__) + if __name__ == '__main__': unittest.main(defaultTest='test_suite') diff --git a/ZConfig/url.py b/ZConfig/url.py index bd6b598..f88c219 100644 --- a/ZConfig/url.py +++ b/ZConfig/url.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2002, 2003 Zope Foundation and Contributors. +# Copyright (c) 2002, 2003, 2018 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, @@ -15,12 +15,14 @@ ZConfig and urllib2 expect file: URLs to consistently use the '//' hostpart seperator; the functions here enforce this constraint. + """ from ZConfig._compat import urlparse as _urlparse urlsplit = _urlparse.urlsplit + def urlnormalize(url): lc = url.lower() if lc.startswith("file:/") and not lc.startswith("file:///"): @@ -33,11 +35,11 @@ def urlunsplit(parts): parts.insert(3, '') url = _urlparse.urlunparse(tuple(parts)) if (parts[0] == "file" - and url.startswith("file:/") - and not url.startswith("file:///")): + and url.startswith("file:/") + and not url.startswith("file:///")): # It may not be possible to get here anymore with # modern urlparse, at least not on posix? - url = "file://" + url[5:] # pragma: no cover + url = "file://" + url[5:] # pragma: no cover return url @@ -51,5 +53,5 @@ def urljoin(base, relurl): if url.startswith("file:/") and not url.startswith("file:///"): # It may not be possible to get here anymore with # modern urlparse, at least not on posix? - url = "file://" + url[5:] # pragma: no cover + url = "file://" + url[5:] # pragma: no cover return url diff --git a/doc/conf.py b/doc/conf.py index bebdc6f..9b9e491 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -158,7 +158,5 @@ ] - - # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'https://docs.python.org/': None} diff --git a/setup.py b/setup.py index 68f04f5..a837214 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,8 @@ author_email="fred@fdrake.net", maintainer="Zope Foundation and Contributors", description="Structured Configuration Library", - keywords='configuration structured simple flexible typed hierarchy logging', + keywords=('configuration structured simple flexible typed hierarchy' + ' logging'), long_description=README + "\n\n" + CHANGES, license="ZPL 2.1", url="https://github.com/zopefoundation/ZConfig/",