Blame bugzilla/base.py

Packit Service 4b33e2
# base.py - the base classes etc. for a Python interface to bugzilla
Packit Service 4b33e2
#
Packit Service 4b33e2
# Copyright (C) 2007, 2008, 2009, 2010 Red Hat Inc.
Packit Service 4b33e2
# Author: Will Woods <wwoods@redhat.com>
Packit Service 4b33e2
#
Packit Service 4b33e2
# This program is free software; you can redistribute it and/or modify it
Packit Service 4b33e2
# under the terms of the GNU General Public License as published by the
Packit Service 4b33e2
# Free Software Foundation; either version 2 of the License, or (at your
Packit Service 4b33e2
# option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
Packit Service 4b33e2
# the full text of the license.
Packit Service 4b33e2
Packit Service 4b33e2
import collections
Packit Service 4b33e2
import getpass
Packit Service 4b33e2
import locale
Packit Service 4b33e2
from logging import getLogger
Packit Service 4b33e2
import mimetypes
Packit Service 4b33e2
import os
Packit Service 4b33e2
import sys
Packit Service 4b33e2
Packit Service 4b33e2
from io import BytesIO
Packit Service 4b33e2
Packit Service 4b33e2
# pylint: disable=import-error
Packit Service 4b33e2
if sys.version_info[0] >= 3:
Packit Service 4b33e2
    # pylint: disable=no-name-in-module
Packit Service 4b33e2
    from configparser import ConfigParser
Packit Service 4b33e2
    from http.cookiejar import LoadError, MozillaCookieJar
Packit Service 4b33e2
    from urllib.parse import urlparse, parse_qsl
Packit Service 4b33e2
    from xmlrpc.client import Binary, Fault
Packit Service 4b33e2
else:
Packit Service 4b33e2
    from ConfigParser import SafeConfigParser as ConfigParser
Packit Service 4b33e2
    from cookielib import LoadError, MozillaCookieJar
Packit Service 4b33e2
    from urlparse import urlparse, parse_qsl
Packit Service 4b33e2
    from xmlrpclib import Binary, Fault
Packit Service 4b33e2
# pylint: enable=import-error
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
from .apiversion import __version__
Packit Service 4b33e2
from .bug import Bug, User
Packit Service 4b33e2
from .transport import BugzillaError, _BugzillaServerProxy, _RequestsTransport
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
log = getLogger(__name__)
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
def _nested_update(d, u):
Packit Service 4b33e2
    # Helper for nested dict update()
Packit Service 4b33e2
    # https://stackoverflow.com/questions/3232943/update-value-of-a-nested-dictionary-of-varying-depth
Packit Service 4b33e2
    for k, v in list(u.items()):
Packit Service 4b33e2
        if isinstance(v, collections.Mapping):
Packit Service 4b33e2
            d[k] = _nested_update(d.get(k, {}), v)
Packit Service 4b33e2
        else:
Packit Service 4b33e2
            d[k] = v
Packit Service 4b33e2
    return d
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
def _default_auth_location(filename):
Packit Service 4b33e2
    """
Packit Service 4b33e2
    Determine auth location for filename, like 'bugzillacookies'. If
Packit Service 4b33e2
    old style ~/.bugzillacookies exists, we use that, otherwise we
Packit Service 4b33e2
    use ~/.cache/python-bugzilla/bugzillacookies. Same for bugzillatoken
Packit Service 4b33e2
    """
Packit Service 4b33e2
    homepath = os.path.expanduser("~/.%s" % filename)
Packit Service 4b33e2
    xdgpath = os.path.expanduser("~/.cache/python-bugzilla/%s" % filename)
Packit Service 4b33e2
    if os.path.exists(xdgpath):
Packit Service 4b33e2
        return xdgpath
Packit Service 4b33e2
    if os.path.exists(homepath):
Packit Service 4b33e2
        return homepath
Packit Service 4b33e2
Packit Service 4b33e2
    if not os.path.exists(os.path.dirname(xdgpath)):
Packit Service 4b33e2
        os.makedirs(os.path.dirname(xdgpath), 0o700)
Packit Service 4b33e2
    return xdgpath
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
def _build_cookiejar(cookiefile):
Packit Service 4b33e2
    cj = MozillaCookieJar(cookiefile)
Packit Service 4b33e2
    if cookiefile is None:
Packit Service 4b33e2
        return cj
Packit Service 4b33e2
    if not os.path.exists(cookiefile):
Packit Service 4b33e2
        # Make sure a new file has correct permissions
Packit Service 4b33e2
        open(cookiefile, 'a').close()
Packit Service 4b33e2
        os.chmod(cookiefile, 0o600)
Packit Service 4b33e2
        cj.save()
Packit Service 4b33e2
        return cj
Packit Service 4b33e2
Packit Service 4b33e2
    try:
Packit Service 4b33e2
        cj.load()
Packit Service 4b33e2
        return cj
Packit Service 4b33e2
    except LoadError:
Packit Service 4b33e2
        raise BugzillaError("cookiefile=%s not in Mozilla format" %
Packit Service 4b33e2
                            cookiefile)
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
_default_configpaths = [
Packit Service 4b33e2
    '/etc/bugzillarc',
Packit Service 4b33e2
    '~/.bugzillarc',
Packit Service 4b33e2
    '~/.config/python-bugzilla/bugzillarc',
Packit Service 4b33e2
]
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
def _open_bugzillarc(configpaths=-1):
Packit Service 4b33e2
    if configpaths == -1:
Packit Service 4b33e2
        configpaths = _default_configpaths[:]
Packit Service 4b33e2
Packit Service 4b33e2
    # pylint: disable=protected-access
Packit Service 4b33e2
    configpaths = [os.path.expanduser(p) for p in
Packit Service 4b33e2
                   Bugzilla._listify(configpaths)]
Packit Service 4b33e2
    # pylint: enable=protected-access
Packit Service 4b33e2
    cfg = ConfigParser()
Packit Service 4b33e2
    read_files = cfg.read(configpaths)
Packit Service 4b33e2
    if not read_files:
Packit Service 4b33e2
        return
Packit Service 4b33e2
Packit Service 4b33e2
    log.info("Found bugzillarc files: %s", read_files)
Packit Service 4b33e2
    return cfg
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
class _FieldAlias(object):
Packit Service 4b33e2
    """
Packit Service 4b33e2
    Track API attribute names that differ from what we expose in users.
Packit Service 4b33e2
Packit Service 4b33e2
    For example, originally 'short_desc' was the name of the property that
Packit Service 4b33e2
    maps to 'summary' on modern bugzilla. We want pre-existing API users
Packit Service 4b33e2
    to be able to continue to use Bug.short_desc, and
Packit Service 4b33e2
    query({"short_desc": "foo"}). This class tracks that mapping.
Packit Service 4b33e2
Packit Service 4b33e2
    @oldname: The old attribute name
Packit Service 4b33e2
    @newname: The modern attribute name
Packit Service 4b33e2
    @is_api: If True, use this mapping for values sent to the xmlrpc API
Packit Service 4b33e2
        (like the query example)
Packit Service 4b33e2
    @is_bug: If True, use this mapping for Bug attribute names.
Packit Service 4b33e2
    """
Packit Service 4b33e2
    def __init__(self, newname, oldname, is_api=True, is_bug=True):
Packit Service 4b33e2
        self.newname = newname
Packit Service 4b33e2
        self.oldname = oldname
Packit Service 4b33e2
        self.is_api = is_api
Packit Service 4b33e2
        self.is_bug = is_bug
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
class _BugzillaAPICache(object):
Packit Service 4b33e2
    """
Packit Service 4b33e2
    Helper class that holds cached API results for things like products,
Packit Service 4b33e2
    components, etc.
Packit Service 4b33e2
    """
Packit Service 4b33e2
    def __init__(self):
Packit Service 4b33e2
        self.products = []
Packit Service 4b33e2
        self.component_names = {}
Packit Service 4b33e2
        self.bugfields = []
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
class Bugzilla(object):
Packit Service 4b33e2
    """
Packit Service 4b33e2
    The main API object. Connects to a bugzilla instance over XMLRPC, and
Packit Service 4b33e2
    provides wrapper functions to simplify dealing with API calls.
Packit Service 4b33e2
Packit Service 4b33e2
    The most common invocation here will just be with just a URL:
Packit Service 4b33e2
Packit Service 4b33e2
        bzapi = Bugzilla("http://bugzilla.example.com")
Packit Service 4b33e2
Packit Service 4b33e2
    If you have previously logged into that URL, and have cached login
Packit Service 4b33e2
    cookies/tokens, you will automatically be logged in. Otherwise to
Packit Service 4b33e2
    log in, you can either pass auth options to __init__, or call a login
Packit Service 4b33e2
    helper like interactive_login().
Packit Service 4b33e2
Packit Service 4b33e2
    If you are not logged in, you won be able to access restricted data like
Packit Service 4b33e2
    user email, or perform write actions like bug create/update. But simple
Packit Service 4b33e2
    querys will work correctly.
Packit Service 4b33e2
Packit Service 4b33e2
    If you are unsure if you are logged in, you can check the .logged_in
Packit Service 4b33e2
    property.
Packit Service 4b33e2
Packit Service 4b33e2
    Another way to specify auth credentials is via a 'bugzillarc' file.
Packit Service 4b33e2
    See readconfig() documentation for details.
Packit Service 4b33e2
    """
Packit Service 4b33e2
Packit Service 4b33e2
    # bugzilla version that the class is targeting. filled in by
Packit Service 4b33e2
    # subclasses
Packit Service 4b33e2
    bz_ver_major = 0
Packit Service 4b33e2
    bz_ver_minor = 0
Packit Service 4b33e2
Packit Service 4b33e2
    @staticmethod
Packit Service 4b33e2
    def url_to_query(url):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Given a big huge bugzilla query URL, returns a query dict that can
Packit Service 4b33e2
        be passed along to the Bugzilla.query() method.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        q = {}
Packit Service 4b33e2
Packit Service 4b33e2
        # pylint: disable=unpacking-non-sequence
Packit Service 4b33e2
        (ignore, ignore, path,
Packit Service 4b33e2
         ignore, query, ignore) = urlparse(url)
Packit Service 4b33e2
Packit Service 4b33e2
        base = os.path.basename(path)
Packit Service 4b33e2
        if base not in ('buglist.cgi', 'query.cgi'):
Packit Service 4b33e2
            return {}
Packit Service 4b33e2
Packit Service 4b33e2
        for (k, v) in parse_qsl(query):
Packit Service 4b33e2
            if k not in q:
Packit Service 4b33e2
                q[k] = v
Packit Service 4b33e2
            elif isinstance(q[k], list):
Packit Service 4b33e2
                q[k].append(v)
Packit Service 4b33e2
            else:
Packit Service 4b33e2
                oldv = q[k]
Packit Service 4b33e2
                q[k] = [oldv, v]
Packit Service 4b33e2
Packit Service 4b33e2
        # Handle saved searches
Packit Service 4b33e2
        if base == "buglist.cgi" and "namedcmd" in q and "sharer_id" in q:
Packit Service 4b33e2
            q = {
Packit Service 4b33e2
                "sharer_id": q["sharer_id"],
Packit Service 4b33e2
                "savedsearch": q["namedcmd"],
Packit Service 4b33e2
            }
Packit Service 4b33e2
Packit Service 4b33e2
        return q
Packit Service 4b33e2
Packit Service 4b33e2
    @staticmethod
Packit Service 4b33e2
    def fix_url(url):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Turn passed url into a bugzilla XMLRPC web url
