Blame dnf/util.py

Packit 6f3914
# util.py
Packit 6f3914
# Basic dnf utils.
Packit 6f3914
#
Packit 6f3914
# Copyright (C) 2012-2016 Red Hat, Inc.
Packit 6f3914
#
Packit 6f3914
# This copyrighted material is made available to anyone wishing to use,
Packit 6f3914
# modify, copy, or redistribute it subject to the terms and conditions of
Packit 6f3914
# the GNU General Public License v.2, or (at your option) any later version.
Packit 6f3914
# This program is distributed in the hope that it will be useful, but WITHOUT
Packit 6f3914
# ANY WARRANTY expressed or implied, including the implied warranties of
Packit 6f3914
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
Packit 6f3914
# Public License for more details.  You should have received a copy of the
Packit 6f3914
# GNU General Public License along with this program; if not, write to the
Packit 6f3914
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit 6f3914
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
Packit 6f3914
# source code or documentation are not subject to the GNU General Public
Packit 6f3914
# License and may only be used or replicated with the express permission of
Packit 6f3914
# Red Hat, Inc.
Packit 6f3914
#
Packit 6f3914
Packit 6f3914
from __future__ import print_function
Packit 6f3914
from __future__ import absolute_import
Packit 6f3914
from __future__ import unicode_literals
Packit 6f3914
Packit 6f3914
from .pycomp import PY3, basestring
Packit 6f3914
from dnf.i18n import _, ucd
Packit 6f3914
from functools import reduce
Packit 6f3914
import argparse
Packit 6f3914
import dnf
Packit 6f3914
import dnf.callback
Packit 6f3914
import dnf.const
Packit 6f3914
import dnf.pycomp
Packit 6f3914
import errno
Packit 6f3914
import itertools
Packit 6f3914
import locale
Packit 6f3914
import logging
Packit 6f3914
import os
Packit 6f3914
import pwd
Packit 6f3914
import shutil
Packit 6f3914
import sys
Packit 6f3914
import tempfile
Packit 6f3914
import time
Packit 6f3914
import libdnf.repo
Packit 6f3914
Packit 6f3914
logger = logging.getLogger('dnf')
Packit 6f3914
Packit 6f3914
MAIN_PROG = argparse.ArgumentParser().prog if argparse.ArgumentParser().prog == "yum" else "dnf"
Packit 6f3914
MAIN_PROG_UPPER = MAIN_PROG.upper()
Packit 6f3914
Packit 6f3914
"""DNF Utilities."""
Packit 6f3914
Packit 6f3914
Packit 6f3914
def _parse_specs(namespace, values):
Packit 6f3914
    """
Packit 6f3914
    Categorize :param values list into packages, groups and filenames
Packit 6f3914
Packit 6f3914
    :param namespace: argparse.Namespace, where specs will be stored
Packit 6f3914
    :param values: list of specs, whether packages ('foo') or groups/modules ('@bar')
Packit 6f3914
                   or filenames ('*.rmp', 'http://*', ...)
Packit 6f3914
Packit 6f3914
    To access packages use: specs.pkg_specs,
Packit 6f3914
    to access groups use: specs.grp_specs,
Packit 6f3914
    to access filenames use: specs.filenames
Packit 6f3914
    """
Packit 6f3914
Packit 6f3914
    setattr(namespace, "filenames", [])
Packit 6f3914
    setattr(namespace, "grp_specs", [])
Packit 6f3914
    setattr(namespace, "pkg_specs", [])
Packit 6f3914
    tmp_set = set()
Packit 6f3914
    for value in values:
Packit 6f3914
        if value in tmp_set:
Packit 6f3914
            continue
Packit 6f3914
        tmp_set.add(value)
Packit 6f3914
        schemes = dnf.pycomp.urlparse.urlparse(value)[0]
Packit 6f3914
        if value.endswith('.rpm'):
Packit 6f3914
            namespace.filenames.append(value)
Packit 6f3914
        elif schemes and schemes in ('http', 'ftp', 'file', 'https'):
Packit 6f3914
            namespace.filenames.append(value)
Packit 6f3914
        elif value.startswith('@'):
Packit 6f3914
            namespace.grp_specs.append(value[1:])
Packit 6f3914
        else:
Packit 6f3914
            namespace.pkg_specs.append(value)
