Skip to content

Commit ff7fe0b

Browse files
authored
Merge pull request #341 from pycontribs/develop
v1.0.10
2 parents 9671acc + 35d2900 commit ff7fe0b

File tree

5 files changed

+36
-20
lines changed

5 files changed

+36
-20
lines changed

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
JIRA Python Library
33
===================
44

5-
.. image:: https://img.shields.io/pypi/pyversions/jira.svg
5+
.. image:: https://img.shields.io/pypi/v/jira.svg
66
:target: https://pypi.python.org/pypi/jira/
77

88
.. image:: https://img.shields.io/pypi/l/jira.svg

jira/client.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,8 @@ class JIRA(object):
196196
AGILE_BASE_URL = GreenHopperResource.AGILE_BASE_URL
197197

198198
def __init__(self, server=None, options=None, basic_auth=None, oauth=None, jwt=None, kerberos=False,
199-
validate=False, get_server_info=True, async=False, logging=True, max_retries=3, proxies=None):
199+
validate=False, get_server_info=True, async=False, logging=True, max_retries=3, proxies=None,
200+
timeout=None):
200201
"""Construct a JIRA client instance.
201202
202203
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
241242
:param get_server_info: If true it will fetch server version info first to determine if some API calls
242243
are available.
243244
:param async: To enable async requests for those actions where we implemented it, like issue update() or delete().
245+
:param timeout: Set a read/connect timeout for the underlying calls to JIRA (default: None)
244246
Obviously this means that you cannot rely on the return code when this is enabled.
245247
"""
246248
# 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
280282
self._try_magic()
281283

282284
if oauth:
283-
self._create_oauth_session(oauth)
285+
self._create_oauth_session(oauth, timeout)
284286
elif basic_auth:
285-
self._create_http_basic_session(*basic_auth)
287+
self._create_http_basic_session(*basic_auth, timeout=timeout)
286288
self._session.headers.update(self._options['headers'])
287289
elif jwt:
288-
self._create_jwt_session(jwt)
290+
self._create_jwt_session(jwt, timeout)
289291
elif kerberos:
290-
self._create_kerberos_session()
292+
self._create_kerberos_session(timeout)
291293
else:
292294
verify = self._options['verify']
293-
self._session = ResilientSession()
295+
self._session = ResilientSession(timeout=timeout)
294296
self._session.verify = verify
295297
self._session.headers.update(self._options['headers'])
296298

@@ -302,7 +304,12 @@ def __init__(self, server=None, options=None, basic_auth=None, oauth=None, jwt=N
302304
if validate:
303305
# This will raise an Exception if you are not allowed to login.
304306
# It's better to fail faster than later.
305-
self.session()
307+
user = self.session()
308+
if user.raw is None:
309+
auth_method = (
310+
oauth or basic_auth or jwt or kerberos or "anonymous"
311+
)
312+
raise JIRAError("Can not log in with %s" % str(auth_method))
306313

307314
self.deploymentType = None
308315
if get_server_info:
@@ -2095,14 +2102,14 @@ def kill_websudo(self):
20952102
return self._session.delete(url)
20962103

20972104
# Utilities
2098-
def _create_http_basic_session(self, username, password):
2105+
def _create_http_basic_session(self, username, password, timeout=None):
20992106
verify = self._options['verify']
2100-
self._session = ResilientSession()
2107+
self._session = ResilientSession(timeout=timeout)
21012108
self._session.verify = verify
21022109
self._session.auth = (username, password)
21032110
self._session.cert = self._options['client_cert']
21042111

2105-
def _create_oauth_session(self, oauth):
2112+
def _create_oauth_session(self, oauth, timeout):
21062113
verify = self._options['verify']
21072114

21082115
from oauthlib.oauth1 import SIGNATURE_RSA
@@ -2114,17 +2121,17 @@ def _create_oauth_session(self, oauth):
21142121
signature_method=SIGNATURE_RSA,
21152122
resource_owner_key=oauth['access_token'],
21162123
resource_owner_secret=oauth['access_token_secret'])
2117-
self._session = ResilientSession()
2124+
self._session = ResilientSession(timeout)
21182125
self._session.verify = verify
21192126
self._session.auth = oauth
21202127

2121-
def _create_kerberos_session(self):
2128+
def _create_kerberos_session(self, timeout):
21222129
verify = self._options['verify']
21232130

21242131
from requests_kerberos import HTTPKerberosAuth
21252132
from requests_kerberos import OPTIONAL
21262133

2127-
self._session = ResilientSession()
2134+
self._session = ResilientSession(timeout=timeout)
21282135
self._session.verify = verify
21292136
self._session.auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL)
21302137

@@ -2135,7 +2142,7 @@ def _timestamp(dt=None):
21352142
t += dt
21362143
return calendar.timegm(t.timetuple())
21372144

2138-
def _create_jwt_session(self, jwt):
2145+
def _create_jwt_session(self, jwt, timeout):
21392146
try:
21402147
jwt_auth = JWTAuth(jwt['secret'], alg='HS256')
21412148
except NameError as e:
@@ -2146,7 +2153,7 @@ def _create_jwt_session(self, jwt):
21462153
jwt_auth.add_field("qsh", QshGenerator(self._options['context_path']))
21472154
for f in jwt['payload'].items():
21482155
jwt_auth.add_field(f[0], f[1])
2149-
self._session = ResilientSession()
2156+
self._session = ResilientSession(timeout=timeout)
21502157
self._session.verify = self._options['verify']
21512158
self._session.auth = jwt_auth
21522159

jira/resilientsession.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,9 @@ class ResilientSession(Session):
7171
At this moment it supports: 502, 503, 504
7272
"""
7373

74-
def __init__(self):
74+
def __init__(self, timeout=None):
7575
self.max_retries = 3
76+
self.timeout = timeout
7677
super(ResilientSession, self).__init__()
7778

7879
# 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):
120121
exception = None
121122
try:
122123
method = getattr(super(ResilientSession, self), verb.lower())
123-
response = method(url, **kwargs)
124+
response = method(url, timeout=self.timeout, **kwargs)
124125
if response.status_code == 200:
125126
return response
126127
except ConnectionError as e:

setup.cfg

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ classifier =
2020
Programming Language :: Python :: 3
2121
Programming Language :: Python :: 3.4
2222
Programming Language :: Python :: 3.5
23+
Programming Language :: Python :: 3.6
2324
Topic :: Software Development :: Libraries :: Python Modules
2425
Topic :: Internet :: WWW/HTTP
2526

@@ -36,8 +37,8 @@ packages =
3637
jira
3738

3839
[entry_points]
39-
pbr.config.drivers =
40-
plain = pbr.cfg.driver:Plain
40+
console_scripts =
41+
jirashell = jira.jirashell:main
4142

4243
[egg_info]
4344
egg_base = .

tests/tests.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2097,6 +2097,13 @@ def test_remove_user_from_group(self):
20972097
self.jira.delete_user(self.test_username)
20982098

20992099

2100+
class JiraShellTests(unittest.TestCase):
2101+
2102+
def test_jirashell_command_exists(self):
2103+
result = os.system('jirashell --help')
2104+
self.assertEqual(result, 0)
2105+
2106+
21002107
if __name__ == '__main__':
21012108

21022109
# when running tests we expect various errors and we don't want to display them by default

0 commit comments

Comments
 (0)