diff --git a/haystack/utils/http_client.py b/haystack/utils/http_client.py index 61d323172c3..7b198e4c4a6 100644 --- a/haystack/utils/http_client.py +++ b/haystack/utils/http_client.py @@ -7,22 +7,14 @@ import httpx -@overload -def init_http_client(http_client_kwargs: dict[str, Any], async_client: Literal[False]) -> httpx.Client: ... @overload def init_http_client( - http_client_kwargs: dict[str, Any] | None, async_client: Literal[False] + http_client_kwargs: dict[str, Any] | None = ..., async_client: Literal[False] = ... ) -> httpx.Client | None: ... @overload -def init_http_client(http_client_kwargs: dict[str, Any], async_client: Literal[True]) -> httpx.AsyncClient: ... -@overload def init_http_client( - http_client_kwargs: dict[str, Any] | None, async_client: Literal[True] + http_client_kwargs: dict[str, Any] | None = ..., async_client: Literal[True] = ... ) -> httpx.AsyncClient | None: ... -@overload -def init_http_client( - http_client_kwargs: dict[str, Any] | None, async_client: bool -) -> httpx.Client | httpx.AsyncClient | None: ... def init_http_client( http_client_kwargs: dict[str, Any] | None = None, async_client: bool = False ) -> httpx.Client | httpx.AsyncClient | None: diff --git a/pyproject.toml b/pyproject.toml index d83e959829c..1b8da4e9367 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -163,7 +163,7 @@ integration-only-slow = 'pytest --maxfail=5 -m "integration and slow" {args:test all = 'pytest {args:test}' # TODO We want to eventually type the whole test folder -types = "mypy --install-types --non-interactive --cache-dir=.mypy_cache/ {args:haystack test/core/ test/marshal/ test/testing/ test/tracing/ test/tools/ test/human_in_the_loop test/evaluation test/document_stores test/dataclasses}" +types = "mypy --install-types --non-interactive --cache-dir=.mypy_cache/ {args:haystack test/core/ test/marshal/ test/testing/ test/tracing/ test/tools/ test/human_in_the_loop test/evaluation test/document_stores test/dataclasses test/utils/}" [tool.hatch.envs.e2e] template = "test" diff --git a/test/utils/test_auth.py b/test/utils/test_auth.py index a7b40424ffe..2445655fe90 100644 --- a/test/utils/test_auth.py +++ b/test/utils/test_auth.py @@ -32,9 +32,9 @@ def test_token_secret(): Secret.from_token("") with pytest.raises(FrozenInstanceError): - secret._token = "abba" + secret._token = "abba" # type: ignore[misc] with pytest.raises(FrozenInstanceError): - secret._type = SecretType.ENV_VAR + secret._type = SecretType.ENV_VAR # type: ignore[misc] def test_env_var_secret(): @@ -52,10 +52,12 @@ def test_env_var_secret(): secret.resolve_value() secret = Secret.from_env_var("TEST_ENV_VAR2", strict=False) + assert isinstance(secret, EnvVarSecret) assert secret._strict is False assert secret.resolve_value() is None secret = Secret.from_env_var(["TEST_ENV_VAR2", "TEST_ENV_VAR1"], strict=True) + assert isinstance(secret, EnvVarSecret) assert secret._env_vars == ("TEST_ENV_VAR2", "TEST_ENV_VAR1") with pytest.raises(ValueError, match="None of the following .* variables are set"): secret.resolve_value() @@ -73,8 +75,8 @@ def test_env_var_secret(): ) with pytest.raises(FrozenInstanceError): - secret._env_vars = ("A", "B") + secret._env_vars = ("A", "B") # type: ignore[misc] with pytest.raises(FrozenInstanceError): - secret._strict = False + secret._strict = False # type: ignore[misc] with pytest.raises(FrozenInstanceError): - secret._type = SecretType.TOKEN + secret._type = SecretType.TOKEN # type: ignore[misc] diff --git a/test/utils/test_callable_serialization.py b/test/utils/test_callable_serialization.py index c39acafe655..3f007f45ad3 100644 --- a/test/utils/test_callable_serialization.py +++ b/test/utils/test_callable_serialization.py @@ -11,7 +11,7 @@ from haystack.utils import deserialize_callable, serialize_callable -def some_random_callable_for_testing(some_ignored_arg: str): +def some_random_callable_for_testing(some_ignored_arg: str) -> None: pass diff --git a/test/utils/test_device.py b/test/utils/test_device.py index abd74827d2d..2d7c1b7d889 100644 --- a/test/utils/test_device.py +++ b/test/utils/test_device.py @@ -57,7 +57,7 @@ def test_device_map(): ) with pytest.raises(TypeError, match="unexpected device"): - DeviceMap.from_hf({"layer1": 0.1}) + DeviceMap.from_hf({"layer1": 0.1}) # type: ignore[dict-item] assert device_map.first_device == Device.gpu(0) assert DeviceMap({}).first_device is None diff --git a/test/utils/test_experimental.py b/test/utils/test_experimental.py index 257d3252ace..627f241ad87 100644 --- a/test/utils/test_experimental.py +++ b/test/utils/test_experimental.py @@ -39,7 +39,7 @@ class MyComponent: def run(self, value: int) -> dict: return {"value": value} - assert MyComponent.__experimental__ is True + assert getattr(MyComponent, "__experimental__") is True # noqa: B009 def test_passes_args_and_kwargs_to_init(self): @_experimental diff --git a/test/utils/test_hf.py b/test/utils/test_hf.py index c3e12b9f4e3..1c0498d5c00 100644 --- a/test/utils/test_hf.py +++ b/test/utils/test_hf.py @@ -59,7 +59,7 @@ def test_convert_message_to_hf_format(): "tool_calls": [{"type": "function", "function": {"name": "weather", "arguments": {"city": "Paris"}}}], } - tool_result = {"weather": "sunny", "temperature": "25"} + tool_result = '{"weather": "sunny", "temperature": "25"}' message = ChatMessage.from_tool( tool_result=tool_result, origin=ToolCall(id="123", tool_name="weather", arguments={"city": "Paris"}) ) @@ -83,7 +83,7 @@ def test_convert_message_to_hf_invalid(): ToolCallResult( result="result!", origin=ToolCall(id="123", tool_name="weather", arguments={"city": "Paris"}), - error=None, + error=None, # type: ignore[arg-type] ), ], ) diff --git a/test/utils/test_http_client.py b/test/utils/test_http_client.py index 8c52c265b2a..43c4c2496c1 100644 --- a/test/utils/test_http_client.py +++ b/test/utils/test_http_client.py @@ -14,7 +14,8 @@ def test_init_http_client(): assert http_client is None # test client is initialized with http_client_kwargs - http_client = init_http_client(http_client_kwargs={"base_url": "https://example.com"}) + client_kwargs = {"base_url": "https://example.com"} + http_client = init_http_client(http_client_kwargs=client_kwargs) assert http_client is not None assert isinstance(http_client, httpx.Client) assert http_client.base_url == "https://example.com" @@ -35,7 +36,7 @@ def test_init_http_client_async(): def test_http_client_kwargs_type_validation(): # test http_client_kwargs is not a dictionary with pytest.raises(TypeError, match="The parameter 'http_client_kwargs' must be a dictionary."): - init_http_client(http_client_kwargs="invalid") + init_http_client(http_client_kwargs="invalid") # type: ignore[call-overload] def test_http_client_kwargs_with_invalid_params(): diff --git a/test/utils/test_jinja2_chat_extension.py b/test/utils/test_jinja2_chat_extension.py index 48e4edf3eea..0c2ec086d96 100644 --- a/test/utils/test_jinja2_chat_extension.py +++ b/test/utils/test_jinja2_chat_extension.py @@ -4,6 +4,7 @@ import base64 import json +from typing import Any from unittest.mock import patch import pytest @@ -97,7 +98,12 @@ def test_system_message(self, jinja_env): """ rendered = jinja_env.from_string(template).render() output = json.loads(rendered.strip()) - expected = {"role": "system", "content": [{"text": "You are a helpful assistant."}], "name": None, "meta": {}} + expected: dict[str, Any] = { + "role": "system", + "content": [{"text": "You are a helpful assistant."}], + "name": None, + "meta": {}, + } assert output == expected def test_user_message_with_variable(self, jinja_env): @@ -108,7 +114,12 @@ def test_user_message_with_variable(self, jinja_env): """ rendered = jinja_env.from_string(template).render(name="Alice") output = json.loads(rendered.strip()) - expected = {"role": "user", "content": [{"text": "Hello, my name is Alice!"}], "name": None, "meta": {}} + expected: dict[str, Any] = { + "role": "user", + "content": [{"text": "Hello, my name is Alice!"}], + "name": None, + "meta": {}, + } assert output == expected def test_assistant_message_with_tool_call(self, jinja_env): @@ -121,7 +132,7 @@ def test_assistant_message_with_tool_call(self, jinja_env): tool_call = ToolCall(tool_name="search", arguments={"query": "an interesting question"}, id="search_1") rendered = jinja_env.from_string(template).render(tool_call=tool_call) output = json.loads(rendered.strip()) - expected = { + expected: dict[str, Any] = { "role": "assistant", "content": [ {"text": "Let me search for that information."}, @@ -149,7 +160,7 @@ def test_assistant_message_with_reasoning(self, jinja_env): reasoning = ReasoningContent(reasoning_text="Let me think about it...", extra={"key": "value"}) rendered = jinja_env.from_string(template).render(reasoning=reasoning) output = json.loads(rendered.strip()) - expected = { + expected: dict[str, Any] = { "role": "assistant", "content": [ {"reasoning": {"reasoning_text": "Let me think about it...", "extra": {"key": "value"}}}, @@ -170,7 +181,7 @@ def test_tool_message(self, jinja_env): tool_result = ToolCallResult(result="Here are the search results", origin=tool_call, error=False) rendered = jinja_env.from_string(template).render(tool_result=tool_result) output = json.loads(rendered.strip()) - expected = { + expected: dict[str, Any] = { "role": "tool", "content": [ { @@ -208,7 +219,7 @@ def test_tool_message_tool_call_result_list(self, jinja_env, base64_image_string ) rendered = jinja_env.from_string(template).render(tool_result=tool_result) output = json.loads(rendered.strip()) - expected = { + expected: dict[str, Any] = { "role": "tool", "content": [ { @@ -250,7 +261,7 @@ def test_user_message_with_image(self, jinja_env, base64_image_string): image = ImageContent(base64_image=base64_image_string, mime_type="image/png") rendered = jinja_env.from_string(template).render(image=image) output = json.loads(rendered.strip()) - expected = { + expected: dict[str, Any] = { "role": "user", "content": [ {"text": "Please describe this image:"}, @@ -284,7 +295,7 @@ def test_user_message_with_multiple_images(self, jinja_env, base64_image_string) ] rendered = jinja_env.from_string(template).render(images=images) output = json.loads(rendered.strip()) - expected = { + expected: dict[str, Any] = { "role": "user", "content": [ {"text": "Compare these images:"}, @@ -331,7 +342,7 @@ def test_user_message_with_multiple_images_and_interleaved_text(self, jinja_env, rendered = jinja_env.from_string(template).render(images=[image, image]) output = json.loads(rendered.strip()) - expected = { + expected: dict[str, Any] = { "role": "user", "content": [ {"text": "Image 1:"}, @@ -372,7 +383,7 @@ def test_user_message_with_file_content(self, jinja_env, base64_pdf_string): rendered = jinja_env.from_string(template).render(file=file) output = json.loads(rendered.strip()) - expected = { + expected: dict[str, Any] = { "role": "user", "content": [ {"text": "Please describe this document:"}, @@ -402,7 +413,7 @@ def test_user_message_multiple_lines(self, jinja_env): """ rendered = jinja_env.from_string(template).render() output = json.loads(rendered.strip()) - expected = { + expected: dict[str, Any] = { "role": "user", "content": [ { @@ -428,7 +439,7 @@ def test_invalid_role(self, jinja_env): def test_templatize_part_filter_with_invalid_type(self, jinja_env): with pytest.raises(TypeError, match="Unsupported type in ChatMessage content"): - templatize_part(jinja_env, 123) + templatize_part(jinja_env, 123) # type: ignore[arg-type] def test_empty_message_content_raises_error(self, jinja_env): error_message = "Message content in template is empty or contains only whitespace characters." @@ -482,7 +493,7 @@ def test_message_with_whitespace_handling(self, jinja_env): {% endmessage %}""", """{% message role="user" %}\tString\t{% endmessage %}""", ] - expected = {"role": "user", "content": [{"text": "String"}], "name": None, "meta": {}} + expected: dict[str, Any] = {"role": "user", "content": [{"text": "String"}], "name": None, "meta": {}} for template in templates: rendered = jinja_env.from_string(template).render() output = json.loads(rendered.strip()) diff --git a/test/utils/test_jinja2_extensions.py b/test/utils/test_jinja2_extensions.py index 0d1dd09770d..e578d444994 100644 --- a/test/utils/test_jinja2_extensions.py +++ b/test/utils/test_jinja2_extensions.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 -from unittest.mock import patch +from unittest.mock import MagicMock, patch import arrow import pytest @@ -26,7 +26,7 @@ def jinja_extension(self, jinja_env: Environment) -> Jinja2TimeExtension: return Jinja2TimeExtension(jinja_env) @patch("haystack.utils.jinja2_extensions.arrow_import") - def test_init_fails_without_arrow(self, arrow_import_mock) -> None: + def test_init_fails_without_arrow(self, arrow_import_mock: MagicMock) -> None: arrow_import_mock.check.side_effect = ImportError with pytest.raises(ImportError): Jinja2TimeExtension(Environment()) diff --git a/test/utils/test_misc.py b/test/utils/test_misc.py index 2e241a872dc..4e2286dea8a 100644 --- a/test/utils/test_misc.py +++ b/test/utils/test_misc.py @@ -70,6 +70,7 @@ def test_scores_decrease_with_rank(self): docs = [Document(id="a"), Document(id="b"), Document(id="c")] result = _reciprocal_rank_fusion([docs]) by_id = {doc.id: doc.score for doc in result} + assert by_id["a"] is not None and by_id["b"] is not None and by_id["c"] is not None assert by_id["a"] > by_id["b"] > by_id["c"] def test_deduplicates_across_lists(self): @@ -85,6 +86,7 @@ def test_higher_ranked_doc_gets_higher_score(self): result = _reciprocal_rank_fusion([docs_a, docs_b]) by_id = {doc.id: doc.score for doc in result} # "a" is ranked 1st and 2nd; "c" is ranked 3rd and 1st — "a" should win + assert by_id["a"] is not None and by_id["c"] is not None assert by_id["a"] > by_id["c"] def test_equal_weights_by_default(self): @@ -103,6 +105,7 @@ def test_weights_influence_scores(self): weighted_by_id = {doc.id: doc.score for doc in result_weighted} # with equal weights both docs score the same; with heavy weight on list_a, "a" should outscore "b" assert equal_by_id["a"] == pytest.approx(equal_by_id["b"]) + assert weighted_by_id["a"] is not None and weighted_by_id["b"] is not None assert weighted_by_id["a"] > weighted_by_id["b"] diff --git a/test/utils/test_requests_utils.py b/test/utils/test_requests_utils.py index fd6de9b302d..9bf621268ae 100644 --- a/test/utils/test_requests_utils.py +++ b/test/utils/test_requests_utils.py @@ -104,10 +104,10 @@ def raise_for_status(): "Service Unavailable", request=error_response.request, response=error_response ) - error_response.raise_for_status = raise_for_status + error_response.raise_for_status = raise_for_status # type: ignore[method-assign] success_response = httpx.Response(status_code=200, request=httpx.Request("GET", "https://example.com")) - success_response.raise_for_status = lambda: None + success_response.raise_for_status = lambda: None # type: ignore[method-assign, assignment, return-value] with patch("httpx.Client.request") as mock_request: # First call returns error status code, second call succeeds @@ -218,10 +218,10 @@ def raise_for_status(): "Service Unavailable", request=error_response.request, response=error_response ) - error_response.raise_for_status = raise_for_status + error_response.raise_for_status = raise_for_status # type: ignore[method-assign] success_response = httpx.Response(status_code=200, request=httpx.Request("GET", "https://example.com")) - success_response.raise_for_status = lambda: None + success_response.raise_for_status = lambda: None # type: ignore[method-assign, assignment, return-value] with patch("httpx.AsyncClient.request") as mock_request: # First call returns error status code, second call succeeds diff --git a/test/utils/test_type_serialization.py b/test/utils/test_type_serialization.py index b4b7536517b..913e22538fc 100644 --- a/test/utils/test_type_serialization.py +++ b/test/utils/test_type_serialization.py @@ -148,7 +148,7 @@ def test_output_type_deserialization(): assert deserialize_type("float") == float assert deserialize_type("bool") == bool assert deserialize_type("None") is None - assert deserialize_type("NoneType") == type(None) # type: ignore + assert deserialize_type("NoneType") == type(None) def test_output_builtin_type_deserialization(): @@ -198,10 +198,10 @@ def test_output_type_deserialization_nested(): def test_output_type_serialization_typing_generic_with_nonetype(): # NoneType used as a regular argument of a typing generic (not the implicit None of Optional) # must be kept, otherwise the serialized type is malformed (e.g. "typing.Dict[str]") or loses information. - assert serialize_type(Dict[str, type(None)]) == "typing.Dict[str, None]" - assert serialize_type(Dict[type(None), str]) == "typing.Dict[None, str]" + assert serialize_type(Dict[str, type(None)]) == "typing.Dict[str, None]" # type: ignore[misc] + assert serialize_type(Dict[type(None), str]) == "typing.Dict[None, str]" # type: ignore[misc] assert serialize_type(Tuple[int, type(None)]) == "typing.Tuple[int, None]" - assert serialize_type(List[type(None)]) == "typing.List[None]" + assert serialize_type(List[type(None)]) == "typing.List[None]" # type: ignore[misc] # A Union with more than two members that includes None must keep None as well. assert serialize_type(Union[str, int, None]) == "typing.Union[str, int, None]" # Optional must still be serialized without a redundant trailing None. @@ -210,10 +210,10 @@ def test_output_type_serialization_typing_generic_with_nonetype(): def test_output_type_round_trip_typing_generic_with_nonetype(): for type_ in [ - Dict[str, type(None)], - Dict[type(None), str], + Dict[str, type(None)], # type: ignore[misc] + Dict[type(None), str], # type: ignore[misc] Tuple[int, type(None)], - List[type(None)], + List[type(None)], # type: ignore[misc] Union[str, int, None], Optional[str], ]: