diff --git a/docs/examples.rst b/docs/examples.rst index d49350dd2..37014e4d6 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -125,6 +125,21 @@ To pass additional options to Kerberos auth use dict ``kerberos_options``, e.g.: .. _jirashell-label: +Headers +------- + +Headers can be provided to the internally used ``requests.Session``. +If the user provides a header that the :py:class:`jira.client.JIRA` also attempts to set, the user provided header will take preference. + +Perhaps you want to use a custom User Agent:: + + from requests_toolbelt import user_agent + + jira = JIRA( + basic_auth=("email", "API token"), + options={"headers": {"User-Agent": user_agent("my_package", "0.0.1")}}, + ) + Issues ------ diff --git a/jira/client.py b/jira/client.py index 6f281f2ac..400387084 100644 --- a/jira/client.py +++ b/jira/client.py @@ -353,6 +353,7 @@ def __init__( * verify -- Verify SSL certs. Defaults to ``True``. * client_cert -- a tuple of (cert,key) for the requests library for client side SSL * check_update -- Check whether using the newest python-jira library version. + * headers -- a dict to update the default headers the session uses for all API requests. basic_auth (Union[None, Tuple[str, str]]): A tuple of username and password to use when establishing a session via HTTP BASIC authentication. @@ -420,7 +421,14 @@ def __init__( self._options: Dict[str, Any] = copy.copy(JIRA.DEFAULT_OPTIONS) + if "headers" in options: + headers = copy.copy(options["headers"]) + del options["headers"] + else: + headers = {} + self._options.update(options) + self._options["headers"].update(headers) self._rank = None diff --git a/tests/test_client.py b/tests/test_client.py index a717d190b..ee37ab6fd 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -55,6 +55,16 @@ def remove_by_slug(): return slug +@pytest.fixture() +def no_fields(monkeypatch): + """When we want to test the __init__ method of the jira.client.JIRA + we don't need any external calls to get the fields. + + We don't need the features of a MagicMock, hence we don't use it here. + """ + monkeypatch.setattr(jira.client.JIRA, "fields", lambda *args, **kwargs: []) + + def test_delete_project(cl_admin, cl_normal, slug): assert cl_admin.delete_project(slug) @@ -119,3 +129,69 @@ def test_result_list_if_empty(): with pytest.raises(StopIteration): next(results) + + +@pytest.mark.parametrize( + "options_arg", + [ + {"headers": {"Content-Type": "application/json;charset=UTF-8"}}, + {"headers": {"random-header": "nice random"}}, + ], + ids=["overwrite", "new"], +) +def test_headers_unclobbered_update(options_arg, no_fields): + + assert "headers" in options_arg, "test case options must contain headers" + + # GIVEN: the headers and the expected value + header_to_check: str = list(options_arg["headers"].keys())[0] + expected_header_value: str = options_arg["headers"][header_to_check] + + invariant_header_name: str = "X-Atlassian-Token" + invariant_header_value: str = jira.client.JIRA.DEFAULT_OPTIONS["headers"][ + invariant_header_name + ] + + # We arbitrarily chose a header to check it remains unchanged/unclobbered + # so should not be overwritten by a test case + assert ( + invariant_header_name not in options_arg["headers"] + ), f"{invariant_header_name} is checked as not being overwritten in this test" + + # WHEN: we initialise the JIRA class and get the headers + jira_client = jira.client.JIRA( + server="https://jira.atlasian.com", + get_server_info=False, + validate=False, + options=options_arg, + ) + + session_headers = jira_client._session.headers + + # THEN: we have set the right headers and not affect the other headers' defaults + assert session_headers[header_to_check] == expected_header_value + assert session_headers[invariant_header_name] == invariant_header_value + + +def test_headers_unclobbered_update_with_no_provided_headers(no_fields): + + options_arg = {} # a dict with "headers" not set + + # GIVEN:the headers and the expected value + invariant_header_name: str = "X-Atlassian-Token" + invariant_header_value: str = jira.client.JIRA.DEFAULT_OPTIONS["headers"][ + invariant_header_name + ] + + # WHEN: we initialise the JIRA class with no provided headers and get the headers + jira_client = jira.client.JIRA( + server="https://jira.atlasian.com", + get_server_info=False, + validate=False, + options=options_arg, + ) + + session_headers = jira_client._session.headers + + # THEN: we have not affected the other headers' defaults + assert session_headers[invariant_header_name] == invariant_header_value