def encode_output(value, output_file): """ Encodes given value so it can be written to given file object. Value may be Unicode, binary string or any other data type. The exact behaviour depends on the Python version: Python 3.x `sys.stdout` is a `_io.TextIOWrapper` instance that accepts `str` (unicode) and breaks on `bytes`. It is OK to simply assume that everything is Unicode unless special handling is introduced in the client code. Thus, no additional processing is performed. Python 2.x `sys.stdout` is a file-like object that accepts `str` (bytes) and breaks when `unicode` is passed to `sys.stdout.write()`. We can expect both Unicode and bytes. They need to be encoded so as to match the file object encoding. The output is binary if the object doesn't explicitly require Unicode. """ if sys.version_info > (3,0): # Python 3: whatever → unicode return compat.text_type(value) else: # Python 2: handle special cases stream_encoding = getattr(output_file, 'encoding', None) if stream_encoding: if stream_encoding.upper() == 'UTF-8': return compat.text_type(value) else: return value.encode(stream_encoding, 'ignore') else: # no explicit encoding requirements; force binary if isinstance(value, compat.text_type): # unicode → binary return value.encode('utf-8') else: return str(value)
def encode_output(value, output_file): """ Encodes given value so it can be written to given file object. Value may be Unicode, binary string or any other data type. The exact behaviour depends on the Python version: Python 3.x `sys.stdout` is a `_io.TextIOWrapper` instance that accepts `str` (unicode) and breaks on `bytes`. It is OK to simply assume that everything is Unicode unless special handling is introduced in the client code. Thus, no additional processing is performed. Python 2.x `sys.stdout` is a file-like object that accepts `str` (bytes) and breaks when `unicode` is passed to `sys.stdout.write()`. We can expect both Unicode and bytes. They need to be encoded so as to match the file object encoding. The output is binary if the object doesn't explicitly require Unicode. """ if sys.version_info > (3, 0): # Python 3: whatever → unicode return compat.text_type(value) else: # Python 2: handle special cases stream_encoding = getattr(output_file, 'encoding', None) if stream_encoding: if stream_encoding.upper() == 'UTF-8': return compat.text_type(value) else: return value.encode(stream_encoding, 'ignore') else: # no explicit encoding requirements; force binary if isinstance(value, compat.text_type): # unicode → binary return value.encode('utf-8') else: return str(value)
def confirm(action, default=None, skip=False): """A shortcut for typical confirmation prompt. :param action: a string describing the action, e.g. "Apply changes". A question mark will be appended. :param default: `bool` or `None`. Determines what happens when user hits :kbd:`Enter` without typing in a choice. If `True`, default choice is "yes". If `False`, it is "no". If `None` the prompt keeps reappearing until user types in a choice (not necessarily acceptable) or until the number of iteration reaches the limit. Default is `None`. :param skip: `bool`; if `True`, no interactive prompt is used and default choice is returned (useful for batch mode). Default is `False`. Usage:: def delete(key, silent=False): item = db.get(Item, args.key) if confirm('Delete '+item.title, default=True, skip=silent): item.delete() print('Item deleted.') else: print('Operation cancelled.') Returns `None` on `KeyboardInterrupt` event. """ MAX_ITERATIONS = 3 if skip: return default else: defaults = {None: ("y", "n"), True: ("Y", "n"), False: ("y", "N")} y, n = defaults[default] prompt = text_type("{action}? ({y}/{n})").format(**locals()) choice = None try: if default is None: cnt = 1 while not choice and cnt < MAX_ITERATIONS: choice = safe_input(prompt) cnt += 1 else: choice = safe_input(prompt) except KeyboardInterrupt: return None if choice in ("yes", "y", "Y"): return True if choice in ("no", "n", "N"): return False if default is not None: return default return None
def _execute_command(args, errors_file): """ Asserts that ``args.function`` is present and callable. Tries different approaches to calling the function (with an `argparse.Namespace` object or with ordinary signature). Yields the results line by line. If :class:`~argh.exceptions.CommandError` is raised, its message is appended to the results (i.e. yielded by the generator as a string). All other exceptions propagate unless marked as wrappable by :func:`wrap_errors`. """ assert hasattr(args, 'function') and hasattr(args.function, '__call__') # the function is nested to catch certain exceptions (see below) def _call(): # Actually call the function if getattr(args.function, ATTR_EXPECTS_NAMESPACE_OBJECT, False): result = args.function(args) else: # namespace -> dictionary _flat_key = lambda key: key.replace('-', '_') all_input = dict((_flat_key(k), v) for k,v in vars(args).items()) # filter the namespace variables so that only those expected by the # actual function will pass spec = get_arg_spec(args.function) positional = [all_input[k] for k in spec.args] kwonly = getattr(spec, 'kwonlyargs', []) keywords = dict((k, all_input[k]) for k in kwonly) # *args if spec.varargs: positional += getattr(args, spec.varargs) # **kwargs varkw = getattr(spec, 'varkw', getattr(spec, 'keywords', [])) if varkw: not_kwargs = ['function'] + spec.args + [spec.varargs] + kwonly extra = [k for k in vars(args) if k not in not_kwargs] for k in extra: keywords[k] = getattr(args, k) result = args.function(*positional, **keywords) # Yield the results if isinstance(result, (GeneratorType, list, tuple)): # yield each line ASAP, convert CommandError message to a line for line in result: yield line else: # yield non-empty non-iterable result as a single line if result is not None: yield result wrappable_exceptions = [CommandError] wrappable_exceptions += getattr(args.function, ATTR_WRAPPED_EXCEPTIONS, []) try: result = _call() for line in result: yield line except tuple(wrappable_exceptions) as e: processor = getattr(args.function, ATTR_WRAPPED_EXCEPTIONS_PROCESSOR, lambda e: '{0.__class__.__name__}: {0}'.format(e)) errors_file.write(compat.text_type(processor(e))) errors_file.write('\n')
def _execute_command(function, namespace_obj, errors_file, pre_call=None): """ Assumes that `function` is a callable. Tries different approaches to call it (with `namespace_obj` or with ordinary signature). Yields the results line by line. If :class:`~argh.exceptions.CommandError` is raised, its message is appended to the results (i.e. yielded by the generator as a string). All other exceptions propagate unless marked as wrappable by :func:`wrap_errors`. """ if pre_call: # XXX undocumented because I'm unsure if it's OK # Actually used in real projects: # * https://google.com/search?q=argh+dispatch+pre_call # * https://github.com/neithere/argh/issues/63 pre_call(namespace_obj) # the function is nested to catch certain exceptions (see below) def _call(): # Actually call the function if getattr(function, ATTR_EXPECTS_NAMESPACE_OBJECT, False): result = function(namespace_obj) else: # namespace -> dictionary _flat_key = lambda key: key.replace('-', '_') all_input = dict((_flat_key(k), v) for k,v in vars(namespace_obj).items()) # filter the namespace variables so that only those expected # by the actual function will pass spec = get_arg_spec(function) positional = [all_input[k] for k in spec.args] kwonly = getattr(spec, 'kwonlyargs', []) keywords = dict((k, all_input[k]) for k in kwonly) # *args if spec.varargs: positional += getattr(namespace_obj, spec.varargs) # **kwargs varkw = getattr(spec, 'varkw', getattr(spec, 'keywords', [])) if varkw: not_kwargs = [DEST_FUNCTION] + spec.args + [spec.varargs] + kwonly for k in vars(namespace_obj): if k.startswith('_') or k in not_kwargs: continue keywords[k] = getattr(namespace_obj, k) result = function(*positional, **keywords) # Yield the results if isinstance(result, (GeneratorType, list, tuple)): # yield each line ASAP, convert CommandError message to a line for line in result: yield line else: # yield non-empty non-iterable result as a single line if result is not None: yield result wrappable_exceptions = [CommandError] wrappable_exceptions += getattr(function, ATTR_WRAPPED_EXCEPTIONS, []) try: result = _call() for line in result: yield line except tuple(wrappable_exceptions) as e: processor = getattr(function, ATTR_WRAPPED_EXCEPTIONS_PROCESSOR, lambda e: '{0.__class__.__name__}: {0}'.format(e)) errors_file.write(compat.text_type(processor(e))) errors_file.write('\n')
def confirm(action, default=None, skip=False): """A shortcut for typical confirmation prompt. :param action: a string describing the action, e.g. "Apply changes". A question mark will be appended. :param default: `bool` or `None`. Determines what happens when user hits :kbd:`Enter` without typing in a choice. If `True`, default choice is "yes". If `False`, it is "no". If `None` the prompt keeps reappearing until user types in a choice (not necessarily acceptable) or until the number of iteration reaches the limit. Default is `None`. :param skip: `bool`; if `True`, no interactive prompt is used and default choice is returned (useful for batch mode). Default is `False`. Usage:: def delete(key, silent=False): item = db.get(Item, args.key) if confirm('Delete '+item.title, default=True, skip=silent): item.delete() print('Item deleted.') else: print('Operation cancelled.') Returns `None` on `KeyboardInterrupt` event. """ MAX_ITERATIONS = 3 if skip: return default else: defaults = { None: ('y', 'n'), True: ('Y', 'n'), False: ('y', 'N'), } y, n = defaults[default] prompt = text_type('{action}? ({y}/{n})').format(**locals()) choice = None try: if default is None: cnt = 1 while not choice and cnt < MAX_ITERATIONS: choice = safe_input(prompt) cnt += 1 else: choice = safe_input(prompt) except KeyboardInterrupt: return None if choice in ('yes', 'y', 'Y'): return True if choice in ('no', 'n', 'N'): return False if default is not None: return default return None
def _execute_command(function, namespace_obj, errors_file, pre_call=None): """ Assumes that `function` is a callable. Tries different approaches to call it (with `namespace_obj` or with ordinary signature). Yields the results line by line. If :class:`~argh.exceptions.CommandError` is raised, its message is appended to the results (i.e. yielded by the generator as a string). All other exceptions propagate unless marked as wrappable by :func:`wrap_errors`. """ if pre_call: # XXX undocumented because I'm unsure if it's OK # Actually used in real projects: # * https://google.com/search?q=argh+dispatch+pre_call # * https://github.com/neithere/argh/issues/63 pre_call(namespace_obj) # the function is nested to catch certain exceptions (see below) def _call(): # Actually call the function if getattr(function, ATTR_EXPECTS_NAMESPACE_OBJECT, False): result = function(namespace_obj) else: # namespace -> dictionary _flat_key = lambda key: key.replace('-', '_') all_input = dict( (_flat_key(k), v) for k, v in vars(namespace_obj).items()) # filter the namespace variables so that only those expected # by the actual function will pass spec = get_arg_spec(function) positional = [all_input[k] for k in spec.args] kwonly = getattr(spec, 'kwonlyargs', []) keywords = dict((k, all_input[k]) for k in kwonly) # *args if spec.varargs: positional += getattr(namespace_obj, spec.varargs) # **kwargs varkw = getattr(spec, 'varkw', getattr(spec, 'keywords', [])) if varkw: not_kwargs = [DEST_FUNCTION] + spec.args + [spec.varargs ] + kwonly for k in vars(namespace_obj): if k.startswith('_') or k in not_kwargs: continue keywords[k] = getattr(namespace_obj, k) result = function(*positional, **keywords) # Yield the results if isinstance(result, (GeneratorType, list, tuple)): # yield each line ASAP, convert CommandError message to a line for line in result: yield line else: # yield non-empty non-iterable result as a single line if result is not None: yield result wrappable_exceptions = [CommandError] wrappable_exceptions += getattr(function, ATTR_WRAPPED_EXCEPTIONS, []) try: result = _call() for line in result: yield line except tuple(wrappable_exceptions) as e: processor = getattr(function, ATTR_WRAPPED_EXCEPTIONS_PROCESSOR, lambda e: '{0.__class__.__name__}: {0}'.format(e)) errors_file.write(compat.text_type(processor(e))) errors_file.write('\n')
def _execute_command(func, namespace_obj, pre_call=None): # noqa: C901 # noinspection SpellCheckingInspection """ Assumes that `function` is a callable. Tries different approaches to call it (with `namespace_obj` or with ordinary signature). Yields the results line by line. If :class:`~argh.exceptions.CommandError` is raised, its message is appended to the results (i.e. yielded by the generator as a string). All other exceptions propagate unless marked as wrappable by :func:`wrap_errors`. """ if pre_call: LOGGER.debug('running pre_call: %s', pre_call) pre_call(namespace_obj) # namespace -> dictionary def _flat_key(key): return key.replace('-', '_') # noinspection SpellCheckingInspection def _call(): # Actually call the function if getattr(func, ATTR_EXPECTS_NAMESPACE_OBJECT, False): result_ = func(namespace_obj) else: all_input = dict((_flat_key(k), v) for k, v in vars(namespace_obj).items()) # filter the namespace variables so that only those expected # by the actual function will pass spec = get_arg_spec(func) positional = [all_input[k] for k in spec.args] # noinspection SpellCheckingInspection kw_only = getattr(spec, 'kwonlyargs', []) keywords = dict((k, all_input[k]) for k in kw_only) # *args if spec.varargs: positional += getattr(namespace_obj, spec.varargs) # **kwargs varkw = getattr(spec, 'varkw', getattr(spec, 'keywords', [])) if varkw: not_kwargs = [DEST_FUNCTION] + spec.args + [spec.varargs] + kw_only for k in vars(namespace_obj): if k.startswith('_') or k in not_kwargs: continue keywords[k] = getattr(namespace_obj, k) result_ = func(*positional, **keywords) # Yield the results if isinstance(result_, (GeneratorType, list, tuple)): # yield each line ASAP, convert CommandError message to a line for line_ in result_: yield line_ else: # yield non-empty non-iterable result as a single line if result_ is not None: yield result_ # noinspection SpellCheckingInspection wrappable_exceptions = [CommandError, Exception] wrappable_exceptions += getattr(func, ATTR_WRAPPED_EXCEPTIONS, []) try: LOGGER.debug('running func: %s', func) result = _call() return '\n'.join(result) # pylint: disable=catching-non-exception except tuple(wrappable_exceptions) as exc: # pylint: disable=unnecessary-lambda processor = getattr( func, ATTR_WRAPPED_EXCEPTIONS_PROCESSOR, lambda exc_: '{0.__class__.__name__}: {0}'.format(exc_) ) LOGGER.error(compat.text_type(processor(exc))) LOGGER.exception(exc)