Packit Service 4b33e2
        """
Packit Service 4b33e2
        if '://' not in url:
Packit Service 4b33e2
            log.debug('No scheme given for url, assuming https')
Packit Service 4b33e2
            url = 'https://' + url
Packit Service 4b33e2
        if url.count('/') < 3:
Packit Service 4b33e2
            log.debug('No path given for url, assuming /xmlrpc.cgi')
Packit Service 4b33e2
            url = url + '/xmlrpc.cgi'
Packit Service 4b33e2
        return url
Packit Service 4b33e2
Packit Service 4b33e2
    @staticmethod
Packit Service 4b33e2
    def _listify(val):
Packit Service 4b33e2
        if val is None:
Packit Service 4b33e2
            return val
Packit Service 4b33e2
        if isinstance(val, list):
Packit Service 4b33e2
            return val
Packit Service 4b33e2
        return [val]
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    def __init__(self, url=-1, user=None, password=None, cookiefile=-1,
Packit Service 4b33e2
                 sslverify=True, tokenfile=-1, use_creds=True, api_key=None,
Packit Service 4b33e2
                 cert=None):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        :param url: The bugzilla instance URL, which we will connect
Packit Service 4b33e2
            to immediately. Most users will want to specify this at
Packit Service 4b33e2
            __init__ time, but you can defer connecting by passing
Packit Service 4b33e2
            url=None and calling connect(URL) manually
Packit Service 4b33e2
        :param user: optional username to connect with
Packit Service 4b33e2
        :param password: optional password for the connecting user
Packit Service 4b33e2
        :param cert: optional certificate file for client side certificate
Packit Service 4b33e2
            authentication
Packit Service 4b33e2
        :param cookiefile: Location to cache the login session cookies so you
Packit Service 4b33e2
            don't have to keep specifying username/password. Bugzilla 5+ will
Packit Service 4b33e2
            use tokens instead of cookies.
Packit Service 4b33e2
            If -1, use the default path. If None, don't use or save
Packit Service 4b33e2
            any cookiefile.
Packit Service 4b33e2
        :param sslverify: Set this to False to skip SSL hostname and CA
Packit Service 4b33e2
            validation checks, like out of date certificate
Packit Service 4b33e2
        :param tokenfile: Location to cache the API login token so youi
Packit Service 4b33e2
            don't have to keep specifying username/password.
Packit Service 4b33e2
            If -1, use the default path. If None, don't use
Packit Service 4b33e2
            or save any tokenfile.
Packit Service 4b33e2
        :param use_creds: If False, this disables cookiefile, tokenfile,
Packit Service 4b33e2
            and any bugzillarc reading. This overwrites any tokenfile
Packit Service 4b33e2
            or cookiefile settings
Packit Service 4b33e2
        :param sslverify: Maps to 'requests' sslverify parameter. Set to
Packit Service 4b33e2
            False to disable SSL verification, but it can also be a path
Packit Service 4b33e2
            to file or directory for custom certs.
Packit Service 4b33e2
        :param api_key: A bugzilla
Packit Service 4b33e2
        """
Packit Service 4b33e2
        if url == -1:
Packit Service 4b33e2
            raise TypeError("Specify a valid bugzilla url, or pass url=None")
Packit Service 4b33e2
Packit Service 4b33e2
        # Settings the user might want to tweak
Packit Service 4b33e2
        self.user = user or ''
Packit Service 4b33e2
        self.password = password or ''
Packit Service 4b33e2
        self.api_key = api_key
Packit Service 4b33e2
        self.cert = cert or ''
Packit Service 4b33e2
        self.url = ''
Packit Service 4b33e2
Packit Service 4b33e2
        self._proxy = None
Packit Service 4b33e2
        self._transport = None
Packit Service 4b33e2
        self._cookiejar = None
Packit Service 4b33e2
        self._sslverify = sslverify
Packit Service 4b33e2
        self._cache = _BugzillaAPICache()
Packit Service 4b33e2
        self._bug_autorefresh = False
Packit Service 4b33e2
Packit Service 4b33e2
        self._field_aliases = []
Packit Service 4b33e2
        self._init_field_aliases()
Packit Service 4b33e2
Packit Service 4b33e2
        self.configpath = _default_configpaths[:]
Packit Service 4b33e2
        if not use_creds:
Packit Service 4b33e2
            cookiefile = None
Packit Service 4b33e2
            tokenfile = None
Packit Service 4b33e2
            self.configpath = []
Packit Service 4b33e2
Packit Service 4b33e2
        if cookiefile == -1:
Packit Service 4b33e2
            cookiefile = _default_auth_location("bugzillacookies")
Packit Service 4b33e2
        if tokenfile == -1:
Packit Service 4b33e2
            tokenfile = _default_auth_location("bugzillatoken")
Packit Service 4b33e2
        log.debug("Using tokenfile=%s", tokenfile)
Packit Service 4b33e2
        self.cookiefile = cookiefile
Packit Service 4b33e2
        self.tokenfile = tokenfile
Packit Service 4b33e2
Packit Service 4b33e2
        if url:
Packit Service 4b33e2
            self.connect(url)
Packit Service 4b33e2
            self._init_class_from_url()
Packit Service 4b33e2
        self._init_class_state()
Packit Service 4b33e2
Packit Service 4b33e2
    def _init_class_from_url(self):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Detect if we should use RHBugzilla class, and if so, set it
Packit Service 4b33e2
        """
Packit Service 4b33e2
        from bugzilla import RHBugzilla
Packit Service 4b33e2
        if isinstance(self, RHBugzilla):
Packit Service 4b33e2
            return
Packit Service 4b33e2
Packit Service 4b33e2
        c = None
Packit Service 4b33e2
        if "bugzilla.redhat.com" in self.url:
Packit Service 4b33e2
            log.info("Using RHBugzilla for URL containing bugzilla.redhat.com")
Packit Service 4b33e2
            c = RHBugzilla
Packit Service 4b33e2
        else:
Packit Service 4b33e2
            try:
Packit Service 4b33e2
                extensions = self._proxy.Bugzilla.extensions()
Packit Service 4b33e2
                if "RedHat" in extensions.get('extensions', {}):
Packit Service 4b33e2
                    log.info("Found RedHat bugzilla extension, "
Packit Service 4b33e2
                        "using RHBugzilla")
Packit Service 4b33e2
                    c = RHBugzilla
Packit Service 4b33e2
            except Fault:
Packit Service 4b33e2
                log.debug("Failed to fetch bugzilla extensions", exc_info=True)
Packit Service 4b33e2
Packit Service 4b33e2
        if not c:
Packit Service 4b33e2
            return
Packit Service 4b33e2
Packit Service 4b33e2
        self.__class__ = c
Packit Service 4b33e2
Packit Service 4b33e2
    def _init_class_state(self):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Hook for subclasses to do any __init__ time setup
Packit Service 4b33e2
        """
Packit Service 4b33e2
        pass
Packit Service 4b33e2
Packit Service 4b33e2
    def _init_field_aliases(self):
Packit Service 4b33e2
        # List of field aliases. Maps old style RHBZ parameter
Packit Service 4b33e2
        # names to actual upstream values. Used for createbug() and
Packit Service 4b33e2
        # query include_fields at least.
Packit Service 4b33e2
        self._add_field_alias('summary', 'short_desc')
Packit Service 4b33e2
        self._add_field_alias('description', 'comment')
Packit Service 4b33e2
        self._add_field_alias('platform', 'rep_platform')
Packit Service 4b33e2
        self._add_field_alias('severity', 'bug_severity')
Packit Service 4b33e2
        self._add_field_alias('status', 'bug_status')
Packit Service 4b33e2
        self._add_field_alias('id', 'bug_id')
Packit Service 4b33e2
        self._add_field_alias('blocks', 'blockedby')
Packit Service 4b33e2
        self._add_field_alias('blocks', 'blocked')
Packit Service 4b33e2
        self._add_field_alias('depends_on', 'dependson')
Packit Service 4b33e2
        self._add_field_alias('creator', 'reporter')
Packit Service 4b33e2
        self._add_field_alias('url', 'bug_file_loc')
Packit Service 4b33e2
        self._add_field_alias('dupe_of', 'dupe_id')
Packit Service 4b33e2
        self._add_field_alias('dupe_of', 'dup_id')
Packit Service 4b33e2
        self._add_field_alias('comments', 'longdescs')
Packit Service 4b33e2
        self._add_field_alias('creation_time', 'opendate')
Packit Service 4b33e2
        self._add_field_alias('creation_time', 'creation_ts')
Packit Service 4b33e2
        self._add_field_alias('whiteboard', 'status_whiteboard')
Packit Service 4b33e2
        self._add_field_alias('last_change_time', 'delta_ts')
Packit Service 4b33e2
Packit Service 4b33e2
    def _get_user_agent(self):
Packit Service 4b33e2
        return 'python-bugzilla/%s' % __version__
Packit Service 4b33e2
    user_agent = property(_get_user_agent)
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    ###################
Packit Service 4b33e2
    # Private helpers #
Packit Service 4b33e2
    ###################
Packit Service 4b33e2
Packit Service 4b33e2
    def _check_version(self, major, minor):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Check if the detected bugzilla version is >= passed major/minor pair.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        if major < self.bz_ver_major:
Packit Service 4b33e2
            return True
Packit Service 4b33e2
        if (major == self.bz_ver_major and minor <= self.bz_ver_minor):
Packit Service 4b33e2
            return True
Packit Service 4b33e2
        return False
Packit Service 4b33e2
Packit Service 4b33e2
    def _add_field_alias(self, *args, **kwargs):
Packit Service 4b33e2
        self._field_aliases.append(_FieldAlias(*args, **kwargs))
Packit Service 4b33e2
Packit Service 4b33e2
    def _get_bug_aliases(self):
Packit Service 4b33e2
        return [(f.newname, f.oldname)
Packit Service 4b33e2
                for f in self._field_aliases if f.is_bug]
Packit Service 4b33e2
Packit Service 4b33e2
    def _get_api_aliases(self):
Packit Service 4b33e2
        return [(f.newname, f.oldname)
Packit Service 4b33e2
                for f in self._field_aliases if f.is_api]
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    ###################
Packit Service 4b33e2
    # Cookie handling #
Packit Service 4b33e2
    ###################
Packit Service 4b33e2
Packit Service 4b33e2
    def _getcookiefile(self):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        cookiefile is the file that bugzilla session cookies are loaded
Packit Service 4b33e2
        and saved from.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        return self._cookiejar.filename
Packit Service 4b33e2
Packit Service 4b33e2
    def _delcookiefile(self):
Packit Service 4b33e2
        self._cookiejar = None
Packit Service 4b33e2
Packit Service 4b33e2
    def _setcookiefile(self, cookiefile):
Packit Service 4b33e2
        if (self._cookiejar and cookiefile == self._cookiejar.filename):
Packit Service 4b33e2
            return
Packit Service 4b33e2
Packit Service 4b33e2
        if self._proxy is not None:
Packit Service 4b33e2
            raise RuntimeError("Can't set cookies with an open connection, "
Packit Service 4b33e2
                               "disconnect() first.")
Packit Service 4b33e2
Packit Service 4b33e2
        log.debug("Using cookiefile=%s", cookiefile)
Packit Service 4b33e2
        self._cookiejar = _build_cookiejar(cookiefile)
Packit Service 4b33e2
Packit Service 4b33e2
    cookiefile = property(_getcookiefile, _setcookiefile, _delcookiefile)
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    #############################
Packit Service 4b33e2
    # Login/connection handling #
Packit Service 4b33e2
    #############################
Packit Service 4b33e2
Packit Service 4b33e2
    def readconfig(self, configpath=None):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        :param configpath: Optional bugzillarc path to read, instead of
Packit Service 4b33e2
            the default list.
Packit Service 4b33e2
Packit Service 4b33e2
        This function is called automatically from Bugzilla connect(), which
Packit Service 4b33e2
        is called at __init__ if a URL is passed. Calling it manually is
Packit Service 4b33e2
        just for passing in a non-standard configpath.
Packit Service 4b33e2
Packit Service 4b33e2
        The locations for the bugzillarc file are preferred in this order:
Packit Service 4b33e2
Packit Service 4b33e2
            ~/.config/python-bugzilla/bugzillarc
Packit Service 4b33e2
            ~/.bugzillarc
Packit Service 4b33e2
            /etc/bugzillarc
Packit Service 4b33e2
Packit Service 4b33e2
        It has content like:
Packit Service 4b33e2
          [bugzilla.yoursite.com]
Packit Service 4b33e2
          user = username
Packit Service 4b33e2
          password = password
Packit Service 4b33e2
        Or
Packit Service 4b33e2
          [bugzilla.yoursite.com]
Packit Service 4b33e2
          api_key = key
Packit Service 4b33e2
Packit Service 4b33e2
        The file can have multiple sections for different bugzilla instances.
Packit Service 4b33e2
        A 'url' field in the [DEFAULT] section can be used to set a default
Packit Service 4b33e2
        URL for the bugzilla command line tool.
Packit Service 4b33e2
Packit Service 4b33e2
        Be sure to set appropriate permissions on bugzillarc if you choose to
Packit Service 4b33e2
        store your password in it!
