Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions haystack/utils/http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
12 changes: 7 additions & 5 deletions test/utils/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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()
Expand All @@ -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]
2 changes: 1 addition & 1 deletion test/utils/test_callable_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
2 changes: 1 addition & 1 deletion test/utils/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test/utils/test_experimental.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions test/utils/test_hf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"})
)
Expand All @@ -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]
),
],
)
Expand Down
5 changes: 3 additions & 2 deletions test/utils/test_http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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():
Expand Down
37 changes: 24 additions & 13 deletions test/utils/test_jinja2_chat_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import base64
import json
from typing import Any
from unittest.mock import patch

import pytest
Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -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."},
Expand Down Expand Up @@ -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"}}},
Expand All @@ -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": [
{
Expand Down Expand Up @@ -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": [
{
Expand Down Expand Up @@ -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:"},
Expand Down Expand Up @@ -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:"},
Expand Down Expand Up @@ -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:"},
Expand Down Expand Up @@ -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:"},
Expand Down Expand Up @@ -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": [
{
Expand All @@ -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."
Expand Down Expand Up @@ -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())
Expand Down
4 changes: 2 additions & 2 deletions test/utils/test_jinja2_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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())
Expand Down
3 changes: 3 additions & 0 deletions test/utils/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -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"]


Expand Down
8 changes: 4 additions & 4 deletions test/utils/test_requests_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
14 changes: 7 additions & 7 deletions test/utils/test_type_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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.
Expand All @@ -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],
]:
Expand Down
Loading