← Back to team overview

python-jenkins-developers team mailing list archive

[Merge] lp:~msabramo/python-jenkins/python3 into lp:python-jenkins

 

Marc Abramowitz has proposed merging lp:~msabramo/python-jenkins/python3 into lp:python-jenkins.

Requested reviews:
  Python Jenkins Developers (python-jenkins-developers)

For more details, see:
https://code.launchpad.net/~msabramo/python-jenkins/python3/+merge/214664

This builds on https://code.launchpad.net/~msabramo/python-jenkins/python3_small_tweaks/+merge/214584 and adds some more aggressive changes to make the tests pass in Python 3.

Of course, there is only 1 test and the coverage is only 35% so we should temper our enthusiasm and work on adding more tests before merging.

```
$ tox
...
  py26: commands succeeded
  py27: commands succeeded
  pypy: commands succeeded
  py33: commands succeeded
  congratulations :)
```
-- 
https://code.launchpad.net/~msabramo/python-jenkins/python3/+merge/214664
Your team Python Jenkins Developers is requested to review the proposed merge of lp:~msabramo/python-jenkins/python3 into lp:python-jenkins.
=== added file '.bzrignore'
--- .bzrignore	1970-01-01 00:00:00 +0000
+++ .bzrignore	2014-04-08 06:02:58 +0000
@@ -0,0 +1,10 @@
+.tox
+MANIFEST
+*.egg-info
+*.egg
+*.pyc
+__pycache__
+build
+dist
+.coverage
+coverage.xml

=== modified file 'jenkins/__init__.py'
--- jenkins/__init__.py	2013-09-28 20:00:43 +0000
+++ jenkins/__init__.py	2014-04-08 06:02:58 +0000
@@ -46,12 +46,27 @@
 '''
 
 #import sys
-import urllib2
-import urllib
+try:
+    # Python 2
+    from urllib2 import Request
+except ImportError:
+    # Python 3
+    from urllib.request import Request
+try:
+    # Python 2
+    from urllib import quote
+except ImportError:
+    # Python 3
+    from urllib.parse import quote
 import base64
 #import traceback
 import json
-import httplib
+try:
+    # Python 2
+    from httplib import BadStatusLine
+except ImportError:
+    # Python 3
+    from http.client import BadStatusLine
 
 LAUNCHER_SSH = 'hudson.plugins.sshslaves.SSHLauncher'
 LAUNCHER_COMMAND = 'hudson.slaves.CommandLauncher'
@@ -131,7 +146,10 @@
     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)
+    auth = auth[:-1]
+    auth = auth.encode('ascii')
+    return b'Basic ' + base64.encodestring(auth)
 
 
 class Jenkins(object):
@@ -159,7 +177,7 @@
     def maybe_add_crumb(self, req):
         # We don't know yet whether we need a crumb
         if self.crumb is None:
-            response = self.jenkins_open(urllib2.Request(
+            response = self.jenkins_open(Request(
                 self.server + CRUMB_URL), add_crumb=False)
             if response:
                 self.crumb = json.loads(response)
@@ -177,7 +195,7 @@
         :returns: dictionary of job information
         '''
         try:
