Skip to content

Commit 1b1e11e

Browse files
committed
bugzilla: Move transport classes to their own file
Just to slim down base.py a bit
1 parent 7352613 commit 1b1e11e

4 files changed

Lines changed: 190 additions & 169 deletions

File tree

bugzilla/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020

2121
from .apiversion import version, __version__
2222
from .base import BugzillaBase as _BugzillaBase
23-
from .base import BugzillaError
24-
from .base import RequestsTransport as _RequestsTransport
23+
from .transport import BugzillaError, _RequestsTransport
2524
from .bugzilla3 import Bugzilla3, Bugzilla32, Bugzilla34, Bugzilla36
2625
from .bugzilla4 import Bugzilla4, Bugzilla42, Bugzilla44
2726
from .rhbugzilla import RHBugzilla, RHBugzilla3, RHBugzilla4

bugzilla/base.py

Lines changed: 5 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,18 @@
2323
from configparser import SafeConfigParser
2424
from http.cookiejar import LoadError, LWPCookieJar, MozillaCookieJar
2525
from urllib.parse import urlparse, parse_qsl
26-
from xmlrpc.client import (
27-
Binary, Fault, ProtocolError, ServerProxy, Transport)
26+
from xmlrpc.client import Binary, Fault
2827
else:
2928
from ConfigParser import SafeConfigParser
3029
from cookielib import LoadError, LWPCookieJar, MozillaCookieJar
3130
from urlparse import urlparse, parse_qsl
32-
from xmlrpclib import (
33-
Binary, Fault, ProtocolError, ServerProxy, Transport)
31+
from xmlrpclib import Binary, Fault
3432

35-
import requests
3633

3734
from .apiversion import __version__
3835
from .bug import _Bug, _User
36+
from .transport import BugzillaError, _BugzillaServerProxy, _RequestsTransport
37+
3938

4039
log = getLogger(__name__)
4140

@@ -110,166 +109,6 @@ def _build_cookiejar(cookiefile):
110109
return retcj
111110

112111