Packit 6f3914
Packit 6f3914
Packit 6f3914
def _urlopen_progress(url, conf, progress=None):
Packit 6f3914
    if progress is None:
Packit 6f3914
        progress = dnf.callback.NullDownloadProgress()
Packit 6f3914
    pload = dnf.repo.RemoteRPMPayload(url, conf, progress)
Packit 6f3914
    if os.path.exists(pload.local_path):
Packit 6f3914
        return pload.local_path
Packit 6f3914
    est_remote_size = sum([pload.download_size])
Packit 6f3914
    progress.start(1, est_remote_size)
Packit 6f3914
    targets = [pload._librepo_target()]
Packit 6f3914
    try:
Packit 6f3914
        libdnf.repo.PackageTarget.downloadPackages(libdnf.repo.VectorPPackageTarget(targets), True)
Packit 6f3914
    except RuntimeError as e:
Packit 6f3914
        if conf.strict:
Packit 6f3914
            raise IOError(str(e))
Packit 6f3914
        logger.error(str(e))
Packit 6f3914
    return pload.local_path
Packit 6f3914
Packit 6f3914
def _urlopen(url, conf=None, repo=None, mode='w+b', **kwargs):
Packit 6f3914
    """
Packit 6f3914
    Open the specified absolute url, return a file object
Packit 6f3914
    which respects proxy setting even for non-repo downloads
Packit 6f3914
    """
Packit 6f3914
    if PY3 and 'b' not in mode:
Packit 6f3914
        kwargs.setdefault('encoding', 'utf-8')
Packit 6f3914
    fo = tempfile.NamedTemporaryFile(mode, **kwargs)
Packit 6f3914
Packit 6f3914
    try:
Packit 6f3914
        if repo:
Packit 6f3914
            repo._repo.downloadUrl(url, fo.fileno())
Packit 6f3914
        else:
Packit 6f3914
            libdnf.repo.Downloader.downloadURL(conf._config if conf else None, url, fo.fileno())
Packit 6f3914
    except RuntimeError as e:
Packit 6f3914
        raise IOError(str(e))
Packit 6f3914
Packit 6f3914
    fo.seek(0)
Packit 6f3914
    return fo
Packit 6f3914
Packit 6f3914
def rtrim(s, r):
Packit 6f3914
    if s.endswith(r):
Packit 6f3914
        s = s[:-len(r)]
Packit 6f3914
    return s
Packit 6f3914
Packit 6f3914
Packit 6f3914
def am_i_root():
Packit 6f3914
    # used by ansible (lib/ansible/modules/packaging/os/dnf.py)
Packit 6f3914
    return os.geteuid() == 0
Packit 6f3914
Packit 6f3914
def clear_dir(path):
Packit 6f3914
    """Remove all files and dirs under `path`
Packit 6f3914
Packit 6f3914
    Also see rm_rf()
Packit 6f3914
Packit 6f3914
    """
Packit 6f3914
    for entry in os.listdir(path):
Packit 6f3914
        contained_path = os.path.join(path, entry)
Packit 6f3914
        rm_rf(contained_path)
Packit 6f3914
Packit 6f3914
def ensure_dir(dname):
Packit 6f3914
    # used by ansible (lib/ansible/modules/packaging/os/dnf.py)
Packit 6f3914
    try:
Packit 6f3914
        os.makedirs(dname, mode=0o755)
Packit 6f3914
    except OSError as e:
Packit 6f3914
        if e.errno != errno.EEXIST or not os.path.isdir(dname):
Packit 6f3914
            raise e
Packit 6f3914
Packit 6f3914
def empty(iterable):
Packit 6f3914
    try:
Packit 6f3914
        l = len(iterable)
Packit 6f3914
    except TypeError:
Packit 6f3914
        l = len(list(iterable))
Packit 6f3914
    return l == 0
Packit 6f3914
Packit 6f3914
def first(iterable):
Packit 6f3914
    """Returns the first item from an iterable or None if it has no elements."""
Packit 6f3914
    it = iter(iterable)
Packit 6f3914
    try:
Packit 6f3914
        return next(it)
Packit 6f3914
    except StopIteration:
Packit 6f3914
        return None
Packit 6f3914
Packit 6f3914
Packit 6f3914
def first_not_none(iterable):
Packit 6f3914
    it = iter(iterable)
Packit 6f3914
    try:
Packit 6f3914
        return next(item for item in it if item is not None)
Packit 6f3914
    except StopIteration:
Packit 6f3914
        return None
Packit 6f3914
Packit 6f3914
Packit 6f3914
def file_age(fn):
Packit 6f3914
    return time.time() - file_timestamp(fn)
Packit 6f3914
Packit 6f3914
def file_timestamp(fn):
Packit 6f3914
    return os.stat(fn).st_mtime
Packit 6f3914
Packit 6f3914
def get_effective_login():
Packit 6f3914
    try:
Packit 6f3914
        return pwd.getpwuid(os.geteuid())[0]
Packit 6f3914
    except KeyError:
Packit 6f3914
        return "UID: %s" % os.geteuid()
Packit 6f3914
Packit 6f3914
def get_in(dct, keys, not_found):
Packit 6f3914
    """Like dict.get() for nested dicts."""
Packit 6f3914
    for k in keys:
Packit 6f3914
        dct = dct.get(k)
Packit 6f3914
        if dct is None:
Packit 6f3914
            return not_found
Packit 6f3914
    return dct
Packit 6f3914
Packit 6f3914
def group_by_filter(fn, iterable):
Packit 6f3914
    def splitter(acc, item):
Packit 6f3914
        acc[not bool(fn(item))].append(item)
Packit 6f3914
        return acc
Packit 6f3914
    return reduce(splitter, iterable, ([], []))
Packit 6f3914
Packit 6f3914
def insert_if(item, iterable, condition):
Packit 6f3914
    """Insert an item into an iterable by a condition."""
Packit 6f3914
    for original_item in iterable:
Packit 6f3914
        if condition(original_item):
Packit 6f3914
            yield item
Packit 6f3914
        yield original_item
Packit 6f3914
Packit 6f3914
def is_exhausted(iterator):
Packit 6f3914
    """Test whether an iterator is exhausted."""
Packit 6f3914
    try:
Packit 6f3914
        next(iterator)
Packit 6f3914
    except StopIteration:
Packit 6f3914
        return True
Packit 6f3914
    else:
Packit 6f3914
        return False
Packit 6f3914
Packit 6f3914
def is_glob_pattern(pattern):
Packit 6f3914
    if is_string_type(pattern):
Packit 6f3914
        pattern = [pattern]
Packit 6f3914
    return (isinstance(pattern, list) and any(set(p) & set("*[?") for p in pattern))
Packit 6f3914
Packit 6f3914
def is_string_type(obj):
Packit 6f3914
    if PY3:
Packit 6f3914
        return isinstance(obj, str)
Packit 6f3914
    else:
Packit 6f3914
        return isinstance(obj, basestring)
Packit 6f3914
Packit 6f3914
def lazyattr(attrname):
Packit 6f3914
    """Decorator to get lazy attribute initialization.
Packit 6f3914
Packit 6f3914
    Composes with @property. Force reinitialization by deleting the <attrname>.
Packit 6f3914
    """
Packit 6f3914
    def get_decorated(fn):
Packit 6f3914
        def cached_getter(obj):
Packit 6f3914
            try:
Packit 6f3914
                return getattr(obj, attrname)
Packit 6f3914
            except AttributeError:
Packit 6f3914
                val = fn(obj)
Packit 6f3914
                setattr(obj, attrname, val)
Packit 6f3914
                return val
Packit 6f3914
        return cached_getter
Packit 6f3914
    return get_decorated
Packit 6f3914
Packit 6f3914
Packit 6f3914
def mapall(fn, *seq):
Packit 6f3914
    """Like functools.map(), but return a list instead of an iterator.
Packit 6f3914
Packit 6f3914
    This means all side effects of fn take place even without iterating the
Packit 6f3914
    result.
Packit 6f3914
Packit 6f3914
    """
Packit 6f3914
    return list(map(fn, *seq))
Packit 6f3914
Packit 6f3914
def normalize_time(timestamp):
Packit 6f3914
    """Convert time into locale aware datetime string object."""
Packit 6f3914
    t = time.strftime("%c", time.localtime(timestamp))
Packit 6f3914
    if not dnf.pycomp.PY3:
Packit 6f3914
        current_locale_setting = locale.getlocale()[1]
Packit 6f3914
        if current_locale_setting:
Packit 6f3914
            t = t.decode(current_locale_setting)
Packit 6f3914
    return t