-            response = self.jenkins_open(urllib2.Request(
+            response = self.jenkins_open(Request(
                 self.server + JOB_INFO % locals()))
             if response:
                 return json.loads(response)
@@ -199,7 +217,7 @@
         :returns: Name of job or None
         '''
         response = self.jenkins_open(
-            urllib2.Request(self.server + JOB_NAME % locals()))
+            Request(self.server + JOB_NAME % locals()))
         if response:
             if json.loads(response)['name'] != name:
                 raise JenkinsException(
@@ -213,7 +231,7 @@
         Print out job info in more readable format
         '''
         for k, v in self.get_job_info(job_name).iteritems():
-            print k, v
+            print(k, v)
 
     def jenkins_open(self, req, add_crumb=True):
         '''
@@ -227,7 +245,7 @@
             if add_crumb:
                 self.maybe_add_crumb(req)
             return urllib2.urlopen(req).read()
-        except urllib2.HTTPError, e:
+        except urllib2.HTTPError as e:
             # Jenkins's funky authentication means its nigh impossible to
             # distinguish errors.
             if e.code in [401, 403, 500]:
@@ -255,7 +273,7 @@
             {u'building': False, u'changeSet': {u'items': [{u'date': u'2011-12-19T18:01:52.540557Z', u'msg': u'test', u'revision': 66, u'user': u'unknown', u'paths': [{u'editType': u'edit', u'file': u'/branches/demo/index.html'}]}], u'kind': u'svn', u'revisions': [{u'module': u'http://eaas-svn01.i3.level3.com/eaas', u'revision': 66}]}, u'builtOn': u'', u'description': None, u'artifacts': [{u'relativePath': u'dist/eaas-87-2011-12-19_18-01-57.war', u'displayPath': u'eaas-87-2011-12-19_18-01-57.war', u'fileName': u'eaas-87-2011-12-19_18-01-57.war'}, {u'relativePath': u'dist/eaas-87-2011-12-19_18-01-57.war.zip', u'displayPath': u'eaas-87-2011-12-19_18-01-57.war.zip', u'fileName': u'eaas-87-2011-12-19_18-01-57.war.zip'}], u'timestamp': 1324317717000, u'number': 87, u'actions': [{u'parameters': [{u'name': u'SERVICE_NAME', u'value': u'eaas'}, {u'name': u'PROJECT_NAME', u'value': u'demo'}]}, {u'causes': [{u'userName': u'anonymous', u'shortDescription': u'Started by user anonymous'}]}, {}, {}, {}], u'id': u'2011-12-19_18-01-57', u'keepLog': False, u'url': u'http://eaas-jenkins01.i3.level3.com:9080/job/build_war/87/', u'culprits': [{u'absoluteUrl': u'http://eaas-jenkins01.i3.level3.com:9080/user/unknown', u'fullName': u'unknown'}], u'result': u'SUCCESS', u'duration': 8826, u'fullDisplayName': u'build_war #87'}
         '''
         try:
-            response = self.jenkins_open(urllib2.Request(
+            response = self.jenkins_open(Request(
                 self.server + BUILD_INFO % locals()))
             if response:
                 return json.loads(response)
@@ -281,7 +299,7 @@
             {u'task': {u'url': u'http://your_url/job/my_job/', u'color': u'aborted_anime', u'name': u'my_job'}, u'stuck': False, u'actions': [{u'causes': [{u'shortDescription': u'Started by timer'}]}], u'buildable': False, u'params': u'', u'buildableStartMilliseconds': 1315087293316, u'why': u'Build #2,532 is already in progress (ETA:10 min)', u'blocked': True}
         '''
         return json.loads(self.jenkins_open(
-            urllib2.Request(self.server + Q_INFO)
+            Request(self.server + Q_INFO)
         ))['items']
 
     def cancel_queue(self, number):
@@ -292,7 +310,7 @@
         '''
         # Jenkins returns a 302 from this URL, unless Referer is not set,
         # then you get a 404.
-        self.jenkins_open(urllib2.Request(self.server +
+        self.jenkins_open(Request(self.server +
                                           CANCEL_QUEUE % locals(),
                                           headers={'Referer': self.server}))
 
@@ -314,11 +332,11 @@
         """
         try:
             return json.loads(self.jenkins_open(
-                urllib2.Request(self.server + INFO)))
+                Request(self.server + INFO)))
         except urllib2.HTTPError:
             raise JenkinsException("Error communicating with server[%s]"
                                    % self.server)
-        except httplib.BadStatusLine:
+        except BadStatusLine:
             raise JenkinsException("Error communicating with server[%s]"
                                    % self.server)
         except ValueError:
@@ -342,7 +360,7 @@
         :param to_name: Name of Jenkins job to copy to, ``str``
         '''
         self.get_job_info(from_name)
-        self.jenkins_open(urllib2.Request(
+        self.jenkins_open(Request(
             self.server + COPY_JOB % locals(), ''))
         if not self.job_exists(to_name):
             raise JenkinsException('create[%s] failed' % (to_name))
@@ -355,7 +373,7 @@
         :param new_name: New Jenkins job name, ``str``
         '''
         self.get_job_info(name)
-        self.jenkins_open(urllib2.Request(
+        self.jenkins_open(Request(
             self.server + RENAME_JOB % locals(), ''))
         if not self.job_exists(new_name):
             raise JenkinsException('rename[%s] failed'%(new_name))
@@ -367,7 +385,7 @@
         :param name: Name of Jenkins job, ``str``
         '''
         self.get_job_info(name)
-        self.jenkins_open(urllib2.Request(
+        self.jenkins_open(Request(
             self.server + DELETE_JOB % locals(), ''))
         if self.job_exists(name):
             raise JenkinsException('delete[%s] failed' % (name))
@@ -379,7 +397,7 @@
         :param name: Name of Jenkins job, ``str``
         '''
         self.get_job_info(name)
-        self.jenkins_open(urllib2.Request(
+        self.jenkins_open(Request(
             self.server + ENABLE_JOB % locals(), ''))
 
     def disable_job(self, name):
@@ -389,7 +407,7 @@
         :param name: Name of Jenkins job, ``str``
         '''
         self.get_job_info(name)
-        self.jenkins_open(urllib2.Request(
+        self.jenkins_open(Request(
             self.server + DISABLE_JOB % locals(), ''))
 
     def job_exists(self, name):
@@ -411,7 +429,7 @@
             raise JenkinsException('job[%s] already exists' % (name))
 
         headers = {'Content-Type': 'text/xml'}
-        self.jenkins_open(urllib2.Request(
+        self.jenkins_open(Request(
             self.server + CREATE_JOB % locals(), config_xml, headers))
         if not self.job_exists(name):
             raise JenkinsException('create[%s] failed' % (name))
@@ -423,8 +441,8 @@
         :param name: Name of Jenkins job, ``str``
         :returns: job configuration (XML format)
         '''
-        request = urllib2.Request(self.server + CONFIG_JOB %
-                                  {"name": urllib.quote(name)})
+        request = Request(self.server + CONFIG_JOB %
+                                  {"name": quote(name)})
         return self.jenkins_open(request)
 
     def reconfig_job(self, name, config_xml):
@@ -438,7 +456,7 @@
         self.get_job_info(name)
         headers = {'Content-Type': 'text/xml'}
         reconfig_url = self.server + CONFIG_JOB % locals()
-        self.jenkins_open(urllib2.Request(reconfig_url, config_xml, headers))
+        self.jenkins_open(Request(reconfig_url, config_xml, headers))
 
     def build_job_url(self, name, parameters=None, token=None):
         '''
@@ -470,7 +488,7 @@
         '''
         if not self.job_exists(name):
             raise JenkinsException('no such job[%s]' % (name))
-        return self.jenkins_open(urllib2.Request(
+        return self.jenkins_open(Request(
             self.build_job_url(name, parameters, token)))
 
     def stop_build(self, name, number):
@@ -480,7 +498,7 @@
         :param name: Name of Jenkins job, ``str``
         :param number: Jenkins build number for the job, ``int``
         '''
-        self.jenkins_open(urllib2.Request(self.server + STOP_BUILD % locals()))
+        self.jenkins_open(Request(self.server + STOP_BUILD % locals()))
 
     def get_node_info(self, name):
         '''
@@ -490,7 +508,7 @@
         :returns: Dictionary of node info, ``dict``
         '''
         try:
-            response = self.jenkins_open(urllib2.Request(
+            response = self.jenkins_open(Request(
                 self.server + NODE_INFO % locals()))
             if response:
                 return json.loads(response)
@@ -520,7 +538,7 @@
         :param name: Name of Jenkins node, ``str``
         '''
         self.get_node_info(name)
-        self.jenkins_open(urllib2.Request(
+        self.jenkins_open(Request(
             self.server + DELETE_NODE % locals(), ''))
         if self.node_exists(name):
             raise JenkinsException('delete[%s] failed' % (name))
@@ -535,7 +553,7 @@
         info = self.get_node_info(name)
         if info['offline']:
             return
-        self.jenkins_open(urllib2.Request(
+        self.jenkins_open(Request(
             self.server + TOGGLE_OFFLINE % locals()))
 
     def enable_node(self, name):
@@ -548,7 +566,7 @@
         if not info['offline']:
             return
         msg = ''
-        self.jenkins_open(urllib2.Request(
+        self.jenkins_open(Request(
             self.server + TOGGLE_OFFLINE % locals()))
 
     def create_node(self, name, numExecutors=2, nodeDescription=None,
@@ -595,7 +613,7 @@
             'json': json.dumps(inner_params)
         }
 
-        self.jenkins_open(urllib2.Request(
+        self.jenkins_open(Request(
             self.server + CREATE_NODE % urllib.urlencode(params)))
 
         if not self.node_exists(name):
@@ -610,7 +628,7 @@
         :returns: Build console output,  ``str``
         '''
         try:
-            response = self.jenkins_open(urllib2.Request(
+            response = self.jenkins_open(Request(
                 self.server + BUILD_CONSOLE_OUTPUT % locals()))
             if response:
                 return response

=== modified file 'tests/test_jenkins.py'
--- tests/test_jenkins.py	2012-05-17 15:24:23 +0000
+++ tests/test_jenkins.py	2014-04-08 06:02:58 +0000
@@ -2,7 +2,7 @@
 
 from mock import patch
 
-from helper import jenkins
+from tests.helper import jenkins
 
 
 class JenkinsTest(unittest.TestCase):

=== added file 'tox.ini'
--- tox.ini	1970-01-01 00:00:00 +0000
+++ tox.ini	2014-04-08 06:02:58 +0000
@@ -0,0 +1,14 @@
+# Tox (http://tox.testrun.org/) is a tool for running tests
+# in multiple virtualenvs. This configuration file will run the
+# test suite on all supported python versions. To use it, "pip install tox"
+# and then run "tox" from this directory.
+
+[tox]
+envlist = py26, py27, pypy, py33
+
+[testenv]
+deps =
+    mock
+    pytest
+    pytest-cov
+commands = py.test --cov=jenkins --cov-report=xml --cov-report=term-missing tests


Follow ups