diff -Nru python-cement-2.8.2/cement/core/backend.py python-cement-2.10.0/cement/core/backend.py --- python-cement-2.8.2/cement/core/backend.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/core/backend.py 2016-07-14 20:36:19.000000000 +0000 @@ -1,7 +1,7 @@ """Cement core backend module.""" -VERSION = (2, 8, 2, 'final', 0) # pragma: nocover +VERSION = (2, 10, 0, 'final', 0) # pragma: nocover # global hooks/handlers (DEPRECATED) -__handlers__ = {} # pragma: nocover -__hooks__ = {} # pragma: nocover +__handlers__ = {} # pragma: nocover +__hooks__ = {} # pragma: nocover diff -Nru python-cement-2.8.2/cement/core/controller.py python-cement-2.10.0/cement/core/controller.py --- python-cement-2.8.2/cement/core/controller.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/core/controller.py 2016-07-14 20:36:19.000000000 +0000 @@ -171,7 +171,7 @@ metadict['func_name'] = func.__name__ metadict['exposed'] = True metadict['hide'] = self.hide - metadict['help'] = self.help + metadict['help'] = self.help or func.__doc__ metadict['aliases'] = self.aliases metadict['aliases_only'] = self.aliases_only metadict['controller'] = None # added by the controller @@ -365,7 +365,7 @@ commands.append(func) # process stacked controllers second for commands and args - for contr in handler.list('controller'): + for contr in self.app.handler.list('controller'): # don't include self here if contr == self.__class__: continue @@ -428,7 +428,6 @@ self._visible_commands.sort() def _get_dispatch_command(self): - default_func = self._meta.default_func default_func_key = re.sub('_', '-', self._meta.default_func) if (len(self.app.argv) <= 0) or (self.app.argv[0].startswith('-')): diff -Nru python-cement-2.8.2/cement/core/extension.py python-cement-2.10.0/cement/core/extension.py --- python-cement-2.8.2/cement/core/extension.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/core/extension.py 2016-07-14 20:36:19.000000000 +0000 @@ -4,9 +4,6 @@ from ..core import exc, interface, handler from ..utils.misc import minimal_logger -if sys.version_info[0] >= 3: - from imp import reload # pragma: no cover - LOG = minimal_logger(__name__) diff -Nru python-cement-2.8.2/cement/core/foundation.py python-cement-2.10.0/cement/core/foundation.py --- python-cement-2.8.2/cement/core/foundation.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/core/foundation.py 2016-07-14 20:36:19.000000000 +0000 @@ -1,25 +1,31 @@ """Cement core foundation module.""" -import re import os import sys import signal -import copy import platform from time import sleep -from ..core import backend, exc, log, config, plugin, interface +from ..core import backend, exc, log, config, plugin from ..core import output, extension, arg, controller, meta, cache, mail from ..core.handler import HandlerManager from ..core.hook import HookManager from ..utils.misc import is_true, minimal_logger from ..utils import fs -if sys.version_info[0] >= 3: - from imp import reload # pragma: nocover +# The `imp` module is deprecated in favor of `importlib` in 3.4, but it +# wasn't introduced until 3.1. Finally, reload is a builtin on Python < 3 +pyver = sys.version_info +if pyver[0] >= 3 and pyver[1] >= 4: # pragma: nocover # noqa + from importlib import reload as reload_module # pragma: nocover # noqa +elif pyver[0] >= 3: # pragma: nocover # noqa + from imp import reload as reload_module # pragma: nocover # noqa +else: # pragma: nocover # noqa + reload_module = reload # pragma: nocover # noqa + LOG = minimal_logger(__name__) if platform.system() == 'Windows': - SIGNALS = [signal.SIGTERM, signal.SIGINT] + SIGNALS = [signal.SIGTERM, signal.SIGINT] # pragma: nocover else: SIGNALS = [signal.SIGTERM, signal.SIGINT, signal.SIGHUP] @@ -36,7 +42,7 @@ return for i in app._meta.handler_override_options: - if i not in interface.list(): + if i not in app.handler.list_types(): LOG.debug("interface '%s'" % i + " is not defined, can not override handlers") continue @@ -204,6 +210,11 @@ ``sys.exit(X)`` where ``X`` is ``self.exit_code``. """ + config_extension = '.conf' + """ + Extension used to identify application and plugin configuration files. + """ + config_files = None """ List of config files to parse. @@ -222,6 +233,9 @@ Files are loaded in order, and have precedence in order. Therefore, the last configuration loaded has precedence (and overwrites settings loaded from previous configuration files). + + Note that ``.conf`` is the default config file extension, defined by + ``CementApp.Meta.config_extension``. """ plugins = [] @@ -234,7 +248,8 @@ plugin_config_dirs = None """ A list of directory paths where plugin config files can be found. - Files must end in `.conf` or they will be ignored. + Files must end in ``.conf`` (or the extension defined by + ``CementApp.Meta.config_extension``) or they will be ignored. Note: Though ``CementApp.Meta.plugin_config_dirs`` is ``None``, Cement will set this to a default list based on ``CementApp.Meta.label``. @@ -252,9 +267,11 @@ plugin_config_dir = None """ - A directory path where plugin config files can be found. Files - must end in `.conf`. By default, this setting is also overridden - by the ``[] -> plugin_config_dir`` config setting parsed in + A directory path where plugin config files can be found. Files must + end in ``.conf`` (or the extension defined by + ``CementApp.Meta.config_extension``) or they will be ignored. By + default, this setting is also overridden by the + ``[] -> plugin_config_dir`` config setting parsed in any of the application configuration files. If set, this item will be **appended** to @@ -399,6 +416,32 @@ config_defaults = None """Default configuration dictionary. Must be of type 'dict'.""" + meta_defaults = {} + """ + Default metadata dictionary used to pass high level options from the + application down to handlers at the point they are registered by the + framework **if the handler has not already been instantiated**. + + For example, if requiring the ``json`` extension, you might want to + override ``JsonOutputHandler.Meta.json_module`` with ``ujson`` by + doing the following + + .. code-block:: python + + from cement.core.foundation import CementApp + from cement.utils.misc import init_defaults + + META = init_defaults('output.json') + META['output.json']['json_module'] = 'ujson' + + class MyApp(CementApp): + class Meta: + label = 'myapp' + extensions = ['json'] + meta_defaults = META + + """ + catch_signals = SIGNALS """ List of signals to catch, and raise exc.CaughtSignal for. @@ -634,6 +677,41 @@ has changed or exists. """ + alternative_module_mapping = {} + """ + EXPERIMENTAL FEATURE: This is an experimental feature added in Cement + 2.9.x and may or may not be removed in future versions of Cement. + + Dictionary of alternative, **drop-in** replacement modules to use + selectively throughout the application, framework, or + extensions. Developers can optionally use the + ``CementApp.__import__()`` method to import simple modules, and if + that module exists in this mapping it will import the alternative + library in it's place. + + This is a low-level feature, and may not produce the results you are + expecting. It's purpose is to allow the developer to replace specific + modules at a high level. Example: For an application wanting to use + ``ujson`` in place of ``json``, the developer could set the following: + + .. code-block:: python + + alternative_module_mapping = { + 'json' : 'ujson', + } + + In the app, you would then load ``json`` as: + + .. code-block:: python + + _json = app.__import__('json') + _json.dumps(data) + + + Obviously, the replacement module **must be** a drop-in replace and + function the same. + """ + def __init__(self, label=None, **kw): super(CementApp, self).__init__(**kw) @@ -651,8 +729,10 @@ self._loaded_bootstrap = None self._parsed_args = None self._last_rendered = None + self._extended_members = [] self.__saved_stdout__ = None self.__saved_stderr__ = None + self.__retry_hooks__ = [] self.handler = None self.hook = None @@ -714,6 +794,8 @@ LOG.debug("extending appication with '.%s' (%s)" % (member_name, member_object)) setattr(self, member_name, member_object) + if member_name not in self._extended_members: + self._extended_members.append(member_name) def _validate_label(self): if not self._meta.label: @@ -756,7 +838,7 @@ self._loaded_bootstrap = sys.modules[self._meta.bootstrap] else: - reload(self._loaded_bootstrap) + reload_module(self._loaded_bootstrap) for res in self.hook.run('pre_setup', self): pass @@ -772,6 +854,9 @@ self._setup_output_handler() self._setup_controllers() + for hook_spec in self.__retry_hooks__: + self.hook.register(*hook_spec) + for res in self.hook.run('post_setup', self): pass @@ -843,6 +928,9 @@ :returns: ``None`` """ LOG.debug('reloading the %s application' % self._meta.label) + for member in self._extended_members: + delattr(self, member) + self._extended_members = [] self.handler.__handlers__ = {} self.hook.__hooks__ = {} self._lay_cement() @@ -931,11 +1019,11 @@ """ if not is_true(self._meta.ignore_deprecation_warnings): - self.log.warn("Cement Deprecation Warning: " + - "CementApp.get_last_rendered() has been " + - "deprecated, and will be removed in future " + - "versions of Cement. You should use the " + - "CementApp.last_rendered property instead.") + self.log.warning("Cement Deprecation Warning: " + + "CementApp.get_last_rendered() has been " + + "deprecated, and will be removed in future " + + "versions of Cement. You should use the " + + "CementApp.last_rendered property instead.") return self._last_rendered @property @@ -987,7 +1075,7 @@ self._suppress_output() # Forward/Backward compat, see Issue #311 - if self._meta.use_backend_globals: + if self._meta.use_backend_globals is True: backend.__hooks__ = {} backend.__handlers__ = {} self.handler = HandlerManager(use_backend_globals=True) @@ -1014,14 +1102,24 @@ self.hook.define(label) # register some built-in framework hooks - self.hook.register( - 'post_setup', add_handler_override_options, weight=-99) + self.hook.register('post_setup', add_handler_override_options, + weight=-99) self.hook.register('post_argument_parsing', handler_override, weight=-99) - # register application hooks from meta - for label, func in self._meta.hooks: - self.hook.register(label, func) + # register application hooks from meta. the hooks listed in + # CementApp.Meta.hooks are registered here, so obviously can not be + # for any hooks other than the builtin framework hooks that we just + # defined here (above). Anything that we couldn't register here + # will be retried after setup + self.__retry_hooks__ = [] + for hook_spec in self._meta.hooks: + if not self.hook.defined(hook_spec[0]): + LOG.debug('hook %s not defined, will retry after setup' % + hook_spec[0]) + self.__retry_hooks__.append(hook_spec) + else: + self.hook.register(*hook_spec) # define and register handlers self.handler.define(extension.IExtension) @@ -1098,7 +1196,17 @@ self.catch_signal(signum) def _resolve_handler(self, handler_type, handler_def, raise_error=True): - han = self.handler.resolve(handler_type, handler_def, raise_error) + meta_defaults = {} + if type(handler_def) == str: + _meta_label = "%s.%s" % (handler_type, handler_def) + meta_defaults = self._meta.meta_defaults.get(_meta_label, {}) + elif hasattr(handler_def, 'Meta'): + _meta_label = "%s.%s" % (handler_type, handler_def.Meta.label) + meta_defaults = self._meta.meta_defaults.get(_meta_label, {}) + + han = self.handler.resolve(handler_type, handler_def, + raise_error=raise_error, + meta_defaults=meta_defaults) if han is not None: han._setup(self) return han @@ -1123,10 +1231,11 @@ if self._meta.config_files is None: label = self._meta.label + ext = self._meta.config_extension self._meta.config_files = [ - os.path.join('/', 'etc', label, '%s.conf' % label), - os.path.join(fs.HOME_DIR, '.%s.conf' % label), + os.path.join('/', 'etc', label, '%s%s' % (label, ext)), + os.path.join(fs.HOME_DIR, '.%s%s' % (label, ext)), os.path.join(fs.HOME_DIR, '.%s' % label, 'config'), ] @@ -1238,10 +1347,14 @@ # template dirs if self._meta.template_dirs is None: - self._meta.template_dirs = [ + self._meta.template_dirs = [] + paths = [ os.path.join(fs.HOME_DIR, '.%s' % label, 'templates'), '/usr/lib/%s/templates' % label, ] + for path in paths: + self.add_template_dir(path) + template_dir = self._meta.template_dir if template_dir is not None: if template_dir not in self._meta.template_dirs: @@ -1327,6 +1440,56 @@ """ pass + def add_template_dir(self, path): + """ + Append a directory path to the list of template directories to parse + for templates. + + :param path: Directory path that contains template files. + + Usage: + + .. code-block:: python + + app.add_template_dir('/path/to/my/templates') + + """ + path = fs.abspath(path) + if path not in self._meta.template_dirs: + self._meta.template_dirs.append(path) + + def remove_template_dir(self, path): + """ + Remove a directory path from the list of template directories to parse + for templates. + + :param path: Directory path that contains template files. + + Usage: + + .. code-block:: python + + app.remove_template_dir('/path/to/my/templates') + + """ + path = fs.abspath(path) + if path in self._meta.template_dirs: + self._meta.template_dirs.remove(path) + + def __import__(self, obj, from_module=None): + # EXPERIMENTAL == UNDOCUMENTED + mapping = self._meta.alternative_module_mapping + + if from_module is not None: + _from = mapping.get(from_module, from_module) + _loaded = __import__(_from, globals(), locals(), [obj], 0) + return getattr(_loaded, obj) + else: + obj = mapping.get(obj, obj) + _loaded = __import__(obj, globals(), locals(), [], 0) + + return _loaded + def __enter__(self): self.setup() return self diff -Nru python-cement-2.8.2/cement/core/handler.py python-cement-2.10.0/cement/core/handler.py --- python-cement-2.8.2/cement/core/handler.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/core/handler.py 2016-07-14 20:36:19.000000000 +0000 @@ -86,6 +86,23 @@ res.append(self.__handlers__[handler_type][label]) return res + def list_types(self): + """ + Return a list of handler types (interface labels). + + :returns: List of handlers types (interface labels). + :rtype: ``list`` + :raises: :class:`cement.core.exc.FrameworkError` + + Usage: + + .. code-block:: python + + app.handler.list_types() + + """ + return self.__handlers__.keys() + def define(self, interface): """ Define a handler based on the provided interface. Defines a handler @@ -141,7 +158,7 @@ else: return False - def register(self, handler_obj): + def register(self, handler_obj, force=False): """ Register a handler object to a handler. If the same object is already registered then no exception is raised, however if a different object @@ -149,6 +166,8 @@ raised. :param handler_obj: The uninstantiated handler object to register. + :param force: Whether to allow replacement if an existing + handler of the same ``label`` is already registered. :raises: :class:`cement.core.exc.InterfaceError` :raises: :class:`cement.core.exc.FrameworkError` @@ -192,9 +211,19 @@ raise exc.FrameworkError("Handler type '%s' doesn't exist." % handler_type) if obj._meta.label in self.__handlers__[handler_type] and \ - self.__handlers__[handler_type][obj._meta.label] != obj: - raise exc.FrameworkError("handlers['%s']['%s'] already exists" % - (handler_type, obj._meta.label)) + self.__handlers__[handler_type][obj._meta.label] != orig_obj: + + if force is True: + LOG.debug( + "handlers['%s']['%s'] already exists" % + (handler_type, obj._meta.label) + + ", but `force==True`" + ) + else: + raise exc.FrameworkError( + "handlers['%s']['%s'] already exists" % + (handler_type, obj._meta.label) + ) interface = self.__handlers__[handler_type]['__interface__'] if hasattr(interface.IMeta, 'validator'): @@ -203,7 +232,7 @@ LOG.debug("Interface '%s' does not have a validator() function!" % interface) - self.__handlers__[handler_type][obj.Meta.label] = orig_obj + self.__handlers__[handler_type][obj._meta.label] = orig_obj def registered(self, handler_type, handler_label): """ @@ -227,18 +256,21 @@ return False - def resolve(self, handler_type, handler_def, raise_error=True): + def resolve(self, handler_type, handler_def, **kwargs): """ Resolves the actual handler, as it can be either a string identifying the handler to load from self.__handlers__, or it can be an instantiated or non-instantiated handler class. :param handler_type: The type of handler (aka the interface label) - :param hander_def: The handler as defined in CementApp.Meta. + :param handler_def: The handler as defined in CementApp.Meta. :type handler_def: str, uninstantiated object, or instantiated object - :param raise_error: Whether or not to raise an exception if unable + :keyword raise_error: Whether or not to raise an exception if unable to resolve the handler. :type raise_error: boolean + :keywork meta_defaults: Optional meta-data dictionary used as + defaults to pass when instantiating uninstantiated handlers. See + ``CementApp.Meta.meta_defaults``. :returns: The instantiated handler object. Usage: @@ -255,15 +287,18 @@ log = app.handler.resolve('log', ColorLogHandler()) """ + raise_error = kwargs.get('raise_error', True) + meta_defaults = kwargs.get('meta_defaults', {}) han = None + if type(handler_def) == str: - han = self.get(handler_type, handler_def)() + han = self.get(handler_type, handler_def)(**meta_defaults) elif hasattr(handler_def, '_meta'): if not self.registered(handler_type, handler_def._meta.label): self.register(handler_def.__class__) han = handler_def elif hasattr(handler_def, 'Meta'): - han = handler_def() + han = handler_def(**meta_defaults) if not self.registered(handler_type, han._meta.label): self.register(handler_def) @@ -416,9 +451,9 @@ # only log debug for now as this won't be removed until Cement 3.x and # we don't have access to CementApp.Meta.ignore_deprecation_warnings here LOG.debug( - 'Cement Deprecation Warning: `handler.get()` has been deprecated, ' + 'Cement Deprecation Warning: `handler.list()` has been deprecated, ' 'and will be removed in future versions of Cement. You should now ' - 'use `CementApp.handler.get()` instead.' + 'use `CementApp.handler.list()` instead.' ) if handler_type not in backend.__handlers__: @@ -511,7 +546,7 @@ return False -def register(handler_obj): +def register(handler_obj, force=False): """ DEPRECATION WARNING: This function is deprecated as of Cement 2.7.x and will be removed in future versions of Cement. @@ -525,6 +560,8 @@ raised. :param handler_obj: The uninstantiated handler object to register. + :param force: Whether to allow replacement if an existing + handler of the same ``label`` is already registered. :raises: cement.core.exc.InterfaceError :raises: cement.core.exc.FrameworkError @@ -578,8 +615,17 @@ handler_type) if obj._meta.label in backend.__handlers__[handler_type] and \ backend.__handlers__[handler_type][obj._meta.label] != obj: - raise exc.FrameworkError("handlers['%s']['%s'] already exists" % - (handler_type, obj._meta.label)) + if force is True: + LOG.debug( + "handlers['%s']['%s'] already exists" % + (handler_type, obj._meta.label) + + ", but `force==True`" + ) + else: + raise exc.FrameworkError( + "handlers['%s']['%s'] already exists" % + (handler_type, obj._meta.label) + ) interface = backend.__handlers__[handler_type]['__interface__'] if hasattr(interface.IMeta, 'validator'): diff -Nru python-cement-2.8.2/cement/core/interface.py python-cement-2.10.0/cement/core/interface.py --- python-cement-2.8.2/cement/core/interface.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/core/interface.py 2016-07-14 20:36:19.000000000 +0000 @@ -10,12 +10,20 @@ def list(): """ + DEPRECATION WARNING: This function is deprecated as of Cement 2.9 + in favor of the `CementApp.handler.list_types()` function, and will be + removed in future versions of Cement. + Return a list of defined interfaces (handler types). :returns: List of defined interfaces :rtype: ``list`` """ + + # FIXME: Can't print a deprecation warning here because we don't have + # access to the app... and this is too deep to use minimal logger... ;\ + return backend.__handlers__.keys() diff -Nru python-cement-2.8.2/cement/core/log.py python-cement-2.10.0/cement/core/log.py --- python-cement-2.8.2/cement/core/log.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/core/log.py 2016-07-14 20:36:19.000000000 +0000 @@ -3,7 +3,7 @@ """ -from ..core import exc, interface, handler +from ..core import interface, handler def log_validator(klass, obj): @@ -14,7 +14,8 @@ 'set_level', 'get_level', 'info', - 'warn', + 'warn', # DEPRECATED + 'warning', 'error', 'fatal', 'debug', @@ -72,7 +73,7 @@ def set_level(): """ Set the log level. Must except atleast one of: - ``['INFO', 'WARN', 'ERROR', 'DEBUG', or 'FATAL']``. + ``['INFO', 'WARNING', 'ERROR', 'DEBUG', or 'FATAL']``. """ @@ -87,9 +88,9 @@ """ - def warn(self, msg): + def warning(self, msg): """ - Log to the 'WARN' facility. + Log to the 'WARNING' facility. :param msg: The message to log. diff -Nru python-cement-2.8.2/cement/core/output.py python-cement-2.10.0/cement/core/output.py --- python-cement-2.8.2/cement/core/output.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/core/output.py 2016-07-14 20:36:19.000000000 +0000 @@ -3,7 +3,8 @@ import os import sys import pkgutil -from ..core import backend, exc, interface, handler +import re +from ..core import exc, interface, handler from ..utils.misc import minimal_logger from ..utils import fs @@ -121,17 +122,19 @@ content = open(full_path, 'r').read() LOG.debug("loaded output template from file %s" % full_path) - return content + return (content, full_path) else: LOG.debug("output template file %s does not exist" % full_path) continue - return None + return (None, None) def _load_template_from_module(self, template_path): template_module = self.app._meta.template_module template_path = template_path.lstrip('/') + full_module_path = "%s.%s" % (template_module, + re.sub('/', '.', template_path)) LOG.debug("attemping to load output template '%s' from module %s" % (template_path, template_module)) @@ -143,25 +146,24 @@ except ImportError as e: LOG.debug("unable to import template module '%s'." % template_module) - return None + return (None, None) # get the template content try: content = pkgutil.get_data(template_module, template_path) LOG.debug("loaded output template '%s' from module %s" % (template_path, template_module)) - return content + return (content, full_module_path) except IOError as e: LOG.debug("output template '%s' does not exist in module %s" % (template_path, template_module)) - return None + return (None, None) def load_template(self, template_path): """ Loads a template file first from ``self.app._meta.template_dirs`` and secondly from ``self.app._meta.template_module``. The ``template_dirs`` have presedence. - :param template_path: The secondary path of the template **after** either ``template_module`` or ``template_dirs`` prefix (set via ``CementApp.Meta``) @@ -169,20 +171,46 @@ :raises: FrameworkError if the template does not exist in either the ``template_module`` or ``template_dirs``. """ + res = self.load_template_with_location(template_path) + content, template_type, path = res + + # only return content for backward compatibility + return content + + # FIX ME: Should eventually replace ``load_template()`` (but that breaks + # compatibility) + def load_template_with_location(self, template_path): + """ + Loads a template file first from ``self.app._meta.template_dirs`` and + secondly from ``self.app._meta.template_module``. The + ``template_dirs`` have presedence. + + :param template_path: The secondary path of the template **after** + either ``template_module`` or ``template_dirs`` prefix (set via + ``CementApp.Meta``) + :returns: A tuple that includes the content of the template (str), + the type of template (str which is one of: ``directory``, or + ``module``), and the ``path`` (str) of the directory or module) + :raises: FrameworkError if the template does not exist in either the + ``template_module`` or ``template_dirs``. + """ if not template_path: raise exc.FrameworkError("Invalid template path '%s'." % template_path) # first attempt to load from file - content = self._load_template_from_file(template_path) + content, path = self._load_template_from_file(template_path) if content is None: # second attempt to load from module - content = self._load_template_from_module(template_path) + content, path = self._load_template_from_module(template_path) + template_type = 'module' + else: + template_type = 'directory' # if content is None, that means we didn't find a template file in # either and that is an exception - if content is not None: - return content - else: + if content is None: raise exc.FrameworkError("Could not locate template: %s" % template_path) + + return (content, template_type, path) diff -Nru python-cement-2.8.2/cement/core/plugin.py python-cement-2.10.0/cement/core/plugin.py --- python-cement-2.8.2/cement/core/plugin.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/core/plugin.py 2016-07-14 20:36:19.000000000 +0000 @@ -1,6 +1,6 @@ """Cement core plugins module.""" -from ..core import backend, exc, interface, handler +from ..core import interface, handler from ..utils.misc import minimal_logger LOG = minimal_logger(__name__) diff -Nru python-cement-2.8.2/cement/ext/ext_argcomplete.py python-cement-2.10.0/cement/ext/ext_argcomplete.py --- python-cement-2.8.2/cement/ext/ext_argcomplete.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_argcomplete.py 2016-07-14 20:36:19.000000000 +0000 @@ -1,6 +1,6 @@ """ The Argcomplete Extension provides the necessary hooks to utilize -the `Argcomplete Library `_, +the `Argcomplete Library `_, and perform auto-completion of command line arguments/options/sub-parsers/etc. Requirements @@ -81,7 +81,7 @@ default See the -`Argcomplete Documentation `_ +`Argcomplete Documentation `_ on how to properly integrate it's usage into your application deployment. This extension simply enables Argcomplete to do it's thing on application startup. diff -Nru python-cement-2.8.2/cement/ext/ext_argparse.py python-cement-2.10.0/cement/ext/ext_argparse.py --- python-cement-2.8.2/cement/ext/ext_argparse.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_argparse.py 2016-07-14 20:36:19.000000000 +0000 @@ -168,7 +168,6 @@ import re import sys from argparse import ArgumentParser, SUPPRESS -from ..core import backend, arg from ..core.handler import CementBaseHandler from ..core.arg import CementArgumentHandler, IArgument from ..core.controller import IController @@ -208,9 +207,22 @@ label = 'argparse' """The string identifier of the handler.""" + ignore_unknown_arguments = False + """ + Whether or not to ignore any arguments passed that are not defined. + Default behavoir by Argparse is to raise an "unknown argument" + exception by Argparse. + + This affectively triggers the difference between using ``parse_args`` + and ``parse_known_args``. Unknown arguments will be accessible as + ``unknown_args``. + """ + def __init__(self, *args, **kw): super(ArgparseArgumentHandler, self).__init__(*args, **kw) self.config = None + self.unknown_args = None + self.parsed_args = None def parse(self, arg_list): """ @@ -222,8 +234,14 @@ :returns: object whose members are the arguments parsed. """ - args = self.parse_args(arg_list) - return args + if self._meta.ignore_unknown_arguments is True: + args, unknown = self.parse_known_args(arg_list) + self.parsed_args = args + self.unknown_args = unknown + else: + args = self.parse_args(arg_list) + self.parsed_args = args + return self.parsed_args def add_argument(self, *args, **kw): """ @@ -725,7 +743,6 @@ label = controller._meta.label LOG.debug("processing commands for '%s' " % label + "controller namespace") - parser = self._get_parser_by_controller(controller) commands = controller._collect_commands() for command in commands: diff -Nru python-cement-2.8.2/cement/ext/ext_colorlog.py python-cement-2.10.0/cement/ext/ext_colorlog.py --- python-cement-2.8.2/cement/ext/ext_colorlog.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_colorlog.py 2016-07-14 20:36:19.000000000 +0000 @@ -62,7 +62,7 @@ app.run() app.log.debug('This is my debug message') app.log.info('This is my info message') - app.log.warn('This is my warning message') + app.log.warning('This is my warning message') app.log.error('This is my error message') app.log.fatal('This is my critical message') diff -Nru python-cement-2.8.2/cement/ext/ext_configobj.py python-cement-2.10.0/cement/ext/ext_configobj.py --- python-cement-2.8.2/cement/ext/ext_configobj.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_configobj.py 2016-07-14 20:36:19.000000000 +0000 @@ -46,9 +46,7 @@ """ -import os -import sys -from ..core import config, exc +from ..core import config from ..utils.misc import minimal_logger from configobj import ConfigObj diff -Nru python-cement-2.8.2/cement/ext/ext_configparser.py python-cement-2.10.0/cement/ext/ext_configparser.py --- python-cement-2.8.2/cement/ext/ext_configparser.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_configparser.py 2016-07-14 20:36:19.000000000 +0000 @@ -35,9 +35,8 @@ # etc. """ -import os import sys -from ..core import backend, config +from ..core import config from ..utils.misc import minimal_logger if sys.version_info[0] < 3: diff -Nru python-cement-2.8.2/cement/ext/ext_dummy.py python-cement-2.10.0/cement/ext/ext_dummy.py --- python-cement-2.8.2/cement/ext/ext_dummy.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_dummy.py 2016-07-14 20:36:19.000000000 +0000 @@ -34,7 +34,7 @@ """ -from ..core import backend, output, mail +from ..core import output, mail from ..utils.misc import minimal_logger LOG = minimal_logger(__name__) diff -Nru python-cement-2.8.2/cement/ext/ext_genshi.py python-cement-2.10.0/cement/ext/ext_genshi.py --- python-cement-2.8.2/cement/ext/ext_genshi.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_genshi.py 2016-07-14 20:36:19.000000000 +0000 @@ -60,8 +60,7 @@ """ -import sys -from ..core import output, exc +from ..core import output from ..utils.misc import minimal_logger from genshi.template import NewTextTemplate diff -Nru python-cement-2.8.2/cement/ext/ext_handlebars.py python-cement-2.10.0/cement/ext/ext_handlebars.py --- python-cement-2.8.2/cement/ext/ext_handlebars.py 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_handlebars.py 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1,255 @@ +""" +The Handlebars Extension provides output templating based on the +`Handlebars Templating Language `_. + +Requirements +------------ + + * pybars3 (``pip install pybars3``) + + +Configuration +------------- + +Application Meta-data +^^^^^^^^^^^^^^^^^^^^^ + +This extension supports the following application meta-data via +``CementApp.Meta``: + + * **handlebars_helpers** - A dictionary of helper functions to register + with the compiler. Will **override** + ``HandlebarsOutputHandler.Meta.helpers``. + * **handlebars_partials** - A list of partials (template file names) to + search for, and pre-load before rendering templates. Will **override** + ``HandlebarsOutputHandler.Meta.partials``. + + +Template Directories +^^^^^^^^^^^^^^^^^^^^ + +To **prepend** a directory to the ``template_dirs`` list defined by the +application/developer, an end-user can add the configuration option +``template_dir`` to their application configuration file under the main +config section: + +.. code-block:: text + + [myapp] + template_dir = /path/to/my/templates + + +Usage +----- + +.. code-block:: python + + class MyApp(CementApp): + class Meta: + label = 'myapp' + extensions = ['handlebars'] + output_handler = 'handlebars' + template_module = 'myapp.templates' + template_dirs = [ + '~/.myapp/templates', + '/usr/lib/myapp/templates', + ] + # ... + +Note that the above ``template_module`` and ``template_dirs`` are the +auto-defined defaults but are added here for clarity. From here, you +would then put a Handlebars template file in +``myapp/templates/my_template.handlebars`` or +``/usr/lib/myapp/templates/my_template.handlebars`` and then render a data +dictionary with it: + +.. code-block:: python + + app.render(some_data, 'my_template.handlebars') + + +Helpers +^^^^^^^ + +Custom helper functions can easily be registered with the compiler via +``CementApp.Meta.handlebars_helpers`` and/or +``HandlebarsOutputHandler.Meta.helpers``. + +.. code-block:: python + + def my_custom_helper(this, arg1, arg2): + # do something with arg1 and arg2 + if arg1 == arg2: + return True + else: + return False + + + class MyApp(CementApp): + class Meta: + label = 'myapp' + extensions = ['handlebars'] + output_handler = 'handlebars' + handlebars_helpers = { + 'myhelper' : my_custom_helper + } + # ... + +You would then access this in your template as: + +.. code-block:: console + + This is my template + + {{#if (myhelper this that)}} + This will only appear if myhelper returns True + {{/if}} + + +See the `Handlebars Documentation `_ for +more information on helpers. + + +Partials +^^^^^^^^ + +Though partials are supported by the library, there is no good way of +automatically loading them in the context and workflow of a typical Cement +application. Therefore, the extension needs a list of partial +template names to know what to preload, in order to make partials work. +Future versions will hopefully automate this. + +Example: + +.. code-block:: python + + class MyApp(CementApp): + class Meta: + label = 'myapp' + extensions = ['handlebars'] + output_handler = 'handlebars' + handlebars_partials = [ + 'header.bars', + 'footer.bars', + ] + +Where ``header.bars`` and ``footer.bars`` are template names that will be +searched for, and loaded just like other templates loaded from template dirs. +These are then referenced in templates as: + +.. code-block:: console + + {{> "header.bars"}} + This is my template + {{> "footer.bars}} + +See the `Handlebars Documentation `_ for +more information on partials. + +""" + +import sys +import pybars._compiler +from cement.core import output, handler +from cement.utils.misc import minimal_logger + +# Monkey patch so we don't escape HTML (not clear how else to do this) +# See: https://github.com/wbond/pybars3/issues/25 +original_prepare = pybars._compiler.prepare + + +def my_prepare(value, escape): + return original_prepare(value, False) +pybars._compiler.prepare = my_prepare + +from pybars import Compiler # noqa + + +LOG = minimal_logger(__name__) + + +class HandlebarsOutputHandler(output.TemplateOutputHandler): + + """ + This class implements the :ref:`IOutput ` + interface. It provides text output from template and uses the + `Handlebars Templating Language `_ for Python + via the ``pybars`` library. Please see the developer documentation on + :ref:`Output Handling `. + + **Note** This extension has an external dependency on ``pybars3``. You + must include ``pybars3`` in your applications dependencies as Cement + explicitly does **not** include external dependencies for optional + extensions. + """ + + class Meta: + + """Handler meta-data.""" + + interface = output.IOutput + label = 'handlebars' + + #: Whether or not to include ``handlebars`` as an available to choice + #: to override the ``output_handler`` via command line options. + overridable = False + + #: Custom helpers + helpers = {} + + #: List of partials to preload + partials = [] + + def __init__(self, *args, **kw): + super(HandlebarsOutputHandler, self).__init__(*args, **kw) + self._raw_partials = {} + + def _setup(self, app): + super(HandlebarsOutputHandler, self)._setup(app) + if hasattr(self.app._meta, 'handlebars_helpers'): + self._meta.helpers = self.app._meta.handlebars_helpers + if hasattr(self.app._meta, 'handlebars_partials'): + self._meta.partials = self.app._meta.handlebars_partials + for partial in self._meta.partials: + self._raw_partials[partial] = self.load_template(partial) + + def _clean_content(self, content): + if sys.version_info[0] >= 3: + if not isinstance(content, str): + content = content.decode('utf-8') + else: + if not isinstance(content, unicode): # pragma: nocover # noqa + content = content.decode('utf-8') # pragma: nocover + return content + + def render_content(self, data, content): + bars = Compiler() + content = bars.compile(self._clean_content(content)) + + # need to render partials + partials = {} + for key, val in self._raw_partials.items(): + partials[key] = bars.compile(self._clean_content(val)) + + return content(data, helpers=self._meta.helpers, partials=partials) + + def render(self, data, template): + """ + Take a data dictionary and render it using the given template file. + + Required Arguments: + + :param data: The data dictionary to render. + :keyword template: The path to the template, after the + ``template_module`` or ``template_dirs`` prefix as defined in the + application. + :returns: str (the rendered template text) + + """ + LOG.debug("rendering output using '%s' as a template." % template) + res = self.render_content(data, self.load_template(template)) + return res + + +def load(app): + handler.register(HandlebarsOutputHandler) diff -Nru python-cement-2.8.2/cement/ext/ext_jinja2.py python-cement-2.10.0/cement/ext/ext_jinja2.py --- python-cement-2.8.2/cement/ext/ext_jinja2.py 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_jinja2.py 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1,134 @@ +""" +The Jinja2 Extension module provides output templating based on the +`Jinja2 Templating Language \ +`_. + + +Requirements +------------ + + * Jinja2 (``pip install Jinja2``) + + +Configuration +------------- + +To **prepend** a directory to the ``template_dirs`` list defined by the +application/developer, an end-user can add the configuration option +``template_dir`` to their application configuration file under the main +config section: + +.. code-block:: text + + [myapp] + template_dir = /path/to/my/templates + + +Usage +----- + +.. code-block:: python + + from cement.core.foundation import CementApp + + class MyApp(CementApp): + class Meta: + label = 'myapp' + extensions = ['jinja2'] + output_handler = 'jinja2' + template_module = 'myapp.templates' + template_dirs = [ + '~/.myapp/templates', + '/usr/lib/myapp/templates', + ] + + with MyApp() as app: + app.run() + + # create some data + data = dict(foo='bar') + + # render the data to STDOUT (default) via a template + app.render(data, 'my_template.jinja2') + + +Note that the above ``template_module`` and ``template_dirs`` are the +auto-defined defaults but are added here for clarity. From here, you +would then put a Jinja2 template file in +``myapp/templates/my_template.jinja2`` or +``/usr/lib/myapp/templates/my_template.jinja2``. + +""" + +import sys +from ..core import output +from ..utils.misc import minimal_logger +from jinja2 import Environment, FileSystemLoader, PackageLoader + +LOG = minimal_logger(__name__) + + +class Jinja2OutputHandler(output.TemplateOutputHandler): + + """ + This class implements the :ref:`IOutput ` + interface. It provides text output from template and uses the + `Jinja2 Templating Language + `_. + Please see the developer documentation on + :ref:`Output Handling `. + + """ + + class Meta: + + """Handler meta-data.""" + + interface = output.IOutput + label = 'jinja2' + + def __init__(self, *args, **kw): + super(Jinja2OutputHandler, self).__init__(*args, **kw) + + # expose Jinja2 Environment instance so that we can manipulate it + # higher in application code if necessary + self.env = Environment(keep_trailing_newline=True) + + def render(self, data_dict, template=None, **kw): + """ + Take a data dictionary and render it using the given template file. + Additional keyword arguments are ignored. + + Required Arguments: + + :param data_dict: The data dictionary to render. + :keyword template: The path to the template, after the + ``template_module`` or ``template_dirs`` prefix as defined in the + application. + :returns: str (the rendered template text) + + """ + + LOG.debug("rendering output using '%s' as a template." % template) + content, _type, path = self.load_template_with_location(template) + + if _type == 'directory': + self.env.loader = FileSystemLoader(self.app._meta.template_dirs) + elif _type == 'module': + parts = self.app._meta.template_module.rsplit('.', 1) + self.env.loader = PackageLoader(parts[0], package_path=parts[1]) + + if sys.version_info[0] >= 3: + if not isinstance(content, str): + content = content.decode('utf-8') + else: + if not isinstance(content, unicode): # pragma: nocover # noqa + content = content.decode('utf-8') # pragma: nocover + + tmpl = self.env.from_string(content) + + return tmpl.render(**data_dict) + + +def load(app): + app.handler.register(Jinja2OutputHandler) diff -Nru python-cement-2.8.2/cement/ext/ext_json_configobj.py python-cement-2.10.0/cement/ext/ext_json_configobj.py --- python-cement-2.8.2/cement/ext/ext_json_configobj.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_json_configobj.py 2016-07-14 20:36:19.000000000 +0000 @@ -13,7 +13,7 @@ Configuration ------------- -This extension does not honor any application configuration settings. +This extension does not support any configuration settings. Usage @@ -55,10 +55,7 @@ """ -import os -import json from ..utils.misc import minimal_logger -from ..utils.fs import abspath from ..ext.ext_configobj import ConfigObjConfigHandler LOG = minimal_logger(__name__) @@ -86,8 +83,17 @@ #: The string identifier of this handler. label = 'json_configobj' + #: Backend JSON module to use (``json``, ``ujson``, etc) + json_module = 'json' + def __init__(self, *args, **kw): super(JsonConfigObjConfigHandler, self).__init__(*args, **kw) + self._json = None + + def _setup(self, app): + super(JsonConfigObjConfigHandler, self)._setup(app) + self._json = __import__(self._meta.json_module, + globals(), locals(), [], 0) def _parse_file(self, file_path): """ @@ -99,7 +105,7 @@ :returns: boolean """ - self.merge(json.load(open(file_path))) + self.merge(self._json.load(open(file_path))) # FIX ME: Should check that file was read properly, however if not it # will likely raise an exception anyhow. diff -Nru python-cement-2.8.2/cement/ext/ext_json.py python-cement-2.10.0/cement/ext/ext_json.py --- python-cement-2.8.2/cement/ext/ext_json.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_json.py 2016-07-14 20:36:19.000000000 +0000 @@ -14,7 +14,7 @@ Configuration ------------- -This extension does not honor any application configuration settings. +This extension does not support any configuration settings. Usage @@ -68,11 +68,34 @@ $ python myapp.py -o json {"foo": "bar"} + +What if I Want To Use UltraJson or Something Else? +-------------------------------------------------- + +It is possible to override the backend ``json`` library module to use, for +example if you wanted to use UltraJson (``ujson``) or another +**drop-in replacement** library. The recommended solution would be to +override the ``JsonOutputHandler`` with you're own sub-classed version, and +modify the ``json_module`` meta-data option. + +.. code-block:: python + + from cement.ext.ext_json import JsonOutputHandler + + class MyJsonHandler(JsonOutputHandler): + class Meta: + json_module = 'ujson' + + # then, the class must be replaced via a 'post_setup' hook + + def override_json(app): + app.handler.register(MyJsonHandler, force=True) + + app.hook.register('post_setup', override_json) + """ -import sys -import json -from ..core import output, backend +from ..core import output from ..utils.misc import minimal_logger from ..ext.ext_configparser import ConfigParserConfigHandler @@ -148,27 +171,37 @@ label = 'json' """The string identifier of this handler.""" - #: Whether or not to include ``json`` as an available to choice + #: Whether or not to include ``json`` as an available choice #: to override the ``output_handler`` via command line options. overridable = True + #: Backend JSON library module to use (`json`, `ujson`) + json_module = 'json' + def __init__(self, *args, **kw): super(JsonOutputHandler, self).__init__(*args, **kw) + self._json = None - def render(self, data_dict, **kw): + def _setup(self, app): + super(JsonOutputHandler, self)._setup(app) + self._json = __import__(self._meta.json_module, + globals(), locals(), [], 0) + + def render(self, data_dict, template=None, **kw): """ Take a data dictionary and render it as Json output. Note that the template option is received here per the interface, however this - handler just ignores it. + handler just ignores it. Additional keyword arguments passed to + ``json.dumps()``. :param data_dict: The data dictionary to render. - :param template: This option is completely ignored. + :keyword template: This option is completely ignored. :returns: A JSON encoded string. :rtype: ``str`` """ LOG.debug("rendering output as Json via %s" % self.__module__) - return json.dumps(data_dict) + return self._json.dumps(data_dict, **kw) class JsonConfigHandler(ConfigParserConfigHandler): @@ -186,8 +219,17 @@ label = 'json' + #: Backend JSON library module to use (`json`, `ujson`). + json_module = 'json' + def __init__(self, *args, **kw): super(JsonConfigHandler, self).__init__(*args, **kw) + self._json = None + + def _setup(self, app): + super(JsonConfigHandler, self)._setup(app) + self._json = __import__(self._meta.json_module, + globals(), locals(), [], 0) def _parse_file(self, file_path): """ @@ -198,7 +240,7 @@ :returns: boolean """ - self.merge(json.load(open(file_path))) + self.merge(self._json.load(open(file_path))) # FIX ME: Should check that file was read properly, however if not it # will likely raise an exception anyhow. diff -Nru python-cement-2.8.2/cement/ext/ext_logging.py python-cement-2.10.0/cement/ext/ext_logging.py --- python-cement-2.8.2/cement/ext/ext_logging.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_logging.py 2016-07-14 20:36:19.000000000 +0000 @@ -43,7 +43,7 @@ with MyApp() as app: app.log.info("This is an info message") - app.log.warn("This is an warning message") + app.log.warning("This is an warning message") app.log.error("This is an error message") app.log.fatal("This is a fatal message") app.log.debug("This is a debug message") @@ -52,7 +52,7 @@ import os import logging -from ..core import exc, log +from ..core import log from ..utils.misc import is_true, minimal_logger from ..utils import fs @@ -135,7 +135,7 @@ max_files=4, ) - levels = ['INFO', 'WARN', 'ERROR', 'DEBUG', 'FATAL'] + levels = ['INFO', 'WARNING', 'WARN', 'ERROR', 'DEBUG', 'FATAL'] def __init__(self, *args, **kw): super(LoggingLogHandler, self).__init__(*args, **kw) @@ -162,11 +162,19 @@ def set_level(self, level): """ Set the log level. Must be one of the log levels configured in - self.levels which are ``['INFO', 'WARN', 'ERROR', 'DEBUG', 'FATAL']``. + self.levels which are + ``['INFO', 'WARNING', 'ERROR', 'DEBUG', 'FATAL']``. :param level: The log level to set. """ + if level.upper() == 'WARN': + level = 'WARNING' + LOG.warning("Cement Deprecation Warning: Use of the `WARN` " + + "level is deprecated as of Cement 2.9.x, and will " + + "be removed in future versions of Cement. You " + + "should use `WARNING` instead.") + self.clear_loggers(self._meta.namespace) for namespace in self._meta.clear_loggers: self.clear_loggers(namespace) @@ -297,7 +305,7 @@ """ Log to the INFO facility. - :param msg: The message the log. + :param msg: The message to log. :param namespace: A log prefix, generally the module ``__name__`` that the log is coming from. Will default to self._meta.namespace if None is passed. @@ -308,11 +316,11 @@ kwargs = self._get_logging_kwargs(namespace, **kw) self.backend.info(msg, **kwargs) - def warn(self, msg, namespace=None, **kw): + def warning(self, msg, namespace=None, **kw): """ - Log to the WARN facility. + Log to the WARNING facility. - :param msg: The message the log. + :param msg: The message to log. :param namespace: A log prefix, generally the module ``__name__`` that the log is coming from. Will default to self._meta.namespace if None is passed. @@ -321,13 +329,30 @@ """ kwargs = self._get_logging_kwargs(namespace, **kw) - self.backend.warn(msg, **kwargs) + self.backend.warning(msg, **kwargs) + + def warn(self, msg, namespace=None, **kw): + """ + DEPRECATION WARNING: This function is deprecated as of Cement 2.9.x + in favor of the ``LoggingLogHandler.warning()`` function, and will be + removed in future versions of Cement. + + See: :ref:LoggingLogHandler.warning(): + + """ + if not is_true(self.app._meta.ignore_deprecation_warnings): + self.debug("Cement Deprecation Warning: " + + "LoggingLogHandler.warn() has been " + + "deprecated, and will be removed in future " + + "versions of Cement. You should use the " + + "LoggingLogHandler.warning() function instead.") + self.warning(msg, namespace, **kw) def error(self, msg, namespace=None, **kw): """ Log to the ERROR facility. - :param msg: The message the log. + :param msg: The message to log. :param namespace: A log prefix, generally the module ``__name__`` that the log is coming from. Will default to self._meta.namespace if None is passed. @@ -342,7 +367,7 @@ """ Log to the FATAL (aka CRITICAL) facility. - :param msg: The message the log. + :param msg: The message to log. :param namespace: A log prefix, generally the module ``__name__`` that the log is coming from. Will default to self._meta.namespace if None is passed. @@ -357,7 +382,7 @@ """ Log to the DEBUG facility. - :param msg: The message the log. + :param msg: The message to log. :param namespace: A log prefix, generally the module ``__name__`` that the log is coming from. Will default to self._meta.namespace if None is passed. For debugging, it can be useful to set this to diff -Nru python-cement-2.8.2/cement/ext/ext_memcached.py python-cement-2.10.0/cement/ext/ext_memcached.py --- python-cement-2.8.2/cement/ext/ext_memcached.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_memcached.py 2016-07-14 20:36:19.000000000 +0000 @@ -98,7 +98,6 @@ """ -import sys import pylibmc from ..core import cache from ..utils.misc import minimal_logger diff -Nru python-cement-2.8.2/cement/ext/ext_mustache.py python-cement-2.10.0/cement/ext/ext_mustache.py --- python-cement-2.8.2/cement/ext/ext_mustache.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_mustache.py 2016-07-14 20:36:19.000000000 +0000 @@ -86,9 +86,8 @@ """ -import sys from pystache.renderer import Renderer -from ..core import output, exc +from ..core import output from ..utils.misc import minimal_logger LOG = minimal_logger(__name__) @@ -100,7 +99,6 @@ self.handler = handler def get(self, template): - stache = Renderer() return self.handler.load_template(template) @@ -134,9 +132,10 @@ super(MustacheOutputHandler, self).__init__(*args, **kw) self._partials_loader = PartialsLoader(self) - def render(self, data_dict, **kw): + def render(self, data_dict, template=None, **kw): """ Take a data dictionary and render it using the given template file. + Additional keyword arguments passed to ``stache.render()``. Required Arguments: @@ -147,12 +146,11 @@ :returns: str (the rendered template text) """ - template = kw.get('template', None) LOG.debug("rendering output using '%s' as a template." % template) content = self.load_template(template) stache = Renderer(partials=self._partials_loader) - return stache.render(content, data_dict) + return stache.render(content, data_dict, **kw) def load(app): diff -Nru python-cement-2.8.2/cement/ext/ext_plugin.py python-cement-2.10.0/cement/ext/ext_plugin.py --- python-cement-2.8.2/cement/ext/ext_plugin.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_plugin.py 2016-07-14 20:36:19.000000000 +0000 @@ -25,7 +25,7 @@ import sys import glob import imp -from ..core import backend, plugin, exc +from ..core import plugin, exc from ..utils.misc import is_true, minimal_logger from ..utils.fs import abspath @@ -84,7 +84,9 @@ else: # sort so that we always load plugins in the same order # regardless of OS (seems some don't sort reliably) - plugin_config_files = glob.glob("%s/*.conf" % config_dir) + path = "%s/*%s" % (config_dir, + self.app._meta.config_extension) + plugin_config_files = glob.glob(path) plugin_config_files.sort() for config in plugin_config_files: @@ -151,25 +153,37 @@ def _load_plugin_from_dir(self, plugin_name, plugin_dir): """ - Load a plugin from file within a plugin directory rather than a - python package within sys.path. - - :param plugin_name: The name of the plugin, also the name of the file - with '.py' appended to the name. - :param plugin_dir: The filesystem directory path where to find the - file. + Load a plugin from a directory path rather than a python package + within sys.path. This would either be ``myplugin.py`` or + ``myplugin/__init__.py`` within the given ``plugin_dir``. + + :param plugin_name: The name of the plugin. + :param plugin_dir: The filesystem directory path where the plugin + exists. """ - full_path = os.path.join(plugin_dir, "%s.py" % plugin_name) - if not os.path.exists(full_path): - LOG.debug("plugin file '%s' does not exist." % full_path) - return False + + # FIX ME: `imp` is deprecated in Python 3.4 and will be going away + # so we need to update forward compatibility for ``importlib``. + # + # See: https://github.com/datafolklabs/cement/issues/386 LOG.debug("attempting to load '%s' from '%s'" % (plugin_name, plugin_dir)) - # We don't catch this because it would make debugging a nightmare - f, path, desc = imp.find_module(plugin_name, [plugin_dir]) + if not os.path.exists(plugin_dir): + LOG.debug("plugin directory '%s' does not exist." % plugin_dir) + return False + + try: + f, path, desc = imp.find_module(plugin_name, [plugin_dir]) + except ImportError: + LOG.debug("plugin '%s' does not exist in '%s'." % + (plugin_name, plugin_dir)) + return False + + # We don't catch this because it would make debugging a + # nightmare mod = imp.load_module(plugin_name, f, path, desc) if mod and hasattr(mod, 'load'): mod.load(self.app) @@ -217,13 +231,13 @@ def load_plugin(self, plugin_name): """ - Load a plugin whose name is 'plugin_name'. First attempt to load + Load a plugin whose name is ``plugin_name``. First attempt to load from a plugin directory (plugin_dir), secondly attempt to load from a bootstrap module (plugin_bootstrap) determined by - self.app._meta.plugin_bootstrap. + ``CementApp.Meta.plugin_bootstrap``. Upon successful loading of a plugin, the plugin name is appended to - the self._loaded_plugins list. + the ``self._loaded_plugins list``. :param plugin_name: The name of the plugin to load. :type plugin_name: ``str`` diff -Nru python-cement-2.8.2/cement/ext/ext_redis.py python-cement-2.10.0/cement/ext/ext_redis.py --- python-cement-2.8.2/cement/ext/ext_redis.py 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_redis.py 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1,229 @@ +""" +The Redis Extension provides application caching and key/value store +support via Redis. + +Requirements +------------ + + * redis (``pip install redis``) + +Configuration +------------- + +This extension honors the following config settings +under a ``[cache.redis]`` section in any configuration file: + + * **expire_time** - The default time in second to expire items in the + cache. Default: 0 (does not expire). + * **host** - Redis server. + * **port** - Redis port. + * **db** - Redis database number. + + +Configurations can be passed as defaults to a CementApp: + +.. code-block:: python + + from cement.core.foundation import CementApp + from cement.utils.misc import init_defaults + + defaults = init_defaults('myapp', 'cache.redis') + defaults['cache.redis']['expire_time'] = 0 + defaults['cache.redis']['host'] = '127.0.0.1' + defaults['cache.redis']['port'] = 6379 + defaults['cache.redis']['db'] = 0 + + class MyApp(CementApp): + class Meta: + label = 'myapp' + config_defaults = defaults + extensions = ['redis'] + cache_handler = 'redis' + + +Additionally, an application configuration file might have a section like +the following: + +.. code-block:: text + + [myapp] + + # set the cache handler to use + cache_handler = redis + + + [cache.redis] + + # time in seconds that an item in the cache will expire + expire_time = 300 + + # Redis server + host = 127.0.0.1 + + # Redis port + port = 6379 + + # Redis database number + db = 0 + + +Usage +----- + +.. code-block:: python + + from cement.core import foundation + from cement.utils.misc import init_defaults + + defaults = init_defaults('myapp', 'redis') + defaults['cache.redis']['expire_time'] = 300 # seconds + defaults['cache.redis']['host'] = '127.0.0.1' + defaults['cache.redis']['port'] = 6379 + defaults['cache.redis']['db'] = 0 + + class MyApp(foundation.CementApp): + class Meta: + label = 'myapp' + config_defaults = defaults + extensions = ['redis'] + cache_handler = 'redis' + + with MyApp() as app: + # Run the app + app.run() + + # Set a cached value + app.cache.set('my_key', 'my value') + + # Get a cached value + app.cache.get('my_key') + + # Delete a cached value + app.cache.delete('my_key') + + # Delete the entire cache + app.cache.purge() + +""" + +import redis +from ..core import cache +from ..utils.misc import minimal_logger + +LOG = minimal_logger(__name__) + + +class RedisCacheHandler(cache.CementCacheHandler): + + """ + This class implements the :ref:`ICache ` + interface. It provides a caching interface using the + `redis `_ library. + + **Note** This extension has an external dependency on ``redis``. You + must include ``redis`` in your applications dependencies as Cement + explicitly does *not* include external dependencies for optional + extensions. + """ + + class Meta: + + """Handler meta-data.""" + + interface = cache.ICache + label = 'redis' + config_defaults = dict( + hosts='127.0.0.1', + port=6379, + db=0, + expire_time=0, + ) + + def __init__(self, *args, **kw): + super(RedisCacheHandler, self).__init__(*args, **kw) + self.mc = None + + def _setup(self, *args, **kw): + super(RedisCacheHandler, self)._setup(*args, **kw) + self.r = redis.StrictRedis( + host=self._config('host', default='127.0.0.1'), + port=self._config('port', default=6379), + db=self._config('db', default=0)) + + def _config(self, key, default=None): + """ + This is a simple wrapper, and is equivalent to: + ``self.app.config.get('cache.redis', )``. + + :param key: The key to get a config value from the 'cache.redis' + config section. + :returns: The value of the given key. + + """ + return self.app.config.get(self._meta.config_section, key) + + def get(self, key, fallback=None, **kw): + """ + Get a value from the cache. Additional keyword arguments are ignored. + + :param key: The key of the item in the cache to get. + :param fallback: The value to return if the item is not found in the + cache. + :returns: The value of the item in the cache, or the `fallback` value. + + """ + LOG.debug("getting cache value using key '%s'" % key) + res = self.r.get(key) + if res is None: + return fallback + else: + return res.decode('utf-8') + + def set(self, key, value, time=None, **kw): + """ + Set a value in the cache for the given ``key``. Additional + keyword arguments are ignored. + + :param key: The key of the item in the cache to set. + :param value: The value of the item to set. + :param time: The expiration time (in seconds) to keep the item cached. + Defaults to `expire_time` as defined in the applications + configuration. + :returns: ``None`` + + """ + if time is None: + time = int(self._config('expire_time')) + + if time == 0: + self.r.set(key, value) + else: + self.r.setex(key, time, value) + + def delete(self, key, **kw): + """ + Delete an item from the cache for the given ``key``. Additional + keyword arguments are ignored. + + :param key: The key to delete from the cache. + :returns: ``None`` + + """ + self.r.delete(key) + + def purge(self, **kw): + """ + Purge the entire cache, all keys and values will be lost. Any + additional keyword arguments will be passed directly to the + redis ``flush_all()`` function. + + :returns: ``None`` + + """ + keys = self.r.keys('*') + if keys: + self.r.delete(*keys) + + +def load(app): + app.handler.register(RedisCacheHandler) diff -Nru python-cement-2.8.2/cement/ext/ext_reload_config.py python-cement-2.10.0/cement/ext/ext_reload_config.py --- python-cement-2.8.2/cement/ext/ext_reload_config.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_reload_config.py 2016-07-14 20:36:19.000000000 +0000 @@ -183,9 +183,8 @@ import os import signal import pyinotify -from ..core import backend from ..utils.misc import minimal_logger -from ..utils import shell, fs +from ..utils import fs LOG = minimal_logger(__name__) MASK = pyinotify.IN_CLOSE_WRITE @@ -234,7 +233,7 @@ watched_dirs.append(plugin_dir) # just want the first one... looks wierd, but python 2/3 compat - res = os.walk(plugin_dir) + # res = os.walk(plugin_dir) ### ? for path, dirs, files in os.walk(plugin_dir): plugin_config_files = files break diff -Nru python-cement-2.8.2/cement/ext/ext_tabulate.py python-cement-2.10.0/cement/ext/ext_tabulate.py --- python-cement-2.8.2/cement/ext/ext_tabulate.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_tabulate.py 2016-07-14 20:36:19.000000000 +0000 @@ -54,9 +54,8 @@ """ -import sys from tabulate import tabulate -from ..core import output, exc +from ..core import output from ..utils.misc import minimal_logger LOG = minimal_logger(__name__) diff -Nru python-cement-2.8.2/cement/ext/ext_watchdog.py python-cement-2.10.0/cement/ext/ext_watchdog.py --- python-cement-2.8.2/cement/ext/ext_watchdog.py 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_watchdog.py 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1,355 @@ +""" +The Watchdog Framework Extension enables applications Built on Cement +(tm) to easily monitor, and react to, changes in filesystem paths based on the +filesystem events monitoring library +`Watchdog `_. + +On application startup, the Watchdog Observer is automatically started and +then upon application close, the observer thread is properly stopped and +joined with the parent process before exit. + +This is a simplified wrapper around the functionality of the Watchdog library. +For full usage, please see the +`Watchdog API Documentation `_. + +Requirements +------------ + + * Watchdog (``pip install watchdog``) + + +Features +-------- + + * Cross platform support for Linux, OSX, Windows, etc. + + +Configuration +------------- + +This extension honors the following application meta-data settings: + + * **watchdog_paths** - A List of tuples that are passed directly as arguments + to ``WatchdogManager.add()`` (shortcut equivalent of + ``app.watchdog.add(...)``. + + +Hooks +----- + +This extension defines the following hooks: + +watchdog_pre_start +^^^^^^^^^^^^^^^^^^ + +Run first when ``CementApp.watchdog.start()`` is called. The application +object is passed as an argument. Nothing is expected in return. + +watchdog_post_start +^^^^^^^^^^^^^^^^^^^ + +Run last when ``CementApp.watchdog.start()`` is called. The application +object is passed as an argument. Nothing is expected in return. + +watchdog_pre_stop +^^^^^^^^^^^^^^^^^ + +Run first when ``CementApp.watchdog.stop()`` is called. The application +object is passed as an argument. Nothing is expected in return. + +watchdog_post_stop +^^^^^^^^^^^^^^^^^^ + +Run last when ``CementApp.watchdog.stop()`` is called. The application +object is passed as an argument. Nothing is expected in return. + +watchdog_pre_join +^^^^^^^^^^^^^^^^^ + +Run first when ``CementApp.watchdog.join()`` is called. The application +object is passed as an argument. Nothing is expected in return. + +watchdog_post_join +^^^^^^^^^^^^^^^^^^ + +Run last when ``CementApp.watchdog.join()`` is called. The application +object is passed as an argument. Nothing is expected in return. + + +Usage +----- + +The following example uses the default ``WatchdogEventHandler`` that simply +logs all events to ``debug``: + +.. code-block:: python + + from time import sleep + from cement.core.foundation import CementApp + from cement.core.exc import CaughtSignal + from cement.ext.ext_watchdog import WatchdogEventHandler + + + class MyApp(CementApp): + class Meta: + label = 'myapp' + extensions = ['watchdog'] + watchdog_paths = [ + ('./tmp/', WatchdogEventHandler), + ] + + + with MyApp() as app: + app.run() + + try: + while True: + sleep(1) + except CaughtSignal as e: + print(e) + + +In the above example, nothing is printed to console however you will see +something like the following via debug logging: + +.. # noqa +.. code-block:: console + + $ python myapp.py --debug 2>&1 | grep -i watchdog + cement.core.extension : loading the 'cement.ext.ext_watchdog' framework extension + cement.core.hook : defining hook 'watchdog_pre_start' + cement.core.hook : defining hook 'watchdog_post_start' + cement.core.hook : defining hook 'watchdog_pre_stop' + cement.core.hook : defining hook 'watchdog_post_stop' + cement.core.hook : defining hook 'watchdog_pre_join' + cement.core.hook : defining hook 'watchdog_post_join' + cement.core.hook : registering hook 'watchdog_extend_app' from cement.ext.ext_watchdog into hooks['post_setup'] + cement.core.hook : registering hook 'watchdog_add_paths' from cement.ext.ext_watchdog into hooks['post_setup'] + cement.core.hook : registering hook 'watchdog_start' from cement.ext.ext_watchdog into hooks['pre_run'] + cement.core.hook : registering hook 'watchdog_cleanup' from cement.ext.ext_watchdog into hooks['pre_close'] + cement.core.hook : running hook 'post_setup' () from cement.ext.ext_watchdog + cement.core.foundation : extending appication with '.watchdog' () + cement.core.hook : running hook 'post_setup' () from cement.ext.ext_watchdog + cement.ext.ext_watchdog : adding path /path/to/tmp with event handler + cement.core.hook : running hook 'pre_run' () from cement.ext.ext_watchdog + cement.ext.ext_watchdog : starting watchdog observer + myapp : Watchdog Event: + myapp : Watchdog Event: + myapp : Watchdog Event: + myapp : Watchdog Event: + myapp : Watchdog Event: + myapp : Watchdog Event: + myapp : Watchdog Event: + myapp : Watchdog Event: + myapp : Watchdog Event: + myapp : Watchdog Event: + myapp : Watchdog Event: + cement.core.hook : running hook 'pre_close' () from cement.ext.ext_watchdog + cement.ext.ext_watchdog : stopping watchdog observer + cement.ext.ext_watchdog : joining watchdog observer + cement.core.foundation : closing the myapp application + + +To expand on the above example, we can add our own event handlers: + +.. code-block:: python + + class MyEventHandler(WatchdogEventHandler): + def on_any_event(self, event): + # do something with the ``event`` object + print("The modified path was: %s" % event.src_path) + + + class MyApp(CementApp): + class Meta: + label = 'myapp' + extensions = ['watchdog'] + watchdog_paths = [ + ('./tmp/', MyEventHandler), + ] + +.. code-block:: console + + $ python myapp.py + The modified path was: /path/to/tmp/test.file + +Note that the ``WatchdogEventHandler`` could be replaced with any other event +handler classes (i.e. those available from ``watchdog`` directly), however +to play nicely with Cement, we sub-class them first in order to pass in our +application object: + +.. code-block:: python + + from watchdog.events import FileSystemEventHandler + + class MyEventHandler(FileSystemEventHandler): + def __init__(self, app, *args, **kw): + super(MyEventHandler, self).__init__(*args, **kw) + self.app = app + + +For full usage of Watchdog event handlers, refer to the +`Watchdog API Documentation `_. +""" + +import os +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler +from ..core.meta import MetaMixin +from ..core.exc import FrameworkError +from ..utils.misc import minimal_logger +from ..utils import fs + +LOG = minimal_logger(__name__) + + +class WatchdogEventHandler(FileSystemEventHandler): + """ + Default event handler used by Cement, that logs all events to the + application's debug log. Additional ``*args`` and ``**kwargs`` are passed + to the parent class. + + :param app: The application object + + """ + + def __init__(self, app, *args, **kw): + super(WatchdogEventHandler, self).__init__(*args, **kw) + self.app = app + + def on_any_event(self, event): + self.app.log.debug("Watchdog Event: %s" % event) # pragma: nocover + + +class WatchdogManager(MetaMixin): + """ + The manager class that is attached to the application object via + ``CementApp.extend()``. + + Usage: + + .. code-block:: python + + with MyApp() as app: + app.watchdog.start() + app.watchdog.stop() + app.watchdog.join() + + """ + class Meta: + #: The observer class to use on the backend + observer = Observer + + #: The default event handler class to use if none is provided + default_event_handler = WatchdogEventHandler + + def __init__(self, app, *args, **kw): + super(WatchdogManager, self).__init__(*args, **kw) + self.app = app + self.paths = [] + self.observer = self._meta.observer() + + def add(self, path, event_handler=None, recursive=True): + """ + Add a directory path and event handler to the observer. + + :param path: A directory path to monitor (str) + :param event_handler: An event handler class used to handle events for + ``path`` (class) + :param recursive: Whether to monitor the ``path`` recursively (bool) + :return: Returns ``True`` if the path is added, ``False`` otherwise. + (bool) + """ + path = fs.abspath(path) + if not os.path.exists(path): + LOG.debug('watchdog path %s does not exist... ignoring' % path) + return False + + if event_handler is None: + event_handler = self._meta.default_event_handler + LOG.debug('adding path %s with event handler %s' % + (path, event_handler)) + self.observer.schedule(event_handler(self.app), + path, recursive=recursive) + return True + + def start(self, *args, **kw): + """ + Start the observer. All ``*args`` and ``**kwargs`` are passed down + to the backend observer. + """ + + for res in self.app.hook.run('watchdog_pre_start', self.app): + pass + LOG.debug('starting watchdog observer') + self.observer.start(*args, **kw) + for res in self.app.hook.run('watchdog_post_start', self.app): + pass + + def stop(self, *args, **kw): + """ + Stop the observer. All ``*args`` and ``**kwargs`` are passed down + to the backend observer. + """ + + for res in self.app.hook.run('watchdog_pre_stop', self.app): + pass + LOG.debug('stopping watchdog observer') + self.observer.stop(*args, **kw) + for res in self.app.hook.run('watchdog_post_stop', self.app): + pass + + def join(self, *args, **kw): + """ + Join the observer with the parent process. All ``*args`` and + ``**kwargs`` are passed down to the backend observer. + """ + + for res in self.app.hook.run('watchdog_pre_join', self.app): + pass + LOG.debug('joining watchdog observer') + self.observer.join(*args, **kw) + for res in self.app.hook.run('watchdog_post_join', self.app): + pass + + +def watchdog_extend_app(app): + app.extend('watchdog', WatchdogManager(app)) + + +def watchdog_start(app): + app.watchdog.start() + + +def watchdog_cleanup(app): + if app.watchdog.observer.is_alive(): + app.watchdog.stop() + app.watchdog.join() + + +def watchdog_add_paths(app): + if hasattr(app._meta, 'watchdog_paths'): + for path_spec in app._meta.watchdog_paths: + # odd... if a tuple is a single item it ends up as a str? + if isinstance(path_spec, str): + app.watchdog.add(path_spec) + elif isinstance(path_spec, tuple): + app.watchdog.add(*path_spec) + else: + raise FrameworkError( + "Watchdog path spec must be a tuple, not '%s' in: %s" % + (type(path_spec).__name__, path_spec) + ) + + +def load(app): + app.hook.define('watchdog_pre_start') + app.hook.define('watchdog_post_start') + app.hook.define('watchdog_pre_stop') + app.hook.define('watchdog_post_stop') + app.hook.define('watchdog_pre_join') + app.hook.define('watchdog_post_join') + app.hook.register('post_setup', watchdog_extend_app, weight=-1) + app.hook.register('post_setup', watchdog_add_paths) + app.hook.register('pre_run', watchdog_start) + app.hook.register('pre_close', watchdog_cleanup) diff -Nru python-cement-2.8.2/cement/ext/ext_yaml_configobj.py python-cement-2.10.0/cement/ext/ext_yaml_configobj.py --- python-cement-2.8.2/cement/ext/ext_yaml_configobj.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_yaml_configobj.py 2016-07-14 20:36:19.000000000 +0000 @@ -53,10 +53,8 @@ """ -import os import yaml from ..utils.misc import minimal_logger -from ..utils.fs import abspath from ..ext.ext_configobj import ConfigObjConfigHandler LOG = minimal_logger(__name__) diff -Nru python-cement-2.8.2/cement/ext/ext_yaml.py python-cement-2.10.0/cement/ext/ext_yaml.py --- python-cement-2.8.2/cement/ext/ext_yaml.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/ext/ext_yaml.py 2016-07-14 20:36:19.000000000 +0000 @@ -67,10 +67,8 @@ """ -import os -import sys import yaml -from ..core import backend, output, config +from ..core import output from ..utils.misc import minimal_logger from ..ext.ext_configparser import ConfigParserConfigHandler @@ -155,19 +153,21 @@ def _setup(self, app_obj): self.app = app_obj - def render(self, data_dict, **kw): + def render(self, data_dict, template=None, **kw): """ Take a data dictionary and render it as Yaml output. Note that the template option is received here per the interface, however this - handler just ignores it. + handler just ignores it. Additional keyword arguments passed to + ``yaml.dump()``. :param data_dict: The data dictionary to render. + :keyword template: Ignored in this output handler implementation. :returns: A Yaml encoded string. :rtype: ``str`` """ LOG.debug("rendering output as yaml via %s" % self.__module__) - return yaml.dump(data_dict) + return yaml.dump(data_dict, **kw) class YamlConfigHandler(ConfigParserConfigHandler): diff -Nru python-cement-2.8.2/cement/utils/misc.py python-cement-2.10.0/cement/utils/misc.py --- python-cement-2.8.2/cement/utils/misc.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/utils/misc.py 2016-07-14 20:36:19.000000000 +0000 @@ -23,25 +23,6 @@ return hashlib.md5(str(salt).encode()).hexdigest() -# class NullLogger(object): -# def __init__(self, namespace, debug, *args, **kw): -# pass - - # def info(self, *args, **kw): - # pass - - # def warn(self, *args, **kw): - # pass - - # def error(self, *args, **kw): - # pass - - # def fatal(self, *args, **kw): - # pass - - # def debug(self, *args, **kw): - # pass - class MinimalLogger(object): def __init__(self, namespace, debug, *args, **kw): @@ -94,10 +75,13 @@ kwargs = self._get_logging_kwargs(namespace, **kw) self.backend.info(msg, **kwargs) - def warn(self, msg, namespace=None, **kw): + def warning(self, msg, namespace=None, **kw): if self.logging_is_enabled: kwargs = self._get_logging_kwargs(namespace, **kw) - self.backend.warn(msg, **kwargs) + self.backend.warning(msg, **kwargs) + + def warn(self, msg, namespace=None, **kw): + self.warning(msg, namespace, **kw) def error(self, msg, namespace=None, **kw): if self.logging_is_enabled: @@ -196,10 +180,10 @@ """ - if sys.version_info[0] < 3: # pragma: no cover - types = [str, unicode] # pragma: no cover - else: # pragma: no cover - types = [str] # pragma: no cover + if sys.version_info[0] < 3: # pragma: no cover # noqa + types = [str, unicode] # pragma: no cover # noqa + else: # pragma: no cover # noqa + types = [str] # pragma: no cover # noqa if type(text) not in types: raise TypeError("Argument `text` must be one of [str, unicode].") diff -Nru python-cement-2.8.2/cement/utils/shell.py python-cement-2.10.0/cement/utils/shell.py --- python-cement-2.8.2/cement/utils/shell.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/utils/shell.py 2016-07-14 20:36:19.000000000 +0000 @@ -319,10 +319,10 @@ else: text = self._meta.text - if sys.version_info[0] < 3: # pragma: nocover - self.input = raw_input("%s " % text) # pragma: nocover - else: # pragma: nocover - self.input = input("%s " % text) # pragma: nocover + if sys.version_info[0] < 3: # pragma: nocover # noqa + self.input = raw_input("%s " % text) # pragma: nocover # noqa + else: # pragma: nocover # noqa + self.input = input("%s " % text) # pragma: nocover # noqa if self.input == '' and self._meta.default is not None: self.input = self._meta.default diff -Nru python-cement-2.8.2/cement/utils/test.py python-cement-2.10.0/cement/utils/test.py --- python-cement-2.8.2/cement/utils/test.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/cement/utils/test.py 2016-07-14 20:36:19.000000000 +0000 @@ -1,15 +1,17 @@ """Cement testing utilities.""" +import os import unittest +import shutil from tempfile import mkstemp, mkdtemp from ..core import backend, foundation from ..utils.misc import rando # shortcuts -from nose import SkipTest +from nose import SkipTest # noqa +from nose.tools import raises # noqa from nose.tools import ok_ as ok from nose.tools import eq_ as eq -from nose.tools import raises from nose.plugins.attrib import attr @@ -40,6 +42,9 @@ def __init__(self, *args, **kw): super(CementTestCase, self).__init__(*args, **kw) + self.tmp_file = None + self.tmp_dir = None + self.rando = None def setUp(self): """ @@ -49,8 +54,25 @@ """ self.app = self.make_app() - _, self.tmp_file = mkstemp() - self.tmp_dir = mkdtemp() + + # recreate temp file and dir for each test + _prefix = "cement.tests.%s.tmp" % self.__class__.__name__ + _, self.tmp_file = mkstemp(prefix=_prefix) + self.tmp_dir = mkdtemp(prefix=_prefix) + + # create a random string for each test (useful to verify things + # uniquely so every test isn't using the same "My Test String") + self.rando = rando()[:12] + + def tearDown(self): + """ + Tears down the test environment (if necessary), removes any temporary + files/directories, etc. + """ + if os.path.exists(self.tmp_file): + os.remove(self.tmp_file) + if os.path.exists(self.tmp_dir): + shutil.rmtree(self.tmp_dir) def make_app(self, *args, **kw): """ diff -Nru python-cement-2.8.2/ChangeLog python-cement-2.10.0/ChangeLog --- python-cement-2.8.2/ChangeLog 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/ChangeLog 2016-07-14 20:36:19.000000000 +0000 @@ -19,27 +19,53 @@ Also check out the :ref:`whats_new` section for details on new features. -2.8.2 - Fri Feb 26, 2016 +2.10.0 - Thu July 14, 2016 ------------------------------------------------------------------------------ Bugs: + * :issue:`363` - CementTestCase does not delete temporary + files/directories * :issue:`346` - AttributeError: 'module' object has no attribute 'SIGHUP' on Windows - + * :issue:`352` - ``CementApp.extend()`` breaks on ``CementApp.reload()`` + * :issue:`366` - Output handler override options dissappear + * :issue:`385` - JsonOutputHandler/YamlOutputHandler/MustacheOutputHandler + Do not pass keyword args down to backend render functions + * :issue:`393` - ``CementApp.Meta.hooks`` ignored for hooks defined by + extensions Features: - * None + * :issue:`350` - Support for plugin directories + * :issue:`370` - Handlebars templating support + * :pr:`371` - Jinja2 templating support + * :issue:`373` - Switch over to using Flake8 for PEP8 and style compliance + * :pr:`375` - Redis cache handler support + * :issue:`379` - Support for alternative config file extensions + * :issue:`380` - Support for Cython/Compiled Plugins + * :issue:`389` - ConfigObj support for Python 3 + * :issue:`394` - Watchdog extension for cross-platform filesystem event + monitoring + * :issue:`395` - Ability to pass metadata keyword arguments to handlers + via ``CementApp.Meta.meta_defaults``. Refactoring; - * None + * :issue:`386` - Partially deprecated use of ``imp`` in favor of + ``importlib`` on Python >= 3.1 + * :issue:`390` - ArgparseArgumentHandler should store unknown arguments Incompatible: * None +Deprecation: + + * :issue:`365` - Deprecated ``LoggingLogHandler.warn()`` + * :issue:`372` - Deprecated Explicit Python 3.2 Support + * :issue:`376` - Deprecated ``cement.core.interface.list()`` + 2.8.0 - Wed Feb 24, 2016 ------------------------------------------------------------------------------ diff -Nru python-cement-2.8.2/CONTRIBUTORS python-cement-2.10.0/CONTRIBUTORS --- python-cement-2.8.2/CONTRIBUTORS 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/CONTRIBUTORS 2016-07-14 20:36:19.000000000 +0000 @@ -7,3 +7,5 @@ * BJ Dierkes (derks) - Creator, Primary Maintainer * Kyle Rockman (rocktavious) * Tomasz Czyż (spinus) + * Ildar Akhmetgaleev (akhilman) + * Nicolas Brisac (zacbri) diff -Nru python-cement-2.8.2/debian/changelog python-cement-2.10.0/debian/changelog --- python-cement-2.8.2/debian/changelog 2016-07-12 10:33:07.000000000 +0000 +++ python-cement-2.10.0/debian/changelog 2016-09-08 08:03:20.000000000 +0000 @@ -1,3 +1,12 @@ +python-cement (2.10.0-1) unstable; urgency=low + + * New upstream release. + * Add python(3)-jinja2 and python(3)-watchdog to b-d so tests can + pass. + * Add python(3)-jinja2 and python(3)-watchdog to Recommends. + + -- Michael Fladischer Thu, 08 Sep 2016 10:03:20 +0200 + python-cement (2.8.2-2) unstable; urgency=medium * Add patch to prevent installation of example package in global diff -Nru python-cement-2.8.2/debian/control python-cement-2.10.0/debian/control --- python-cement-2.8.2/debian/control 2016-07-12 10:33:07.000000000 +0000 +++ python-cement-2.10.0/debian/control 2016-09-08 08:03:20.000000000 +0000 @@ -10,6 +10,7 @@ python-colorlog, python-configobj, python-genshi, + python-jinja2, python-mock, python-nose, python-pyinotify, @@ -17,12 +18,14 @@ python-pystache, python-setuptools, python-tabulate, + python-watchdog, python-yaml, python3-all, python3-argcomplete, python3-colorlog, python3-configobj, python3-genshi, + python3-jinja2, python3-mock, python3-nose, python3-pyinotify, @@ -32,6 +35,7 @@ python3-sphinx, python3-sphinx-rtd-theme, python3-tabulate, + python3-watchdog, python3-yaml Standards-Version: 3.9.8 Vcs-Browser: https://anonscm.debian.org/cgit/python-modules/packages/python-cement.git @@ -48,10 +52,12 @@ python-colorlog, python-configobj, python-genshi, + python-jinja2, python-pyinotify, python-pylibmc, python-pystache, python-tabulate, + python-watchdog, python-yaml Suggests: python-cement-doc Description: CLI Application Framework (Python2 version) @@ -84,10 +90,12 @@ python3-colorlog, python3-configobj, python3-genshi, + python3-jinja2, python3-pyinotify, python3-pylibmc, python3-pystache, python3-tabulate, + python3-watchdog, python3-yaml Suggests: python-cement-doc Description: CLI Application Framework (Python3 version) diff -Nru python-cement-2.8.2/debian/.git-dpm python-cement-2.10.0/debian/.git-dpm --- python-cement-2.8.2/debian/.git-dpm 2016-07-12 10:33:07.000000000 +0000 +++ python-cement-2.10.0/debian/.git-dpm 2016-09-08 08:03:20.000000000 +0000 @@ -1,8 +1,8 @@ # see git-dpm(1) from git-dpm package -61784ba9ca4fac6a63f00dfb82a4d15b57187f99 -61784ba9ca4fac6a63f00dfb82a4d15b57187f99 -71b6945457b2a54e3bbda057f92942fcadb82305 -71b6945457b2a54e3bbda057f92942fcadb82305 -python-cement_2.8.2.orig.tar.gz -ec56a0785c21c41bc0bce39abc1021a0ecafe8af -165697 +fdc13a0fe2adb52ec2a77b4464b13d297251e8f8 +fdc13a0fe2adb52ec2a77b4464b13d297251e8f8 +b82fca7bb8fa81246ba820510a1a6b3d6488223e +b82fca7bb8fa81246ba820510a1a6b3d6488223e +python-cement_2.10.0.orig.tar.gz +b36f995240475e3d8ee35e94700d63c84910bd88 +179447 diff -Nru python-cement-2.8.2/debian/patches/0001-Remove-badges-and-mailinglist-signup-form-from-docum.patch python-cement-2.10.0/debian/patches/0001-Remove-badges-and-mailinglist-signup-form-from-docum.patch --- python-cement-2.8.2/debian/patches/0001-Remove-badges-and-mailinglist-signup-form-from-docum.patch 2016-07-12 10:33:07.000000000 +0000 +++ python-cement-2.10.0/debian/patches/0001-Remove-badges-and-mailinglist-signup-form-from-docum.patch 2016-09-08 08:03:20.000000000 +0000 @@ -1,4 +1,4 @@ -From e482d27faec5373ae0019f0a2106798ce2c4563c Mon Sep 17 00:00:00 2001 +From 48dbf9f38f0da9ee918c4bb0b374bd8d128952a2 Mon Sep 17 00:00:00 2001 From: Michael Fladischer Date: Fri, 20 May 2016 08:55:11 +0200 Subject: Remove badges and mailinglist signup form from documentation. @@ -9,7 +9,7 @@ 1 file changed, 28 deletions(-) diff --git a/doc/source/index.rst b/doc/source/index.rst -index 81be775..de3fd7f 100644 +index a59ec03..7aacc12 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -19,10 +19,6 @@ seen several iterations in design, and has continued to grow and improve diff -Nru python-cement-2.8.2/debian/patches/0002-Don-t-use-license-text-in-documentation.patch python-cement-2.10.0/debian/patches/0002-Don-t-use-license-text-in-documentation.patch --- python-cement-2.8.2/debian/patches/0002-Don-t-use-license-text-in-documentation.patch 2016-07-12 10:33:07.000000000 +0000 +++ python-cement-2.10.0/debian/patches/0002-Don-t-use-license-text-in-documentation.patch 2016-09-08 08:03:20.000000000 +0000 @@ -1,4 +1,4 @@ -From 36c633c0cfa25d9f9ef65c6b6e264d647f1ecf69 Mon Sep 17 00:00:00 2001 +From dfbd98f56e435c63c415bf97245913ed167c858b Mon Sep 17 00:00:00 2001 From: Michael Fladischer Date: Fri, 20 May 2016 09:17:21 +0200 Subject: Don't use license text in documentation. @@ -9,7 +9,7 @@ 1 file changed, 1 deletion(-) diff --git a/doc/source/index.rst b/doc/source/index.rst -index de3fd7f..01ef356 100644 +index 7aacc12..5fd98d2 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -74,7 +74,6 @@ Documentation diff -Nru python-cement-2.8.2/debian/patches/0003-Do-not-install-examples-package.patch python-cement-2.10.0/debian/patches/0003-Do-not-install-examples-package.patch --- python-cement-2.8.2/debian/patches/0003-Do-not-install-examples-package.patch 2016-07-12 10:33:07.000000000 +0000 +++ python-cement-2.10.0/debian/patches/0003-Do-not-install-examples-package.patch 2016-09-08 08:03:20.000000000 +0000 @@ -1,4 +1,4 @@ -From 61784ba9ca4fac6a63f00dfb82a4d15b57187f99 Mon Sep 17 00:00:00 2001 +From dc6701b6981ea2b2490551f76ac1d64f0927fd44 Mon Sep 17 00:00:00 2001 From: Michael Fladischer Date: Tue, 12 Jul 2016 10:30:37 +0200 Subject: Do not install examples package. diff -Nru python-cement-2.8.2/debian/patches/0004-Skip-tests-if-redis-package-is-not-found.patch python-cement-2.10.0/debian/patches/0004-Skip-tests-if-redis-package-is-not-found.patch --- python-cement-2.8.2/debian/patches/0004-Skip-tests-if-redis-package-is-not-found.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/debian/patches/0004-Skip-tests-if-redis-package-is-not-found.patch 2016-09-08 08:03:20.000000000 +0000 @@ -0,0 +1,38 @@ +From 159c9974debeeca6b640d5615b6d19b46d0050bd Mon Sep 17 00:00:00 2001 +From: Michael Fladischer +Date: Mon, 18 Jul 2016 20:04:09 +0200 +Subject: Skip tests if redis package is not found. + +--- + tests/ext/redis_tests.py | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/tests/ext/redis_tests.py b/tests/ext/redis_tests.py +index 6fea662..cc71bda 100644 +--- a/tests/ext/redis_tests.py ++++ b/tests/ext/redis_tests.py +@@ -1,18 +1,23 @@ + """Tests for cement.ext.ext_redis.""" + + import sys +-import redis + from time import sleep + from random import random + from cement.core import handler + from cement.utils import test + from cement.utils.misc import init_defaults + ++try: ++ import redis ++except ImportError: ++ redis = None + + class RedisExtTestCase(test.CementTestCase): + + def setUp(self): + super(RedisExtTestCase, self).setUp() ++ if not redis: ++ self.skipTest("No redis module installed") + self.key = "cement-tests-random-key-%s" % random() + defaults = init_defaults('tests', 'cache.redis') + defaults['cache.redis']['host'] = '127.0.0.1' diff -Nru python-cement-2.8.2/debian/patches/0005-Skip-tests-if-pybars-package-is-not-found.patch python-cement-2.10.0/debian/patches/0005-Skip-tests-if-pybars-package-is-not-found.patch --- python-cement-2.8.2/debian/patches/0005-Skip-tests-if-pybars-package-is-not-found.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/debian/patches/0005-Skip-tests-if-pybars-package-is-not-found.patch 2016-09-08 08:03:20.000000000 +0000 @@ -0,0 +1,43 @@ +From fdc13a0fe2adb52ec2a77b4464b13d297251e8f8 Mon Sep 17 00:00:00 2001 +From: Michael Fladischer +Date: Mon, 18 Jul 2016 20:04:37 +0200 +Subject: Skip tests if pybars package is not found. + +--- + tests/ext/handlebars_tests.py | 13 +++++++++++-- + 1 file changed, 11 insertions(+), 2 deletions(-) + +diff --git a/tests/ext/handlebars_tests.py b/tests/ext/handlebars_tests.py +index 91779cd..c81b7fd 100644 +--- a/tests/ext/handlebars_tests.py ++++ b/tests/ext/handlebars_tests.py +@@ -6,6 +6,11 @@ from cement.core import exc, foundation, handler, backend, controller + from cement.utils import test + from nose.plugins.attrib import attr + ++try: ++ import pybars ++except ImportError: ++ pybars = None ++ + class HandlebarsTestApp(test.TestApp): + class Meta: + extensions = ['handlebars'] +@@ -14,11 +19,15 @@ class HandlebarsTestApp(test.TestApp): + template_dirs = [] + handlebars_helpers = {} + handlebars_partials = ['test_partial_template.handlebars'] +- +-@attr('ext_handlebars') ++ ++@attr('ext_handlebars') + class HandlebarsExtTestCase(test.CementExtTestCase): + app_class = HandlebarsTestApp + ++ def setUp(self): ++ if not pybars: ++ self.skipTest("No pybars module installed") ++ + def test_handlebars(self): + self.app.setup() + rando = random.random() diff -Nru python-cement-2.8.2/debian/patches/series python-cement-2.10.0/debian/patches/series --- python-cement-2.8.2/debian/patches/series 2016-07-12 10:33:07.000000000 +0000 +++ python-cement-2.10.0/debian/patches/series 2016-09-08 08:03:20.000000000 +0000 @@ -1,3 +1,5 @@ 0001-Remove-badges-and-mailinglist-signup-form-from-docum.patch 0002-Don-t-use-license-text-in-documentation.patch 0003-Do-not-install-examples-package.patch +0004-Skip-tests-if-redis-package-is-not-found.patch +0005-Skip-tests-if-pybars-package-is-not-found.patch diff -Nru python-cement-2.8.2/doc/source/api/ext/ext_genshi.rst python-cement-2.10.0/doc/source/api/ext/ext_genshi.rst --- python-cement-2.8.2/doc/source/api/ext/ext_genshi.rst 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/doc/source/api/ext/ext_genshi.rst 2016-07-14 20:36:19.000000000 +0000 @@ -9,7 +9,7 @@ :show-inheritance: Genshi Syntax Basics --------------------- +^^^^^^^^^^^^^^^^^^^^ **Printing Variables** diff -Nru python-cement-2.8.2/doc/source/api/ext/ext_handlebars.rst python-cement-2.10.0/doc/source/api/ext/ext_handlebars.rst --- python-cement-2.8.2/doc/source/api/ext/ext_handlebars.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/doc/source/api/ext/ext_handlebars.rst 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1,9 @@ +.. _cement.ext.ext_handlebars: + +:mod:`cement.ext.ext_handlebars` +-------------------------------- + +.. automodule:: cement.ext.ext_handlebars + :members: + :private-members: + :show-inheritance: diff -Nru python-cement-2.8.2/doc/source/api/ext/ext_jinja2.rst python-cement-2.10.0/doc/source/api/ext/ext_jinja2.rst --- python-cement-2.8.2/doc/source/api/ext/ext_jinja2.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/doc/source/api/ext/ext_jinja2.rst 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1,9 @@ +.. _cement.ext.ext_jinja2: + +:mod:`cement.ext.ext_jinja2` +---------------------------- + +.. automodule:: cement.ext.ext_jinja2 + :members: + :private-members: + :show-inheritance: diff -Nru python-cement-2.8.2/doc/source/api/ext/ext_redis.rst python-cement-2.10.0/doc/source/api/ext/ext_redis.rst --- python-cement-2.8.2/doc/source/api/ext/ext_redis.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/doc/source/api/ext/ext_redis.rst 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1,9 @@ +.. _cement.ext.ext_redis: + +:mod:`cement.ext.ext_redis` +--------------------------- + +.. automodule:: cement.ext.ext_redis + :members: + :private-members: + :show-inheritance: diff -Nru python-cement-2.8.2/doc/source/api/ext/ext_watchdog.rst python-cement-2.10.0/doc/source/api/ext/ext_watchdog.rst --- python-cement-2.8.2/doc/source/api/ext/ext_watchdog.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/doc/source/api/ext/ext_watchdog.rst 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1,9 @@ +.. _cement.ext.ext_watchdog: + +:mod:`cement.ext.ext_watchdog` +------------------------------ + +.. automodule:: cement.ext.ext_watchdog + :members: + :private-members: + :show-inheritance: diff -Nru python-cement-2.8.2/doc/source/api/index.rst python-cement-2.10.0/doc/source/api/index.rst --- python-cement-2.8.2/doc/source/api/index.rst 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/doc/source/api/index.rst 2016-07-14 20:36:19.000000000 +0000 @@ -56,14 +56,19 @@ ext/ext_daemon ext/ext_dummy ext/ext_genshi + ext/ext_handlebars + ext/ext_jinja2 ext/ext_json ext/ext_json_configobj ext/ext_logging ext/ext_memcached ext/ext_mustache ext/ext_plugin + ext/ext_redis ext/ext_reload_config ext/ext_smtp ext/ext_tabulate ext/ext_yaml ext/ext_yaml_configobj + ext/ext_watchdog + diff -Nru python-cement-2.8.2/doc/source/changes.rst python-cement-2.10.0/doc/source/changes.rst --- python-cement-2.8.2/doc/source/changes.rst 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/doc/source/changes.rst 2016-07-14 20:36:19.000000000 +0000 @@ -19,27 +19,53 @@ Also check out the :ref:`whats_new` section for details on new features. -2.8.2 - Fri Feb 26, 2016 +2.10.0 - Thu July 14, 2016 ------------------------------------------------------------------------------ Bugs: + * :issue:`363` - CementTestCase does not delete temporary + files/directories * :issue:`346` - AttributeError: 'module' object has no attribute 'SIGHUP' on Windows - + * :issue:`352` - ``CementApp.extend()`` breaks on ``CementApp.reload()`` + * :issue:`366` - Output handler override options dissappear + * :issue:`385` - JsonOutputHandler/YamlOutputHandler/MustacheOutputHandler + Do not pass keyword args down to backend render functions + * :issue:`393` - ``CementApp.Meta.hooks`` ignored for hooks defined by + extensions Features: - * None + * :issue:`350` - Support for plugin directories + * :issue:`370` - Handlebars templating support + * :pr:`371` - Jinja2 templating support + * :issue:`373` - Switch over to using Flake8 for PEP8 and style compliance + * :pr:`375` - Redis cache handler support + * :issue:`379` - Support for alternative config file extensions + * :issue:`380` - Support for Cython/Compiled Plugins + * :issue:`389` - ConfigObj support for Python 3 + * :issue:`394` - Watchdog extension for cross-platform filesystem event + monitoring + * :issue:`395` - Ability to pass metadata keyword arguments to handlers + via ``CementApp.Meta.meta_defaults``. Refactoring; - * None + * :issue:`386` - Partially deprecated use of ``imp`` in favor of + ``importlib`` on Python >= 3.1 + * :issue:`390` - ArgparseArgumentHandler should store unknown arguments Incompatible: * None +Deprecation: + + * :issue:`365` - Deprecated ``LoggingLogHandler.warn()`` + * :issue:`372` - Deprecated Explicit Python 3.2 Support + * :issue:`376` - Deprecated ``cement.core.interface.list()`` + 2.8.0 - Wed Feb 24, 2016 ------------------------------------------------------------------------------ diff -Nru python-cement-2.8.2/doc/source/conf.py python-cement-2.10.0/doc/source/conf.py --- python-cement-2.8.2/doc/source/conf.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/doc/source/conf.py 2016-07-14 20:36:19.000000000 +0000 @@ -83,8 +83,10 @@ 'sphinx.ext.intersphinx' ] -extlinks = {'issue' : ('https://github.com/datafolklabs/cement/issues/%s', - 'Issue #')} +extlinks = { + 'issue' : ('https://github.com/datafolklabs/cement/issues/%s', 'Issue #'), + 'pr' : ('https://github.com/datafolklabs/cement/pull/%s', 'PR #'), +} intersphinx_mapping = {'py': ('https://docs.python.org/3.4', None)} # Add any paths that contain templates here, relative to this directory. diff -Nru python-cement-2.8.2/doc/source/contributors.rst python-cement-2.10.0/doc/source/contributors.rst --- python-cement-2.8.2/doc/source/contributors.rst 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/doc/source/contributors.rst 2016-07-14 20:36:19.000000000 +0000 @@ -7,3 +7,5 @@ * BJ Dierkes (derks) - Creator, Primary Maintainer * Kyle Rockman (rocktavious) * Tomasz Czyż (spinus) + * Ildar Akhmetgaleev (akhilman) + * Nicolas Brisac (zacbri) diff -Nru python-cement-2.8.2/doc/source/dev/application_design.rst python-cement-2.10.0/doc/source/dev/application_design.rst --- python-cement-2.8.2/doc/source/dev/application_design.rst 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/doc/source/dev/application_design.rst 2016-07-14 20:36:19.000000000 +0000 @@ -70,7 +70,7 @@ Larger applications need to be properly organized to keep code clean, and to keep a high level of maintainability (read: to keep things from getting -shitty). `The Boss Project `_ provides our recommended +shitty). `The Boss Project `_ provides our recommended application layout, and is a great starting point for anyone new to Cement. The primary detail about how to layout your code is this: All CLI/Cement diff -Nru python-cement-2.8.2/doc/source/dev/boss_templates.rst python-cement-2.10.0/doc/source/dev/boss_templates.rst --- python-cement-2.8.2/doc/source/dev/boss_templates.rst 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/doc/source/dev/boss_templates.rst 2016-07-14 20:36:19.000000000 +0000 @@ -3,7 +3,7 @@ Starting Projects from Boss Templates ===================================== -`The Boss Project `_ provides 'Baseline Open Source +`The Boss Project `_ provides 'Baseline Open Source Software' templates and development tools. It has similarities to PasteScript with regards to templating, but far easier to extend. The official template repository includes a number of templates specifically for Cement, and are the diff -Nru python-cement-2.8.2/doc/source/dev/interfaces_and_handlers.rst python-cement-2.10.0/doc/source/dev/interfaces_and_handlers.rst --- python-cement-2.8.2/doc/source/dev/interfaces_and_handlers.rst 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/doc/source/dev/interfaces_and_handlers.rst 2016-07-14 20:36:19.000000000 +0000 @@ -333,8 +333,8 @@ app = CementApp('myapp', log_handler=MyLogHandler) -Hander Default Configuration Settings -------------------------------------- +Handler Default Configuration Settings +-------------------------------------- All handlers can define default config file settings via their ``config_defaults`` meta option. These will be merged into the ``app.config`` diff -Nru python-cement-2.8.2/doc/source/dev/logging.rst python-cement-2.10.0/doc/source/dev/logging.rst --- python-cement-2.8.2/doc/source/dev/logging.rst 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/doc/source/dev/logging.rst 2016-07-14 20:36:19.000000000 +0000 @@ -43,7 +43,7 @@ app.log.info('This is an info message.') # Log a warning message - app.log.warn('This is a warning message.') + app.log.warning('This is a warning message.') # Log an error message app.log.error('This is an error message.') @@ -58,8 +58,8 @@ The above is displayed in order of 'severity' you can say. If the log level is set to 'INFO', you will receive all 'info' messages and above .. including warning, error, and fatal. However, you will not receive DEBUG level messages. -The same goes for a log level of 'WARN', where you will receive warning, error, -and fatal... but you will not receive INFO, or DEBUG level messages. +The same goes for a log level of 'WARNING', where you will receive warning, +error, and fatal... but you will not receive INFO, or DEBUG level messages. Changing Log Level ------------------ @@ -73,7 +73,7 @@ from cement.utils.misc import init_defaults defaults = init_defaults('myapp', 'log.logging') - defaults['log.logging']['level'] = 'WARN' + defaults['log.logging']['level'] = 'WARNING' app = foundation.CementApp('myapp', config_defaults=defaults) app.setup() diff -Nru python-cement-2.8.2/doc/source/dev/output.rst python-cement-2.10.0/doc/source/dev/output.rst --- python-cement-2.8.2/doc/source/dev/output.rst 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/doc/source/dev/output.rst 2016-07-14 20:36:19.000000000 +0000 @@ -20,6 +20,8 @@ * :class:`cement.ext.ext_json.JsonOutputHandler` * :class:`cement.ext.ext_yaml.YamlOutputHandler` * :class:`cement.ext.ext_genshi.GenshiOutputHandler` + * :class:`cement.ext.ext_handlebars.HandlebarsOutputHandler` + * :class:`cement.ext.ext_jinja2.Jinja2OutputHandler` * :class:`cement.ext.ext_mustache.MustacheOutputHandler` * :class:`cement.ext.ext_tabulate.TabulateOutputHandler` @@ -165,4 +167,3 @@ $ python myapp.py -o json {"foo": "bar"} - diff -Nru python-cement-2.8.2/doc/source/dev/plugins.rst python-cement-2.10.0/doc/source/dev/plugins.rst --- python-cement-2.8.2/doc/source/dev/plugins.rst 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/doc/source/dev/plugins.rst 2016-07-14 20:36:19.000000000 +0000 @@ -32,7 +32,8 @@ plugin_config_dirs = ``None`` A list of directory paths where plugin config files can be found. - Files must end in `.conf` or they will be ignored. + Files must end in ``.conf`` (or the extension defined by + ``CementApp.Meta.config_extension``), or they will be ignored. Note: Though ``CementApp.Meta.plugin_config_dirs`` is ``None``, Cement will set this to a default list based on ``CementApp.Meta.label``. This @@ -48,9 +49,11 @@ loaded from previous configuration files). plugin_config_dir = ``None`` - A directory path where plugin config files can be found. Files - must end in `.conf`. By default, this setting is also overridden - by the ``[] -> plugin_config_dir`` config setting parsed in any + A directory path where plugin config files can be found. Files must end + in ``.conf`` (or the extension defined by + ``CementApp.Meta.config_extension``), or they will be ignored. By + default, this setting is also overridden by the + ``[] -> plugin_config_dir`` config setting parsed in any of the application configuration files. If set, this item will be **appended** to @@ -65,7 +68,8 @@ plugin_bootstrap = ``None`` A python package (dotted import path) where plugin code can be loaded from. This is generally something like ``myapp.plugins`` - where a plugin file would live at ``myapp/plugins/myplugin.py``. + where a plugin file would live at ``myapp/plugins/myplugin.py`` or + ``myapp/plugins/myplugin/__init__.py``. This provides a facility for applications that have builtin plugins that ship with the applications source code and live in the same Python module. @@ -352,3 +356,65 @@ after that, you can do anything you like. +Single File Plugins vs. Plugin Directories +------------------------------------------ + +As of Cement 2.9.x, plugins can be either a single file (i.e ``myplugin.py``) +or a python module directory (i.e. ``myplugin/__init__.py``). Both will be +loaded and executed the exact same way. + +One caveat however, is that the submodules referenced from within a plugin +directory must be relative path. For example: + +**myplugin/__init__.py** + +.. code-block:: python + + from .controllers import MyPluginController + + def load(app): + app.handler.register(MyPluginController) + +**myplugin/controllers.py** + +.. code-block:: python + + from cement.core.controller import CementBaseController, expose + + class MyPluginController(CementBaseController): + class Meta: + label = 'myplugin' + stacked_on = 'base' + stacked_type = 'embedded' + + @expose() + def my_command(self): + print('Inside MyPluginController.my_command()') + + +Loading Templates From Plugin Directories +----------------------------------------- + +A common use case for complex applications is to use an output handler the +uses templates, such as Mustache, Genshi, Jinja2, etc. In order for a plugin +to use it's own template files it's templates directory first needs to be +added to the list of template directories to be parsed. In the future, this +will be more streamlined however currently the following is the recommeded +way: + +**myplugin/__init__.py** + +.. code-block:: python + + def add_template_dir(app): + path = os.path.join(os.path.basename(self.__file__, 'templates') + app.add_template_dir(path) + + def load(app): + app.hook.register('post_setup', add_template_dir) + + +The above will append the directory ``/path/to/myplugin/templates`` to the +list of template directories that the applications output handler with search +for template files. + diff -Nru python-cement-2.8.2/doc/source/dev/quickstart.rst python-cement-2.10.0/doc/source/dev/quickstart.rst --- python-cement-2.8.2/doc/source/dev/quickstart.rst 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/doc/source/dev/quickstart.rst 2016-07-14 20:36:19.000000000 +0000 @@ -258,7 +258,7 @@ command2 (aliases: cmd2) more of nothing - some-other-command (aliases: some-cmd) + second-cmd1 (aliases: some-cmd) this is some command optional arguments: diff -Nru python-cement-2.8.2/doc/source/dev/testing.rst python-cement-2.10.0/doc/source/dev/testing.rst --- python-cement-2.8.2/doc/source/dev/testing.rst 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/doc/source/dev/testing.rst 2016-07-14 20:36:19.000000000 +0000 @@ -12,7 +12,7 @@ For more information on testing, please see the following: * `UnitTest `_ - * `Nose `_ + * `Nose `_ * `Coverage `_ API Reference: @@ -47,7 +47,7 @@ # Perform basic assertion checks. You can do this anywhere # in the test function, depending on what the assertion is # checking. - self.ok(app.config.has_key('myapp', 'debug)) + self.ok(app.config.has_key('myapp', 'debug')) self.eq(app.config.get('myapp', 'debug'), False) # Run the applicaion, if necessary @@ -56,7 +56,7 @@ # Test the last rendered output (if app.render was used) data, output = app.get_last_rendered() self.eq(data, {'foo':'bar'}) - self.eq(output, 'some rendered output text) + self.eq(output, 'some rendered output text') @test.raises(Exception) def test_exception(self): @@ -120,6 +120,6 @@ app.run() def test_myapp_foo(self): - with MyTestApp(argv=['--foo', 'bar]) as app: + with MyTestApp(argv=['--foo', 'bar']) as app: app.run() \ No newline at end of file diff -Nru python-cement-2.8.2/doc/source/examples/sighup_reload.rst python-cement-2.10.0/doc/source/examples/sighup_reload.rst --- python-cement-2.8.2/doc/source/examples/sighup_reload.rst 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/doc/source/examples/sighup_reload.rst 2016-07-14 20:36:19.000000000 +0000 @@ -42,9 +42,9 @@ try: app.run() except CaughtSignal as e: - app.log.warn(e.msg) + app.log.warning(e.msg) if e.signum in [signal.SIGHUP]: - app.log.warn('Reloading MyApp') + app.log.warning('Reloading MyApp') app.reload() keep_alive = True else: diff -Nru python-cement-2.8.2/doc/source/index.rst python-cement-2.10.0/doc/source/index.rst --- python-cement-2.8.2/doc/source/index.rst 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/doc/source/index.rst 2016-07-14 20:36:19.000000000 +0000 @@ -38,10 +38,10 @@ * Cache handler interface adds caching support for improved performance * Controller handler supports sub-commands, and nested controllers * Zero external dependencies* of the core library - * 100% test coverage using ``nose`` - * 100% PEP8 compliant using ``pep8`` and ``autopep8`` tools + * 100% test coverage using ``nose`` and ``coverage`` + * 100% PEP8 and style compliant using ``flake8`` * Extensive Sphinx documentation - * Tested on Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 + * Tested on Python 2.6, 2.7, 3.3, 3.4, 3.5 *Note that argparse is required as an external dependency for Python < 2.7 and < 3.2. Additionally, some optional extensions that are shipped with @@ -54,7 +54,7 @@ Getting More Information ------------------------ - * DOCS: http://builtoncement.com/2.8/ + * DOCS: http://builtoncement.com/2.10/ * CODE: http://github.com/datafolklabs/cement/ * PYPI: http://pypi.python.org/pypi/cement/ * SITE: http://builtoncement.com/ diff -Nru python-cement-2.8.2/doc/source/upgrading.rst python-cement-2.10.0/doc/source/upgrading.rst --- python-cement-2.8.2/doc/source/upgrading.rst 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/doc/source/upgrading.rst 2016-07-14 20:36:19.000000000 +0000 @@ -6,6 +6,26 @@ This section outlines any information and changes that might need to be made in order to update your application built on previous versions of Cement. +Upgrading from 2.8.x to 2.9.x +----------------------------- + +Cement 2.9 introduces a few incompatible changes from the previous 2.8 stable +release, as noted in the :ref:`ChangeLog `. + +Deprecated: cement.core.interface.list() +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This function should no longer be used in favor of +``CementApp.handler.list_types()``. It will continue to work throughout +Cement 2.x, however is not compatible if +``CementApp.Meta.use_backend_globals == False``. + +Related: + + * :issue:`366` + * :issue:`376` + + Upgrading from 2.6.x to 2.8.x ----------------------------- diff -Nru python-cement-2.8.2/doc/source/whats_new.rst python-cement-2.10.0/doc/source/whats_new.rst --- python-cement-2.8.2/doc/source/whats_new.rst 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/doc/source/whats_new.rst 2016-07-14 20:36:19.000000000 +0000 @@ -3,6 +3,162 @@ What's New ========== +New Features in Cement 2.10 +--------------------------- + +Support for Multiple File Plugin Directories +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Prior to Cement 2.10, application plugins were only supported as single files +such as ``myplugin.py``. Plugins can now be a single file, or full python +modules like ``myplugin/__init__.py``. + +An example plugin might look like: + +.. code-block:: console + + myplugin/ + __init__.py + controllers.py + templates/ + cmd1.mustache + cmd2.mustache + cmd3.mustache + +The only thing required in a plugin is that it supply a ``load()`` function +either in a ``myplugin.py`` or ``myplugin/__init__.py``. The rest is up to +the developer. + +See :ref:`Application Plugins ` for more information. + +Related: + + * :issue:`350` + + +Cross Platform Filesystem Event Monitoring via Watchdog +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Applications can now monitor, and react to, filesystem events with a very +easy wrapper around the +`Watchdog `_ library. The extension +makes it possible to add a list of directories to monitor, and link them +with the class to handle any events while automating the proper setup, and +teardown of the backend observer. + +The Watchdog Extension will make it possible in future releases to +properly handle reloading a running application any time configuration files +are modified (partially implemented by the `reload_config` extension that has +limitations and does not support reloading the app). Another common use case +is the ability to reload a long running process any time source files are +modified which will be useful for development when working on daemon-like apps +so that the developer doesn't need to stop/start everytime changes are made. + +See the :ref:`Watchdog Extension ` for more +information. + +Related: + + * :issue:`326` + * :issue:`394` + + +Ability To Pass Meta Defaults From CementApp.Meta Down To Handlers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Cement handlers are often referenced by their label, and not passed as +pre-instantiated objects which requires the framework to instantiate them +dynamically with no keyword arguments. + +For example: + +.. code-block:: python + + from cement.core.foundation import CementApp + + class MyApp(CementApp): + class Meta: + label = 'myapp' + extensions = ['json'] + +In the above, Cement will load the ``json`` extension, which +registers ``JsonOutputHandler``. When it comes time to recall that handler, +it is looked up as ``output.json`` where ``output`` is the handler type +(interface) and ``json`` is the handler label. The class is then instantiated +without any arguments or keyword arguments before use. If a developer needed +to override any meta options in ``JsonOutputHandler.Meta`` they would +**previously** have had to sub-class it. Consider the following example, +where we sub-class ``JsonOutputHandler`` in order to override +``JsonOutputHandler.Meta.json_module``: + +.. code-block:: python + + from cement.core.foundation import CementApp + from cement.ext.ext_json import JsonOutputHandler + + class MyJsonOutputHandler(JsonOutputHandler): + class Meta: + json_module = 'ujson' + + def override_json_output_handler(app): + app.handler.register(MyJsonOutputHandler, force=True) + + class MyApp(CementApp): + class Meta: + label = 'myapp' + extensions = ['json'] + hooks = [ + ('post_setup', override_json_output_handler) + ] + + +If there were anything else in the ``JsonOutputHandler`` that the developer +needed to subclass, this would be fine. However the purpose of the above is +soley to override ``JsonOutputHandler.Meta.json_module``, which is tedious. + +As of Cement 2.10, the above can be accomplished more-easily by the following +by way of ``CementApp.Meta.meta_defaults`` (similar to how ``config_defaults`` +are handled: + +.. code-block:: python + + from cement.core.foundation import CementApp + from cement.utils.misc import init_defaults + + META = init_defaults('output.json') + META['output.json']['json_module'] = 'ujson' + + class MyApp(CementApp): + class Meta: + label = 'myapp' + extensions = ['json'] + output_handler = 'json' + meta_defaults = META + + +When ``JsonOutputHandler`` is instantiated, the defaults from +``META['output.json']`` will be passed as ``**kwargs`` (overriding builtin +meta options). + +Related: + + * :issue:`395` + + +Additional Extensions +^^^^^^^^^^^^^^^^^^^^^ + + * :ref:`Jinja2 ` - Provides template based output + handling using the Jinja2 templating language + * :ref:`Redis ` - Provides caching support using + Redis backend + * :ref:`Watchdog ` - Provides cross-platform + filesystem event monitoring using the Watchdog library. + * :ref:`Handlebars ` - Provides template based + output handling using the Handlebars templating language + + + New Features in Cement 2.8 -------------------------- @@ -107,7 +263,8 @@ print('Caught Exception: %s' % e) -When the ``with`` statement is initialized, the ``app`` object is created, and then right away ``app.setup()`` is called before entering the block. When +When the ``with`` statement is initialized, the ``app`` object is created, and +then right away ``app.setup()`` is called before entering the block. When the ``with`` block is exited ``app.close()`` is also called. This offers a much cleaner approach, while still ensuring that the essential pieces are run appropriately. If you require more control over how/when ``app.setup()`` and diff -Nru python-cement-2.8.2/.gitignore python-cement-2.10.0/.gitignore --- python-cement-2.8.2/.gitignore 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/.gitignore 2016-07-14 20:36:19.000000000 +0000 @@ -65,3 +65,6 @@ .env .vagrant myapp*.py + +# redis test artifacts +dump.rdb diff -Nru python-cement-2.8.2/README.md python-cement-2.10.0/README.md --- python-cement-2.8.2/README.md 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/README.md 2016-07-14 20:36:19.000000000 +0000 @@ -30,10 +30,10 @@ * Cache handler interface adds caching support for improved performance * Controller handler supports sub-commands, and nested controllers * Zero external dependencies* (not including optional extensions) - * 100% test coverage using `nose` - * 100% PEP8 compliant using `pep8` and `autopep8` tools + * 100% test coverage using `nose` and `coverage` + * 100% PEP8 and style compliant using `flake8` * Extensive Sphinx documentation - * Tested on Python 2.6, 2.7, 3.2, 3.3, 3.4, and 3.5 + * Tested on Python 2.6, 2.7, 3.3, 3.4, and 3.5 *Note that argparse is required as an external dependency for Python < 2.7 and < 3.2. Additionally, some optional extensions that are shipped with the @@ -45,7 +45,7 @@ More Information ---------------- - * DOCS: http://builtoncement.com/2.8/ + * DOCS: http://builtoncement.com/2.10/ * CODE: http://github.com/datafolklabs/cement/ * PYPI: http://pypi.python.org/pypi/cement/ * SITE: http://builtoncement.com/ diff -Nru python-cement-2.8.2/requirements-dev-py3.txt python-cement-2.10.0/requirements-dev-py3.txt --- python-cement-2.8.2/requirements-dev-py3.txt 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/requirements-dev-py3.txt 2016-07-14 20:36:19.000000000 +0000 @@ -11,5 +11,10 @@ pystache pyYaml colorlog +configobj tabulate pylibmc +redis +jinja2 +watchdog +pybars3 diff -Nru python-cement-2.8.2/requirements-dev.txt python-cement-2.10.0/requirements-dev.txt --- python-cement-2.8.2/requirements-dev.txt 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/requirements-dev.txt 2016-07-14 20:36:19.000000000 +0000 @@ -4,6 +4,7 @@ sphinx pep8 autopep8 +flake8 mock sphinx_rtd_theme @@ -13,6 +14,10 @@ pyYaml configobj pylibmc +redis genshi colorlog tabulate +jinja2 +watchdog +pybars3 diff -Nru python-cement-2.8.2/scripts/devtools.py python-cement-2.10.0/scripts/devtools.py --- python-cement-2.8.2/scripts/devtools.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/scripts/devtools.py 2016-07-14 20:36:19.000000000 +0000 @@ -76,12 +76,12 @@ self._do_error("\n\nNose tests did not pass.\n\n" + "$ %s\n%s" % (' '.join(cmd_args), err)) - def _do_pep8(self): - print("Checking PEP8 Compliance") - cmd_args = ['pep8', '-r', 'cement/', '--exclude=*.pyc'] + def _do_flake8(self): + print("Checking Flake8 Compliance (pep8, pycodestyle, mccabe, etc)") + cmd_args = ['flake8', 'cement/'] out, err, res = shell.exec_cmd(cmd_args) if res > 0: - self._do_error("\n\nPEP8 checks did not pass.\n\n" + + self._do_error("\n\nFlake8 checks did not pass.\n\n" + "$ %s\n%s" % (' '.join(cmd_args), str(out))) @expose(help='run all unit tests') @@ -91,13 +91,13 @@ print('') print("Running Tests for Cement Version %s" % VERSION) print('-' * 77) - self._do_pep8() + self._do_flake8() self._do_tests() print('') - @expose(help='run pep8 tests') - def pep8(self): - self._do_pep8() + @expose(help='run flake8 tests') + def flake8(self): + self._do_flake8() def _do_sphinx(self, dest_path): print("Building Documentation") @@ -123,7 +123,7 @@ os.makedirs(os.path.join(tmp, 'source')) os.makedirs(os.path.join(tmp, 'doc')) - self._do_pep8() + self._do_flake8() self._do_tests() self._do_git() self._do_sphinx(os.path.join(tmp, 'doc')) diff -Nru python-cement-2.8.2/scripts/travis.sh python-cement-2.10.0/scripts/travis.sh --- python-cement-2.8.2/scripts/travis.sh 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/scripts/travis.sh 2016-07-14 20:36:19.000000000 +0000 @@ -23,3 +23,4 @@ python setup.py nosetests exit $? + diff -Nru python-cement-2.8.2/scripts/vagrant/up.sh python-cement-2.10.0/scripts/vagrant/up.sh --- python-cement-2.8.2/scripts/vagrant/up.sh 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/scripts/vagrant/up.sh 2016-07-14 20:36:19.000000000 +0000 @@ -1,45 +1,66 @@ #!/bin/bash +set -e cd /vagrant -sudo apt-get update && \ -sudo apt-get upgrade -y && \ -sudo apt-get dist-upgrade -y && \ +export DEBIAN_FRONTEND=noninteractive + +sudo apt-get update +sudo apt-get upgrade -y +sudo apt-get dist-upgrade -y +sudo apt-get install -y apt-transport-https ca-certificates +sudo apt-key adv \ + --keyserver hkp://p80.pool.sks-keyservers.net:80 \ + --recv-keys 58118E89F3A912897C070ADBF76221572C52609D +sudo add-apt-repository \ + -y "deb https://apt.dockerproject.org/repo ubuntu-trusty main" +sudo apt-get update +sudo apt-cache policy docker-engine + sudo apt-get install -y \ python \ - python-dev \ python-pip \ + python-dev \ + python-virtualenv \ python3 \ python3-dev \ python3-pip \ memcached \ libmemcached-dev \ zlib1g-dev \ - docker.io + docker-engine sudo apt-get autoremove -y +sudo pip3 install virtualenv # for docker stuff sudo usermod -aG docker vagrant -sudo pip install -U fig -PY3_VER=$(python3 -c 'import sys; print("%s.%s" % (sys.version_info[0], sys.version_info[1]))') -sudo pip install virtualenv -virtualenv ~/.env/cement -sudo pip3 install virtualenv -virtualenv-${PY3_VER} ~/.env/cement-py3 +### fix me - install docker-compose here + +python /usr/bin/virtualenv /vagrant/.env/cement-py2 +python3 /usr/bin/virtualenv /vagrant/.env/cement # for tests -killall memcached -memcached & +sudo /etc/init.d/memcached stop +sudo /etc/init.d/memcached start deactivate ||: -source ~/.env/cement/bin/activate +source /vagrant/.env/cement-py2/bin/activate pip install -r requirements-dev-linux.txt python setup.py develop +deactivate -source ~/.env/cement-py3/bin/activate +source /vagrant/.env/cement/bin/activate pip install -r requirements-dev-py3-linux.txt python setup.py develop +deactivate + +cat >>~/.bashrc < disabled -> enable defaults = init_defaults(APP, 'myplugin') - tmpdir = mkdtemp() - f = open(os.path.join(tmpdir, 'a.conf'), 'w') + f = open(os.path.join(self.tmp_dir, 'a.conf'), 'w') f.write(CONF) # enabled config f.close() - f = open(os.path.join(tmpdir, 'b.conf'), 'w') + f = open(os.path.join(self.tmp_dir, 'b.conf'), 'w') f.write(CONF2) # disabled config f.close() - f = open(os.path.join(tmpdir, 'c.conf'), 'w') + f = open(os.path.join(self.tmp_dir, 'c.conf'), 'w') f.write(CONF) # enabled config f.close() - f = open(os.path.join(tmpdir, 'e.conf'), 'w') + f = open(os.path.join(self.tmp_dir, 'e.conf'), 'w') f.write(CONF2) # disabled config f.close() - f = open(os.path.join(tmpdir, 'f.conf'), 'w') + f = open(os.path.join(self.tmp_dir, 'f.conf'), 'w') f.write(CONF) # enabled config f.close() - f = open(os.path.join(tmpdir, 'myplugin.py'), 'w') + f = open(os.path.join(self.tmp_dir, 'myplugin.py'), 'w') f.write(PLUGIN) f.close() app = self.make_app(APP, config_defaults=defaults, config_files=[], - plugin_config_dir=tmpdir, - plugin_dir=tmpdir, + plugin_config_dir=self.tmp_dir, + plugin_dir=self.tmp_dir, plugin_bootstrap=None, ) app.setup() + res = 'myplugin' in app.plugin._enabled_plugins + self.ok(res) - try: - res = 'myplugin' in app.plugin._enabled_plugins - self.ok(res) - - res = 'myplugin' not in app.plugin._disabled_plugins - self.ok(res) - - finally: - shutil.rmtree(tmpdir) + res = 'myplugin' not in app.plugin._disabled_plugins + self.ok(res) def test_load_plugins_from_config(self): - tmpdir = mkdtemp() - f = open(os.path.join(tmpdir, 'myplugin.py'), 'w') + f = open(os.path.join(self.tmp_dir, 'myplugin.py'), 'w') f.write(PLUGIN) f.close() @@ -289,17 +258,13 @@ defaults['myplugin2']['enable_plugin'] = False app = self.make_app(APP, config_defaults=defaults, config_files=[], - plugin_config_dir=tmpdir, - plugin_dir=tmpdir, + plugin_config_dir=self.tmp_dir, + plugin_dir=self.tmp_dir, plugin_bootstrap=None, ) app.setup() - - try: - han = app.handler.get('output', 'test_output_handler')() - self.eq(han._meta.label, 'test_output_handler') - finally: - shutil.rmtree(tmpdir) + han = app.handler.get('output', 'test_output_handler')() + self.eq(han._meta.label, 'test_output_handler') # some more checks res = 'myplugin' in app.plugin.get_enabled_plugins() @@ -318,23 +283,22 @@ self.ok(res) def test_disabled_plugins_from_files(self): - tmpdir = mkdtemp() - f = open(os.path.join(tmpdir, 'myplugin.conf'), 'w') + f = open(os.path.join(self.tmp_dir, 'myplugin.conf'), 'w') f.write(CONF2) f.close() - f = open(os.path.join(tmpdir, 'myplugin.py'), 'w') + f = open(os.path.join(self.tmp_dir, 'myplugin.py'), 'w') f.write(PLUGIN) f.close() app = self.make_app(APP, config_files=[], - plugin_config_dir=tmpdir, - plugin_dir=tmpdir, + plugin_config_dir=self.tmp_dir, + plugin_dir=self.tmp_dir, plugin_bootstrap=None, ) app.setup() - shutil.rmtree(tmpdir) + shutil.rmtree(self.tmp_dir) res = 'test_output_handler' not in app.handler.__handlers__['output'] self.ok(res) @@ -343,38 +307,36 @@ self.ok(res) def test_bogus_plugin_from_files(self): - tmpdir = mkdtemp() - f = open(os.path.join(tmpdir, 'myplugin.conf'), 'w') + f = open(os.path.join(self.tmp_dir, 'myplugin.conf'), 'w') f.write(CONF3) f.close() # do this for coverage... empty config file - f = open(os.path.join(tmpdir, 'bogus.conf'), 'w') + f = open(os.path.join(self.tmp_dir, 'bogus.conf'), 'w') f.write(CONF5) f.close() app = self.make_app(APP, config_files=[], - plugin_config_dir=tmpdir, - plugin_dir=tmpdir, + plugin_config_dir=self.tmp_dir, + plugin_dir=self.tmp_dir, plugin_bootstrap=None, ) app.setup() - shutil.rmtree(tmpdir) + shutil.rmtree(self.tmp_dir) res = 'bogus_plugin' not in app.plugin.get_enabled_plugins() self.ok(res) @test.raises(exc.FrameworkError) def test_bad_plugin_dir(self): - tmpdir = mkdtemp() - f = open(os.path.join(tmpdir, 'myplugin.conf'), 'w') + f = open(os.path.join(self.tmp_dir, 'myplugin.conf'), 'w') f.write(CONF) f.close() app = self.make_app(APP, config_files=[], - plugin_config_dir=tmpdir, + plugin_config_dir=self.tmp_dir, plugin_dir='./some/bogus/path', plugin_bootstrap=None, ) @@ -384,27 +346,22 @@ raise except exc.FrameworkError as e: raise - finally: - shutil.rmtree(tmpdir) def test_load_plugin_from_module(self): # We mock this out by loading a cement ext, but it is essentially the # same type of code. - tmpdir = mkdtemp() del sys.modules['cement.ext.ext_json'] - f = open(os.path.join(tmpdir, 'ext_json.conf'), 'w') + f = open(os.path.join(self.tmp_dir, 'ext_json.conf'), 'w') f.write(CONF4) f.close() app = self.make_app(APP, config_files=[], - plugin_config_dir=tmpdir, - plugin_dir=tmpdir, + plugin_config_dir=self.tmp_dir, + plugin_dir=self.tmp_dir, plugin_bootstrap='cement.ext', ) app.setup() res = 'ext_json' in app.plugin.get_enabled_plugins() self.ok(res) - - shutil.rmtree(tmpdir) diff -Nru python-cement-2.8.2/tests/ext/alarm_tests.py python-cement-2.10.0/tests/ext/alarm_tests.py --- python-cement-2.8.2/tests/ext/alarm_tests.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/tests/ext/alarm_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -10,6 +10,7 @@ class AlarmExtTestCase(test.CementExtTestCase): def setUp(self): + super(AlarmExtTestCase, self).setUp() self.app = self.make_app('tests', extensions=['alarm'], argv=[] diff -Nru python-cement-2.8.2/tests/ext/argparse_tests.py python-cement-2.10.0/tests/ext/argparse_tests.py --- python-cement-2.8.2/tests/ext/argparse_tests.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/tests/ext/argparse_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -285,37 +285,43 @@ app._meta.argv = ['cmd2'] res = app.run() self.eq(res, "Inside Second.cmd2") - + self.tearDown() + self.setUp() with self.app as app: app._meta.argv = ['third', 'cmd3'] res = app.run() self.eq(res, "Inside Third.cmd3") + self.tearDown() self.setUp() with self.app as app: app._meta.argv = ['third', 'cmd4'] res = app.run() self.eq(res, "Inside Fourth.cmd4") + self.tearDown() self.setUp() with self.app as app: app._meta.argv = ['third', 'fifth', 'cmd5'] res = app.run() self.eq(res, "Inside Fifth.cmd5") + self.tearDown() self.setUp() with self.app as app: app._meta.argv = ['third', 'fifth', 'sixth', 'cmd6'] res = app.run() self.eq(res, "Inside Sixth.cmd6") + self.tearDown() self.setUp() with self.app as app: app._meta.argv = ['third', 'cmd7'] res = app.run() self.eq(res, "Inside Seventh.cmd7") - + self.tearDown() + def test_base_cmd1_parsing(self): with self.app as app: app._meta.argv = ['--foo=bar', 'cmd1'] @@ -597,3 +603,21 @@ app._setup_arg_handler() res = app.run() self.eq(res, "Inside Aliases.aliases_cmd1") + + def test_unknown_arguments(self): + self.reset_backend() + + class MyArgumentHandler(ArgparseArgumentHandler): + class Meta: + label = 'my_argument_handler' + ignore_unknown_arguments = True + self.app = self.make_app(APP, + argument_handler=MyArgumentHandler, + ) + with self.app as app: + app._meta.argv = ['-l', 'some-other-argument'] + app.run() + res = '-l' in app.args.unknown_args + self.ok(res) + res = 'some-other-argument' in app.args.unknown_args + self.ok(res) diff -Nru python-cement-2.8.2/tests/ext/configobj_tests.py python-cement-2.10.0/tests/ext/configobj_tests.py --- python-cement-2.8.2/tests/ext/configobj_tests.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/tests/ext/configobj_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -2,17 +2,10 @@ import os import sys -from tempfile import mkstemp from cement.core import handler, backend, log from cement.utils import test from cement.utils.misc import rando -if sys.version_info[0] < 3: - import configobj -else: - raise test.SkipTest( - 'ConfigObj does not support Python 3') # pragma: no cover - APP = rando()[:12] @@ -25,21 +18,17 @@ class ConfigObjExtTestCase(test.CementTestCase): def setUp(self): - _, self.tmppath = mkstemp() - f = open(self.tmppath, 'w+') + super(ConfigObjExtTestCase, self).setUp() + f = open(self.tmp_file, 'w+') f.write(CONFIG) f.close() self.app = self.make_app(APP, extensions=['configobj'], config_handler='configobj', - config_files=[self.tmppath], + config_files=[self.tmp_file], argv=[] ) - def tearDown(self): - if os.path.exists(self.tmppath): - os.remove(self.tmppath) - def test_configobj(self): self.app.setup() diff -Nru python-cement-2.8.2/tests/ext/daemon_tests.py python-cement-2.10.0/tests/ext/daemon_tests.py --- python-cement-2.8.2/tests/ext/daemon_tests.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/tests/ext/daemon_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -5,7 +5,7 @@ # sub-process is forked. import os -import tempfile +#import tempfile from random import random from cement.core import handler, backend, log, hook, exc from cement.utils import shell @@ -19,6 +19,7 @@ class DaemonExtTestCase(test.CementExtTestCase): def setUp(self): + super(DaemonExtTestCase, self).setUp() self.app = self.make_app() def test_switch(self): @@ -26,32 +27,24 @@ env.switch() def test_switch_with_pid(self): - (_, tmpfile) = tempfile.mkstemp() - os.remove(tmpfile) - env = ext_daemon.Environment(pid_file=tmpfile) + os.remove(self.tmp_file) + env = ext_daemon.Environment(pid_file=self.tmp_file) env.switch() - - try: - self.ok(os.path.exists(tmpfile)) - finally: - os.remove(tmpfile) + self.ok(os.path.exists(self.tmp_file)) @test.raises(exc.FrameworkError) def test_pid_exists(self): - (_, tmpfile) = tempfile.mkstemp() - - env = ext_daemon.Environment(pid_file=tmpfile) + env = ext_daemon.Environment(pid_file=self.tmp_file) env.switch() try: - self.ok(os.path.exists(tmpfile)) + self.ok(os.path.exists(self.tmp_file)) except exc.FrameworkError as e: self.ok(e.msg.startswith('Process already running')) raise finally: env = ext_daemon.Environment() env.switch() - os.remove(tmpfile) @test.raises(exc.FrameworkError) def test_bogus_user(self): @@ -80,8 +73,7 @@ env.switch() def test_daemon(self): - (_, tmpfile) = tempfile.mkstemp() - os.remove(tmpfile) + os.remove(self.tmp_file) from cement.utils import shell # Test in a sub-process to avoid Nose hangup @@ -90,7 +82,7 @@ extensions=['daemon']) app.setup() - app.config.set('daemon', 'pid_file', tmpfile) + app.config.set('daemon', 'pid_file', self.tmp_file) try: # FIX ME: Can't daemonize, because nose loses sight of it diff -Nru python-cement-2.8.2/tests/ext/genshi_tests.py python-cement-2.10.0/tests/ext/genshi_tests.py --- python-cement-2.8.2/tests/ext/genshi_tests.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/tests/ext/genshi_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -15,6 +15,7 @@ class GenshiExtTestCase(test.CementExtTestCase): def setUp(self): + super(GenshiExtTestCase, self).setUp() self.app = self.make_app('tests', extensions=['genshi'], output_handler='genshi', diff -Nru python-cement-2.8.2/tests/ext/handlebars_tests.py python-cement-2.10.0/tests/ext/handlebars_tests.py --- python-cement-2.8.2/tests/ext/handlebars_tests.py 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/tests/ext/handlebars_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1,61 @@ +"""Tests for cement.ext.ext_handlebars.""" + +import sys +import random +from cement.core import exc, foundation, handler, backend, controller +from cement.utils import test +from nose.plugins.attrib import attr + +class HandlebarsTestApp(test.TestApp): + class Meta: + extensions = ['handlebars'] + output_handler = 'handlebars' + template_module = 'tests.templates' + template_dirs = [] + handlebars_helpers = {} + handlebars_partials = ['test_partial_template.handlebars'] + +@attr('ext_handlebars') +class HandlebarsExtTestCase(test.CementExtTestCase): + app_class = HandlebarsTestApp + + def test_handlebars(self): + self.app.setup() + rando = random.random() + res = self.app.render(dict(foo=rando), 'test_template.handlebars') + handlebars_res = "foo equals %s\n" % rando + self.eq(res, handlebars_res) + + def test_handlebars_partials(self): + # FIX ME: Not sure what's going on here + self.app.setup() + + rando = random.random() + res = self.app.render(dict(foo=rando), 'test_base_template.handlebars') + handlebars_res = "Inside partial > foo equals %s\n" % rando + self.eq(res, handlebars_res) + + @test.raises(exc.FrameworkError) + def test_handlebars_bad_template(self): + self.app.setup() + res = self.app.render(dict(foo='bar'), 'bad_template2.handlebars') + + @test.raises(exc.FrameworkError) + def test_handlebars_nonexistent_template(self): + self.app.setup() + res = self.app.render(dict(foo='bar'), 'missing_template.handlebars') + + @test.raises(exc.FrameworkError) + def test_handlebars_none_template(self): + self.app.setup() + try: + res = self.app.render(dict(foo='bar'), None) + except exc.FrameworkError as e: + self.eq(e.msg, "Invalid template path 'None'.") + raise + + @test.raises(exc.FrameworkError) + def test_handlebars_bad_module(self): + self.app.setup() + self.app._meta.template_module = 'this_is_a_bogus_module' + res = self.app.render(dict(foo='bar'), 'bad_template.handlebars') diff -Nru python-cement-2.8.2/tests/ext/jinja2_tests.py python-cement-2.10.0/tests/ext/jinja2_tests.py --- python-cement-2.8.2/tests/ext/jinja2_tests.py 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/tests/ext/jinja2_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +"""Tests for cement.ext.ext_jinja2.""" + +import os +import sys +import random +from shutil import copyfile, rmtree + +from cement.core import exc, foundation, handler, backend, controller +from cement.utils import test + + +class Jinja2ExtTestCase(test.CementExtTestCase): + + def setUp(self): + super(Jinja2ExtTestCase, self).setUp() + self.app = self.make_app('tests', + extensions=['jinja2'], + output_handler='jinja2', + argv=[] + ) + + def test_jinja2(self): + self.app.setup() + rando = random.random() + res = self.app.render(dict(foo=rando), 'test_template.jinja2') + jinja2_res = "foo equals %s\n" % rando + self.eq(res, jinja2_res) + + def test_jinja2_utf8(self): + self.app.setup() + rando = random.random() + res = self.app.render(dict(foo=rando), 'test_template_utf8.jinja2') + jinja2_res = u"foo est égal à %s\n" % rando + self.eq(res, jinja2_res) + + def test_jinja2_filesystemloader(self): + self.app.setup() + self.app._meta.template_dirs = [self.tmp_dir] + + # make sure it doesn't load from the tests directory module regardless + self.app._meta.template_module = 'some.bogus.module.path' + + tests_dir = os.path.dirname(os.path.dirname(__file__)) + + from_file = os.path.join(tests_dir, 'templates', + 'test_template_parent.jinja2') + to_file = os.path.join(self.tmp_dir, 'test_template_parent.jinja2') + copyfile(from_file, to_file) + + from_file = os.path.join(tests_dir, 'templates', + 'test_template_child.jinja2') + to_file = os.path.join(self.tmp_dir, 'test_template_child.jinja2') + copyfile(from_file, to_file) + + rando = random.random() + res = self.app.render(dict(foo=rando), 'test_template_child.jinja2') + jinja2_res = "foo equals %s\n" % rando + self.eq(res, jinja2_res) + + def test_jinja2_packageloader(self): + self.app.setup() + self.app._meta.template_module = 'tests.templates' + self.app._meta.template_dirs = [] + rando = random.random() + res = self.app.render(dict(foo=rando), 'test_template_child.jinja2') + jinja2_res = "foo equals %s\n" % rando + self.eq(res, jinja2_res) + + @test.raises(exc.FrameworkError) + def test_jinja2_bad_template(self): + self.app.setup() + res = self.app.render(dict(foo='bar'), 'bad_template2.jinja2') + + @test.raises(exc.FrameworkError) + def test_jinja2_nonexistent_template(self): + self.app.setup() + res = self.app.render(dict(foo='bar'), 'missing_template.jinja2') + + @test.raises(exc.FrameworkError) + def test_jinja2_none_template(self): + self.app.setup() + try: + res = self.app.render(dict(foo='bar'), None) + except exc.FrameworkError as e: + self.eq(e.msg, "Invalid template path 'None'.") + raise + + @test.raises(exc.FrameworkError) + def test_jinja2_bad_module(self): + self.app.setup() + self.app._meta.template_module = 'this_is_a_bogus_module' + res = self.app.render(dict(foo='bar'), 'bad_template.jinja2') diff -Nru python-cement-2.8.2/tests/ext/json_configobj_tests.py python-cement-2.10.0/tests/ext/json_configobj_tests.py --- python-cement-2.8.2/tests/ext/json_configobj_tests.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/tests/ext/json_configobj_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -2,16 +2,9 @@ import json import sys -from tempfile import mkstemp from cement.core import handler, backend, hook from cement.utils import test -if sys.version_info[0] < 3: - import configobj -else: - raise test.SkipTest( - 'ConfigObj does not support Python 3') # pragma: no cover - class JsonConfigObjExtTestCase(test.CementExtTestCase): CONFIG = '''{ @@ -37,14 +30,14 @@ ) def setUp(self): - _, self.tmppath = mkstemp() - f = open(self.tmppath, 'w+') + super(JsonConfigObjExtTestCase, self).setUp() + f = open(self.tmp_file, 'w+') f.write(self.CONFIG) f.close() self.app = self.make_app('tests', extensions=['json_configobj'], config_handler='json_configobj', - config_files=[self.tmppath], + config_files=[self.tmp_file], ) def test_has_section(self): diff -Nru python-cement-2.8.2/tests/ext/json_tests.py python-cement-2.10.0/tests/ext/json_tests.py --- python-cement-2.8.2/tests/ext/json_tests.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/tests/ext/json_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -2,7 +2,6 @@ import json import sys -from tempfile import mkstemp from cement.core import handler, backend, hook from cement.utils import test from cement.utils.misc import rando @@ -34,15 +33,15 @@ ) def setUp(self): - _, self.tmppath = mkstemp() - f = open(self.tmppath, 'w+') + super(JsonExtTestCase, self).setUp() + f = open(self.tmp_file, 'w+') f.write(self.CONFIG) f.close() self.app = self.make_app('tests', extensions=['json'], output_handler='json', config_handler='json', - config_files=[self.tmppath], + config_files=[self.tmp_file], argv=['-o', 'json'] ) diff -Nru python-cement-2.8.2/tests/ext/logging_tests.py python-cement-2.10.0/tests/ext/logging_tests.py --- python-cement-2.8.2/tests/ext/logging_tests.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/tests/ext/logging_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -2,7 +2,7 @@ import os import logging -from tempfile import mkstemp +import shutil from cement.core import handler, backend, log from cement.ext import ext_logging from cement.utils import test @@ -32,23 +32,30 @@ app = self.make_app(config_defaults=defaults) app.setup() app.log.info('TEST', extra=dict(namespace=__name__)) - app.log.warn('TEST', extra=dict(namespace=__name__)) + app.log.warning('TEST', extra=dict(namespace=__name__)) app.log.error('TEST', extra=dict(namespace=__name__)) app.log.fatal('TEST', extra=dict(namespace=__name__)) app.log.debug('TEST', extra=dict(namespace=__name__)) app.log.info('TEST', __name__, extra=dict(foo='bar')) - app.log.warn('TEST', __name__, extra=dict(foo='bar')) + app.log.warning('TEST', __name__, extra=dict(foo='bar')) app.log.error('TEST', __name__, extra=dict(foo='bar')) app.log.fatal('TEST', __name__, extra=dict(foo='bar')) app.log.debug('TEST', __name__, extra=dict(foo='bar')) app.log.info('TEST', __name__) - app.log.warn('TEST', __name__) + app.log.warning('TEST', __name__) app.log.error('TEST', __name__) app.log.fatal('TEST', __name__) app.log.debug('TEST', __name__) + def test_deprecated_warn(self): + defaults = init_defaults(APP, 'log.logging') + defaults['log.logging']['level'] = 'warn' + app = self.make_app(config_defaults=defaults) + app.setup() + app.log.warn('Warn Message') + def test_bad_level(self): defaults = init_defaults() defaults['log.logging'] = dict( @@ -98,13 +105,12 @@ self.eq(os.path.exists("%s.3" % log_file), False) def test_missing_log_dir(self): - _, tmp_path = mkstemp() - if os.path.exists(tmp_path): - os.remove(tmp_path) + if os.path.exists(self.tmp_dir): + shutil.rmtree(self.tmp_dir) defaults = init_defaults() defaults['log.logging'] = dict( - file=os.path.join(tmp_path, '%s.log' % APP), + file=os.path.join(self.tmp_dir, '%s.log' % APP), ) app = self.make_app(config_defaults=defaults) app.setup() diff -Nru python-cement-2.8.2/tests/ext/memcached_tests.py python-cement-2.10.0/tests/ext/memcached_tests.py --- python-cement-2.8.2/tests/ext/memcached_tests.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/tests/ext/memcached_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -12,6 +12,7 @@ class MemcachedExtTestCase(test.CementTestCase): def setUp(self): + super(MemcachedExtTestCase, self).setUp() self.key = "cement-tests-random-key-%s" % random() defaults = init_defaults('tests', 'cache.memcached') defaults['cache.memcached']['hosts'] = '127.0.0.1, localhost' @@ -23,6 +24,7 @@ self.app.setup() def tearDown(self): + super(MemcachedExtTestCase, self).tearDown() self.app.cache.delete(self.key) def test_memcache_list_type_config(self): diff -Nru python-cement-2.8.2/tests/ext/mustache_tests.py python-cement-2.10.0/tests/ext/mustache_tests.py --- python-cement-2.8.2/tests/ext/mustache_tests.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/tests/ext/mustache_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -10,6 +10,7 @@ class MustacheExtTestCase(test.CementExtTestCase): def setUp(self): + super(MustacheExtTestCase, self).setUp() self.app = self.make_app('tests', extensions=['mustache'], output_handler='mustache', diff -Nru python-cement-2.8.2/tests/ext/redis_tests.py python-cement-2.10.0/tests/ext/redis_tests.py --- python-cement-2.8.2/tests/ext/redis_tests.py 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/tests/ext/redis_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1,55 @@ +"""Tests for cement.ext.ext_redis.""" + +import sys +import redis +from time import sleep +from random import random +from cement.core import handler +from cement.utils import test +from cement.utils.misc import init_defaults + + +class RedisExtTestCase(test.CementTestCase): + + def setUp(self): + super(RedisExtTestCase, self).setUp() + self.key = "cement-tests-random-key-%s" % random() + defaults = init_defaults('tests', 'cache.redis') + defaults['cache.redis']['host'] = '127.0.0.1' + defaults['cache.redis']['port'] = 6379 + defaults['cache.redis']['db'] = 0 + self.app = self.make_app('tests', + config_defaults=defaults, + extensions=['redis'], + cache_handler='redis', + ) + self.app.setup() + + def tearDown(self): + super(RedisExtTestCase, self).tearDown() + self.app.cache.delete(self.key) + + def test_redis_set(self): + self.app.cache.set(self.key, 1001) + self.eq(int(self.app.cache.get(self.key)), 1001) + + def test_redis_get(self): + # get empty value + self.app.cache.delete(self.key) + self.eq(self.app.cache.get(self.key), None) + + # get empty value with fallback + self.eq(self.app.cache.get(self.key, 1234), 1234) + + def test_redis_delete(self): + self.app.cache.delete(self.key) + + def test_redis_purge(self): + self.app.cache.set(self.key, 1002) + self.app.cache.purge() + self.eq(self.app.cache.get(self.key), None) + + def test_memcache_expire(self): + self.app.cache.set(self.key, 1003, time=2) + sleep(3) + self.eq(self.app.cache.get(self.key), None) diff -Nru python-cement-2.8.2/tests/ext/tabulate_tests.py python-cement-2.10.0/tests/ext/tabulate_tests.py --- python-cement-2.8.2/tests/ext/tabulate_tests.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/tests/ext/tabulate_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -8,6 +8,7 @@ class TabulateExtTestCase(test.CementExtTestCase): def setUp(self): + super(TabulateExtTestCase, self).setUp() self.app = self.make_app('tests', extensions=['tabulate'], output_handler='tabulate', diff -Nru python-cement-2.8.2/tests/ext/watchdog_tests.py python-cement-2.10.0/tests/ext/watchdog_tests.py --- python-cement-2.8.2/tests/ext/watchdog_tests.py 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/tests/ext/watchdog_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1,94 @@ +"""Tests for cement.ext.ext_watchdog.""" + +import os +from cement.utils import test +from cement.core.exc import FrameworkError +from cement.ext.ext_watchdog import WatchdogEventHandler + + +class MyEventHandler(WatchdogEventHandler): + pass + +class WatchdogExtTestCase(test.CementExtTestCase): + + def setUp(self): + super(WatchdogExtTestCase, self).setUp() + self.app = self.make_app('tests', + extensions=['watchdog'], + argv=[] + ) + + def test_watchdog(self): + self.app.setup() + self.app.watchdog.add(self.tmp_dir, event_handler=MyEventHandler) + self.app.run() + + # trigger an event + f = open(os.path.join(self.tmp_dir, 'test.file'), 'w') + f.write('test data') + f.close() + + self.app.close() + + def test_watchdog_app_paths(self): + self.reset_backend() + self.app = self.make_app('tests', + extensions=['watchdog'], + argv=[] + ) + self.app._meta.watchdog_paths = [ + (self.tmp_dir), + (self.tmp_dir, WatchdogEventHandler) + ] + self.app.setup() + self.app.run() + + # trigger an event + f = open(os.path.join(self.tmp_dir, 'test.file'), 'w') + f.write('test data') + f.close() + + self.app.close() + + @test.raises(FrameworkError) + def test_watchdog_app_paths_bad_spec(self): + self.reset_backend() + self.app = self.make_app('tests', + extensions=['watchdog'], + argv=[] + ) + self.app._meta.watchdog_paths = [ + [self.tmp_dir, WatchdogEventHandler] + ] + self.app.setup() + + def test_watchdog_default_event_handler(self): + self.app.setup() + self.app.watchdog.add(self.tmp_dir) + self.app.run() + self.app.close() + + def test_watchdog_bad_path(self): + self.app.setup() + self.app.watchdog.add(os.path.join(self.tmp_dir, 'bogus_sub_dir')) + self.app.run() + self.app.close() + + def test_watchdog_hooks(self): + # FIX ME: this is only coverage... + def test_hook(app): + app.counter += 1 + + self.app.setup() + self.app.counter = 0 + self.app.hook.register('watchdog_pre_start', test_hook) + self.app.hook.register('watchdog_post_start', test_hook) + self.app.hook.register('watchdog_pre_stop', test_hook) + self.app.hook.register('watchdog_post_stop', test_hook) + self.app.hook.register('watchdog_pre_join', test_hook) + self.app.hook.register('watchdog_post_join', test_hook) + self.app.run() + self.app.close() + + # yup, the function was run 6 times (once for each hook) + self.eq(self.app.counter, 6) diff -Nru python-cement-2.8.2/tests/ext/yaml_configobj_tests.py python-cement-2.10.0/tests/ext/yaml_configobj_tests.py --- python-cement-2.8.2/tests/ext/yaml_configobj_tests.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/tests/ext/yaml_configobj_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -3,16 +3,9 @@ import os import sys import yaml -from tempfile import mkstemp from cement.core import handler, hook from cement.utils import test -if sys.version_info[0] < 3: - import configobj -else: - raise test.SkipTest( - 'ConfigObj does not support Python 3') # pragma: no cover - class YamlConfigObjExtTestCase(test.CementTestCase): CONFIG = ''' @@ -38,20 +31,16 @@ ) def setUp(self): - _, self.tmppath = mkstemp() - f = open(self.tmppath, 'w+') + super(YamlConfigObjExtTestCase, self).setUp() + f = open(self.tmp_file, 'w+') f.write(self.CONFIG) f.close() self.app = self.make_app('tests', extensions=['yaml_configobj'], config_handler='yaml_configobj', - config_files=[self.tmppath], + config_files=[self.tmp_file], ) - def tearDown(self): - if os.path.exists(self.tmppath): - os.remove(self.tmppath) - def test_has_section(self): self.app.setup() self.ok(self.app.config.has_section('section')) diff -Nru python-cement-2.8.2/tests/ext/yaml_tests.py python-cement-2.10.0/tests/ext/yaml_tests.py --- python-cement-2.8.2/tests/ext/yaml_tests.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/tests/ext/yaml_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -3,7 +3,6 @@ import os import sys import yaml -from tempfile import mkstemp from cement.core import handler, hook from cement.utils import test from cement.utils.misc import rando @@ -35,22 +34,18 @@ ) def setUp(self): - _, self.tmppath = mkstemp() - f = open(self.tmppath, 'w+') + super(YamlExtTestCase, self).setUp() + f = open(self.tmp_file, 'w+') f.write(self.CONFIG) f.close() self.app = self.make_app('tests', extensions=['yaml'], config_handler='yaml', output_handler='yaml', - config_files=[self.tmppath], + config_files=[self.tmp_file], argv=['-o', 'yaml'] ) - def tearDown(self): - if os.path.exists(self.tmppath): - os.remove(self.tmppath) - def test_yaml(self): self.app.setup() self.app.run() diff -Nru python-cement-2.8.2/tests/templates/test_base_template.handlebars python-cement-2.10.0/tests/templates/test_base_template.handlebars --- python-cement-2.8.2/tests/templates/test_base_template.handlebars 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/tests/templates/test_base_template.handlebars 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1 @@ +{{> "test_partial_template.handlebars"}} \ No newline at end of file diff -Nru python-cement-2.8.2/tests/templates/test_partial_template.handlebars python-cement-2.10.0/tests/templates/test_partial_template.handlebars --- python-cement-2.8.2/tests/templates/test_partial_template.handlebars 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/tests/templates/test_partial_template.handlebars 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1 @@ +Inside partial > foo equals {{foo}} diff -Nru python-cement-2.8.2/tests/templates/test_template_child.jinja2 python-cement-2.10.0/tests/templates/test_template_child.jinja2 --- python-cement-2.8.2/tests/templates/test_template_child.jinja2 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/tests/templates/test_template_child.jinja2 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1,2 @@ +{% extends 'test_template_parent.jinja2' %} +{% block test %}foo equals{% endblock %} diff -Nru python-cement-2.8.2/tests/templates/test_template.handlebars python-cement-2.10.0/tests/templates/test_template.handlebars --- python-cement-2.8.2/tests/templates/test_template.handlebars 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/tests/templates/test_template.handlebars 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1 @@ +foo equals {{foo}} diff -Nru python-cement-2.8.2/tests/templates/test_template.jinja2 python-cement-2.10.0/tests/templates/test_template.jinja2 --- python-cement-2.8.2/tests/templates/test_template.jinja2 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/tests/templates/test_template.jinja2 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1 @@ +foo equals {{foo}} diff -Nru python-cement-2.8.2/tests/templates/test_template_parent.jinja2 python-cement-2.10.0/tests/templates/test_template_parent.jinja2 --- python-cement-2.8.2/tests/templates/test_template_parent.jinja2 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/tests/templates/test_template_parent.jinja2 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1 @@ +{% block test %}{% endblock %} {{ foo }} diff -Nru python-cement-2.8.2/tests/templates/test_template_utf8.jinja2 python-cement-2.10.0/tests/templates/test_template_utf8.jinja2 --- python-cement-2.8.2/tests/templates/test_template_utf8.jinja2 1970-01-01 00:00:00.000000000 +0000 +++ python-cement-2.10.0/tests/templates/test_template_utf8.jinja2 2016-07-14 20:36:19.000000000 +0000 @@ -0,0 +1 @@ +foo est égal à {{foo}} diff -Nru python-cement-2.8.2/tests/utils/fs_tests.py python-cement-2.10.0/tests/utils/fs_tests.py --- python-cement-2.8.2/tests/utils/fs_tests.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/tests/utils/fs_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -1,7 +1,7 @@ """Tests for cement.utils.fs""" import os -import tempfile +import shutil from cement.utils import fs, test @@ -12,19 +12,24 @@ self.ok(path.startswith('/')) def test_backup(self): - _, tmpfile = tempfile.mkstemp() - bkfile = fs.backup(tmpfile) - self.eq("%s.bak" % os.path.basename(tmpfile), os.path.basename(bkfile)) - bkfile = fs.backup(tmpfile) + tmp_file = os.path.join(self.tmp_dir, 'test.file') + tmp_dir = os.path.join(self.tmp_dir, 'test.dir') + open(tmp_file, 'w').close() + os.makedirs(tmp_dir) + + bkfile = fs.backup(tmp_file) + self.eq("%s.bak" % os.path.basename(tmp_file), + os.path.basename(bkfile)) + bkfile = fs.backup(tmp_file) self.eq("%s.bak.0" % - os.path.basename(tmpfile), os.path.basename(bkfile)) - bkfile = fs.backup(tmpfile) + os.path.basename(tmp_file), os.path.basename(bkfile)) + bkfile = fs.backup(tmp_file) self.eq("%s.bak.1" % - os.path.basename(tmpfile), os.path.basename(bkfile)) + os.path.basename(tmp_file), os.path.basename(bkfile)) - tmpdir = tempfile.mkdtemp() - bkdir = fs.backup(tmpdir) - self.eq("%s.bak" % os.path.basename(tmpdir), os.path.basename(bkdir)) + bkdir = fs.backup(tmp_dir) + self.eq("%s.bak" % os.path.basename(tmp_dir), + os.path.basename(bkdir)) res = fs.backup('someboguspath') self.eq(res, None) diff -Nru python-cement-2.8.2/tests/utils/misc_tests.py python-cement-2.10.0/tests/utils/misc_tests.py --- python-cement-2.8.2/tests/utils/misc_tests.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/tests/utils/misc_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -21,7 +21,7 @@ log = misc.minimal_logger(__name__) log = misc.minimal_logger(__name__, debug=True) log.info('info test') - log.warn('warn test') + log.warning('warning test') log.error('error test') log.fatal('fatal test') log.debug('debug test') @@ -34,7 +34,10 @@ # set logging back to non-debug misc.minimal_logger(__name__, debug=False) - pass + + def test_minimal_logger_deprecated_warn(self): + log = misc.minimal_logger(__name__) + log.warn('warning test') def test_wrap_str(self): text = "aaaaa bbbbb ccccc" diff -Nru python-cement-2.8.2/tests/utils/version_tests.py python-cement-2.10.0/tests/utils/version_tests.py --- python-cement-2.8.2/tests/utils/version_tests.py 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/tests/utils/version_tests.py 2016-07-14 20:36:19.000000000 +0000 @@ -7,7 +7,7 @@ def test_get_version(self): ver = version.get_version() - self.ok(ver.startswith('2.8')) + self.ok(ver.startswith('2.10')) ver = version.get_version((2, 1, 1, 'alpha', 1)) self.eq(ver, '2.1.1a1') diff -Nru python-cement-2.8.2/.travis.yml python-cement-2.10.0/.travis.yml --- python-cement-2.8.2/.travis.yml 2016-02-26 22:36:57.000000000 +0000 +++ python-cement-2.10.0/.travis.yml 2016-07-14 20:36:19.000000000 +0000 @@ -6,9 +6,9 @@ python: - 2.6 - 2.7 - - 3.2 - 3.3 - 3.4 - 3.5 services: - memcached + - redis