Packit 6f3914
Packit 6f3914
def on_ac_power():
Packit 6f3914
    """Decide whether we are on line power.
Packit 6f3914
Packit 6f3914
    Returns True if we are on line power, False if not, None if it can not be
Packit 6f3914
    decided.
Packit 6f3914
Packit 6f3914
    """
Packit 6f3914
    try:
Packit 6f3914
        with open("/sys/class/power_supply/AC/online") as ac_status:
Packit 6f3914
            data = ac_status.read()
Packit 6f3914
            return int(data) == 1
Packit 6f3914
    except (IOError, ValueError):
Packit 6f3914
        return None
Packit 6f3914
Packit 6f3914
Packit 6f3914
def on_metered_connection():
Packit 6f3914
    """Decide whether we are on metered connection.
Packit 6f3914
Packit 6f3914
    Returns:
Packit 6f3914
      True: if on metered connection
Packit 6f3914
      False: if not
Packit 6f3914
      None: if it can not be decided
Packit 6f3914
    """
Packit 6f3914
    try:
Packit 6f3914
        import dbus
Packit 6f3914
    except ImportError:
Packit 6f3914
        return None
Packit 6f3914
    try:
Packit 6f3914
        bus = dbus.SystemBus()
Packit 6f3914
        proxy = bus.get_object("org.freedesktop.NetworkManager",
Packit 6f3914
                               "/org/freedesktop/NetworkManager")
Packit 6f3914
        iface = dbus.Interface(proxy, "org.freedesktop.DBus.Properties")
Packit 6f3914
        metered = iface.Get("org.freedesktop.NetworkManager", "Metered")
Packit 6f3914
    except dbus.DBusException:
Packit 6f3914
        return None
Packit 6f3914
    if metered == 0: # NM_METERED_UNKNOWN
Packit 6f3914
        return None
Packit 6f3914
    elif metered in (1, 3): # NM_METERED_YES, NM_METERED_GUESS_YES
Packit 6f3914
        return True
Packit 6f3914
    elif metered in (2, 4): # NM_METERED_NO, NM_METERED_GUESS_NO
Packit 6f3914
        return False
Packit 6f3914
    else: # Something undocumented (at least at this moment)
Packit 6f3914
        raise ValueError("Unknown value for metered property: %r", metered)
Packit 6f3914
Packit 6f3914
def partition(pred, iterable):
Packit 6f3914
    """Use a predicate to partition entries into false entries and true entries.
Packit 6f3914
Packit 6f3914
    Credit: Python library itertools' documentation.
Packit 6f3914
Packit 6f3914
    """
Packit 6f3914
    t1, t2 = itertools.tee(iterable)
Packit 6f3914
    return dnf.pycomp.filterfalse(pred, t1), filter(pred, t2)
Packit 6f3914
Packit 6f3914
def rm_rf(path):
Packit 6f3914
    try:
Packit 6f3914
        shutil.rmtree(path)
Packit 6f3914
    except OSError:
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
def split_by(iterable, condition):
Packit 6f3914
    """Split an iterable into tuples by a condition.
Packit 6f3914
Packit 6f3914
    Inserts a separator before each item which meets the condition and then
Packit 6f3914
    cuts the iterable by these separators.
Packit 6f3914
Packit 6f3914
    """
Packit 6f3914
    separator = object()  # A unique object.
Packit 6f3914
    # Create a function returning tuple of objects before the separator.
Packit 6f3914
    def next_subsequence(it):
Packit 6f3914
        return tuple(itertools.takewhile(lambda e: e != separator, it))
Packit 6f3914
Packit 6f3914
    # Mark each place where the condition is met by the separator.
Packit 6f3914
    marked = insert_if(separator, iterable, condition)
Packit 6f3914
Packit 6f3914
    # The 1st subsequence may be empty if the 1st item meets the condition.
Packit 6f3914
    yield next_subsequence(marked)
Packit 6f3914
Packit 6f3914
    while True:
Packit 6f3914
        subsequence = next_subsequence(marked)
Packit 6f3914
        if not subsequence:
Packit 6f3914
            break
Packit 6f3914
        yield subsequence
Packit 6f3914
Packit 6f3914
def strip_prefix(s, prefix):
Packit 6f3914
    if s.startswith(prefix):
Packit 6f3914
        return s[len(prefix):]
Packit 6f3914
    return None
