Skip to content

Commit

Permalink
Merge pull request #341 from pycontribs/develop
Browse files Browse the repository at this point in the history
v1.0.10
  • Loading branch information
ssbarnea committed Feb 10, 2017
2 parents 9671acc + 35d2900 commit ff7fe0b
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 20 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
JIRA Python Library
===================

.. image:: https://img.shields.io/pypi/pyversions/jira.svg
.. image:: https://img.shields.io/pypi/v/jira.svg
:target: https://pypi.python.org/pypi/jira/

.. image:: https://img.shields.io/pypi/l/jira.svg
Expand Down
37 changes: 22 additions & 15 deletions jira/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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'])

Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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)

Expand All @@ -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:
Expand All @@ -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

Expand Down
5 changes: 3 additions & 2 deletions jira/resilientsession.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
5 changes: 3 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ classifier =
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Topic :: Software Development :: Libraries :: Python Modules
Topic :: Internet :: WWW/HTTP

Expand All @@ -36,8 +37,8 @@ packages =
jira

[entry_points]
pbr.config.drivers =
plain = pbr.cfg.driver:Plain
console_scripts =
jirashell = jira.jirashell:main

[egg_info]
egg_base = .
Expand Down
7 changes: 7 additions & 0 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit ff7fe0b

Please sign in to comment.