Packit Service 4b33e2
        """
Packit Service 4b33e2
        cfg = _open_bugzillarc(configpath or self.configpath)
Packit Service 4b33e2
        if not cfg:
Packit Service 4b33e2
            return
Packit Service 4b33e2
Packit Service 4b33e2
        section = ""
Packit Service 4b33e2
        log.debug("bugzillarc: Searching for config section matching %s",
Packit Service 4b33e2
            self.url)
Packit Service 4b33e2
        for s in sorted(cfg.sections()):
Packit Service 4b33e2
            # Substring match - prefer the longest match found
Packit Service 4b33e2
            if s in self.url:
Packit Service 4b33e2
                log.debug("bugzillarc: Found matching section: %s", s)
Packit Service 4b33e2
                section = s
Packit Service 4b33e2
Packit Service 4b33e2
        if not section:
Packit Service 4b33e2
            log.debug("bugzillarc: No section found")
Packit Service 4b33e2
            return
Packit Service 4b33e2
Packit Service 4b33e2
        for key, val in cfg.items(section):
Packit Service 4b33e2
            if key == "api_key":
Packit Service 4b33e2
                log.debug("bugzillarc: setting api_key")
Packit Service 4b33e2
                self.api_key = val
Packit Service 4b33e2
            elif key == "user":
Packit Service 4b33e2
                log.debug("bugzillarc: setting user=%s", val)
Packit Service 4b33e2
                self.user = val
Packit Service 4b33e2
            elif key == "password":
Packit Service 4b33e2
                log.debug("bugzillarc: setting password")
Packit Service 4b33e2
                self.password = val
Packit Service 4b33e2
            elif key == "cert":
Packit Service 4b33e2
                log.debug("bugzillarc: setting cert")
Packit Service 4b33e2
                self.cert = val
Packit Service 4b33e2
            else:
Packit Service 4b33e2
                log.debug("bugzillarc: unknown key=%s", key)
Packit Service 4b33e2
Packit Service 4b33e2
    def _set_bz_version(self, version):
Packit Service 4b33e2
        try:
Packit Service 4b33e2
            self.bz_ver_major, self.bz_ver_minor = [
Packit Service 4b33e2
                int(i) for i in version.split(".")[0:2]]
Packit Service 4b33e2
        except Exception:
Packit Service 4b33e2
            log.debug("version doesn't match expected format X.Y.Z, "
Packit Service 4b33e2
                    "assuming 5.0", exc_info=True)
Packit Service 4b33e2
            self.bz_ver_major = 5
Packit Service 4b33e2
            self.bz_ver_minor = 0
Packit Service 4b33e2
Packit Service 4b33e2
    def connect(self, url=None):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Connect to the bugzilla instance with the given url. This is
Packit Service 4b33e2
        called by __init__ if a URL is passed. Or it can be called manually
Packit Service 4b33e2
        at any time with a passed URL.
Packit Service 4b33e2
Packit Service 4b33e2
        This will also read any available config files (see readconfig()),
Packit Service 4b33e2
        which may set 'user' and 'password', and others.
Packit Service 4b33e2
Packit Service 4b33e2
        If 'user' and 'password' are both set, we'll run login(). Otherwise
Packit Service 4b33e2
        you'll have to login() yourself before some methods will work.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        if self._transport:
Packit Service 4b33e2
            self.disconnect()
Packit Service 4b33e2
Packit Service 4b33e2
        if url is None and self.url:
Packit Service 4b33e2
            url = self.url
Packit Service 4b33e2
        url = self.fix_url(url)
Packit Service 4b33e2
Packit Service 4b33e2
        self._transport = _RequestsTransport(
Packit Service 4b33e2
            url, self._cookiejar, sslverify=self._sslverify, cert=self.cert)
Packit Service 4b33e2
        self._transport.user_agent = self.user_agent
Packit Service 4b33e2
        self._proxy = _BugzillaServerProxy(url, self.tokenfile,
Packit Service 4b33e2
            self._transport)
Packit Service 4b33e2
Packit Service 4b33e2
        self.url = url
Packit Service 4b33e2
        # we've changed URLs - reload config
Packit Service 4b33e2
        self.readconfig()
Packit Service 4b33e2
Packit Service 4b33e2
        if (self.user and self.password):
Packit Service 4b33e2
            log.info("user and password present - doing login()")
Packit Service 4b33e2
            self.login()
Packit Service 4b33e2
Packit Service 4b33e2
        if self.api_key:
Packit Service 4b33e2
            log.debug("using API key")
Packit Service 4b33e2
            self._proxy.use_api_key(self.api_key)
Packit Service 4b33e2
Packit Service 4b33e2
        version = self._proxy.Bugzilla.version()["version"]
Packit Service 4b33e2
        log.debug("Bugzilla version string: %s", version)
Packit Service 4b33e2
        self._set_bz_version(version)
Packit Service 4b33e2
Packit Service 4b33e2
    def disconnect(self):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Disconnect from the given bugzilla instance.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        self._proxy = None
Packit Service 4b33e2
        self._transport = None
Packit Service 4b33e2
        self._cache = _BugzillaAPICache()
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    def _login(self, user, password):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Backend login method for Bugzilla3
Packit Service 4b33e2
        """
Packit Service 4b33e2
        return self._proxy.User.login({'login': user, 'password': password})
Packit Service 4b33e2
Packit Service 4b33e2
    def _logout(self):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Backend login method for Bugzilla3
Packit Service 4b33e2
        """
Packit Service 4b33e2
        return self._proxy.User.logout()
Packit Service 4b33e2
Packit Service 4b33e2
    def login(self, user=None, password=None):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Attempt to log in using the given username and password. Subsequent
Packit Service 4b33e2
        method calls will use this username and password. Returns False if
Packit Service 4b33e2
        login fails, otherwise returns some kind of login info - typically
Packit Service 4b33e2
        either a numeric userid, or a dict of user info.
Packit Service 4b33e2
Packit Service 4b33e2
        If user is not set, the value of Bugzilla.user will be used. If *that*
Packit Service 4b33e2
        is not set, ValueError will be raised. If login fails, BugzillaError
Packit Service 4b33e2
        will be raised.
Packit Service 4b33e2
Packit Service 4b33e2
        This method will be called implicitly at the end of connect() if user
Packit Service 4b33e2
        and password are both set. So under most circumstances you won't need
Packit Service 4b33e2
        to call this yourself.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        if self.api_key:
Packit Service 4b33e2
            raise ValueError("cannot login when using an API key")
Packit Service 4b33e2
Packit Service 4b33e2
        if user:
Packit Service 4b33e2
            self.user = user
Packit Service 4b33e2
        if password:
Packit Service 4b33e2
            self.password = password
Packit Service 4b33e2
Packit Service 4b33e2
        if not self.user:
Packit Service 4b33e2
            raise ValueError("missing username")
Packit Service 4b33e2
        if not self.password:
Packit Service 4b33e2
            raise ValueError("missing password")
Packit Service 4b33e2
Packit Service 4b33e2
        try:
Packit Service 4b33e2
            ret = self._login(self.user, self.password)
Packit Service 4b33e2
            self.password = ''
Packit Service 4b33e2
            log.info("login successful for user=%s", self.user)
Packit Service 4b33e2
            return ret
Packit Service 4b33e2
        except Fault as e:
Packit Service 4b33e2
            raise BugzillaError("Login failed: %s" % str(e.faultString))
Packit Service 4b33e2
Packit Service 4b33e2
    def interactive_login(self, user=None, password=None, force=False):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Helper method to handle login for this bugzilla instance.
Packit Service 4b33e2
Packit Service 4b33e2
        :param user: bugzilla username. If not specified, prompt for it.
Packit Service 4b33e2
        :param password: bugzilla password. If not specified, prompt for it.
Packit Service 4b33e2
        :param force: Unused
Packit Service 4b33e2
        """
Packit Service 4b33e2
        ignore = force
Packit Service 4b33e2
        log.debug('Calling interactive_login')
Packit Service 4b33e2
Packit Service 4b33e2
        if not user:
Packit Service 4b33e2
            sys.stdout.write('Bugzilla Username: ')
Packit Service 4b33e2
            sys.stdout.flush()
Packit Service 4b33e2
            user = sys.stdin.readline().strip()
Packit Service 4b33e2
        if not password:
Packit Service 4b33e2
            password = getpass.getpass('Bugzilla Password: ')
Packit Service 4b33e2
Packit Service 4b33e2
        log.info('Logging in... ')
Packit Service 4b33e2
        self.login(user, password)
Packit Service 4b33e2
        log.info('Authorization cookie received.')
Packit Service 4b33e2
Packit Service 4b33e2
    def logout(self):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Log out of bugzilla. Drops server connection and user info, and
Packit Service 4b33e2
        destroys authentication cookies.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        self._logout()
Packit Service 4b33e2
        self.disconnect()
Packit Service 4b33e2
        self.user = ''
Packit Service 4b33e2
        self.password = ''
Packit Service 4b33e2
Packit Service 4b33e2
    @property
Packit Service 4b33e2
    def logged_in(self):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        This is True if this instance is logged in else False.
Packit Service 4b33e2
Packit Service 4b33e2
        We test if this session is authenticated by calling the User.get()
Packit Service 4b33e2
        XMLRPC method with ids set. Logged-out users cannot pass the 'ids'
Packit Service 4b33e2
        parameter and will result in a 505 error. If we tried to login with a
Packit Service 4b33e2
        token, but the token was incorrect or expired, the server returns a
Packit Service 4b33e2
        32000 error.
Packit Service 4b33e2
Packit Service 4b33e2
        For Bugzilla 5 and later, a new method, User.valid_login is available
Packit Service 4b33e2
        to test the validity of the token. However, this will require that the
Packit Service 4b33e2
        username be cached along with the token in order to work effectively in
Packit Service 4b33e2
        all scenarios and is not currently used. For more information, refer to
Packit Service 4b33e2
        the following url.
Packit Service 4b33e2
Packit Service 4b33e2
        http://bugzilla.readthedocs.org/en/latest/api/core/v1/user.html#valid-login
Packit Service 4b33e2
        """
Packit Service 4b33e2
        try:
Packit Service 4b33e2
            self._proxy.User.get({'ids': []})
Packit Service 4b33e2
            return True
Packit Service 4b33e2
        except Fault as e:
Packit Service 4b33e2
            if e.faultCode == 505 or e.faultCode == 32000:
Packit Service 4b33e2
                return False
Packit Service 4b33e2
            raise e
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    ######################
Packit Service 4b33e2
    # Bugfields querying #
Packit Service 4b33e2
    ######################
Packit Service 4b33e2
Packit Service 4b33e2
    def _getbugfields(self):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Get the list of valid fields for Bug objects
Packit Service 4b33e2
        """
Packit Service 4b33e2
        r = self._proxy.Bug.fields({'include_fields': ['name']})
Packit Service 4b33e2
        return [f['name'] for f in r['fields']]
Packit Service 4b33e2
Packit Service 4b33e2
    def getbugfields(self, force_refresh=False):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Calls getBugFields, which returns a list of fields in each bug
Packit Service 4b33e2
        for this bugzilla instance. This can be used to set the list of attrs
Packit Service 4b33e2
        on the Bug object.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        if force_refresh or not self._cache.bugfields:
Packit Service 4b33e2
            log.debug("Refreshing bugfields")
Packit Service 4b33e2
            self._cache.bugfields = self._getbugfields()
Packit Service 4b33e2
            self._cache.bugfields.sort()
Packit Service 4b33e2
            log.debug("bugfields = %s", self._cache.bugfields)
Packit Service 4b33e2
Packit Service 4b33e2
        return self._cache.bugfields
Packit Service 4b33e2
    bugfields = property(fget=lambda self: self.getbugfields(),
Packit Service 4b33e2
                         fdel=lambda self: setattr(self, '_bugfields', None))
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    ####################
Packit Service 4b33e2
    # Product querying #
Packit Service 4b33e2
    ####################
Packit Service 4b33e2
Packit Service 4b33e2
    def product_get(self, ids=None, names=None,
Packit Service 4b33e2
                    include_fields=None, exclude_fields=None,
Packit Service 4b33e2
                    ptype=None):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Raw wrapper around Product.get
Packit Service 4b33e2
        https://bugzilla.readthedocs.io/en/latest/api/core/v1/product.html#get-product
Packit Service 4b33e2
Packit Service 4b33e2
        This does not perform any caching like other product API calls.
Packit Service 4b33e2
        If ids, names, or ptype is not specified, we default to
Packit Service 4b33e2
        ptype=accessible for historical reasons
Packit Service 4b33e2
Packit Service 4b33e2
        @ids: List of product IDs to lookup
Packit Service 4b33e2
        @names: List of product names to lookup
Packit Service 4b33e2
        @ptype: Either 'accessible', 'selectable', or 'enterable'. If
Packit Service 4b33e2
            specified, we return data for all those
Packit Service 4b33e2
        @include_fields: Only include these fields in the output
Packit Service 4b33e2
        @exclude_fields: Do not include these fields in the output
Packit Service 4b33e2
        """
Packit Service 4b33e2
        if ids is None and names is None and ptype is None:
Packit Service 4b33e2
            ptype = "accessible"
Packit Service 4b33e2
Packit Service 4b33e2
        if ptype:
Packit Service 4b33e2
            raw = None
Packit Service 4b33e2
            if ptype == "accessible":
Packit Service 4b33e2
                raw = self._proxy.Product.get_accessible_products()
Packit Service 4b33e2
            elif ptype == "selectable":
Packit Service 4b33e2
                raw = self._proxy.Product.get_selectable_products()
Packit Service 4b33e2
            elif ptype == "enterable":
Packit Service 4b33e2
                raw = self._proxy.Product.get_enterable_products()
Packit Service 4b33e2
Packit Service 4b33e2
            if raw is None:
Packit Service 4b33e2
                raise RuntimeError("Unknown ptype=%s" % ptype)
Packit Service 4b33e2
            ids = raw['ids']
Packit Service 4b33e2
            log.debug("For ptype=%s found ids=%s", ptype, ids)
Packit Service 4b33e2
Packit Service 4b33e2
        kwargs = {}
Packit Service 4b33e2
        if ids:
Packit Service 4b33e2
            kwargs["ids"] = self._listify(ids)
Packit Service 4b33e2
        if names:
Packit Service 4b33e2
            kwargs["names"] = self._listify(names)
Packit Service 4b33e2
        if include_fields:
Packit Service 4b33e2
            kwargs["include_fields"] = include_fields
Packit Service 4b33e2
        if exclude_fields:
Packit Service 4b33e2
            kwargs["exclude_fields"] = exclude_fields
Packit Service 4b33e2
Packit Service 4b33e2
        ret = self._proxy.Product.get(kwargs)
Packit Service 4b33e2
        return ret['products']
Packit Service 4b33e2
Packit Service 4b33e2
    def refresh_products(self, **kwargs):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Refresh a product's cached info. Basically calls product_get
Packit Service 4b33e2
        with the passed arguments, and tries to intelligently update
Packit Service 4b33e2
        our product cache.
Packit Service 4b33e2
Packit Service 4b33e2
        For example, if we already have cached info for product=foo,
Packit Service 4b33e2
        and you pass in names=["bar", "baz"], the new cache will have
Packit Service 4b33e2
        info for products foo, bar, baz. Individual product fields are
Packit Service 4b33e2
        also updated.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        for product in self.product_get(**kwargs):
Packit Service 4b33e2
            updated = False
Packit Service 4b33e2
            for current in self._cache.products[:]:
Packit Service 4b33e2
                if (current.get("id", -1) != product.get("id", -2) and
Packit Service 4b33e2
                    current.get("name", -1) != product.get("name", -2)):
Packit Service 4b33e2
                    continue
Packit Service 4b33e2
Packit Service 4b33e2
                _nested_update(current, product)
Packit Service 4b33e2
                updated = True
Packit Service 4b33e2
                break
Packit Service 4b33e2
            if not updated:
Packit Service 4b33e2
                self._cache.products.append(product)
Packit Service 4b33e2
Packit Service 4b33e2
    def getproducts(self, force_refresh=False, **kwargs):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Query all products and return the raw dict info. Takes all the
Packit Service 4b33e2
        same arguments as product_get.
Packit Service 4b33e2
Packit Service 4b33e2
        On first invocation this will contact bugzilla and internally
Packit Service 4b33e2
        cache the results. Subsequent getproducts calls or accesses to
Packit Service 4b33e2
        self.products will return this cached data only.
Packit Service 4b33e2
Packit Service 4b33e2
        :param force_refresh: force refreshing via refresh_products()
Packit Service 4b33e2
        """
Packit Service 4b33e2
        if force_refresh or not self._cache.products:
Packit Service 4b33e2
            self.refresh_products(**kwargs)
Packit Service 4b33e2
        return self._cache.products
Packit Service 4b33e2
Packit Service 4b33e2
    products = property(
Packit Service 4b33e2
        fget=lambda self: self.getproducts(),
Packit Service 4b33e2
        fdel=lambda self: setattr(self, '_products', None),
Packit Service 4b33e2
        doc="Helper for accessing the products cache. If nothing "
Packit Service 4b33e2
            "has been cached yet, this calls getproducts()")
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    #######################
Packit Service 4b33e2
    # components querying #
Packit Service 4b33e2
    #######################
Packit Service 4b33e2
Packit Service 4b33e2
    def _lookup_product_in_cache(self, productname):
Packit Service 4b33e2
        prodstr = isinstance(productname, str) and productname or None
Packit Service 4b33e2
        prodint = isinstance(productname, int) and productname or None
Packit Service 4b33e2
        for proddict in self._cache.products:
Packit Service 4b33e2
            if prodstr == proddict.get("name", -1):
Packit Service 4b33e2
                return proddict
Packit Service 4b33e2
            if prodint == proddict.get("id", "nope"):
Packit Service 4b33e2
                return proddict
Packit Service 4b33e2
        return {}
Packit Service 4b33e2
Packit Service 4b33e2
    def getcomponentsdetails(self, product, force_refresh=False):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Wrapper around Product.get(include_fields=["components"]),
Packit Service 4b33e2
        returning only the "components" data for the requested product,
Packit Service 4b33e2
        slightly reworked to a dict mapping of components.name: components,
Packit Service 4b33e2
        for historical reasons.
Packit Service 4b33e2
Packit Service 4b33e2
        This uses the product cache, but will update it if the product
Packit Service 4b33e2
        isn't found or "components" isn't cached for the product.
Packit Service 4b33e2
Packit Service 4b33e2
        In cases like bugzilla.redhat.com where there are tons of
Packit Service 4b33e2
        components for some products, this API will time out. You
Packit Service 4b33e2
        should use product_get instead.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        proddict = self._lookup_product_in_cache(product)
Packit Service 4b33e2
Packit Service 4b33e2
        if (force_refresh or not proddict or "components" not in proddict):
Packit Service 4b33e2
            self.refresh_products(names=[product],
Packit Service 4b33e2
                                  include_fields=["name", "id", "components"])
Packit Service 4b33e2
            proddict = self._lookup_product_in_cache(product)
Packit Service 4b33e2
Packit Service 4b33e2
        ret = {}
Packit Service 4b33e2
        for compdict in proddict["components"]:
Packit Service 4b33e2
            ret[compdict["name"]] = compdict
Packit Service 4b33e2
        return ret
Packit Service 4b33e2
Packit Service 4b33e2
    def getcomponentdetails(self, product, component, force_refresh=False):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Helper for accessing a single component's info. This is a wrapper
Packit Service 4b33e2
        around getcomponentsdetails, see that for explanation
Packit Service 4b33e2
        """
Packit Service 4b33e2
        d = self.getcomponentsdetails(product, force_refresh)
Packit Service 4b33e2
        return d[component]
Packit Service 4b33e2
Packit Service 4b33e2
    def getcomponents(self, product, force_refresh=False):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Return a list of component names for the passed product.
Packit Service 4b33e2
Packit Service 4b33e2
        This can be implemented with Product.get, but behind the
Packit Service 4b33e2
        scenes it uses Bug.legal_values. Reason being that on bugzilla
Packit Service 4b33e2
        instances with tons of components, like bugzilla.redhat.com
Packit Service 4b33e2
        Product=Fedora for example, there's a 10x speed difference
Packit Service 4b33e2
        even with properly limited Product.get calls.
Packit Service 4b33e2
Packit Service 4b33e2
        On first invocation the value is cached, and subsequent calls
Packit Service 4b33e2
        will return the cached data.
Packit Service 4b33e2
Packit Service 4b33e2
        :param force_refresh: Force refreshing the cache, and return
Packit Service 4b33e2
            the new data
Packit Service 4b33e2
        """
Packit Service 4b33e2
        proddict = self._lookup_product_in_cache(product)
Packit Service 4b33e2
        product_id = proddict.get("id", None)
Packit Service 4b33e2
Packit Service 4b33e2
        if (force_refresh or
Packit Service 4b33e2
            product_id is None or
Packit Service 4b33e2
            product_id not in self._cache.component_names):
Packit Service 4b33e2
            self.refresh_products(names=[product],
Packit Service 4b33e2
                                  include_fields=["names", "id"])
Packit Service 4b33e2
            proddict = self._lookup_product_in_cache(product)
Packit Service 4b33e2
            product_id = proddict["id"]
Packit Service 4b33e2
Packit Service 4b33e2
            opts = {'product_id': product_id, 'field': 'component'}
Packit Service 4b33e2
            names = self._proxy.Bug.legal_values(opts)["values"]
Packit Service 4b33e2
            self._cache.component_names[product_id] = names
Packit Service 4b33e2
Packit Service 4b33e2
        return self._cache.component_names[product_id]
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    ############################
Packit Service 4b33e2
    # component adding/editing #
Packit Service 4b33e2
    ############################
Packit Service 4b33e2
Packit Service 4b33e2
    def _component_data_convert(self, data, update=False):
Packit Service 4b33e2
        # Back compat for the old RH interface
Packit Service 4b33e2
        convert_fields = [
Packit Service 4b33e2
            ("initialowner", "default_assignee"),
Packit Service 4b33e2
            ("initialqacontact", "default_qa_contact"),
Packit Service 4b33e2
            ("initialcclist", "default_cc"),
Packit Service 4b33e2
        ]
Packit Service 4b33e2
        for old, new in convert_fields:
Packit Service 4b33e2
            if old in data:
Packit Service 4b33e2
                data[new] = data.pop(old)
Packit Service 4b33e2
Packit Service 4b33e2
        if update:
Packit Service 4b33e2
            names = {"product": data.pop("product"),
Packit Service 4b33e2
                     "component": data.pop("component")}
Packit Service 4b33e2
            updates = {}
Packit Service 4b33e2
            for k in list(data.keys()):
Packit Service 4b33e2
                updates[k] = data.pop(k)
Packit Service 4b33e2
Packit Service 4b33e2
            data["names"] = [names]
Packit Service 4b33e2
            data["updates"] = updates
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    def addcomponent(self, data):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        A method to create a component in Bugzilla. Takes a dict, with the
Packit Service 4b33e2
        following elements:
Packit Service 4b33e2
Packit Service 4b33e2
        product: The product to create the component in
Packit Service 4b33e2
        component: The name of the component to create
Packit Service 4b33e2
        desription: A one sentence summary of the component
Packit Service 4b33e2
        default_assignee: The bugzilla login (email address) of the initial
Packit Service 4b33e2
                          owner of the component
Packit Service 4b33e2
        default_qa_contact (optional): The bugzilla login of the
Packit Service 4b33e2
                                       initial QA contact
Packit Service 4b33e2
        default_cc: (optional) The initial list of users to be CC'ed on
Packit Service 4b33e2
                               new bugs for the component.
Packit Service 4b33e2
        is_active: (optional) If False, the component is hidden from
Packit Service 4b33e2
                              the component list when filing new bugs.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        data = data.copy()
Packit Service 4b33e2
        self._component_data_convert(data)
Packit Service 4b33e2
        return self._proxy.Component.create(data)
Packit Service 4b33e2
Packit Service 4b33e2
    def editcomponent(self, data):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        A method to edit a component in Bugzilla. Takes a dict, with
Packit Service 4b33e2
        mandatory elements of product. component, and initialowner.
Packit Service 4b33e2
        All other elements are optional and use the same names as the
Packit Service 4b33e2
        addcomponent() method.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        data = data.copy()
Packit Service 4b33e2
        self._component_data_convert(data, update=True)
Packit Service 4b33e2
        return self._proxy.Component.update(data)
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    ###################
Packit Service 4b33e2
    # getbug* methods #
Packit Service 4b33e2
    ###################
Packit Service 4b33e2
Packit Service 4b33e2
    def _process_include_fields(self, include_fields, exclude_fields,
Packit Service 4b33e2
                                extra_fields):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Internal helper to process include_fields lists