Packit 6f3914
Packit 6f3914
Packit 6f3914
def touch(path, no_create=False):
Packit 6f3914
    """Create an empty file if it doesn't exist or bump it's timestamps.
Packit 6f3914
Packit 6f3914
    If no_create is True only bumps the timestamps.
Packit 6f3914
    """
Packit 6f3914
    if no_create or os.access(path, os.F_OK):
Packit 6f3914
        return os.utime(path, None)
Packit 6f3914
    with open(path, 'a'):
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
Packit 6f3914
def _terminal_messenger(tp='write', msg="", out=sys.stdout):
Packit 6f3914
    try:
Packit 6f3914
        if tp == 'write':
Packit 6f3914
            out.write(msg)
Packit 6f3914
        elif tp == 'flush':
Packit 6f3914
            out.flush()
Packit 6f3914
        elif tp == 'write_flush':
Packit 6f3914
            out.write(msg)
Packit 6f3914
            out.flush()
Packit 6f3914
        elif tp == 'print':
Packit 6f3914
            print(msg, file=out)
Packit 6f3914
        else:
Packit 6f3914
            raise ValueError('Unsupported type: ' + tp)
Packit 6f3914
    except IOError as e:
Packit 6f3914
        logger.critical('{}: {}'.format(type(e).__name__, ucd(e)))
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
Packit 6f3914
def _format_resolve_problems(resolve_problems):
Packit 6f3914
    """
Packit 6f3914
    Format string about problems in resolve
Packit 6f3914
Packit 6f3914
    :param resolve_problems: list with list of strings (output of goal.problem_rules())
Packit 6f3914
    :return: string
Packit 6f3914
    """
Packit 6f3914
    msg = ""
Packit 6f3914
    count_problems = (len(resolve_problems) > 1)
Packit 6f3914
    for i, rs in enumerate(resolve_problems, start=1):
Packit 6f3914
        if count_problems:
Packit 6f3914
            msg += "\n " + _("Problem") + " %d: " % i
Packit 6f3914
        else:
Packit 6f3914
            msg += "\n " + _("Problem") + ": "
Packit 6f3914
        msg += "\n  - ".join(rs)
Packit 6f3914
    return msg
Packit 6f3914
Packit 6f3914
Packit 6f3914
def _te_nevra(te):
Packit 6f3914
    nevra = te.N() + '-'
Packit 6f3914
    if te.E() is not None and te.E() != '0':
Packit 6f3914
        nevra += te.E() + ':'
Packit 6f3914
    return nevra + te.V() + '-' + te.R() + '.' + te.A()
Packit 6f3914
Packit 6f3914
Packit 6f3914
def _log_rpm_trans_with_swdb(rpm_transaction, swdb_transaction):
Packit 6f3914
    logger.debug("Logging transaction elements")
Packit 6f3914
    for rpm_el in rpm_transaction:
Packit 6f3914
        tsi = rpm_el.Key()
Packit 6f3914
        tsi_state = None
Packit 6f3914
        if tsi is not None:
Packit 6f3914
            tsi_state = tsi.state
Packit 6f3914
        msg = "RPM element: '{}', Key(): '{}', Key state: '{}', Failed() '{}': ".format(
Packit 6f3914
            _te_nevra(rpm_el), tsi, tsi_state, rpm_el.Failed())
Packit 6f3914
        logger.debug(msg)
Packit 6f3914
    for tsi in swdb_transaction:
Packit 6f3914
        msg = "SWDB element: '{}', State: '{}', Action: '{}', From repo: '{}', Reason: '{}', " \
Packit 6f3914
              "Get reason: '{}'".format(str(tsi), tsi.state, tsi.action, tsi.from_repo, tsi.reason,
Packit 6f3914
                                        tsi.get_reason())
Packit 6f3914
        logger.debug(msg)
Packit 6f3914
Packit 6f3914
Packit 6f3914
def _sync_rpm_trans_with_swdb(rpm_transaction, swdb_transaction):
Packit 6f3914
    revert_actions = {libdnf.transaction.TransactionItemAction_DOWNGRADED,
Packit 6f3914
                      libdnf.transaction.TransactionItemAction_OBSOLETED,
Packit 6f3914
                      libdnf.transaction.TransactionItemAction_REMOVE,
Packit 6f3914
                      libdnf.transaction.TransactionItemAction_UPGRADED,
Packit 6f3914
                      libdnf.transaction.TransactionItemAction_REINSTALLED}