113-
class _BugzillaToken(object):
114-
def __init__(self, uri, tokenfilename):
115-
self.tokenfilename = tokenfilename
116-
self.tokenfile = SafeConfigParser()
117-
self.domain = urlparse(uri)[1]
118-
119-
if self.tokenfilename:
120-
self.tokenfile.read(self.tokenfilename)
121-
122-
if self.domain not in self.tokenfile.sections():
123-
self.tokenfile.add_section(self.domain)
124-
125-
@property
126-
def value(self):
127-
if self.tokenfile.has_option(self.domain, 'token'):
128-
return self.tokenfile.get(self.domain, 'token')
129-
else:
130-
return None
131-
132-
@value.setter
133-
def value(self, value):
134-
if self.value == value:
135-
return
136-
137-
if value is None:
138-
self.tokenfile.remove_option(self.domain, 'token')
139-
else:
140-
self.tokenfile.set(self.domain, 'token', value)
141-
142-
if self.tokenfilename:
143-
with open(self.tokenfilename, 'w') as tokenfile:
144-
log.debug("Saving to tokenfile")
145-
self.tokenfile.write(tokenfile)
146-
147-
def __repr__(self):
148-
return '<Bugzilla Token :: %s>' % (self.value)
149-
150-
151-
class _BugzillaServerProxy(ServerProxy):
152-
def __init__(self, uri, tokenfile, *args, **kwargs):
153-
# pylint: disable=super-init-not-called
154-
# No idea why pylint complains here, must be a bug
155-
ServerProxy.__init__(self, uri, *args, **kwargs)
156-
self.token = _BugzillaToken(uri, tokenfile)
157-
158-
def clear_token(self):
159-
self.token.value = None
160-
161-
def _ServerProxy__request(self, methodname, params):
162-
if self.token.value is not None:
163-
if len(params) == 0:
164-
params = ({}, )
165-
166-
if 'Bugzilla_token' not in params[0]:
167-
params[0]['Bugzilla_token'] = self.token.value
168-
169-
# pylint: disable=maybe-no-member
170-
ret = ServerProxy._ServerProxy__request(self, methodname, params)
171-
# pylint: enable=maybe-no-member
172-
173-
if isinstance(ret, dict) and 'token' in ret.keys():
174-
self.token.value = ret.get('token')
175-
return ret
176-
177-
178-
class RequestsTransport(Transport):
179-
user_agent = 'Python/Bugzilla'
180-
181-
def __init__(self, url, cookiejar=None,
182-
sslverify=True, sslcafile=None, debug=0):
183-
# pylint: disable=W0231
184-
# pylint does not handle multiple import of Transport well
185-
if hasattr(Transport, "__init__"):
186-
Transport.__init__(self, use_datetime=False)
187-
188-
self.verbose = debug
189-
self._cookiejar = cookiejar
190-
191-
# transport constructor needs full url too, as xmlrpc does not pass
192-
# scheme to request
193-
self.scheme = urlparse(url)[0]
194-
if self.scheme not in ["http", "https"]:
195-
raise Exception("Invalid URL scheme: %s (%s)" % (self.scheme, url))
196-
197-
self.use_https = self.scheme == 'https'
198-
199-
self.request_defaults = {
200-
'cert': sslcafile if self.use_https else None,
201-
'cookies': cookiejar,
202-
'verify': sslverify,
203-
'headers': {
204-
'Content-Type': 'text/xml',
205-
'User-Agent': self.user_agent,
206-
}
207-
}
208-
209-
# Using an explicit Session, rather than requests.get, will use
210-
# HTTP KeepAlive if the server supports it.
211-
self.session = requests.Session()
212-
213-
def parse_response(self, response):
214-
""" Parse XMLRPC response """
215-
parser, unmarshaller = self.getparser()
216-
parser.feed(response.text.encode('utf-8'))
217-
parser.close()
218-
return unmarshaller.close()
219-
220-
def _request_helper(self, url, request_body):
221-
"""
222-
A helper method to assist in making a request and provide a parsed
223-
response.
224-
"""
225-
response = None
226-
try:
227-
response = self.session.post(
228-
url, data=request_body, **self.request_defaults)
229-
230-
# We expect utf-8 from the server
231-
response.encoding = 'UTF-8'
232-
233-
# update/set any cookies
234-
if self._cookiejar is not None:
235-
for cookie in response.cookies:
236-
self._cookiejar.set_cookie(cookie)
237-
238-
if self._cookiejar.filename is not None:
239-
# Save is required only if we have a filename
240-
self._cookiejar.save()
241-
242-
response.raise_for_status()
243-
return self.parse_response(response)
244-
except requests.RequestException:
245-
e = sys.exc_info()[1]
246-
if not response:
247-
raise
248-
raise ProtocolError(
249-
url, response.status_code, str(e), response.headers)
250-
except Fault:
251-
raise sys.exc_info()[1]
252-
except Exception:
253-
# pylint: disable=W0201
254-
e = BugzillaError(str(sys.exc_info()[1]))
255-
e.__traceback__ = sys.exc_info()[2]
256-
raise e
257-
258-
def request(self, host, handler, request_body, verbose=0):
259-
self.verbose = verbose
260-
url = "%s://%s%s" % (self.scheme, host, handler)
261-
262-
# xmlrpclib fails to escape \r
263-
request_body = request_body.replace(b'\r', b'&#xd;')
264-
265-
return self._request_helper(url, request_body)
266-
267-
268-
class BugzillaError(Exception):
269-
'''Error raised in the Bugzilla client code.'''
270-
pass
271-
272-
273112
class _FieldAlias(object):
274113
"""
275114
Track API attribute names that differ from what we expose in users.
@@ -616,7 +455,7 @@ def connect(self, url=None):
616455
url = self.url
617456
url = self.fix_url(url)
618457

619-
self._transport = RequestsTransport(
458+
self._transport = _RequestsTransport(
620459
url, self._cookiejar, sslverify=self._sslverify)
621460
self._transport.user_agent = self.user_agent
622461
self._proxy = _BugzillaServerProxy(url, self.tokenfile,

bugzilla/transport.py

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# This program is free software; you can redistribute it and/or modify it
2+
# under the terms of the GNU General Public License as published by the
3+
# Free Software Foundation; either version 2 of the License, or (at your
4+
# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
5+
# the full text of the license.
6+
7+
from logging import getLogger
8+
import sys
9+
10+
if hasattr(sys.version_info, "major") and sys.version_info.major >= 3:
11+
# pylint: disable=import-error,no-name-in-module
12+
from configparser import SafeConfigParser
13+
from urllib.parse import urlparse
14+
from xmlrpc.client import Fault, ProtocolError, ServerProxy, Transport
15+
else:
16+
from ConfigParser import SafeConfigParser
17+
from urlparse import urlparse # pylint: disable=ungrouped-imports
18+
from xmlrpclib import Fault, ProtocolError, ServerProxy, Transport
19+
20+
import requests
21+
22+
23+
log = getLogger(__name__)
24+
25+
26+
class BugzillaError(Exception):
27+
'''Error raised in the Bugzilla client code.'''
28+
pass
29+
30+
31+
class _BugzillaToken(object):
32+
def __init__(self, uri, tokenfilename):
33+
self.tokenfilename = tokenfilename
34+
self.tokenfile = SafeConfigParser()
35+
self.domain = urlparse(uri)[1]
36+
37+
if self.tokenfilename:
38+
self.tokenfile.read(self.tokenfilename)
39+
40+
if self.domain not in self.tokenfile.sections():
41+
self.tokenfile.add_section(self.domain)
42+
43+
@property
44+
def value(self):
45+
if self.tokenfile.has_option(self.domain, 'token'):
46+
return self.tokenfile.get(self.domain, 'token')
47+
else:
48+
return None
49+
50+
@value.setter
51+
def value(self, value):
52+
if self.value == value:
53+
return
54+
55+
if value is None:
56+
self.tokenfile.remove_option(self.domain, 'token')
57+
else:
58+
self.tokenfile.set(self.domain, 'token', value)
59+
60+
if self.tokenfilename:
61+
with open(self.tokenfilename, 'w') as tokenfile:
62+
log.debug("Saving to tokenfile")
63+
self.tokenfile.write(tokenfile)
64+
65+
def __repr__(self):
66+
return '<Bugzilla Token :: %s>' % (self.value)
67+
68+
69+
class _BugzillaServerProxy(ServerProxy):
70+
def __init__(self, uri, tokenfile, *args, **kwargs):
71+
# pylint: disable=super-init-not-called
72+
# No idea why pylint complains here, must be a bug
73+
ServerProxy.__init__(self, uri, *args, **kwargs)
74+
self.token = _BugzillaToken(uri, tokenfile)
75+
76+
def clear_token(self):
77+
self.token.value = None
78+
79+
def _ServerProxy__request(self, methodname, params):
80+
if self.token.value is not None:
81+
if len(params) == 0:
82+
params = ({}, )
83+
84+
if 'Bugzilla_token' not in params[0]:
85+
params[0]['Bugzilla_token'] = self.token.value
86+
87+
# pylint: disable=maybe-no-member
88+
ret = ServerProxy._ServerProxy__request(self, methodname, params)
89+
# pylint: enable=maybe-no-member
90+
91+
if isinstance(ret, dict) and 'token' in ret.keys():
92+
self.token.value = ret.get('token')
93+
return ret
94+
95+
96+
class _RequestsTransport(Transport):
97+
user_agent = 'Python/Bugzilla'
98+
99+
def __init__(self, url, cookiejar=None,
100+
sslverify=True, sslcafile=None, debug=0):
101+
# pylint: disable=W0231
102+
# pylint does not handle multiple import of Transport well
103+
if hasattr(Transport, "__init__"):
104+
Transport.__init__(self, use_datetime=False)
105+
106+
self.verbose = debug
107+
self._cookiejar = cookiejar
108+
109+
# transport constructor needs full url too, as xmlrpc does not pass
110+
# scheme to request
111+
self.scheme = urlparse(url)[0]
112+
if self.scheme not in ["http", "https"]:
113+
raise Exception("Invalid URL scheme: %s (%s)" % (self.scheme, url))
114+
115+
self.use_https = self.scheme == 'https'
116+
117+
self.request_defaults = {
118+
'cert': sslcafile if self.use_https else None,
119+
'cookies': cookiejar,
120+
'verify': sslverify,
121+
'headers': {
122+
'Content-Type': 'text/xml',
123+
'User-Agent': self.user_agent,
124+
}
125+
}
126+
127+
# Using an explicit Session, rather than requests.get, will use
128+
# HTTP KeepAlive if the server supports it.
129+
self.session = requests.Session()
130+
131+
def parse_response(self, response):
132+
""" Parse XMLRPC response """
133+
parser, unmarshaller = self.getparser()
134+
parser.feed(response.text.encode('utf-8'))
135+
parser.close()
136+
return unmarshaller.close()
137+
138+
def _request_helper(self, url, request_body):
139+
"""
140+
A helper method to assist in making a request and provide a parsed
141+
response.
142+
"""
143+
response = None
144+
try:
145+
response = self.session.post(
146+
url, data=request_body, **self.request_defaults)
147+
148+
# We expect utf-8 from the server
149+
response.encoding = 'UTF-8'
150+
151+
# update/set any cookies
152+
if self._cookiejar is not None:
153+
for cookie in response.cookies:
154+
self._cookiejar.set_cookie(cookie)
155+
156+
if self._cookiejar.filename is not None:
157+
# Save is required only if we have a filename
158+
self._cookiejar.save()
159+
160+
response.raise_for_status()
161+
return self.parse_response(response)
162+
except requests.RequestException:
163+
e = sys.exc_info()[1]
164+
if not response:
165+
raise
166+
raise ProtocolError(
167+
url, response.status_code, str(e), response.headers)
168+
except Fault:
169+
raise sys.exc_info()[1]
170+
except Exception:
171+
# pylint: disable=W0201
172+
e = BugzillaError(str(sys.exc_info()[1]))
173+
e.__traceback__ = sys.exc_info()[2]
174+
raise e
175+
176+
def request(self, host, handler, request_body, verbose=0):
177+
self.verbose = verbose
178+
url = "%s://%s%s" % (self.scheme, host, handler)
179+
180+
# xmlrpclib fails to escape \r
181+
request_body = request_body.replace(b'\r', b'&#xd;')
182+
183+
return self._request_helper(url, request_body)

0 commit comments

Comments
 (0)