# HG changeset patch # User Jeff Hammel # Date 1291936592 28800 # Node ID c819c8388ad13532982e3fbe07ed5b49ead37b64 # Parent 9090fe0cdb720ee7b9df354e83ed0b3599e6a7df * separate api class * read from ~/.bz dotfile diff -r 9090fe0cdb72 -r c819c8388ad1 bzconsole/api.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bzconsole/api.py Thu Dec 09 15:16:32 2010 -0800 @@ -0,0 +1,179 @@ +""" +console API to bugzilla +""" + +import httplib +import json +import os +import subprocess +import sys +import urllib +import urllib2 + +from StringIO import StringIO + +from utils import tmpbuffer + + +class BZapi(object): + """ + console API to bugzilla + """ + + config_cache = '.bzconfiguration' + # XXX currently this is hard coded for one server + + def __init__(self, + server='https://api-dev.bugzilla.mozilla.org/latest', + refresh=False, + print_request=None, + username=None, + password=None): + """ + - refresh : refresh the (cached) configuration + - print_request : log any request information to a file + """ + self.server = server + self.refresh = refresh + self.print_request = print_request + self.username = username + self.password = password + + def products(self, classification=None): + """list bugzilla products""" + configuration = self.configuration() + if classification: + products = [i for i in configuration['product'] if configuration['product'][i]['classification'] == 'Components'] + return sorted(products) + else: + return sorted(configuration['product'].keys()) + + def components(self, product): + """list bugzilla components for a particular product""" + configuration = self.configuration() + assert product in configuration['product'], 'Product %s not found' % product + return sorted(configuration['product'][product]['component'].keys()) + + def _unique_components(self): + """returns a dict of unique component, product""" + retval = {} + dupe = set() + for product in self.products(): + for component in self.components(product): + if component in retval: + dupe.add(component) + else: + retval[component] = product + for d in dupe: + del retval[d] + return retval, dupe + + def new(self, product, component, title, version=None, description=None): + """file a new bug. username and password must be set""" + + # sanity check + assert product in self.products(), "Product not found" + assert component in self.components(product), "Component not found" + assert title, 'Must provide a bug summary' + assert self.username and self.password, "Must be authenticated" + + # infer version if not given + if version is None: + version = 'unspecified' + assert version in self._configuration['product'][product]['version'], 'Version not found' + + # get the bug description + if not description: + description = tmpbuffer() + assert description, "Must provide a non-empty description" + + # create the needed data structure + request = dict(product=product, component=component, + summary=title, version=version, + comments=[self._comment(description)], + op_sys='All', platform='All',) + + # POST the request + try: + results = self._request('/bug', request) + except Exception, e: + raise + + # return the URL + return results['ref'] + + def configuration(self): + """bugzilla configuration""" + + if not hasattr(self, '_configuration'): + config_cache = os.path.join(os.environ['HOME'], self.config_cache) + if not self.refresh: + try: + self._configuration = json.loads(file(config_cache).read()) + except: + pass + if not getattr(self, '_configuration', None): + self._configuration = self._request('/configuration') + if not self.print_request: + f = file(config_cache, 'w') + print >> f, json.dumps(self._configuration) + self.refresh = False + return self._configuration + + ### internal methods + + def _comment(self, text): + retval = {'is_private': False, 'text': text} + return retval + + def _request(self, path, data=None): + url = self.server + path + query = {} + if self.username: + query['username'] = self.username + if self.password: + query['password'] = self.password + if query: + query = urllib.urlencode(query) + url += '?' + query + headers = {'Accept': 'application/json', + 'Content-Type': 'application/json'} + if data: + data = json.dumps(data) + req = urllib2.Request(url, data, headers) + + # print out the request + # from http://stackoverflow.com/questions/603856/how-do-you-get-default-headers-in-a-urllib2-request + if self.print_request: + + f = file(self.print_request, 'a') + class MyHTTPConnection(httplib.HTTPConnection): + def send(self, s): + print >> f, s # or save them, or whatever! + httplib.HTTPConnection.send(self, s) + + class MyHTTPSConnection(httplib.HTTPSConnection): + def send(self, s): + print >> f, s + httplib.HTTPSConnection.send(self, s) + + class MyHTTPHandler(urllib2.HTTPHandler): + def http_open(self, req): + return self.do_open(MyHTTPConnection, req) + class MyHTTPSHandler(urllib2.HTTPSHandler): + def https_open(self, req): + return self.do_open(MyHTTPSConnection, req) + + if self.server.startswith('https://'): + opener = urllib2.build_opener(MyHTTPSHandler) + else: + opener = urllib2.build_opener(MyHTTPHandler) + opener.open(req) + return + + try: + response = urllib2.urlopen(req) + except Exception, e: + import pdb; pdb.set_trace() + the_page = response.read() + return json.loads(the_page) diff -r 9090fe0cdb72 -r c819c8388ad1 bzconsole/command.py --- a/bzconsole/command.py Thu Dec 09 13:51:46 2010 -0800 +++ b/bzconsole/command.py Thu Dec 09 15:16:32 2010 -0800 @@ -4,9 +4,14 @@ import inspect import json +import os import sys from optparse import OptionParser from pprint import pprint + +class Undefined(object): + def __init__(self, default): + self.default=default class CommandParser(OptionParser): # TODO: add `help` command @@ -31,6 +36,10 @@ self.disable_interspersed_args() + def add_option(self, *args, **kwargs): + kwargs['default'] = Undefined(kwargs.get('default')) + OptionParser.add_option(self, *args, **kwargs) + def print_help(self): OptionParser.print_help(self) @@ -84,7 +93,28 @@ name, args = self.parse(args) # setup - _object = self._class(**self.options.__dict__) + options = {} + dotfile = os.path.join(os.environ['HOME'], '.' + self.get_prog_name()) + if os.path.exists(dotfile): + f = file(dotfile) + for line in f.readlines(): + line = line.strip() + if not line: + continue + if ':' in line: + key, value = [i.strip() + for i in line.split(':', 1)] + options[key] = value + else: + print >> sys.stderr, "Bad option line: " + line + for key, value in self.options.__dict__.items(): + if isinstance(value, Undefined): + if key in options: + continue + options[key] = value.default + else: + options[key] = value.default + _object = self._class(**options) # command specific args command = self.commands[name] diff -r 9090fe0cdb72 -r c819c8388ad1 bzconsole/main.py --- a/bzconsole/main.py Thu Dec 09 13:51:46 2010 -0800 +++ b/bzconsole/main.py Thu Dec 09 15:16:32 2010 -0800 @@ -1,187 +1,37 @@ #!/usr/bin/env python -""" -console API to bugzilla -""" -import httplib -import json -import os -import subprocess import sys -import urllib -import urllib2 - +from api import BZapi +from command import CommandParser from StringIO import StringIO -from command import CommandParser -from utils import tmpbuffer - - -class BZapi(object): - """ - console API to bugzilla - """ - - config_cache = '.bzconfiguration' - # XXX currently this is hard coded for one server - - def __init__(self, - server='https://api-dev.bugzilla.mozilla.org/latest', - refresh=False, - print_request=None, - username=None, - password=None): - """ - - refresh : refresh the (cached) configuration - - print_request : log any request information to a file - """ - self.server = server - self.refresh = refresh - self.print_request = print_request - self.username = username - self.password = password - - def products(self, classification=None): - """list bugzilla products""" - configuration = self.configuration() - if classification: - products = [i for i in configuration['product'] if configuration['product'][i]['classification'] == 'Components'] - return sorted(products) - else: - return sorted(configuration['product'].keys()) +class BZcli(BZapi): + """command line interface front-end for the API class""" - def components(self, product): - """list bugzilla components for a particular product""" - configuration = self.configuration() - assert product in configuration['product'], 'Product %s not found' % product - return sorted(configuration['product'][product]['component'].keys()) - - def _unique_components(self): - """returns a dict of unique component, product""" - retval = {} - dupe = set() - for product in self.products(): - for component in self.components(product): - if component in retval: - dupe.add(component) - else: - retval[component] = product - for d in dupe: - del retval[d] - return retval - - def new(self, product, component, title, version=None, description=None): - """file a new bug. username and password must be set""" - - # sanity check - assert product in self.products(), "Product not found" - assert component in self.components(product), "Component not found" - assert title, 'Must provide a bug summary' - assert self.username and self.password, "Must be authenticated" - - # infer version if not given - if version is None: - version = 'unspecified' - assert version in self._configuration['product'][product]['version'], 'Version not found' - - # get the bug description - if not description: - description = tmpbuffer() - assert description, "Must provide a non-empty description" - - # create the needed data structure - request = dict(product=product, component=component, - summary=title, version=version, - comments=[self._comment(description)], - op_sys='All', platform='All',) + def unique(self, component=None): + """display unique and duplicated components""" + unique, dupe = self._unique_components() - # POST the request - try: - results = self._request('/bug', request) - except Exception, e: - raise - - # return the URL - return results['ref'] - - def configuration(self): - """bugzilla configuration""" - - if not hasattr(self, '_configuration'): - config_cache = os.path.join(os.environ['HOME'], self.config_cache) - if not self.refresh: - try: - self._configuration = json.loads(file(config_cache).read()) - except: - pass - if not getattr(self, '_configuration', None): - self._configuration = self._request('/configuration') - if not self.print_request: - f = file(config_cache, 'w') - print >> f, json.dumps(self._configuration) - self.refresh = False - return self._configuration - - ### internal methods - - def _comment(self, text): - retval = {'is_private': False, 'text': text} - return retval - - def _request(self, path, data=None): - url = self.server + path - query = {} - if self.username: - query['username'] = self.username - if self.password: - query['password'] = self.password - if query: - query = urllib.urlencode(query) - url += '?' + query - headers = {'Accept': 'application/json', - 'Content-Type': 'application/json'} - if data: - data = json.dumps(data) - req = urllib2.Request(url, data, headers) - - # print out the request - # from http://stackoverflow.com/questions/603856/how-do-you-get-default-headers-in-a-urllib2-request - if self.print_request: - - f = file(self.print_request, 'a') - class MyHTTPConnection(httplib.HTTPConnection): - def send(self, s): - print >> f, s # or save them, or whatever! - httplib.HTTPConnection.send(self, s) - - class MyHTTPSConnection(httplib.HTTPSConnection): - def send(self, s): - print >> f, s - httplib.HTTPSConnection.send(self, s) - - class MyHTTPHandler(urllib2.HTTPHandler): - def http_open(self, req): - return self.do_open(MyHTTPConnection, req) - class MyHTTPSHandler(urllib2.HTTPSHandler): - def https_open(self, req): - return self.do_open(MyHTTPSConnection, req) - - if self.server.startswith('https://'): - opener = urllib2.build_opener(MyHTTPSHandler) - else: - opener = urllib2.build_opener(MyHTTPHandler) - opener.open(req) - return - - try: - response = urllib2.urlopen(req) - except Exception, e: - import pdb; pdb.set_trace() - the_page = response.read() - return json.loads(the_page) + if component: + # determine if the component is unique + if component in unique: + return '%s: %s' % (component, unique[component]) + if component in dupe: + return '>>>DUPLICATE<<<' + return 'Component "%s" not found' % component + + buffer = StringIO() + print >> buffer, 'Unique:' + for key in sorted(unique.keys()): + print >> buffer, ' %s: %s' % (key, unique[key]) + print >> buffer + print >> buffer, 'Duplicate:' + for value in sorted(dupe): + print >> buffer, ' %s' % value + return buffer.getvalue() def main(args=sys.argv[1:]): - parser = CommandParser(BZapi) + parser = CommandParser(BZcli) parser.invoke(args) if __name__ == '__main__':