Packit 6f3914
    cached_tsi = [tsi for tsi in swdb_transaction]
Packit 6f3914
    el_not_found = False
Packit 6f3914
    error = False
Packit 6f3914
    for rpm_el in rpm_transaction:
Packit 6f3914
        te_nevra = _te_nevra(rpm_el)
Packit 6f3914
        tsi = rpm_el.Key()
Packit 6f3914
        if tsi is None or not hasattr(tsi, "pkg"):
Packit 6f3914
            for tsi_candidate in cached_tsi:
Packit 6f3914
                if tsi_candidate.state != libdnf.transaction.TransactionItemState_UNKNOWN:
Packit 6f3914
                    continue
Packit 6f3914
                if tsi_candidate.action not in revert_actions:
Packit 6f3914
                    continue
Packit 6f3914
                if str(tsi_candidate) == te_nevra:
Packit 6f3914
                    tsi = tsi_candidate
Packit 6f3914
                    break
Packit 6f3914
        if tsi is None or not hasattr(tsi, "pkg"):
Packit 6f3914
            logger.critical(_("TransactionItem not found for key: {}").format(te_nevra))
Packit 6f3914
            el_not_found = True
Packit 6f3914
            continue
Packit 6f3914
        if rpm_el.Failed():
Packit 6f3914
            tsi.state = libdnf.transaction.TransactionItemState_ERROR
Packit 6f3914
            error = True
Packit 6f3914
        else:
Packit 6f3914
            tsi.state = libdnf.transaction.TransactionItemState_DONE
Packit 6f3914
    for tsi in cached_tsi:
Packit 6f3914
        if tsi.state == libdnf.transaction.TransactionItemState_UNKNOWN:
Packit 6f3914
            logger.critical(_("TransactionSWDBItem not found for key: {}").format(str(tsi)))
Packit 6f3914
            el_not_found = True
Packit 6f3914
    if error:
Packit 6f3914
        logger.debug(_('Errors occurred during transaction.'))
Packit 6f3914
    if el_not_found:
Packit 6f3914
        _log_rpm_trans_with_swdb(rpm_transaction, cached_tsi)
Packit 6f3914
Packit 6f3914
Packit 6f3914
class tmpdir(object):
Packit 6f3914
    # used by subscription-manager (src/dnf-plugins/product-id.py)
Packit 6f3914
    def __init__(self):
Packit 6f3914
        prefix = '%s-' % dnf.const.PREFIX
Packit 6f3914
        self.path = tempfile.mkdtemp(prefix=prefix)
Packit 6f3914
Packit 6f3914
    def __enter__(self):
Packit 6f3914
        return self.path
Packit 6f3914
Packit 6f3914
    def __exit__(self, exc_type, exc_value, traceback):
Packit 6f3914
        rm_rf(self.path)
Packit 6f3914
Packit 6f3914
class Bunch(dict):
Packit 6f3914
    """Dictionary with attribute accessing syntax.
Packit 6f3914
Packit 6f3914
    In DNF, prefer using this over dnf.yum.misc.GenericHolder.
Packit 6f3914
Packit 6f3914
    Credit: Alex Martelli, Doug Hudgeon
Packit 6f3914
Packit 6f3914
    """
Packit 6f3914
    def __init__(self, *args, **kwds):
Packit 6f3914
         super(Bunch, self).__init__(*args, **kwds)
Packit 6f3914
         self.__dict__ = self
Packit 6f3914
Packit 6f3914
    def __hash__(self):
Packit 6f3914
        return id(self)
Packit 6f3914
Packit 6f3914
Packit 6f3914
class MultiCallList(list):
Packit 6f3914
    def __init__(self, iterable):
Packit 6f3914
        super(MultiCallList, self).__init__()
Packit 6f3914
        self.extend(iterable)
Packit 6f3914
Packit 6f3914
    def __getattr__(self, what):
Packit 6f3914
        def fn(*args, **kwargs):
Packit 6f3914
            def call_what(v):
Packit 6f3914
                method = getattr(v, what)
Packit 6f3914
                return method(*args, **kwargs)
Packit 6f3914
            return list(map(call_what, self))
Packit 6f3914
        return fn
Packit 6f3914
Packit 6f3914
    def __setattr__(self, what, val):
Packit 6f3914
        def setter(item):
Packit 6f3914
            setattr(item, what, val)
Packit 6f3914
        return list(map(setter, self))