diff --git a/jenkins/__init__.py b/jenkins/__init__.py index 4efe64b..3029472 100644 --- a/jenkins/__init__.py +++ b/jenkins/__init__.py @@ -46,10 +46,13 @@ See examples at :doc:`example` ''' import base64 -from httplib import BadStatusLine import json -import urllib -from urllib2 import Request, HTTPError, urlopen + +import six +from six.moves.http_client import BadStatusLine +from six.moves.urllib.error import HTTPError +from six.moves.urllib.parse import quote, urlencode +from six.moves.urllib.request import Request, urlopen LAUNCHER_SSH = 'hudson.plugins.sshslaves.SSHLauncher' LAUNCHER_COMMAND = 'hudson.slaves.CommandLauncher' @@ -129,7 +132,10 @@ def auth_headers(username, password): Simple implementation of HTTP Basic Authentication. Returns the 'Authentication' header value. ''' - return 'Basic ' + base64.encodestring('%s:%s' % (username, password))[:-1] + auth = '%s:%s' % (username, password) + if isinstance(auth, six.text_type): + auth = auth.encode('utf-8') + return b'Basic ' + base64.encodestring(auth)[:-1] class Jenkins(object): @@ -160,7 +166,7 @@ class Jenkins(object): response = self.jenkins_open(Request( self.server + CRUMB_URL), add_crumb=False) if response: - self.crumb = json.loads(response) + self.crumb = json.loads(response.decode('utf-8')) else: # Don't need crumbs self.crumb = False @@ -212,8 +218,8 @@ class Jenkins(object): ''' Print out job info in more readable format ''' - for k, v in self.get_job_info(job_name).iteritems(): - print k, v + for k, v in self.get_job_info(job_name).items(): + print(k, v) def jenkins_open(self, req, add_crumb=True): ''' @@ -227,7 +233,7 @@ class Jenkins(object): if add_crumb: self.maybe_add_crumb(req) return urlopen(req).read() - except HTTPError, e: + except HTTPError as e: # Jenkins's funky authentication means its nigh impossible to # distinguish errors. if e.code in [401, 403, 500]: @@ -425,7 +431,7 @@ class Jenkins(object): :returns: job configuration (XML format) ''' request = Request(self.server + CONFIG_JOB % - {"name": urllib.quote(name)}) + {"name": quote(name)}) return self.jenkins_open(request) def reconfig_job(self, name, config_xml): @@ -454,10 +460,10 @@ class Jenkins(object): if token: parameters['token'] = token return (self.server + BUILD_WITH_PARAMS_JOB % locals() + - '?' + urllib.urlencode(parameters)) + '?' + urlencode(parameters)) elif token: return (self.server + BUILD_JOB % locals() + - '?' + urllib.urlencode({'token': token})) + '?' + urlencode({'token': token})) else: return self.server + BUILD_JOB % locals() @@ -597,7 +603,7 @@ class Jenkins(object): } self.jenkins_open(Request( - self.server + CREATE_NODE % urllib.urlencode(params))) + self.server + CREATE_NODE % urlencode(params))) if not self.node_exists(name): raise JenkinsException('create[%s] failed' % (name)) diff --git a/test-requirements.txt b/test-requirements.txt index 69825cc..3cbc320 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -2,4 +2,4 @@ coverage>=3.6 discover flake8 mock -unittest2 +six diff --git a/tests/helper.py b/tests/helper.py index 4962a61..a712654 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -3,4 +3,3 @@ import sys sys.path.insert(0, os.path.abspath('..')) import jenkins # noqa -from StringIO import StringIO # noqa diff --git a/tests/test_jenkins.py b/tests/test_jenkins.py index 3bba110..8206372 100644 --- a/tests/test_jenkins.py +++ b/tests/test_jenkins.py @@ -1,9 +1,20 @@ import json -import unittest2 as unittest +import sys +if sys.version_info < (2, 7): + import unittest2 as unittest +else: + import unittest from mock import patch +import six -from tests.helper import jenkins, StringIO +from tests.helper import jenkins + + +def get_mock_urlopen_return_value(a_dict=None): + if a_dict is None: + a_dict = {} + return six.BytesIO(json.dumps(a_dict).encode('utf-8')) class JenkinsTest(unittest.TestCase): @@ -11,13 +22,13 @@ class JenkinsTest(unittest.TestCase): def test_constructor_url_with_trailing_slash(self): j = jenkins.Jenkins('http://example.com/', 'test', 'test') self.assertEqual(j.server, 'http://example.com/') - self.assertEqual(j.auth, 'Basic dGVzdDp0ZXN0') + self.assertEqual(j.auth, b'Basic dGVzdDp0ZXN0') self.assertEqual(j.crumb, None) def test_constructor_url_without_trailing_slash(self): j = jenkins.Jenkins('http://example.com', 'test', 'test') self.assertEqual(j.server, 'http://example.com/') - self.assertEqual(j.auth, 'Basic dGVzdDp0ZXN0') + self.assertEqual(j.auth, b'Basic dGVzdDp0ZXN0') self.assertEqual(j.crumb, None) def test_constructor_without_user_or_password(self): @@ -26,6 +37,14 @@ class JenkinsTest(unittest.TestCase): self.assertEqual(j.auth, None) self.assertEqual(j.crumb, None) + def test_constructor_unicode_password(self): + j = jenkins.Jenkins('http://example.com', + six.u('nonascii'), + six.u('\xe9\u20ac')) + self.assertEqual(j.server, 'http://example.com/') + self.assertEqual(j.auth, b'Basic bm9uYXNjaWk6w6nigqw=') + self.assertEqual(j.crumb, None) + @patch.object(jenkins.Jenkins, 'jenkins_open') def test_get_job_config_encodes_job_name(self, jenkins_mock): """ @@ -40,7 +59,7 @@ class JenkinsTest(unittest.TestCase): @patch('jenkins.urlopen') def test_maybe_add_crumb(self, jenkins_mock): - jenkins_mock.return_value = StringIO() + jenkins_mock.return_value = get_mock_urlopen_return_value() j = jenkins.Jenkins('http://example.com/', 'test', 'test') request = jenkins.Request('http://example.com/job/TestJob') @@ -58,7 +77,7 @@ class JenkinsTest(unittest.TestCase): "crumb": "dab177f483b3dd93483ef6716d8e792d", "crumbRequestField": ".crumb", } - jenkins_mock.return_value = StringIO(json.dumps(crumb_data)) + jenkins_mock.return_value = get_mock_urlopen_return_value(crumb_data) j = jenkins.Jenkins('http://example.com/', 'test', 'test') request = jenkins.Request('http://example.com/job/TestJob') @@ -78,8 +97,8 @@ class JenkinsTest(unittest.TestCase): } data = {'foo': 'bar'} jenkins_mock.side_effect = [ - StringIO(json.dumps(crumb_data)), - StringIO(json.dumps(data)), + get_mock_urlopen_return_value(crumb_data), + get_mock_urlopen_return_value(data), ] j = jenkins.Jenkins('http://example.com/', 'test', 'test') request = jenkins.Request('http://example.com/job/TestJob') @@ -89,7 +108,7 @@ class JenkinsTest(unittest.TestCase): self.assertEqual( jenkins_mock.call_args[0][0].get_full_url(), 'http://example.com/job/TestJob') - self.assertEqual(response, json.dumps(data)) + self.assertEqual(response, json.dumps(data).encode('utf-8')) self.assertEqual(j.crumb, crumb_data) self.assertEqual(request.headers['.crumb'], crumb_data['crumb']) diff --git a/tox.ini b/tox.ini index d9424b8..a5f8ced 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] minversion = 1.6 skipsdist = True -envlist = pep8, py26, py27 +envlist = pep8, py26, py27, pypy, py33, py34 [testenv] setenv VIRTUAL_ENV={envdir} @@ -15,6 +15,10 @@ commands = coverage run -m discover coverage report --show-missing +[testenv:py26] +deps = -r{toxinidir}/test-requirements.txt + unittest2 + [tox:jenkins] downloadcache = ~/cache/pip