diff --git a/python/changelog.d/+optional-rest-api-key.fix.md b/python/changelog.d/+optional-rest-api-key.fix.md new file mode 100644 index 0000000000..736b9b4ea6 --- /dev/null +++ b/python/changelog.d/+optional-rest-api-key.fix.md @@ -0,0 +1 @@ +Allowed `RestConfig` to omit `api_key`, creating a REST client without a default `Authorization` header. diff --git a/python/lib/sift_client/_internal/rest.py b/python/lib/sift_client/_internal/rest.py index 6a9d1c9d14..db8106b4fe 100644 --- a/python/lib/sift_client/_internal/rest.py +++ b/python/lib/sift_client/_internal/rest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from abc import ABC from typing import TypedDict @@ -15,7 +17,7 @@ class SiftRestConfig(TypedDict): """ Config class used to to interact with services that use Sift's REST API.`. - `uri`: The URI of Sift's REST API. The scheme portion of the URI i.e. `https://` should be ommitted. - - `apikey`: User-generated API key generated via the Sift application. + - `apikey`: User-generated API key, or None to omit the Authorization header. - `retry`: Urllib3 Retry configuration. If not provided, a default of 3 retries is used. - `use_ssl`: INTERNAL USE. Meant to be used for local development. - `cert_via_openssl`: Enable this if you want to use OpenSSL to load the certificates. @@ -24,7 +26,7 @@ class SiftRestConfig(TypedDict): """ uri: str - apikey: str + apikey: str | None retry: NotRequired[Retry] use_ssl: NotRequired[bool] cert_via_openssl: NotRequired[bool] @@ -75,7 +77,8 @@ def __init__(self, rest_conf: SiftRestConfig): self._apikey = rest_conf["apikey"] self._session = requests.Session() - self._session.headers = {"Authorization": f"Bearer {self._apikey}"} + if self._apikey is not None: + self._session.headers = {"Authorization": f"Bearer {self._apikey}"} adapter = _SiftHTTPAdapter(rest_conf) self._session.mount("https://", adapter) diff --git a/python/lib/sift_client/_tests/_internal/test_transport.py b/python/lib/sift_client/_tests/_internal/test_transport.py index 0cac17a87c..7f4567fc73 100644 --- a/python/lib/sift_client/_tests/_internal/test_transport.py +++ b/python/lib/sift_client/_tests/_internal/test_transport.py @@ -3,6 +3,7 @@ import threading import pytest +import requests from sift_client._internal.grpc_transport.transport import ( _compute_channel_options, @@ -114,6 +115,33 @@ def test_url_keeps_http(self): assert config.base_url == "http://rest.sift.com" +class TestRestAuthentication: + @pytest.mark.parametrize( + ("config_kwargs", "expected_authorization"), + [ + ({}, None), + ({"api_key": None}, None), + ({"api_key": "api"}, "Bearer api"), + ({"api_key": ""}, "Bearer "), + ], + ) + def test_authorization_header(self, config_kwargs, expected_authorization): + client = RestClient(RestConfig(base_url="https://vendor.example", **config_kwargs)) + with client._client._session as session: + request = session.prepare_request(requests.Request("GET", client.base_url)) + assert request.headers.get("Authorization") == expected_authorization + + def test_custom_authorization_without_api_key(self): + client = RestClient(RestConfig(base_url="https://vendor.example")) + with client._client._session as session: + request = session.prepare_request( + requests.Request( + "GET", client.base_url, headers={"Authorization": "Basic vendor-credential"} + ) + ) + assert request.headers["Authorization"] == "Basic vendor-credential" + + class TestRestRequestTimeout: """The REST client applies a default per-request timeout so a stalled socket fails fast instead of blocking the calling thread forever. diff --git a/python/lib/sift_client/transport/rest_transport.py b/python/lib/sift_client/transport/rest_transport.py index 77eef7ae08..ab6e1414f1 100644 --- a/python/lib/sift_client/transport/rest_transport.py +++ b/python/lib/sift_client/transport/rest_transport.py @@ -31,7 +31,7 @@ class RestConfig: def __init__( self, base_url: str, - api_key: str, + api_key: str | None = None, use_ssl: bool = True, cert_via_openssl: bool = False, retry: Retry = _DEFAULT_REST_RETRY, @@ -41,7 +41,7 @@ def __init__( Args: base_url: The base URL of the API. - api_key: The API key for authentication. + api_key: The API key for authentication. None omits the Authorization header. use_ssl: Whether to use HTTPS. cert_via_openssl: Whether to use OpenSSL for SSL/TLS. retry: The retry configuration for requests.