Skip to content

Commit 65ce2c6

Browse files
author
Will Woods
committed
initial import
0 parents  commit 65ce2c6

1 file changed

Lines changed: 199 additions & 0 deletions

File tree

bugzilla.py

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
#!/usr/bin/python
2+
# bugzilla.py - a Python interface to Bugzilla, using xmlrpclib.
3+
#
4+
# Copyright (C) 2007 Red Hat Inc.
5+
# Author: Will Woods <[email protected]>
6+
#
7+
# This program is free software; you can redistribute it and/or modify it
8+
# under the terms of the GNU General Public License as published by the
9+
# Free Software Foundation; either version 2 of the License, or (at your
10+
# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
11+
# the full text of the license.
12+
13+
import xmlrpclib, urllib2, cookielib
14+
15+
version = '0.1'
16+
user_agent = 'bugzilla.py/%s (Python-urllib2/%s)' % \
17+
(version,urllib2.__version__)
18+
19+
class Bugzilla(object):
20+
def __init__(self,**kwargs):
21+
# Settings the user might want to tweak
22+
self.user = ''
23+
self.password = ''
24+
self.url = ''
25+
# Bugzilla object state info that users shouldn't mess with
26+
self._cookiejar = None
27+
self._proxy = None
28+
self._opener = None
29+
if 'cookies' in kwargs:
30+
self.readcookiefile(kwargs['cookies'])
31+
if 'url' in kwargs:
32+
self.connect(kwargs['url'])
33+
if 'user' in kwargs:
34+
self.user = kwargs['user']
35+
if 'password' in kwargs:
36+
self.password = kwargs['password']
37+
38+
def readcookiefile(self,cookiefile):
39+
'''Read the given (Mozilla-style) cookie file and fill in the cookiejar,
40+
allowing us to use the user's saved credentials to access bugzilla.'''
41+
cj = cookielib.MozillaCookieJar()
42+
cj.load(cookiefile)
43+
self._cookiejar = cj
44+
self._cookiejar.filename = cookiefile
45+
46+
def connect(self,url):
47+
'''Connect to the bugzilla instance with the given url.'''
48+
# Set up the transport
49+
if url.startswith('https'):
50+
self._transport = SafeCookieTransport()
51+
else:
52+
self._transport = CookieTransport()
53+
self._transport.user_agent = user_agent
54+
self._transport.cookiejar = self._cookiejar or cookielib.CookieJar()
55+
# Set up the proxy, using the transport
56+
self._proxy = xmlrpclib.ServerProxy(url,self._transport)
57+
# Set up the urllib2 opener (using the same cookiejar)
58+
handler = urllib2.HTTPCookieProcessor(self._cookiejar)
59+
self._opener = urllib2.build_opener(handler)
60+
self._opener.addheaders = [('User-agent',user_agent)]
61+
62+
def login(self,user,password):
63+
'''Attempt to log in using the given username and password. Subsequent
64+
method calls will use this username and password. Returns False if
65+
login fails, otherwise returns a dict of user info.
66+
67+
Note that it is not required to login before calling other methods;
68+
you may just set user and password and call whatever methods you like.
69+
'''
70+
self.user = user
71+
self.password = password
72+
try:
73+
r = self._proxy.bugzilla.login(self.user,self.password)
74+
except xmlrpclib.Fault, f:
75+
r = False
76+
return r
77+
78+
# ARGLE this should use properties or do some kind of caching or something
79+
def components(self,product):
80+
'''Return a dict of components for the given product.'''
81+
return self._proxy.bugzilla.getProdCompInfo(product, self.user,
82+
self.password)
83+
84+
def products(self):
85+
'''Return a dict of product names and product descriptions.'''
86+
return self._proxy.bugzilla.getProdInfo(self.user, self.password)
87+
88+
def getbug(self,id):
89+
'''Return a dict of full bug info for the given bug id'''
90+
return self._proxy.bugzilla.getBug(id, self.user, self.password)
91+
92+
def getbugsimple(self,id):
93+
'''Return a short dict of simple bug info for the given bug id'''
94+
return self._proxy.bugzilla.getBugSimple(id, self.user, self.password)
95+
96+
# TODO: createbug, addcomment, attachfile, searchbugs
97+
98+
class CookieTransport(xmlrpclib.Transport):
99+
'''A subclass of xmlrpclib.Transport that supports cookies.'''
100+
cookiejar = None
101+
scheme = 'http'
102+
103+
# Cribbed from xmlrpclib.Transport.send_user_agent
104+
def send_cookies(self, connection, cookie_request):
105+
if self.cookiejar is None:
106+
self.cookiejar = cookielib.CookieJar()
107+
elif self.cookiejar:
108+
# Let the cookiejar figure out what cookies are appropriate
109+
self.cookiejar.add_cookie_header(cookie_request)
110+
# Pull the cookie headers out of the request object...
111+
cookielist=list()
112+
for h,v in cookie_request.header_items():
113+
if h.startswith('Cookie'):
114+
cookielist.append([h,v])
115+
# ...and put them over the connection
116+
for h,v in cookielist:
117+
connection.putheader(h,v)
118+
119+
# This is the same request() method from xmlrpclib.Transport,
120+
# with a couple additions noted below
121+
def request(self, host, handler, request_body, verbose=0):
122+
h = self.make_connection(host)
123+
if verbose:
124+
h.set_debuglevel(1)
125+
126+
# ADDED: construct the URL and Request object for proper cookie handling
127+
request_url = "%s://%s/" % (self.scheme,host)
128+
cookie_request = urllib2.Request(request_url)
129+
130+
self.send_request(h,handler,request_body)
131+
self.send_host(h,host)
132+
self.send_cookies(h,cookie_request) # ADDED. creates cookiejar if None.
133+
self.send_user_agent(h)
134+
self.send_content(h,request_body)
135+
136+
errcode, errmsg, headers = h.getreply()
137+
138+
# ADDED: parse headers and get cookies here
139+
# fake a response object that we can fill with the headers above
140+
class CookieResponse:
141+
def __init__(self,headers): self.headers = headers
142+
def info(self): return self.headers
143+
cookie_response = CookieResponse(headers)
144+
# Okay, extract the cookies from the headers
145+
self.cookiejar.extract_cookies(cookie_response,cookie_request)
146+
# And write back any changes
147+
self.cookiejar.save(self.cookiejar.filename)
148+
149+
if errcode != 200:
150+
raise xmlrpclib.ProtocolError(
151+
host + handler,
152+
errcode, errmsg,
153+
headers
154+
)
155+
156+
self.verbose = verbose
157+
158+
try:
159+
sock = h._conn.sock
160+
except AttributeError:
161+
sock = None
162+
163+
return self._parse_response(h.getfile(), sock)
164+
165+
class SafeCookieTransport(xmlrpclib.SafeTransport,CookieTransport):
166+
'''SafeTransport subclass that supports cookies.'''
167+
scheme = 'https'
168+
request = CookieTransport.request
169+
170+
import os, glob
171+
172+
def find_firefox_cookiefile():
173+
cookieglob = os.path.expanduser('~/.mozilla/firefox/default.*/cookies.txt')
174+
cookiefiles = glob.glob(cookieglob)
175+
if cookiefiles:
176+
# TODO return whichever is newest
177+
return cookiefiles[0]
178+
179+
def selftest():
180+
url = 'https://bugzilla.redhat.com/xmlrpc.cgi'
181+
cookies = find_firefox_cookiefile()
182+
public_bug = 1
183+
private_bug = 250666
184+
print "Woo, welcome to the bugzilla.py self-test."
185+
print "Using bugzilla at " + url
186+
print "Reading cookies from " + cookies
187+
b = Bugzilla(url=url,cookies=cookies)
188+
print "Reading product list"
189+
print b.products()
190+
print "Reading public bug (#%i)" % public_bug
191+
print b.getbug(public_bug)
192+
print "Reading private bug (#%i)" % private_bug
193+
try:
194+
print b.getbug(private_bug)
195+
except xmlrpclib.Fault, e:
196+
if 'NotPermitted' in e.faultString:
197+
print "Failed: Not authorized."
198+
else:
199+
print "Failed: Unknown XMLRPC error: %s" % e

0 commit comments

Comments
 (0)