Beispiel #1
0
def smart_getter(obj, strict=False):
    '''Returns a smart getter for `obj`.

    If `obj` is a mapping, it returns the ``.get()`` method bound to the
    object `obj`, otherwise it returns a partial of ``getattr`` on `obj`.

    :param strict: Set this to True so that the returned getter checks that
                   keys/attrs exists.  If `strict` is True the getter may
                   raise a KeyError or an AttributeError.

    .. versionchanged:: 1.5.3 Added the parameter `strict`.

    '''
    from .types import is_mapping
    if is_mapping(obj):
        if not strict:
            return obj.get
        else:
            def _get(key, default=Unset):
                try:
                    return obj[key]
                except KeyError:
                    if default is Unset:
                        raise
                    else:
                        return default
            return _get
    else:
        if not strict:
            return lambda attr, default=None: getattr(obj, attr, default)
        else:
            def _partial(attr, default=Unset):
                try:
                    return getattr(obj, attr)
                except AttributeError:
                    if default is Unset:
                        raise
                    else:
                        return default
            return _partial
Beispiel #2
0
def smart_copy(*args, **kwargs):
    '''Copies the first apparition of attributes (or keys) from `sources` to
    `target`.

    :param sources: The objects from which to extract keys or attributes.

    :param target: The object to fill.

    :param defaults: Default values for the attributes to be copied as
                     explained below. Defaults to False.

    :type defaults: Either a bool, a dictionary, an iterable or a callable.

    Every `sources` and `target` are always positional arguments. There should
    be at least one source. `target` will always be the last positional
    argument.

    If `defaults` is a dictionary or an iterable then only the names provided
    by itering over `defaults` will be copied. If `defaults` is a dictionary,
    and one of its key is not found in any of the `sources`, then the value of
    the key in the dictionary is copied to `target` unless:

    - It's the value :class:`xoutil.types.Required` or an instance of Required.

    - An exception object

    - A sequence with is first value being a subclass of Exception. In which
      case :class:`xoutil.data.adapt_exception` is used.

    In these cases a KeyError is raised if the key is not found in the
    sources.

    If `default` is an iterable and a key is not found in any of the sources,
    None is copied to `target`.

    If `defaults` is a callable then it should receive one positional
    arguments for the current `attribute name` and several keyword arguments
    (we pass ``source``) and return either True or False if the attribute
    should be copied.

    If `defaults` is False (or None) only the attributes that do not start
    with a "_" are copied, if it's True all attributes are copied.

    When `target` is not a mapping only valid Python identifiers will be
    copied.

    Each `source` is considered a mapping if it's an instance of
    `collections.Mapping` or a `MappingProxyType`.

    The `target` is considered a mapping if it's an instance of
    `collections.MutableMapping`.

    :returns: `target`.

    .. versionchanged:: 1.7.0 `defaults` is now keyword only.

    '''
    from collections import MutableMapping
    from xoutil.types import is_collection, is_mapping, Required
    from xoutil.data import adapt_exception
    from xoutil.validators.identifiers import is_valid_identifier
    defaults = kwargs.pop('defaults', False)
    if kwargs:
        raise TypeError('smart_copy does not accept a "%s" keyword argument'
                        % kwargs.keys()[0])
    sources, target = args[:-1], args[-1]
    if not sources:
        raise TypeError('smart_copy requires at least one source')
    if isinstance(target, (bool, type(None), int, float, str_base)):
        raise TypeError('target should be a mutable object, not %s' %
                        type(target))
    if isinstance(target, MutableMapping):
        def setter(key, val):
            target[key] = val
    else:
        def setter(key, val):
            if is_valid_identifier(key):
                setattr(target, key, val)
    _mapping = is_mapping(defaults)
    if _mapping or is_collection(defaults):
        for key, val in ((key, get_first_of(sources, key, default=Unset))
                         for key in defaults):
            if val is Unset:
                if _mapping:
                    val = defaults.get(key, None)
                else:
                    val = None
                exc = adapt_exception(val, key=key)
                if exc or val is Required or isinstance(val, Required):
                    raise KeyError(key)
            setter(key, val)
    else:
        keys = []
        for source in sources:
            get = smart_getter(source)
            if is_mapping(source):
                items = (name for name in source)
            else:
                items = dir(source)
            for key in items:
                private = isinstance(key, str_base) and key.startswith('_')
                if (defaults is False or defaults is None) and private:
                    copy = False
                elif callable(defaults):
                    copy = defaults(key, source=source)
                else:
                    copy = True
                if key not in keys:
                    keys.append(key)
                    if copy:
                        setter(key, get(key))
    return target