Repository: pycontribs/jira

Issue:
easy_install fails to install jira 1.0.8
I'm trying to install jira using "easy_install" on Ubuntu 14.04 and I'm getting "No eggs found in ... (setup script problem?)", and jira is not installed. pip works, and installing version 1.0.7 works too. Maybe this is related to the move to pbr?

    ➜  easy_install jira                                                                                                                                                                        17-01-22 12:42
    Searching for jira
    Reading http://pypi.python.org/simple/jira/
    Best match: jira 1.0.8
    Downloading https://pypi.python.org/packages/18/5a/09e92af6270e7e009a7d4612469208c459c990d43d7e130880c1238ce409/jira-1.0.8.tar.gz#md5=08212dca38996b1ffaf65774677c3977
    Processing jira-1.0.8.tar.gz
    Writing /tmp/easy_install-8EAvQo/jira-1.0.8/setup.cfg
    Running jira-1.0.8/setup.py -q bdist_egg --dist-dir /tmp/easy_install-8EAvQo/jira-1.0.8/egg-dist-tmp-dRbdrS
    zip_safe flag not set; analyzing archive contents...

    Installed /tmp/easy_install-8EAvQo/jira-1.0.8/.eggs/pytest_runner-2.10.1-py2.7.egg
    Searching for pbr>=1.9
    Reading http://pypi.python.org/simple/pbr/
    Best match: pbr 1.10.0
    Downloading https://pypi.python.org/packages/c3/2c/63275fab26a0fd8cadafca71a3623e4d0f0ee8ed7124a5bb128853d178a7/pbr-1.10.0.tar.gz#md5=8e4968c587268f030e38329feb9c8f17
    Processing pbr-1.10.0.tar.gz
    Writing /tmp/easy_install-8EAvQo/jira-1.0.8/temp/easy_install-Q2MvSA/pbr-1.10.0/setup.cfg
    Running pbr-1.10.0/setup.py -q bdist_egg --dist-dir /tmp/easy_install-8EAvQo/jira-1.0.8/temp/easy_install-Q2MvSA/pbr-1.10.0/egg-dist-tmp-rgLtS7
    creating /tmp/easy_install-8EAvQo/jira-1.0.8/.eggs/pbr-1.10.0-py2.7.egg
    Extracting pbr-1.10.0-py2.7.egg to /tmp/easy_install-8EAvQo/jira-1.0.8/.eggs

    Installed /tmp/easy_install-8EAvQo/jira-1.0.8/.eggs/pbr-1.10.0-py2.7.egg
    No eggs found in /tmp/easy_install-8EAvQo/jira-1.0.8/egg-dist-tmp-dRbdrS (setup script problem?)
    ➜  python                                                                                                                                                                                                                    17-01-22 12:42
    Python 2.7.8 (default, Mar 18 2015, 12:07:35)
    [GCC 4.8.2] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import jira
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ImportError: No module named jira



Discussion:

Resolving patch:
diff --git a/jira/client.py b/jira/client.py
--- a/jira/client.py
+++ b/jira/client.py
@@ -196,7 +196,8 @@ class JIRA(object):
     AGILE_BASE_URL = GreenHopperResource.AGILE_BASE_URL
 
     def __init__(self, server=None, options=None, basic_auth=None, oauth=None, jwt=None, kerberos=False,
-                 validate=False, get_server_info=True, async=False, logging=True, max_retries=3, proxies=None):
+                 validate=False, get_server_info=True, async=False, logging=True, max_retries=3, proxies=None,
+                 timeout=None):
         """Construct a JIRA client instance.
 
         Without any arguments, this client will connect anonymously to the JIRA instance
@@ -241,6 +242,7 @@ def __init__(self, server=None, options=None, basic_auth=None, oauth=None, jwt=N
         :param get_server_info: If true it will fetch server version info first to determine if some API calls
             are available.
         :param async: To enable async requests for those actions where we implemented it, like issue update() or delete().
+        :param timeout: Set a read/connect timeout for the underlying calls to JIRA (default: None)
         Obviously this means that you cannot rely on the return code when this is enabled.
         """
         # force a copy of the tuple to be used in __del__() because