Packit Service 4b33e2
        """
Packit Service 4b33e2
        def _convert_fields(_in):
Packit Service 4b33e2
            if not _in:
Packit Service 4b33e2
                return _in
Packit Service 4b33e2
Packit Service 4b33e2
            for newname, oldname in self._get_api_aliases():
Packit Service 4b33e2
                if oldname in _in:
Packit Service 4b33e2
                    _in.remove(oldname)
Packit Service 4b33e2
                    if newname not in _in:
Packit Service 4b33e2
                        _in.append(newname)
Packit Service 4b33e2
            return _in
Packit Service 4b33e2
Packit Service 4b33e2
        ret = {}
Packit Service 4b33e2
        if self._check_version(4, 0):
Packit Service 4b33e2
            if include_fields:
Packit Service 4b33e2
                include_fields = _convert_fields(include_fields)
Packit Service 4b33e2
                if "id" not in include_fields:
Packit Service 4b33e2
                    include_fields.append("id")
Packit Service 4b33e2
                ret["include_fields"] = include_fields
Packit Service 4b33e2
            if exclude_fields:
Packit Service 4b33e2
                exclude_fields = _convert_fields(exclude_fields)
Packit Service 4b33e2
                ret["exclude_fields"] = exclude_fields
Packit Service 4b33e2
        if self._supports_getbug_extra_fields:
Packit Service 4b33e2
            if extra_fields:
Packit Service 4b33e2
                ret["extra_fields"] = _convert_fields(extra_fields)
Packit Service 4b33e2
        return ret
Packit Service 4b33e2
Packit Service 4b33e2
    def _get_bug_autorefresh(self):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        This value is passed to Bug.autorefresh for all fetched bugs.
Packit Service 4b33e2
        If True, and an uncached attribute is requested from a Bug,
Packit Service 4b33e2
            the Bug will update its contents and try again.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        return self._bug_autorefresh
Packit Service 4b33e2
Packit Service 4b33e2
    def _set_bug_autorefresh(self, val):
Packit Service 4b33e2
        self._bug_autorefresh = bool(val)
Packit Service 4b33e2
    bug_autorefresh = property(_get_bug_autorefresh, _set_bug_autorefresh)
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    # getbug_extra_fields: Extra fields that need to be explicitly
Packit Service 4b33e2
    # requested from Bug.get in order for the data to be returned.
Packit Service 4b33e2
    #
Packit Service 4b33e2
    # As of Dec 2012 it seems like only RH bugzilla actually has behavior
Packit Service 4b33e2
    # like this, for upstream bz it returns all info for every Bug.get()
Packit Service 4b33e2
    _getbug_extra_fields = []
Packit Service 4b33e2
    _supports_getbug_extra_fields = False
Packit Service 4b33e2
Packit Service 4b33e2
    def _getbugs(self, idlist, permissive,
Packit Service 4b33e2
            include_fields=None, exclude_fields=None, extra_fields=None):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Return a list of dicts of full bug info for each given bug id.
Packit Service 4b33e2
        bug ids that couldn't be found will return None instead of a dict.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        oldidlist = idlist
Packit Service 4b33e2
        idlist = []
Packit Service 4b33e2
        for i in oldidlist:
Packit Service 4b33e2
            try:
Packit Service 4b33e2
                idlist.append(int(i))
Packit Service 4b33e2
            except ValueError:
Packit Service 4b33e2
                # String aliases can be passed as well
Packit Service 4b33e2
                idlist.append(i)
Packit Service 4b33e2
Packit Service 4b33e2
        extra_fields = self._listify(extra_fields or [])
Packit Service 4b33e2
        extra_fields += self._getbug_extra_fields
Packit Service 4b33e2
Packit Service 4b33e2
        getbugdata = {"ids": idlist}
Packit Service 4b33e2
        if permissive:
Packit Service 4b33e2
            getbugdata["permissive"] = 1
Packit Service 4b33e2
Packit Service 4b33e2
        getbugdata.update(self._process_include_fields(
Packit Service 4b33e2
            include_fields, exclude_fields, extra_fields))
Packit Service 4b33e2
Packit Service 4b33e2
        r = self._proxy.Bug.get(getbugdata)
Packit Service 4b33e2
Packit Service 4b33e2
        if self._check_version(4, 0):
Packit Service 4b33e2
            bugdict = dict([(b['id'], b) for b in r['bugs']])
Packit Service 4b33e2
        else:
Packit Service 4b33e2
            bugdict = dict([(b['id'], b['internals']) for b in r['bugs']])
Packit Service 4b33e2
Packit Service 4b33e2
        ret = []
Packit Service 4b33e2
        for i in idlist:
Packit Service 4b33e2
            found = None
Packit Service 4b33e2
            if i in bugdict:
Packit Service 4b33e2
                found = bugdict[i]
Packit Service 4b33e2
            else:
Packit Service 4b33e2
                # Need to map an alias
Packit Service 4b33e2
                for valdict in bugdict.values():
Packit Service 4b33e2
                    if i in self._listify(valdict.get("alias", None)):
Packit Service 4b33e2
                        found = valdict
Packit Service 4b33e2
                        break
Packit Service 4b33e2
Packit Service 4b33e2
            ret.append(found)
Packit Service 4b33e2
Packit Service 4b33e2
        return ret
Packit Service 4b33e2
Packit Service 4b33e2
    def _getbug(self, objid, **kwargs):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Thin wrapper around _getbugs to handle the slight argument tweaks
Packit Service 4b33e2
        for fetching a single bug. The main bit is permissive=False, which
Packit Service 4b33e2
        will tell bugzilla to raise an explicit error if we can't fetch
Packit Service 4b33e2
        that bug.
Packit Service 4b33e2
Packit Service 4b33e2
        This logic is called from Bug() too
Packit Service 4b33e2
        """
Packit Service 4b33e2
        return self._getbugs([objid], permissive=False, **kwargs)[0]
Packit Service 4b33e2
Packit Service 4b33e2
    def getbug(self, objid,
Packit Service 4b33e2
               include_fields=None, exclude_fields=None, extra_fields=None):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Return a Bug object with the full complement of bug data
Packit Service 4b33e2
        already loaded.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        data = self._getbug(objid,
Packit Service 4b33e2
            include_fields=include_fields, exclude_fields=exclude_fields,
Packit Service 4b33e2
            extra_fields=extra_fields)
Packit Service 4b33e2
        return Bug(self, dict=data, autorefresh=self.bug_autorefresh)
Packit Service 4b33e2
Packit Service 4b33e2
    def getbugs(self, idlist,
Packit Service 4b33e2
                include_fields=None, exclude_fields=None, extra_fields=None,
Packit Service 4b33e2
                permissive=True):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Return a list of Bug objects with the full complement of bug data
Packit Service 4b33e2
        already loaded. If there's a problem getting the data for a given id,
Packit Service 4b33e2
        the corresponding item in the returned list will be None.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        data = self._getbugs(idlist, include_fields=include_fields,
Packit Service 4b33e2
            exclude_fields=exclude_fields, extra_fields=extra_fields,
Packit Service 4b33e2
            permissive=permissive)
Packit Service 4b33e2
        return [(b and Bug(self, dict=b,
Packit Service 4b33e2
                           autorefresh=self.bug_autorefresh)) or None
Packit Service 4b33e2
                for b in data]
Packit Service 4b33e2
Packit Service 4b33e2
    def get_comments(self, idlist):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Returns a dictionary of bugs and comments.  The comments key will
Packit Service 4b33e2
        be empty.  See bugzilla docs for details
Packit Service 4b33e2
        """
Packit Service 4b33e2
        return self._proxy.Bug.comments({'ids': idlist})
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    #################
Packit Service 4b33e2
    # query methods #
Packit Service 4b33e2
    #################
Packit Service 4b33e2
Packit Service 4b33e2
    def build_query(self,
Packit Service 4b33e2
                    product=None,
Packit Service 4b33e2
                    component=None,
Packit Service 4b33e2
                    version=None,
Packit Service 4b33e2
                    long_desc=None,
Packit Service 4b33e2
                    bug_id=None,
Packit Service 4b33e2
                    short_desc=None,
Packit Service 4b33e2
                    cc=None,
Packit Service 4b33e2
                    assigned_to=None,
Packit Service 4b33e2
                    reporter=None,
Packit Service 4b33e2
                    qa_contact=None,
Packit Service 4b33e2
                    status=None,
Packit Service 4b33e2
                    blocked=None,
Packit Service 4b33e2
                    dependson=None,
Packit Service 4b33e2
                    keywords=None,
Packit Service 4b33e2
                    keywords_type=None,
Packit Service 4b33e2
                    url=None,
Packit Service 4b33e2
                    url_type=None,
Packit Service 4b33e2
                    status_whiteboard=None,
Packit Service 4b33e2
                    status_whiteboard_type=None,
Packit Service 4b33e2
                    fixed_in=None,
Packit Service 4b33e2
                    fixed_in_type=None,
Packit Service 4b33e2
                    flag=None,
Packit Service 4b33e2
                    alias=None,
Packit Service 4b33e2
                    qa_whiteboard=None,
Packit Service 4b33e2
                    devel_whiteboard=None,
Packit Service 4b33e2
                    boolean_query=None,
Packit Service 4b33e2
                    bug_severity=None,
Packit Service 4b33e2
                    priority=None,
Packit Service 4b33e2
                    target_release=None,
Packit Service 4b33e2
                    target_milestone=None,
Packit Service 4b33e2
                    emailtype=None,
Packit Service 4b33e2
                    booleantype=None,
Packit Service 4b33e2
                    include_fields=None,
Packit Service 4b33e2
                    quicksearch=None,
Packit Service 4b33e2
                    savedsearch=None,
Packit Service 4b33e2
                    savedsearch_sharer_id=None,
Packit Service 4b33e2
                    sub_component=None,
Packit Service 4b33e2
                    tags=None,
Packit Service 4b33e2
                    exclude_fields=None,
Packit Service 4b33e2
                    extra_fields=None):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Build a query string from passed arguments. Will handle
Packit Service 4b33e2
        query parameter differences between various bugzilla versions.
Packit Service 4b33e2
Packit Service 4b33e2
        Most of the parameters should be self explanatory. However
Packit Service 4b33e2
        if you want to perform a complex query, and easy way is to
Packit Service 4b33e2
        create it with the bugzilla web UI, copy the entire URL it
Packit Service 4b33e2
        generates, and pass it to the static method
Packit Service 4b33e2
Packit Service 4b33e2
        Bugzilla.url_to_query
Packit Service 4b33e2
Packit Service 4b33e2
        Then pass the output to Bugzilla.query()
Packit Service 4b33e2
Packit Service 4b33e2
        For details about the specific argument formats, see the bugzilla docs:
Packit Service 4b33e2
        https://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#search-bugs
Packit Service 4b33e2
        """
Packit Service 4b33e2
        if boolean_query or booleantype:
Packit Service 4b33e2
            raise RuntimeError("boolean_query format is no longer supported. "
Packit Service 4b33e2
                "If you need complicated URL queries, look into "
Packit Service 4b33e2
                "query --from-url/url_to_query().")
Packit Service 4b33e2
Packit Service 4b33e2
        query = {
Packit Service 4b33e2
            "alias": alias,
Packit Service 4b33e2
            "product": self._listify(product),
Packit Service 4b33e2
            "component": self._listify(component),
Packit Service 4b33e2
            "version": version,
Packit Service 4b33e2
            "id": bug_id,
Packit Service 4b33e2
            "short_desc": short_desc,
Packit Service 4b33e2
            "bug_status": status,
Packit Service 4b33e2
            "bug_severity": bug_severity,
Packit Service 4b33e2
            "priority": priority,
Packit Service 4b33e2
            "target_release": target_release,
Packit Service 4b33e2
            "target_milestone": target_milestone,
Packit Service 4b33e2
            "tag": self._listify(tags),
Packit Service 4b33e2
            "quicksearch": quicksearch,
Packit Service 4b33e2
            "savedsearch": savedsearch,
Packit Service 4b33e2
            "sharer_id": savedsearch_sharer_id,
Packit Service 4b33e2
Packit Service 4b33e2
            # RH extensions... don't add any more. See comment below
Packit Service 4b33e2
            "sub_components": self._listify(sub_component),
Packit Service 4b33e2
        }
Packit Service 4b33e2
Packit Service 4b33e2
        def add_bool(bzkey, value, bool_id, booltype=None):
Packit Service 4b33e2
            value = self._listify(value)
Packit Service 4b33e2
            if value is None:
Packit Service 4b33e2
                return bool_id
Packit Service 4b33e2
Packit Service 4b33e2
            query["query_format"] = "advanced"
Packit Service 4b33e2
            for boolval in value:
Packit Service 4b33e2
                def make_bool_str(prefix):
Packit Service 4b33e2
                    # pylint: disable=cell-var-from-loop
Packit Service 4b33e2
                    return "%s%i-0-0" % (prefix, bool_id)
Packit Service 4b33e2
Packit Service 4b33e2
                query[make_bool_str("field")] = bzkey
Packit Service 4b33e2
                query[make_bool_str("value")] = boolval
Packit Service 4b33e2
                query[make_bool_str("type")] = booltype or "substring"
Packit Service 4b33e2
Packit Service 4b33e2
                bool_id += 1
Packit Service 4b33e2
            return bool_id
Packit Service 4b33e2
Packit Service 4b33e2
        # RH extensions that we have to maintain here for back compat,
Packit Service 4b33e2
        # but all future custom fields should be specified via
Packit Service 4b33e2
        # cli --field option, or via extending the query dict() manually.
Packit Service 4b33e2
        # No more supporting custom fields in this API
Packit Service 4b33e2
        bool_id = 0
Packit Service 4b33e2
        bool_id = add_bool("keywords", keywords, bool_id, keywords_type)
Packit Service 4b33e2
        bool_id = add_bool("blocked", blocked, bool_id)
Packit Service 4b33e2
        bool_id = add_bool("dependson", dependson, bool_id)
Packit Service 4b33e2
        bool_id = add_bool("bug_file_loc", url, bool_id, url_type)
Packit Service 4b33e2
        bool_id = add_bool("cf_fixed_in", fixed_in, bool_id, fixed_in_type)
Packit Service 4b33e2
        bool_id = add_bool("flagtypes.name", flag, bool_id)
Packit Service 4b33e2
        bool_id = add_bool("status_whiteboard",
Packit Service 4b33e2
                           status_whiteboard, bool_id, status_whiteboard_type)
Packit Service 4b33e2
        bool_id = add_bool("cf_qa_whiteboard", qa_whiteboard, bool_id)
Packit Service 4b33e2
        bool_id = add_bool("cf_devel_whiteboard", devel_whiteboard, bool_id)
Packit Service 4b33e2
Packit Service 4b33e2
        def add_email(key, value, count):
Packit Service 4b33e2
            if value is None:
Packit Service 4b33e2
                return count
Packit Service 4b33e2
            if not emailtype:
Packit Service 4b33e2
                query[key] = value
Packit Service 4b33e2
                return count
Packit Service 4b33e2
Packit Service 4b33e2
            query["query_format"] = "advanced"
Packit Service 4b33e2
            query['email%i' % count] = value
Packit Service 4b33e2
            query['email%s%i' % (key, count)] = True
Packit Service 4b33e2
            query['emailtype%i' % count] = emailtype
Packit Service 4b33e2
            return count + 1
Packit Service 4b33e2
Packit Service 4b33e2
        email_count = 1
Packit Service 4b33e2
        email_count = add_email("cc", cc, email_count)
Packit Service 4b33e2
        email_count = add_email("assigned_to", assigned_to, email_count)
Packit Service 4b33e2
        email_count = add_email("reporter", reporter, email_count)
Packit Service 4b33e2
        email_count = add_email("qa_contact", qa_contact, email_count)
Packit Service 4b33e2
Packit Service 4b33e2
        if long_desc is not None:
Packit Service 4b33e2
            query["query_format"] = "advanced"
Packit Service 4b33e2
            query["longdesc"] = long_desc
Packit Service 4b33e2
            query["longdesc_type"] = "allwordssubstr"
Packit Service 4b33e2
Packit Service 4b33e2
        # 'include_fields' only available for Bugzilla4+
Packit Service 4b33e2
        # 'extra_fields' is an RHBZ extension
Packit Service 4b33e2
        query.update(self._process_include_fields(
Packit Service 4b33e2
            include_fields, exclude_fields, extra_fields))
Packit Service 4b33e2
Packit Service 4b33e2
        # Strip out None elements in the dict
Packit Service 4b33e2
        for k, v in query.copy().items():
Packit Service 4b33e2
            if v is None:
Packit Service 4b33e2
                del(query[k])
Packit Service 4b33e2
Packit Service 4b33e2
        self.pre_translation(query)
Packit Service 4b33e2
        return query
Packit Service 4b33e2
Packit Service 4b33e2
    def query(self, query):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Query bugzilla and return a list of matching bugs.
Packit Service 4b33e2
        query must be a dict with fields like those in in querydata['fields'].
Packit Service 4b33e2
        Returns a list of Bug objects.
Packit Service 4b33e2
        Also see the _query() method for details about the underlying
Packit Service 4b33e2
        implementation.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        try:
Packit Service 4b33e2
            r = self._proxy.Bug.search(query)
Packit Service 4b33e2
        except Fault as e:
Packit Service 4b33e2
Packit Service 4b33e2
            # Try to give a hint in the error message if url_to_query
Packit Service 4b33e2
            # isn't supported by this bugzilla instance
Packit Service 4b33e2
            if ("query_format" not in str(e) or
Packit Service 4b33e2
                "RHBugzilla" in str(e.__class__) or
Packit Service 4b33e2
                self._check_version(5, 0)):
Packit Service 4b33e2
                raise
Packit Service 4b33e2
            raise BugzillaError("%s\nYour bugzilla instance does not "
Packit Service 4b33e2
                "appear to support API queries derived from bugzilla "
Packit Service 4b33e2
                "web URL queries." % e)
Packit Service 4b33e2
Packit Service 4b33e2
        log.debug("Query returned %s bugs", len(r['bugs']))
Packit Service 4b33e2
        return [Bug(self, dict=b,
Packit Service 4b33e2
                autorefresh=self.bug_autorefresh) for b in r['bugs']]
Packit Service 4b33e2
Packit Service 4b33e2
    def pre_translation(self, query):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        In order to keep the API the same, Bugzilla4 needs to process the
Packit Service 4b33e2
        query and the result. This also applies to the refresh() function
Packit Service 4b33e2
        """
Packit Service 4b33e2
        pass
Packit Service 4b33e2
Packit Service 4b33e2
    def post_translation(self, query, bug):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        In order to keep the API the same, Bugzilla4 needs to process the
Packit Service 4b33e2
        query and the result. This also applies to the refresh() function
Packit Service 4b33e2
        """
Packit Service 4b33e2
        pass
Packit Service 4b33e2
Packit Service 4b33e2
    def bugs_history_raw(self, bug_ids):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Experimental. Gets the history of changes for
Packit Service 4b33e2
        particular bugs in the database.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        return self._proxy.Bug.history({'ids': bug_ids})
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    #######################################
Packit Service 4b33e2
    # Methods for modifying existing bugs #
Packit Service 4b33e2
    #######################################
Packit Service 4b33e2
Packit Service 4b33e2
    # Bug() also has individual methods for many ops, like setassignee()
Packit Service 4b33e2
Packit Service 4b33e2
    def update_bugs(self, ids, updates):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        A thin wrapper around bugzilla Bug.update(). Used to update all
Packit Service 4b33e2
        values of an existing bug report, as well as add comments.
Packit Service 4b33e2
Packit Service 4b33e2
        The dictionary passed to this function should be generated with
Packit Service 4b33e2
        build_update(), otherwise we cannot guarantee back compatibility.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        tmp = updates.copy()
Packit Service 4b33e2
        tmp["ids"] = self._listify(ids)
Packit Service 4b33e2
Packit Service 4b33e2
        return self._proxy.Bug.update(tmp)
Packit Service 4b33e2
Packit Service 4b33e2
    def update_tags(self, idlist, tags_add=None, tags_remove=None):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Updates the 'tags' field for a bug.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        tags = {}
Packit Service 4b33e2
        if tags_add:
Packit Service 4b33e2
            tags["add"] = self._listify(tags_add)
Packit Service 4b33e2
        if tags_remove:
Packit Service 4b33e2
            tags["remove"] = self._listify(tags_remove)
Packit Service 4b33e2
Packit Service 4b33e2
        d = {
Packit Service 4b33e2
            "ids": self._listify(idlist),
Packit Service 4b33e2
            "tags": tags,
Packit Service 4b33e2
        }
Packit Service 4b33e2
Packit Service 4b33e2
        return self._proxy.Bug.update_tags(d)
Packit Service 4b33e2
Packit Service 4b33e2
    def update_flags(self, idlist, flags):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        A thin back compat wrapper around build_update(flags=X)
Packit Service 4b33e2
        """
Packit Service 4b33e2
        return self.update_bugs(idlist, self.build_update(flags=flags))
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    def build_update(self,
Packit Service 4b33e2
                     alias=None,
Packit Service 4b33e2
                     assigned_to=None,
Packit Service 4b33e2
                     blocks_add=None,
Packit Service 4b33e2
                     blocks_remove=None,
Packit Service 4b33e2
                     blocks_set=None,
Packit Service 4b33e2
                     depends_on_add=None,
Packit Service 4b33e2
                     depends_on_remove=None,
Packit Service 4b33e2
                     depends_on_set=None,
Packit Service 4b33e2
                     cc_add=None,
Packit Service 4b33e2
                     cc_remove=None,
Packit Service 4b33e2
                     is_cc_accessible=None,
Packit Service 4b33e2
                     comment=None,
Packit Service 4b33e2
                     comment_private=None,
Packit Service 4b33e2
                     component=None,
Packit Service 4b33e2
                     deadline=None,
Packit Service 4b33e2
                     dupe_of=None,
Packit Service 4b33e2
                     estimated_time=None,
Packit Service 4b33e2
                     groups_add=None,
Packit Service 4b33e2
                     groups_remove=None,
Packit Service 4b33e2
                     keywords_add=None,
Packit Service 4b33e2
                     keywords_remove=None,
Packit Service 4b33e2
                     keywords_set=None,
Packit Service 4b33e2
                     op_sys=None,
Packit Service 4b33e2
                     platform=None,
Packit Service 4b33e2
                     priority=None,
Packit Service 4b33e2
                     product=None,
Packit Service 4b33e2
                     qa_contact=None,
Packit Service 4b33e2
                     is_creator_accessible=None,
Packit Service 4b33e2
                     remaining_time=None,
Packit Service 4b33e2
                     reset_assigned_to=None,
Packit Service 4b33e2
                     reset_qa_contact=None,
Packit Service 4b33e2
                     resolution=None,
Packit Service 4b33e2
                     see_also_add=None,
Packit Service 4b33e2
                     see_also_remove=None,
Packit Service 4b33e2
                     severity=None,
Packit Service 4b33e2
                     status=None,
Packit Service 4b33e2
                     summary=None,
Packit Service 4b33e2
                     target_milestone=None,
Packit Service 4b33e2
                     target_release=None,
Packit Service 4b33e2
                     url=None,
Packit Service 4b33e2
                     version=None,
Packit Service 4b33e2
                     whiteboard=None,
Packit Service 4b33e2
                     work_time=None,
Packit Service 4b33e2
                     fixed_in=None,
Packit Service 4b33e2
                     qa_whiteboard=None,
Packit Service 4b33e2
                     devel_whiteboard=None,
Packit Service 4b33e2
                     internal_whiteboard=None,
Packit Service 4b33e2
                     sub_component=None,
Packit Service 4b33e2
                     flags=None,
Packit Service 4b33e2
                     comment_tags=None):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Returns a python dict() with properly formatted parameters to
Packit Service 4b33e2
        pass to update_bugs(). See bugzilla documentation for the format
Packit Service 4b33e2
        of the individual fields:
Packit Service 4b33e2
Packit Service 4b33e2
        https://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#create-bug
Packit Service 4b33e2
        """
Packit Service 4b33e2
        ret = {}
Packit Service 4b33e2
Packit Service 4b33e2
        # These are only supported for rhbugzilla
Packit Service 4b33e2
        for key, val in [
Packit Service 4b33e2
            ("fixed_in", fixed_in),
Packit Service 4b33e2
            ("devel_whiteboard", devel_whiteboard),
Packit Service 4b33e2
            ("qa_whiteboard", qa_whiteboard),
Packit Service 4b33e2
            ("internal_whiteboard", internal_whiteboard),
Packit Service 4b33e2
            ("sub_component", sub_component),
Packit Service 4b33e2
        ]:
Packit Service 4b33e2
            if val is not None:
Packit Service 4b33e2
                raise ValueError("bugzilla instance does not support "
Packit Service 4b33e2
                                 "updating '%s'" % key)
Packit Service 4b33e2
Packit Service 4b33e2
        def s(key, val, convert=None):
Packit Service 4b33e2
            if val is None:
Packit Service 4b33e2
                return
Packit Service 4b33e2
            if convert:
Packit Service 4b33e2
                val = convert(val)
Packit Service 4b33e2
            ret[key] = val
Packit Service 4b33e2
Packit Service 4b33e2
        def add_dict(key, add, remove, _set=None, convert=None):
Packit Service 4b33e2
            if add is remove is _set is None:
Packit Service 4b33e2
                return
Packit Service 4b33e2
Packit Service 4b33e2
            def c(val):
Packit Service 4b33e2
                val = self._listify(val)
Packit Service 4b33e2
                if convert:
Packit Service 4b33e2
                    val = [convert(v) for v in val]
Packit Service 4b33e2
                return val
Packit Service 4b33e2
Packit Service 4b33e2
            newdict = {}
Packit Service 4b33e2
            if add is not None:
Packit Service 4b33e2
                newdict["add"] = c(add)
Packit Service 4b33e2
            if remove is not None:
Packit Service 4b33e2
                newdict["remove"] = c(remove)
Packit Service 4b33e2
            if _set is not None:
Packit Service 4b33e2
                newdict["set"] = c(_set)
Packit Service 4b33e2
            ret[key] = newdict
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
        s("alias", alias)
Packit Service 4b33e2
        s("assigned_to", assigned_to)
Packit Service 4b33e2
        s("is_cc_accessible", is_cc_accessible, bool)
Packit Service 4b33e2
        s("component", component)
Packit Service 4b33e2
        s("deadline", deadline)
Packit Service 4b33e2
        s("dupe_of", dupe_of, int)
Packit Service 4b33e2
        s("estimated_time", estimated_time, int)
Packit Service 4b33e2
        s("op_sys", op_sys)
Packit Service 4b33e2
        s("platform", platform)
Packit Service 4b33e2
        s("priority", priority)
Packit Service 4b33e2
        s("product", product)
Packit Service 4b33e2
        s("qa_contact", qa_contact)
Packit Service 4b33e2
        s("is_creator_accessible", is_creator_accessible, bool)
Packit Service 4b33e2
        s("remaining_time", remaining_time, float)
Packit Service 4b33e2
        s("reset_assigned_to", reset_assigned_to, bool)
Packit Service 4b33e2
        s("reset_qa_contact", reset_qa_contact, bool)
Packit Service 4b33e2
        s("resolution", resolution)
Packit Service 4b33e2
        s("severity", severity)
Packit Service 4b33e2
        s("status", status)
Packit Service 4b33e2
        s("summary", summary)
Packit Service 4b33e2
        s("target_milestone", target_milestone)
Packit Service 4b33e2
        s("target_release", target_release)
Packit Service 4b33e2
        s("url", url)
Packit Service 4b33e2
        s("version", version)
Packit Service 4b33e2
        s("whiteboard", whiteboard)
Packit Service 4b33e2
        s("work_time", work_time, float)
Packit Service 4b33e2
        s("flags", flags)
Packit Service 4b33e2
        s("comment_tags", comment_tags, self._listify)
Packit Service 4b33e2
Packit Service 4b33e2
        add_dict("blocks", blocks_add, blocks_remove, blocks_set,
Packit Service 4b33e2
                 convert=int)
Packit Service 4b33e2
        add_dict("depends_on", depends_on_add, depends_on_remove,
Packit Service 4b33e2
                 depends_on_set, convert=int)
Packit Service 4b33e2
        add_dict("cc", cc_add, cc_remove)
Packit Service 4b33e2
        add_dict("groups", groups_add, groups_remove)
Packit Service 4b33e2
        add_dict("keywords", keywords_add, keywords_remove, keywords_set)
Packit Service 4b33e2
        add_dict("see_also", see_also_add, see_also_remove)
Packit Service 4b33e2
Packit Service 4b33e2
        if comment is not None:
Packit Service 4b33e2
            ret["comment"] = {"comment": comment}
Packit Service 4b33e2
            if comment_private:
Packit Service 4b33e2
                ret["comment"]["is_private"] = comment_private
Packit Service 4b33e2
Packit Service 4b33e2
        return ret
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    ########################################
Packit Service 4b33e2
    # Methods for working with attachments #
Packit Service 4b33e2
    ########################################
Packit Service 4b33e2
Packit Service 4b33e2
    def _attachment_uri(self, attachid):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Returns the URI for the given attachment ID.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        att_uri = self.url.replace('xmlrpc.cgi', 'attachment.cgi')
Packit Service 4b33e2
        att_uri = att_uri + '?id=%s' % attachid
Packit Service 4b33e2
        return att_uri
Packit Service 4b33e2
Packit Service 4b33e2
    def attachfile(self, idlist, attachfile, description, **kwargs):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Attach a file to the given bug IDs. Returns the ID of the attachment
Packit Service 4b33e2
        or raises XMLRPC Fault if something goes wrong.
Packit Service 4b33e2
Packit Service 4b33e2
        attachfile may be a filename (which will be opened) or a file-like
Packit Service 4b33e2
        object, which must provide a 'read' method. If it's not one of these,
Packit Service 4b33e2
        this method will raise a TypeError.
Packit Service 4b33e2
        description is the short description of this attachment.
Packit Service 4b33e2
Packit Service 4b33e2
        Optional keyword args are as follows:
Packit Service 4b33e2
            file_name:  this will be used as the filename for the attachment.
Packit Service 4b33e2
                       REQUIRED if attachfile is a file-like object with no
Packit Service 4b33e2
                       'name' attribute, otherwise the filename or .name
Packit Service 4b33e2
                       attribute will be used.
Packit Service 4b33e2
            comment:   An optional comment about this attachment.
Packit Service 4b33e2
            is_private: Set to True if the attachment should be marked private.
Packit Service 4b33e2
            is_patch:   Set to True if the attachment is a patch.
Packit Service 4b33e2
            content_type: The mime-type of the attached file. Defaults to
Packit Service 4b33e2
                          application/octet-stream if not set. NOTE that text
Packit Service 4b33e2
                          files will *not* be viewable in bugzilla unless you
Packit Service 4b33e2
                          remember to set this to text/plain. So remember that!
Packit Service 4b33e2
Packit Service 4b33e2
        Returns the list of attachment ids that were added. If only one
Packit Service 4b33e2
        attachment was added, we return the single int ID for back compat
Packit Service 4b33e2
        """
Packit Service 4b33e2
        if isinstance(attachfile, str):
Packit Service 4b33e2
            f = open(attachfile, "rb")
Packit Service 4b33e2
        elif hasattr(attachfile, 'read'):
Packit Service 4b33e2
            f = attachfile
Packit Service 4b33e2
        else:
Packit Service 4b33e2
            raise TypeError("attachfile must be filename or file-like object")
Packit Service 4b33e2
Packit Service 4b33e2
        # Back compat
Packit Service 4b33e2
        if "contenttype" in kwargs:
Packit Service 4b33e2
            kwargs["content_type"] = kwargs.pop("contenttype")
Packit Service 4b33e2
        if "ispatch" in kwargs:
Packit Service 4b33e2
            kwargs["is_patch"] = kwargs.pop("ispatch")
Packit Service 4b33e2
        if "isprivate" in kwargs:
Packit Service 4b33e2
            kwargs["is_private"] = kwargs.pop("isprivate")
Packit Service 4b33e2
        if "filename" in kwargs:
Packit Service 4b33e2
            kwargs["file_name"] = kwargs.pop("filename")
Packit Service 4b33e2
Packit Service 4b33e2
        kwargs['summary'] = description
Packit Service 4b33e2
Packit Service 4b33e2
        data = f.read()
Packit Service 4b33e2
        if not isinstance(data, bytes):
Packit Service 4b33e2
            data = data.encode(locale.getpreferredencoding())
Packit Service 4b33e2
        kwargs['data'] = Binary(data)
Packit Service 4b33e2
Packit Service 4b33e2
        kwargs['ids'] = self._listify(idlist)
Packit Service 4b33e2
Packit Service 4b33e2
        if 'file_name' not in kwargs and hasattr(f, "name"):
Packit Service 4b33e2
            kwargs['file_name'] = os.path.basename(f.name)
Packit Service 4b33e2
        if 'content_type' not in kwargs:
Packit Service 4b33e2
            ctype = None
Packit Service 4b33e2
            if kwargs['file_name']:
Packit Service 4b33e2
                ctype = mimetypes.guess_type(
Packit Service 4b33e2
                    kwargs['file_name'], strict=False)[0]
Packit Service 4b33e2
            kwargs['content_type'] = ctype or 'application/octet-stream'
Packit Service 4b33e2
Packit Service 4b33e2
        ret = self._proxy.Bug.add_attachment(kwargs)
Packit Service 4b33e2
Packit Service 4b33e2
        if "attachments" in ret:
Packit Service 4b33e2
            # Up to BZ 4.2
Packit Service 4b33e2
            ret = [int(k) for k in ret["attachments"].keys()]
Packit Service 4b33e2
        elif "ids" in ret:
Packit Service 4b33e2
            # BZ 4.4+
Packit Service 4b33e2
            ret = ret["ids"]
Packit Service 4b33e2
Packit Service 4b33e2
        if isinstance(ret, list) and len(ret) == 1:
Packit Service 4b33e2
            ret = ret[0]
Packit Service 4b33e2
        return ret
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    def openattachment(self, attachid):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Get the contents of the attachment with the given attachment ID.
Packit Service 4b33e2
        Returns a file-like object.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        attachments = self.get_attachments(None, attachid)
Packit Service 4b33e2
        data = attachments["attachments"][str(attachid)]
Packit Service 4b33e2
        xmlrpcbinary = data["data"]
Packit Service 4b33e2
Packit Service 4b33e2
        ret = BytesIO()
Packit Service 4b33e2
        ret.write(xmlrpcbinary.data)
Packit Service 4b33e2
        ret.name = data["file_name"]
Packit Service 4b33e2
        ret.seek(0)
Packit Service 4b33e2
        return ret
Packit Service 4b33e2
Packit Service 4b33e2
    def updateattachmentflags(self, bugid, attachid, flagname, **kwargs):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Updates a flag for the given attachment ID.
Packit Service 4b33e2
        Optional keyword args are:
Packit Service 4b33e2
            status:    new status for the flag ('-', '+', '?', 'X')
Packit Service 4b33e2
            requestee: new requestee for the flag
Packit Service 4b33e2
        """
Packit Service 4b33e2
        # Bug ID was used for the original custom redhat API, no longer
Packit Service 4b33e2
        # needed though
Packit Service 4b33e2
        ignore = bugid
Packit Service 4b33e2
Packit Service 4b33e2
        flags = {"name": flagname}
Packit Service 4b33e2
        flags.update(kwargs)
Packit Service 4b33e2
        update = {'ids': [int(attachid)], 'flags': [flags]}
Packit Service 4b33e2
Packit Service 4b33e2
        return self._proxy.Bug.update_attachment(update)
Packit Service 4b33e2
Packit Service 4b33e2
    def get_attachments(self, ids, attachment_ids,
Packit Service 4b33e2
                        include_fields=None, exclude_fields=None):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Wrapper for Bug.attachments. One of ids or attachment_ids is required
Packit Service 4b33e2
Packit Service 4b33e2
        :param ids: Get attachments for this bug ID
Packit Service 4b33e2
        :param attachment_ids: Specific attachment ID to get
Packit Service 4b33e2
Packit Service 4b33e2
        https://bugzilla.readthedocs.io/en/latest/api/core/v1/attachment.html#get-attachment
