diff --git a/packages/abstractions/kiota_abstractions/base_request_configuration.py b/packages/abstractions/kiota_abstractions/base_request_configuration.py index e15293f1..ebfbdc84 100644 --- a/packages/abstractions/kiota_abstractions/base_request_configuration.py +++ b/packages/abstractions/kiota_abstractions/base_request_configuration.py @@ -3,7 +3,7 @@ # Licensed under the MIT License. # See License in the project root for license information. # ------------------------------------ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Generic, Optional, TypeVar from warnings import warn @@ -19,7 +19,7 @@ class RequestConfiguration(Generic[QueryParameters]): Configuration for the request such as headers, query parameters, and middleware options. """ # Request headers - headers: HeadersCollection = HeadersCollection() + headers: HeadersCollection = field(default_factory=HeadersCollection) # Request options options: Optional[list[RequestOption]] = None # Request query parameters diff --git a/packages/abstractions/tests/test_base_request_configuration.py b/packages/abstractions/tests/test_base_request_configuration.py index 1a274e6e..e40166ab 100644 --- a/packages/abstractions/tests/test_base_request_configuration.py +++ b/packages/abstractions/tests/test_base_request_configuration.py @@ -1,6 +1,13 @@ +import importlib +import warnings + import pytest -from kiota_abstractions.base_request_configuration import BaseRequestConfiguration +from kiota_abstractions import base_request_configuration +from kiota_abstractions.base_request_configuration import ( + BaseRequestConfiguration, + RequestConfiguration, +) def test_base_request_configuration_deprecation_warning(): @@ -9,5 +16,17 @@ def test_base_request_configuration_deprecation_warning(): def test_import_base_request_configuration_no_warning(): - from kiota_abstractions.base_request_configuration import BaseRequestConfiguration, RequestConfiguration - assert len(pytest.warns()) == 0 + with warnings.catch_warnings(): + warnings.simplefilter("error") + importlib.reload(base_request_configuration) + + +def test_request_configurations_do_not_share_a_headers_collection(): + first = RequestConfiguration() + second = RequestConfiguration() + + first.headers.add("Prefer", "outlook.body-content-type=text") + + assert first.headers is not second.headers + assert second.headers.try_get("Prefer") is False + assert RequestConfiguration().headers.count() == 0 diff --git a/packages/http/httpx/kiota_http/middleware/headers_inspection_handler.py b/packages/http/httpx/kiota_http/middleware/headers_inspection_handler.py index 85cc8b51..81b03f40 100644 --- a/packages/http/httpx/kiota_http/middleware/headers_inspection_handler.py +++ b/packages/http/httpx/kiota_http/middleware/headers_inspection_handler.py @@ -4,6 +4,8 @@ # See License in the project root for license information. # ------------------------------------ +from typing import Optional + from kiota_abstractions.request_option import RequestOption import httpx @@ -21,16 +23,16 @@ class HeadersInspectionHandler(BaseMiddleware): def __init__( self, - options: HeadersInspectionHandlerOption = HeadersInspectionHandlerOption(), + options: Optional[HeadersInspectionHandlerOption] = None, ): """Create an instance of HeadersInspectionHandler Args: options (HeadersInspectionHandlerOption, optional): Default options to apply to the - handler. Defaults to HeadersInspectionHandlerOption(). + handler. A new HeadersInspectionHandlerOption per handler when not provided. """ super().__init__() - self.options = options + self.options = options if options is not None else HeadersInspectionHandlerOption() async def send( self, request: httpx.Request, transport: httpx.AsyncBaseTransport diff --git a/packages/http/httpx/kiota_http/middleware/options/headers_inspection_handler_option.py b/packages/http/httpx/kiota_http/middleware/options/headers_inspection_handler_option.py index 09feb2af..1c88a975 100644 --- a/packages/http/httpx/kiota_http/middleware/options/headers_inspection_handler_option.py +++ b/packages/http/httpx/kiota_http/middleware/options/headers_inspection_handler_option.py @@ -3,70 +3,34 @@ # Licensed under the MIT License. # See License in the project root for license information. # ------------------------------------ +from dataclasses import dataclass, field +from typing import ClassVar + from kiota_abstractions.headers_collection import HeadersCollection from kiota_abstractions.request_option import RequestOption +@dataclass(eq=False) class HeadersInspectionHandlerOption(RequestOption): - """Config options for the HeaderInspectionHandler""" - - HEADERS_INSPECTION_HANDLER_OPTION_KEY = "HeadersInspectionHandlerOption" - - def __init__( - self, - inspect_request_headers: bool = True, - inspect_response_headers: bool = True, - request_headers: HeadersCollection = HeadersCollection(), - response_headers: HeadersCollection = HeadersCollection(), - ) -> None: - """Creates an instance of headers inspection handler option. - - Args: - inspect_request_headers (bool, optional): whether the request headers - should be inspected. Defaults to True. - inspect_response_headers (bool, optional): whether the response headers - should be inspected. Defaults to True. - """ - self._inspect_request_headers = inspect_request_headers - self._inspect_response_headers = inspect_response_headers - self._request_headers = request_headers if request_headers else HeadersCollection() - self._response_headers = response_headers if response_headers else HeadersCollection() - - @property - def inspect_request_headers(self): - """Whether the request headers should be inspected.""" - return self._inspect_request_headers - - @inspect_request_headers.setter - def inspect_request_headers(self, value: bool): - self._inspect_request_headers = value - - @property - def inspect_response_headers(self): - """Whether the response headers should be inspected.""" - return self._inspect_response_headers - - @inspect_response_headers.setter - def inspect_response_headers(self, value: bool): - self._inspect_response_headers = value - - @property - def request_headers(self): - """Gets the request headers to for the current request.""" - return self._request_headers - - @request_headers.setter - def request_headers(self, value: HeadersCollection): - self._request_headers = value - - @property - def response_headers(self): - """Gets the response headers to for the current request.""" - return self._response_headers - - @response_headers.setter - def response_headers(self, value: HeadersCollection): - self._response_headers = value + """Config options for the HeadersInspectionHandler. + + Args: + inspect_request_headers (bool, optional): whether the request headers + should be inspected. Defaults to True. + inspect_response_headers (bool, optional): whether the response headers + should be inspected. Defaults to True. + request_headers (HeadersCollection, optional): collection that receives the + request headers. A new one per option when not provided. + response_headers (HeadersCollection, optional): collection that receives the + response headers. A new one per option when not provided. + """ + + HEADERS_INSPECTION_HANDLER_OPTION_KEY: ClassVar[str] = "HeadersInspectionHandlerOption" + + inspect_request_headers: bool = True + inspect_response_headers: bool = True + request_headers: HeadersCollection = field(default_factory=HeadersCollection) + response_headers: HeadersCollection = field(default_factory=HeadersCollection) @staticmethod def get_key() -> str: diff --git a/packages/http/httpx/tests/middleware_tests/test_headers_inspection_handler.py b/packages/http/httpx/tests/middleware_tests/test_headers_inspection_handler.py index d47b9442..d0704140 100644 --- a/packages/http/httpx/tests/middleware_tests/test_headers_inspection_handler.py +++ b/packages/http/httpx/tests/middleware_tests/test_headers_inspection_handler.py @@ -19,6 +19,36 @@ def test_no_config(): assert isinstance(options.request_headers, HeadersCollection) +def test_options_do_not_share_header_collections(): + """ + Two options built without explicit collections must not see each other's headers. + """ + first = HeadersInspectionHandlerOption() + second = HeadersInspectionHandlerOption() + + first.request_headers.add('test_request', 'test_request_header') + first.response_headers.add('test_response', 'test_response_header') + + assert first.request_headers is not second.request_headers + assert first.response_headers is not second.response_headers + assert second.request_headers.try_get('test_request') is False + assert second.response_headers.try_get('test_response') is False + + +def test_handlers_do_not_share_options(): + """ + Two handlers built without explicit options must not share one option object, + or the headers one client inspects show up on, and get cleared by, another. + """ + first = HeadersInspectionHandler() + second = HeadersInspectionHandler() + + first.options.request_headers.add('test_request', 'test_request_header') + + assert first.options is not second.options + assert second.options.request_headers.try_get('test_request') is False + + def test_custom_config(): """ Ensures that setting is_enabled to False.