diff --git a/haystack/components/converters/markdown.py b/haystack/components/converters/markdown.py index 7b0320293e..c633225890 100644 --- a/haystack/components/converters/markdown.py +++ b/haystack/components/converters/markdown.py @@ -12,6 +12,7 @@ from haystack.components.converters.utils import get_bytestream_from_source, normalize_metadata from haystack.dataclasses import ByteStream from haystack.lazy_imports import LazyImport +from haystack.utils import get_progress_bar_setting with LazyImport("Run 'pip install markdown-it-py mdit_plain'") as markdown_conversion_imports: from markdown_it import MarkdownIt @@ -54,7 +55,8 @@ def __init__(self, table_to_single_line: bool = False, progress_bar: bool = True markdown_conversion_imports.check() self.table_to_single_line = table_to_single_line - self.progress_bar = progress_bar + self._progress_bar_param = progress_bar + self.progress_bar = get_progress_bar_setting(progress_bar) self.store_full_path = store_full_path @component.output_types(documents=list[Document]) diff --git a/haystack/components/embedders/azure_document_embedder.py b/haystack/components/embedders/azure_document_embedder.py index cc4471bd7a..d873d5f4e5 100644 --- a/haystack/components/embedders/azure_document_embedder.py +++ b/haystack/components/embedders/azure_document_embedder.py @@ -9,7 +9,13 @@ from haystack import component, default_from_dict, default_to_dict, logging from haystack.components.embedders import OpenAIDocumentEmbedder -from haystack.utils import Secret, deserialize_callable, deserialize_secrets_inplace, serialize_callable +from haystack.utils import ( + Secret, + deserialize_callable, + deserialize_secrets_inplace, + get_progress_bar_setting, + serialize_callable, +) from haystack.utils.http_client import init_http_client logger = logging.getLogger(__name__) @@ -136,7 +142,8 @@ def __init__( # noqa: PLR0913 (too-many-arguments) # pylint: disable=too-many-p self.prefix = prefix self.suffix = suffix self.batch_size = batch_size - self.progress_bar = progress_bar + self._progress_bar_param = progress_bar + self.progress_bar = get_progress_bar_setting(progress_bar) self.meta_fields_to_embed = meta_fields_to_embed or [] self.embedding_separator = embedding_separator self.timeout = timeout if timeout is not None else float(os.environ.get("OPENAI_TIMEOUT", "30.0")) @@ -186,7 +193,7 @@ def to_dict(self) -> dict[str, Any]: prefix=self.prefix, suffix=self.suffix, batch_size=self.batch_size, - progress_bar=self.progress_bar, + progress_bar=self._progress_bar_param, meta_fields_to_embed=self.meta_fields_to_embed, embedding_separator=self.embedding_separator, api_key=self.api_key.to_dict() if self.api_key is not None else None, diff --git a/haystack/components/embedders/hugging_face_api_document_embedder.py b/haystack/components/embedders/hugging_face_api_document_embedder.py index 9ad4727829..7c9e8ab4f4 100644 --- a/haystack/components/embedders/hugging_face_api_document_embedder.py +++ b/haystack/components/embedders/hugging_face_api_document_embedder.py @@ -11,7 +11,7 @@ from haystack import component, default_from_dict, default_to_dict, logging from haystack.dataclasses import Document from haystack.lazy_imports import LazyImport -from haystack.utils import Secret, deserialize_secrets_inplace +from haystack.utils import Secret, deserialize_secrets_inplace, get_progress_bar_setting from haystack.utils.hf import HFEmbeddingAPIType, HFModelType, check_valid_model from haystack.utils.url_validation import is_valid_http_url @@ -179,7 +179,8 @@ def __init__( self.truncate = truncate self.normalize = normalize self.batch_size = batch_size - self.progress_bar = progress_bar + self._progress_bar_param = progress_bar + self.progress_bar = get_progress_bar_setting(progress_bar) self.meta_fields_to_embed = meta_fields_to_embed or [] self.embedding_separator = embedding_separator self._client = InferenceClient(**client_args) @@ -202,7 +203,7 @@ def to_dict(self) -> dict[str, Any]: truncate=self.truncate, normalize=self.normalize, batch_size=self.batch_size, - progress_bar=self.progress_bar, + progress_bar=self._progress_bar_param, meta_fields_to_embed=self.meta_fields_to_embed, embedding_separator=self.embedding_separator, ) diff --git a/haystack/components/embedders/image/sentence_transformers_doc_image_embedder.py b/haystack/components/embedders/image/sentence_transformers_doc_image_embedder.py index cadf09e155..f8cabab318 100644 --- a/haystack/components/embedders/image/sentence_transformers_doc_image_embedder.py +++ b/haystack/components/embedders/image/sentence_transformers_doc_image_embedder.py @@ -16,6 +16,7 @@ _SentenceTransformersEmbeddingBackendFactory, ) from haystack.lazy_imports import LazyImport +from haystack.utils import get_progress_bar_setting from haystack.utils.auth import Secret, deserialize_secrets_inplace from haystack.utils.device import ComponentDevice from haystack.utils.hf import deserialize_hf_model_kwargs, serialize_hf_model_kwargs @@ -140,7 +141,8 @@ def __init__( # noqa: PLR0913 self.device = ComponentDevice.resolve_device(device) self.token = token self.batch_size = batch_size - self.progress_bar = progress_bar + self._progress_bar_param = progress_bar + self.progress_bar = get_progress_bar_setting(progress_bar) self.normalize_embeddings = normalize_embeddings self.trust_remote_code = trust_remote_code self.local_files_only = local_files_only @@ -167,7 +169,7 @@ def to_dict(self) -> dict[str, Any]: device=self.device.to_dict(), token=self.token.to_dict() if self.token else None, batch_size=self.batch_size, - progress_bar=self.progress_bar, + progress_bar=self._progress_bar_param, normalize_embeddings=self.normalize_embeddings, trust_remote_code=self.trust_remote_code, local_files_only=self.local_files_only, diff --git a/haystack/components/embedders/openai_document_embedder.py b/haystack/components/embedders/openai_document_embedder.py index 99eaa19353..6c296dfe8f 100644 --- a/haystack/components/embedders/openai_document_embedder.py +++ b/haystack/components/embedders/openai_document_embedder.py @@ -12,7 +12,7 @@ from tqdm.asyncio import tqdm as async_tqdm from haystack import Document, component, default_from_dict, default_to_dict, logging -from haystack.utils import Secret, deserialize_secrets_inplace +from haystack.utils import Secret, deserialize_secrets_inplace, get_progress_bar_setting from haystack.utils.http_client import init_http_client logger = logging.getLogger(__name__) @@ -115,7 +115,8 @@ def __init__( # noqa: PLR0913 (too-many-arguments) # pylint: disable=too-many-p self.prefix = prefix self.suffix = suffix self.batch_size = batch_size - self.progress_bar = progress_bar + self._progress_bar_param = progress_bar + self.progress_bar = get_progress_bar_setting(progress_bar) self.meta_fields_to_embed = meta_fields_to_embed or [] self.embedding_separator = embedding_separator self.timeout = timeout @@ -164,7 +165,7 @@ def to_dict(self) -> dict[str, Any]: prefix=self.prefix, suffix=self.suffix, batch_size=self.batch_size, - progress_bar=self.progress_bar, + progress_bar=self._progress_bar_param, meta_fields_to_embed=self.meta_fields_to_embed, embedding_separator=self.embedding_separator, timeout=self.timeout, diff --git a/haystack/components/embedders/sentence_transformers_document_embedder.py b/haystack/components/embedders/sentence_transformers_document_embedder.py index b902537fa0..29cdee6649 100644 --- a/haystack/components/embedders/sentence_transformers_document_embedder.py +++ b/haystack/components/embedders/sentence_transformers_document_embedder.py @@ -10,7 +10,7 @@ _SentenceTransformersEmbeddingBackend, _SentenceTransformersEmbeddingBackendFactory, ) -from haystack.utils import ComponentDevice, Secret, deserialize_secrets_inplace +from haystack.utils import ComponentDevice, Secret, deserialize_secrets_inplace, get_progress_bar_setting from haystack.utils.hf import deserialize_hf_model_kwargs, serialize_hf_model_kwargs @@ -131,7 +131,8 @@ def __init__( # noqa: PLR0913 # pylint: disable=too-many-positional-arguments,t self.prefix = prefix self.suffix = suffix self.batch_size = batch_size - self.progress_bar = progress_bar + self._progress_bar_param = progress_bar + self.progress_bar = get_progress_bar_setting(progress_bar) self.normalize_embeddings = normalize_embeddings self.meta_fields_to_embed = meta_fields_to_embed or [] self.embedding_separator = embedding_separator @@ -168,7 +169,7 @@ def to_dict(self) -> dict[str, Any]: prefix=self.prefix, suffix=self.suffix, batch_size=self.batch_size, - progress_bar=self.progress_bar, + progress_bar=self._progress_bar_param, normalize_embeddings=self.normalize_embeddings, meta_fields_to_embed=self.meta_fields_to_embed, embedding_separator=self.embedding_separator, diff --git a/haystack/components/embedders/sentence_transformers_sparse_document_embedder.py b/haystack/components/embedders/sentence_transformers_sparse_document_embedder.py index 965dde9042..43030723e5 100644 --- a/haystack/components/embedders/sentence_transformers_sparse_document_embedder.py +++ b/haystack/components/embedders/sentence_transformers_sparse_document_embedder.py @@ -10,7 +10,7 @@ _SentenceTransformersSparseEmbeddingBackendFactory, _SentenceTransformersSparseEncoderEmbeddingBackend, ) -from haystack.utils import ComponentDevice, Secret, deserialize_secrets_inplace +from haystack.utils import ComponentDevice, Secret, deserialize_secrets_inplace, get_progress_bar_setting from haystack.utils.hf import deserialize_hf_model_kwargs, serialize_hf_model_kwargs @@ -112,7 +112,8 @@ def __init__( # noqa: PLR0913 self.prefix = prefix self.suffix = suffix self.batch_size = batch_size - self.progress_bar = progress_bar + self._progress_bar_param = progress_bar + self.progress_bar = get_progress_bar_setting(progress_bar) self.meta_fields_to_embed = meta_fields_to_embed or [] self.embedding_separator = embedding_separator self.trust_remote_code = trust_remote_code @@ -145,7 +146,7 @@ def to_dict(self) -> dict[str, Any]: prefix=self.prefix, suffix=self.suffix, batch_size=self.batch_size, - progress_bar=self.progress_bar, + progress_bar=self._progress_bar_param, meta_fields_to_embed=self.meta_fields_to_embed, embedding_separator=self.embedding_separator, trust_remote_code=self.trust_remote_code, diff --git a/haystack/components/embedders/sentence_transformers_text_embedder.py b/haystack/components/embedders/sentence_transformers_text_embedder.py index c2c9f5b695..d3f42f8f0e 100644 --- a/haystack/components/embedders/sentence_transformers_text_embedder.py +++ b/haystack/components/embedders/sentence_transformers_text_embedder.py @@ -9,7 +9,7 @@ _SentenceTransformersEmbeddingBackend, _SentenceTransformersEmbeddingBackendFactory, ) -from haystack.utils import ComponentDevice, Secret, deserialize_secrets_inplace +from haystack.utils import ComponentDevice, Secret, deserialize_secrets_inplace, get_progress_bar_setting from haystack.utils.hf import deserialize_hf_model_kwargs, serialize_hf_model_kwargs @@ -120,7 +120,8 @@ def __init__( # noqa: PLR0913 # pylint: disable=too-many-positional-arguments self.prefix = prefix self.suffix = suffix self.batch_size = batch_size - self.progress_bar = progress_bar + self._progress_bar_param = progress_bar + self.progress_bar = get_progress_bar_setting(progress_bar) self.normalize_embeddings = normalize_embeddings self.trust_remote_code = trust_remote_code self.revision = revision @@ -155,7 +156,7 @@ def to_dict(self) -> dict[str, Any]: prefix=self.prefix, suffix=self.suffix, batch_size=self.batch_size, - progress_bar=self.progress_bar, + progress_bar=self._progress_bar_param, normalize_embeddings=self.normalize_embeddings, trust_remote_code=self.trust_remote_code, revision=self.revision, diff --git a/haystack/components/evaluators/context_relevance.py b/haystack/components/evaluators/context_relevance.py index b08d005579..bd8c9378f7 100644 --- a/haystack/components/evaluators/context_relevance.py +++ b/haystack/components/evaluators/context_relevance.py @@ -198,7 +198,7 @@ def to_dict(self) -> dict[str, Any]: self, chat_generator=component_to_dict(obj=self._chat_generator, name="chat_generator"), examples=self.examples, - progress_bar=self.progress_bar, + progress_bar=self._progress_bar_param, raise_on_failure=self.raise_on_failure, ) diff --git a/haystack/components/evaluators/faithfulness.py b/haystack/components/evaluators/faithfulness.py index 3b722cc98f..dd4dd7dd59 100644 --- a/haystack/components/evaluators/faithfulness.py +++ b/haystack/components/evaluators/faithfulness.py @@ -192,7 +192,7 @@ def to_dict(self) -> dict[str, Any]: self, chat_generator=component_to_dict(obj=self._chat_generator, name="chat_generator"), examples=self.examples, - progress_bar=self.progress_bar, + progress_bar=self._progress_bar_param, raise_on_failure=self.raise_on_failure, ) diff --git a/haystack/components/evaluators/llm_evaluator.py b/haystack/components/evaluators/llm_evaluator.py index ec7477bb68..b56359b741 100644 --- a/haystack/components/evaluators/llm_evaluator.py +++ b/haystack/components/evaluators/llm_evaluator.py @@ -13,7 +13,7 @@ from haystack.components.generators.chat.types import ChatGenerator from haystack.core.serialization import component_to_dict from haystack.dataclasses.chat_message import ChatMessage -from haystack.utils import deserialize_chatgenerator_inplace, deserialize_type, serialize_type +from haystack.utils import deserialize_chatgenerator_inplace, deserialize_type, get_progress_bar_setting, serialize_type logger = logging.getLogger(__name__) @@ -97,7 +97,8 @@ def __init__( # pylint: disable=too-many-positional-arguments self.inputs = inputs self.outputs = outputs self.examples = examples - self.progress_bar = progress_bar + self._progress_bar_param = progress_bar + self.progress_bar = get_progress_bar_setting(progress_bar) template = self.prepare_template() self.builder = PromptBuilder(template=template) @@ -298,7 +299,7 @@ def to_dict(self) -> dict[str, Any]: outputs=self.outputs, examples=self.examples, chat_generator=component_to_dict(obj=self._chat_generator, name="chat_generator"), - progress_bar=self.progress_bar, + progress_bar=self._progress_bar_param, ) @classmethod diff --git a/haystack/utils/__init__.py b/haystack/utils/__init__.py index 7439e97658..15ac27e8d5 100644 --- a/haystack/utils/__init__.py +++ b/haystack/utils/__init__.py @@ -18,6 +18,7 @@ "jinja2_extensions": ["Jinja2TimeExtension"], "jupyter": ["is_in_jupyter"], "misc": ["expit", "expand_page_range"], + "progress_helper": ["get_progress_bar_setting", "HAYSTACK_PROGRESS_ENV"], "requests_utils": ["request_with_retry", "async_request_with_retry"], "type_serialization": ["deserialize_type", "serialize_type"], } @@ -44,6 +45,8 @@ from .jupyter import is_in_jupyter as is_in_jupyter from .misc import expand_page_range as expand_page_range from .misc import expit as expit + from .progress_helper import HAYSTACK_PROGRESS_ENV as HAYSTACK_PROGRESS_ENV + from .progress_helper import get_progress_bar_setting as get_progress_bar_setting from .requests_utils import async_request_with_retry as async_request_with_retry from .requests_utils import request_with_retry as request_with_retry from .type_serialization import deserialize_type as deserialize_type diff --git a/haystack/utils/progress_helper.py b/haystack/utils/progress_helper.py new file mode 100644 index 0000000000..84dc7f97f1 --- /dev/null +++ b/haystack/utils/progress_helper.py @@ -0,0 +1,82 @@ +# SPDX-FileCopyrightText: 2022-present deepset GmbH +# +# SPDX-License-Identifier: Apache-2.0 + +"""Utilities for controlling progress bar behavior across Haystack components.""" + +import os + +__all__ = ["HAYSTACK_PROGRESS_ENV", "get_progress_bar_setting"] + +HAYSTACK_PROGRESS_ENV = "HAYSTACK_PROGRESS_BARS" +_DISABLE_VALUES = {"0", "false", "no", "off", "disable", "disabled"} +_ENABLE_VALUES = {"1", "true", "yes", "on", "enable", "enabled"} + + +def get_progress_bar_setting(component_default: bool = True, env_var: str = HAYSTACK_PROGRESS_ENV) -> bool: + """ + Determine whether to show progress bars based on component default and environment variable. + + This function allows users to globally control progress bar behavior across all Haystack + components using the ``HAYSTACK_PROGRESS_BARS`` environment variable, while still + allowing individual components to override this behavior. + + Priority order: + 1. Environment variable ``HAYSTACK_PROGRESS_BARS`` (if set and valid) + 2. Component's ``progress_bar`` parameter (``component_default``) + + :param component_default: The component's default progress_bar value. + This is used when the environment variable is not set or has an invalid value. + :param env_var: Name of the environment variable to check. + Defaults to ``HAYSTACK_PROGRESS_BARS``. + :return: ``True`` if progress bars should be shown, ``False`` otherwise. + + **Environment variable values:** + + To **disable** progress bars, set one of: + - ``"0"``, ``"false"``, ``"no"``, ``"off"``, ``"disable"``, ``"disabled"`` + + To **enable** progress bars, set one of: + - ``"1"``, ``"true"``, ``"yes"``, ``"on"``, ``"enable"``, ``"enabled"`` + + If the environment variable is unset or contains an invalid value, + the ``component_default`` value is used. + + **Examples:** + + Disable all progress bars globally: + + .. code-block:: bash + + export HAYSTACK_PROGRESS_BARS=0 + + .. code-block:: python + + from haystack.utils import get_progress_bar_setting + + class MyComponent: + def __init__(self, progress_bar: bool = True): + self.progress_bar = get_progress_bar_setting(progress_bar) + + Enable all progress bars globally (even if component default is False): + + .. code-block:: bash + + export HAYSTACK_PROGRESS_BARS=1 + + No environment variable set - use component default: + + .. code-block:: python + + assert get_progress_bar_setting(True) is True + assert get_progress_bar_setting(False) is False + """ + env_value = os.getenv(env_var, "").lower() + + if env_value in _DISABLE_VALUES: + return False + if env_value in _ENABLE_VALUES: + return True + + # Environment variable not set or has invalid value -> use component default + return component_default diff --git a/releasenotes/notes/add-haystack-progress-bars-env-var.yaml b/releasenotes/notes/add-haystack-progress-bars-env-var.yaml new file mode 100644 index 0000000000..6fbcaf1d10 --- /dev/null +++ b/releasenotes/notes/add-haystack-progress-bars-env-var.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add support for the ``HAYSTACK_PROGRESS_BARS`` environment variable to globally + control progress bar behavior across all Haystack components. Users can now disable + all progress bars by setting ``HAYSTACK_PROGRESS_BARS=0`` or enable them with + ``HAYSTACK_PROGRESS_BARS=1``. This restores functionality from Haystack 1.x. diff --git a/test/utils/test_progress_helper.py b/test/utils/test_progress_helper.py new file mode 100644 index 0000000000..fe8ef9fc6b --- /dev/null +++ b/test/utils/test_progress_helper.py @@ -0,0 +1,72 @@ +# SPDX-FileCopyrightText: 2022-present deepset GmbH +# +# SPDX-License-Identifier: Apache-2.0 + +from haystack.utils import HAYSTACK_PROGRESS_ENV, get_progress_bar_setting + + +def test_progress_bar_env_disabled(monkeypatch): + """HAYSTACK_PROGRESS_BARS set to disable values should disable progress bars.""" + disable_values = ["0", "false", "no", "off", "disable", "disabled"] + for val in disable_values: + monkeypatch.setenv(HAYSTACK_PROGRESS_ENV, val) + assert get_progress_bar_setting(component_default=True) is False + + +def test_progress_bar_env_enabled(monkeypatch): + """HAYSTACK_PROGRESS_BARS set to enable values should enable progress bars.""" + enable_values = ["1", "true", "yes", "on", "enable", "enabled"] + for val in enable_values: + monkeypatch.setenv(HAYSTACK_PROGRESS_ENV, val) + assert get_progress_bar_setting(component_default=False) is True + + +def test_progress_bar_env_case_insensitive(monkeypatch): + """Environment variable should be case-insensitive.""" + monkeypatch.setenv(HAYSTACK_PROGRESS_ENV, "TRUE") + assert get_progress_bar_setting(component_default=False) is True + + monkeypatch.setenv(HAYSTACK_PROGRESS_ENV, "FALSE") + assert get_progress_bar_setting(component_default=True) is False + + monkeypatch.setenv(HAYSTACK_PROGRESS_ENV, "OFF") + assert get_progress_bar_setting(component_default=True) is False + + +def test_progress_bar_env_unset(monkeypatch): + """Unset environment variable should use component default.""" + monkeypatch.delenv(HAYSTACK_PROGRESS_ENV, raising=False) + + assert get_progress_bar_setting(component_default=True) is True + assert get_progress_bar_setting(component_default=False) is False + + +def test_progress_bar_env_invalid_value(monkeypatch): + """Invalid environment variable value should fall back to component default.""" + invalid_values = ["", "invalid", "2", "maybe", "2.5"] + for val in invalid_values: + monkeypatch.setenv(HAYSTACK_PROGRESS_ENV, val) + # Should fall back to component_default + assert get_progress_bar_setting(component_default=True) is True + assert get_progress_bar_setting(component_default=False) is False + + +def test_progress_bar_custom_env_var(monkeypatch): + """Should support custom environment variable name.""" + monkeypatch.setenv("MY_CUSTOM_PROGRESS_VAR", "0") + assert get_progress_bar_setting(component_default=True, env_var="MY_CUSTOM_PROGRESS_VAR") is False + + monkeypatch.setenv("MY_CUSTOM_PROGRESS_VAR", "1") + assert get_progress_bar_setting(component_default=False, env_var="MY_CUSTOM_PROGRESS_VAR") is True + + +def test_progress_bar_constant_accessible(): + """HAYSTACK_PROGRESS_ENV constant should be accessible.""" + assert HAYSTACK_PROGRESS_ENV == "HAYSTACK_PROGRESS_BARS" + + +def test_progress_bar_default_parameter(): + """Component default should be used when env var is not set.""" + # No env var set, should return component default + assert get_progress_bar_setting() is True + assert get_progress_bar_setting(component_default=False) is False