Packit Service 4b33e2
        """
Packit Service 4b33e2
        params = {
Packit Service 4b33e2
            "ids": self._listify(ids) or [],
Packit Service 4b33e2
            "attachment_ids": self._listify(attachment_ids) or [],
Packit Service 4b33e2
        }
Packit Service 4b33e2
        if include_fields:
Packit Service 4b33e2
            params["include_fields"] = self._listify(include_fields)
Packit Service 4b33e2
        if exclude_fields:
Packit Service 4b33e2
            params["exclude_fields"] = self._listify(exclude_fields)
Packit Service 4b33e2
Packit Service 4b33e2
        return self._proxy.Bug.attachments(params)
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    #####################
Packit Service 4b33e2
    # createbug methods #
Packit Service 4b33e2
    #####################
Packit Service 4b33e2
Packit Service 4b33e2
    createbug_required = ('product', 'component', 'summary', 'version',
Packit Service 4b33e2
                          'description')
Packit Service 4b33e2
Packit Service 4b33e2
    def build_createbug(self,
Packit Service 4b33e2
        product=None,
Packit Service 4b33e2
        component=None,
Packit Service 4b33e2
        version=None,
Packit Service 4b33e2
        summary=None,
Packit Service 4b33e2
        description=None,
Packit Service 4b33e2
        comment_private=None,
Packit Service 4b33e2
        blocks=None,
Packit Service 4b33e2
        cc=None,
Packit Service 4b33e2
        assigned_to=None,
Packit Service 4b33e2
        keywords=None,
Packit Service 4b33e2
        depends_on=None,
Packit Service 4b33e2
        groups=None,
Packit Service 4b33e2
        op_sys=None,
Packit Service 4b33e2
        platform=None,
Packit Service 4b33e2
        priority=None,
Packit Service 4b33e2
        qa_contact=None,
Packit Service 4b33e2
        resolution=None,
Packit Service 4b33e2
        severity=None,
Packit Service 4b33e2
        status=None,
Packit Service 4b33e2
        target_milestone=None,
Packit Service 4b33e2
        target_release=None,
Packit Service 4b33e2
        url=None,
Packit Service 4b33e2
        sub_component=None,
Packit Service 4b33e2
        alias=None,
Packit Service 4b33e2
        comment_tags=None):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Returns a python dict() with properly formatted parameters to
Packit Service 4b33e2
        pass to createbug(). See bugzilla documentation for the format
Packit Service 4b33e2
        of the individual fields:
Packit Service 4b33e2
Packit Service 4b33e2
        https://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#update-bug
Packit Service 4b33e2
        """
Packit Service 4b33e2
Packit Service 4b33e2
        localdict = {}
Packit Service 4b33e2
        if blocks:
Packit Service 4b33e2
            localdict["blocks"] = self._listify(blocks)
Packit Service 4b33e2
        if cc:
Packit Service 4b33e2
            localdict["cc"] = self._listify(cc)
Packit Service 4b33e2
        if depends_on:
Packit Service 4b33e2
            localdict["depends_on"] = self._listify(depends_on)
Packit Service 4b33e2
        if groups:
Packit Service 4b33e2
            localdict["groups"] = self._listify(groups)
Packit Service 4b33e2
        if keywords:
Packit Service 4b33e2
            localdict["keywords"] = self._listify(keywords)
Packit Service 4b33e2
        if description:
Packit Service 4b33e2
            localdict["description"] = description
Packit Service 4b33e2
            if comment_private:
Packit Service 4b33e2
                localdict["comment_is_private"] = True
Packit Service 4b33e2
Packit Service 4b33e2
        # Most of the machinery and formatting here is the same as
Packit Service 4b33e2
        # build_update, so reuse that as much as possible
Packit Service 4b33e2
        ret = self.build_update(product=product, component=component,
Packit Service 4b33e2
                version=version, summary=summary, op_sys=op_sys,
Packit Service 4b33e2
                platform=platform, priority=priority, qa_contact=qa_contact,
Packit Service 4b33e2
                resolution=resolution, severity=severity, status=status,
Packit Service 4b33e2
                target_milestone=target_milestone,
Packit Service 4b33e2
                target_release=target_release, url=url,
Packit Service 4b33e2
                assigned_to=assigned_to, sub_component=sub_component,
Packit Service 4b33e2
                alias=alias, comment_tags=comment_tags)
Packit Service 4b33e2
Packit Service 4b33e2
        ret.update(localdict)
Packit Service 4b33e2
        return ret
Packit Service 4b33e2
Packit Service 4b33e2
    def _validate_createbug(self, *args, **kwargs):
Packit Service 4b33e2
        # Previous API required users specifying keyword args that mapped
Packit Service 4b33e2
        # to the XMLRPC arg names. Maintain that bad compat, but also allow
Packit Service 4b33e2
        # receiving a single dictionary like query() does
Packit Service 4b33e2
        if kwargs and args:
Packit Service 4b33e2
            raise BugzillaError("createbug: cannot specify positional "
Packit Service 4b33e2
                                "args=%s with kwargs=%s, must be one or the "
Packit Service 4b33e2
                                "other." % (args, kwargs))
Packit Service 4b33e2
        if args:
Packit Service 4b33e2
            if len(args) > 1 or not isinstance(args[0], dict):
Packit Service 4b33e2
                raise BugzillaError("createbug: positional arguments only "
Packit Service 4b33e2
                                    "accept a single dictionary.")
Packit Service 4b33e2
            data = args[0]
Packit Service 4b33e2
        else:
Packit Service 4b33e2
            data = kwargs
Packit Service 4b33e2
Packit Service 4b33e2
        # If we're getting a call that uses an old fieldname, convert it to the
Packit Service 4b33e2
        # new fieldname instead.
Packit Service 4b33e2
        for newname, oldname in self._get_api_aliases():
Packit Service 4b33e2
            if (newname in self.createbug_required and
Packit Service 4b33e2
                newname not in data and
Packit Service 4b33e2
                oldname in data):
Packit Service 4b33e2
                data[newname] = data.pop(oldname)
Packit Service 4b33e2
Packit Service 4b33e2
        # Back compat handling for check_args
Packit Service 4b33e2
        if "check_args" in data:
Packit Service 4b33e2
            del(data["check_args"])
Packit Service 4b33e2
Packit Service 4b33e2
        return data
Packit Service 4b33e2
Packit Service 4b33e2
    def createbug(self, *args, **kwargs):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Create a bug with the given info. Returns a new Bug object.
Packit Service 4b33e2
        Check bugzilla API documentation for valid values, at least
Packit Service 4b33e2
        product, component, summary, version, and description need to
Packit Service 4b33e2
        be passed.
Packit Service 4b33e2
        """
Packit Service 4b33e2
        data = self._validate_createbug(*args, **kwargs)
Packit Service 4b33e2
        rawbug = self._proxy.Bug.create(data)
Packit Service 4b33e2
        return Bug(self, bug_id=rawbug["id"],
Packit Service 4b33e2
                   autorefresh=self.bug_autorefresh)
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    ##############################
Packit Service 4b33e2
    # Methods for handling Users #
Packit Service 4b33e2
    ##############################
Packit Service 4b33e2
Packit Service 4b33e2
    def _getusers(self, ids=None, names=None, match=None):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Return a list of users that match criteria.
Packit Service 4b33e2
Packit Service 4b33e2
        :kwarg ids: list of user ids to return data on
Packit Service 4b33e2
        :kwarg names: list of user names to return data on
Packit Service 4b33e2
        :kwarg match: list of patterns.  Returns users whose real name or
Packit Service 4b33e2
            login name match the pattern.
Packit Service 4b33e2
        :raises XMLRPC Fault: Code 51: if a Bad Login Name was sent to the
Packit Service 4b33e2
                names array.
Packit Service 4b33e2
            Code 304: if the user was not authorized to see user they
Packit Service 4b33e2
                requested.
Packit Service 4b33e2
            Code 505: user is logged out and can't use the match or ids
Packit Service 4b33e2
                parameter.
Packit Service 4b33e2
Packit Service 4b33e2
        Available in Bugzilla-3.4+
Packit Service 4b33e2
        """
Packit Service 4b33e2
        params = {}
Packit Service 4b33e2
        if ids:
Packit Service 4b33e2
            params['ids'] = self._listify(ids)
Packit Service 4b33e2
        if names:
Packit Service 4b33e2
            params['names'] = self._listify(names)
Packit Service 4b33e2
        if match:
Packit Service 4b33e2
            params['match'] = self._listify(match)
Packit Service 4b33e2
        if not params:
Packit Service 4b33e2
            raise BugzillaError('_get() needs one of ids, '
Packit Service 4b33e2
                                ' names, or match kwarg.')
Packit Service 4b33e2
Packit Service 4b33e2
        return self._proxy.User.get(params)
Packit Service 4b33e2
Packit Service 4b33e2
    def getuser(self, username):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Return a bugzilla User for the given username
Packit Service 4b33e2
Packit Service 4b33e2
        :arg username: The username used in bugzilla.
Packit Service 4b33e2
        :raises XMLRPC Fault: Code 51 if the username does not exist
Packit Service 4b33e2
        :returns: User record for the username
Packit Service 4b33e2
        """
Packit Service 4b33e2
        ret = self.getusers(username)
Packit Service 4b33e2
        return ret and ret[0]
Packit Service 4b33e2
Packit Service 4b33e2
    def getusers(self, userlist):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Return a list of Users from .
Packit Service 4b33e2
Packit Service 4b33e2
        :userlist: List of usernames to lookup
Packit Service 4b33e2
        :returns: List of User records
Packit Service 4b33e2
        """
Packit Service 4b33e2
        userobjs = [User(self, **rawuser) for rawuser in
Packit Service 4b33e2
                    self._getusers(names=userlist).get('users', [])]
Packit Service 4b33e2
Packit Service 4b33e2
        # Return users in same order they were passed in
Packit Service 4b33e2
        ret = []
Packit Service 4b33e2
        for u in userlist:
Packit Service 4b33e2
            for uobj in userobjs[:]:
Packit Service 4b33e2
                if uobj.email == u:
Packit Service 4b33e2
                    userobjs.remove(uobj)
Packit Service 4b33e2
                    ret.append(uobj)
Packit Service 4b33e2
                    break
Packit Service 4b33e2
        ret += userobjs
Packit Service 4b33e2
        return ret
Packit Service 4b33e2
Packit Service 4b33e2
Packit Service 4b33e2
    def searchusers(self, pattern):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Return a bugzilla User for the given list of patterns
Packit Service 4b33e2
Packit Service 4b33e2
        :arg pattern: List of patterns to match against.
Packit Service 4b33e2
        :returns: List of User records
Packit Service 4b33e2
        """
Packit Service 4b33e2
        return [User(self, **rawuser) for rawuser in
Packit Service 4b33e2
                self._getusers(match=pattern).get('users', [])]
Packit Service 4b33e2
Packit Service 4b33e2
    def createuser(self, email, name='', password=''):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        Return a bugzilla User for the given username
Packit Service 4b33e2
Packit Service 4b33e2
        :arg email: The email address to use in bugzilla
Packit Service 4b33e2
        :kwarg name: Real name to associate with the account
Packit Service 4b33e2
        :kwarg password: Password to set for the bugzilla account
Packit Service 4b33e2
        :raises XMLRPC Fault: Code 501 if the username already exists
Packit Service 4b33e2
            Code 500 if the email address isn't valid
Packit Service 4b33e2
            Code 502 if the password is too short
Packit Service 4b33e2
            Code 503 if the password is too long
Packit Service 4b33e2
        :return: User record for the username
Packit Service 4b33e2
        """
Packit Service 4b33e2
        self._proxy.User.create(email, name, password)
Packit Service 4b33e2
        return self.getuser(email)
Packit Service 4b33e2
Packit Service 4b33e2
    def updateperms(self, user, action, groups):
Packit Service 4b33e2
        """
Packit Service 4b33e2
        A method to update the permissions (group membership) of a bugzilla
Packit Service 4b33e2
        user.
Packit Service 4b33e2
Packit Service 4b33e2
        :arg user: The e-mail address of the user to be acted upon. Can
Packit Service 4b33e2
            also be a list of emails.
Packit Service 4b33e2
        :arg action: add, remove, or set
Packit Service 4b33e2
        :arg groups: list of groups to be added to (i.e. ['fedora_contrib'])
Packit Service 4b33e2
        """
Packit Service 4b33e2
        groups = self._listify(groups)
Packit Service 4b33e2
        if action == "rem":
Packit Service 4b33e2
            action = "remove"
Packit Service 4b33e2
        if action not in ["add", "remove", "set"]:
Packit Service 4b33e2
            raise BugzillaError("Unknown user permission action '%s'" % action)
Packit Service 4b33e2
Packit Service 4b33e2
        update = {
Packit Service 4b33e2
            "names": self._listify(user),
Packit Service 4b33e2
            "groups": {
Packit Service 4b33e2
                action: groups,
Packit Service 4b33e2
            }
Packit Service 4b33e2
        }
Packit Service 4b33e2
Packit Service 4b33e2
        return self._proxy.User.update(update)