@@ -280,17 +282,17 @@ def __init__(self, server=None, options=None, basic_auth=None, oauth=None, jwt=N
         self._try_magic()
 
         if oauth:
-            self._create_oauth_session(oauth)
+            self._create_oauth_session(oauth, timeout)
         elif basic_auth:
-            self._create_http_basic_session(*basic_auth)
+            self._create_http_basic_session(*basic_auth, timeout=timeout)
             self._session.headers.update(self._options['headers'])
         elif jwt:
-            self._create_jwt_session(jwt)
+            self._create_jwt_session(jwt, timeout)
         elif kerberos:
-            self._create_kerberos_session()
+            self._create_kerberos_session(timeout)
         else:
             verify = self._options['verify']
-            self._session = ResilientSession()
+            self._session = ResilientSession(timeout=timeout)
             self._session.verify = verify
         self._session.headers.update(self._options['headers'])
 
@@ -302,7 +304,12 @@ def __init__(self, server=None, options=None, basic_auth=None, oauth=None, jwt=N
         if validate:
             # This will raise an Exception if you are not allowed to login.
             # It's better to fail faster than later.
-            self.session()
+            user = self.session()
+            if user.raw is None:
+                auth_method = (
+                    oauth or basic_auth or jwt or kerberos or "anonymous"
+                )
+                raise JIRAError("Can not log in with %s" % str(auth_method))
 
         self.deploymentType = None
         if get_server_info:
@@ -2095,14 +2102,14 @@ def kill_websudo(self):
             return self._session.delete(url)
 
     # Utilities
-    def _create_http_basic_session(self, username, password):
+    def _create_http_basic_session(self, username, password, timeout=None):
         verify = self._options['verify']
-        self._session = ResilientSession()
+        self._session = ResilientSession(timeout=timeout)
         self._session.verify = verify
         self._session.auth = (username, password)
         self._session.cert = self._options['client_cert']
 
-    def _create_oauth_session(self, oauth):
+    def _create_oauth_session(self, oauth, timeout):
         verify = self._options['verify']
 
         from oauthlib.oauth1 import SIGNATURE_RSA
@@ -2114,17 +2121,17 @@ def _create_oauth_session(self, oauth):
             signature_method=SIGNATURE_RSA,
             resource_owner_key=oauth['access_token'],
             resource_owner_secret=oauth['access_token_secret'])
-        self._session = ResilientSession()
+        self._session = ResilientSession(timeout)
         self._session.verify = verify
         self._session.auth = oauth
 
-    def _create_kerberos_session(self):
+    def _create_kerberos_session(self, timeout):
         verify = self._options['verify']
 
         from requests_kerberos import HTTPKerberosAuth
         from requests_kerberos import OPTIONAL
 
-        self._session = ResilientSession()
+        self._session = ResilientSession(timeout=timeout)
         self._session.verify = verify
         self._session.auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL)
 
@@ -2135,7 +2142,7 @@ def _timestamp(dt=None):
             t += dt
         return calendar.timegm(t.timetuple())
 
-    def _create_jwt_session(self, jwt):
+    def _create_jwt_session(self, jwt, timeout):
         try:
             jwt_auth = JWTAuth(jwt['secret'], alg='HS256')
         except NameError as e:
@@ -2146,7 +2153,7 @@ def _create_jwt_session(self, jwt):
         jwt_auth.add_field("qsh", QshGenerator(self._options['context_path']))
         for f in jwt['payload'].items():
             jwt_auth.add_field(f[0], f[1])
-        self._session = ResilientSession()
+        self._session = ResilientSession(timeout=timeout)
         self._session.verify = self._options['verify']
         self._session.auth = jwt_auth
 
diff --git a/jira/resilientsession.py b/jira/resilientsession.py
--- a/jira/resilientsession.py
+++ b/jira/resilientsession.py
@@ -71,8 +71,9 @@ class ResilientSession(Session):
     At this moment it supports: 502, 503, 504
     """
 
-    def __init__(self):
+    def __init__(self, timeout=None):
         self.max_retries = 3
+        self.timeout = timeout
         super(ResilientSession, self).__init__()
 
         # Indicate our preference for JSON to avoid https://bitbucket.org/bspeakmon/jira-python/issue/46 and https://jira.atlassian.com/browse/JRA-38551
@@ -120,7 +121,7 @@ def __verb(self, verb, url, retry_data=None, **kwargs):
             exception = None
             try:
                 method = getattr(super(ResilientSession, self), verb.lower())
-                response = method(url, **kwargs)
+                response = method(url, timeout=self.timeout, **kwargs)
                 if response.status_code == 200:
                     return response
             except ConnectionError as e:


Test patch:
diff --git a/tests/tests.py b/tests/tests.py
--- a/tests/tests.py
+++ b/tests/tests.py
@@ -2097,6 +2097,13 @@ def test_remove_user_from_group(self):
         self.jira.delete_user(self.test_username)
 
 
+class JiraShellTests(unittest.TestCase):
+
+    def test_jirashell_command_exists(self):
+        result = os.system('jirashell --help')
+        self.assertEqual(result, 0)
+
+
 if __name__ == '__main__':
 
     # when running tests we expect various errors and we don't want to display them by default