6c7348023b
* chore: enable ANN ruff ruleset, exclude components and testing dirs
- Add ANN (flake8-annotations) to ruff select in pyproject.toml
- Globally ignore ANN401 (Any) for legitimate dynamic types
- Exclude haystack/components/** and haystack/testing/** from ANN checks
(mirrors existing mypy disallow_incomplete_defs=false overrides)
- Fix all 43 ANN violations in the remaining modules:
- Add -> None to __post_init__ in breakpoints, file_content,
image_content, sparse_embedding, streaming_chunk, tool, toolset, auth
- Add -> str to __str__ in filter_policy, auth, hf (2x)
- Add *args: Any, **kwargs: Any + return types to metaclass __call__
in component.py and document.py, and __new__ in chat_message.py
- Add -> None to async _runner() in async_pipeline.py
- Type _check_callable_compatibility args and return bool
- Add Callable return type to _dispatch_bm25
- Type send_telemetry decorator params and return
- Type __init_track__ and __setattr_warn__ wrapper functions
- Type _parse_date, _parse_generic_args, async run()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: fix ANN violations in .github/utils and docs-website scripts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: fix mypy errors introduced by ANN annotations
- async_pipeline.py: _runner() returns Mapping[str, Any], not None
- dataclasses.py: extend type: ignore to cover arg-type in addition to call-arg
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* alphabetical order of ruff rules
* fix: add ANN type annotations to haystack/components and remove per-file-ignore
Fix all ANN (flake8-annotations) violations in haystack/components/ so
the per-file-ignore for that directory can be removed entirely.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: remove haystack.components.* from mypy disallow_incomplete_defs override
All ANN type annotations have been added to haystack/components/, so the
mypy override is no longer needed for that module.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
58 lines
2.4 KiB
Python
58 lines
2.4 KiB
Python
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
import warnings
|
|
from functools import wraps
|
|
from typing import Any, TypeVar
|
|
|
|
T = TypeVar("T")
|
|
|
|
|
|
def _warn_on_inplace_mutation(cls: T) -> T:
|
|
"""
|
|
Decorator that warns if the dataclass is mutated in-place.
|
|
"""
|
|
initializing = set()
|
|
|
|
# mypy requires using getattr/setattr for dunder access, but ruff prefers
|
|
# direct attribute access. We silence mypy here in favor of the more explicit syntax.
|
|
original_init = cls.__init__ # type: ignore[misc]
|
|
original_setattr = cls.__setattr__
|
|
|
|
@wraps(original_init)
|
|
def __init_track__(self: T, *args: Any, **kwargs: Any) -> None:
|
|
# We don't raise warnings during initialization, i.e. during the first call to __init__ and __post_init__.
|
|
initializing.add(id(self))
|
|
try:
|
|
return original_init(self, *args, **kwargs)
|
|
finally:
|
|
initializing.discard(id(self))
|
|
|
|
@wraps(original_setattr)
|
|
def __setattr_warn__(self: T, name: str, value: Any) -> None:
|
|
# We raise warnings if the dataclass is mutated in-place after initialization.
|
|
if (
|
|
id(self) not in initializing
|
|
and name in getattr(self, "__dataclass_fields__", {})
|
|
and name in getattr(self, "__dict__", {})
|
|
):
|
|
# We raise a warning if the attribute is a dataclass field and a dictionary key.
|
|
warnings.warn(
|
|
f"Mutating attribute '{name}' on an instance of "
|
|
f"'{type(self).__name__}' can lead to unexpected behavior by affecting other parts of the pipeline "
|
|
"that use the same dataclass instance. "
|
|
f"Use `dataclasses.replace(instance, {name}=new_value)` instead. "
|
|
"See https://docs.haystack.deepset.ai/docs/custom-components#requirements for details.",
|
|
Warning,
|
|
stacklevel=2,
|
|
)
|
|
# mypy infers original_setattr as bound to the type, expecting (str, Any), we call the unbound form
|
|
return original_setattr(self, name, value) # type: ignore[call-arg, arg-type]
|
|
|
|
# mypy considers direct dunder access on a class unsound, ruff prefers direct access
|
|
cls.__init__ = __init_track__ # type: ignore[misc]
|
|
# mypy does not allow assigning to a method, ruff prefers direct access
|
|
cls.__setattr__ = __setattr_warn__ # type: ignore[method-assign, assignment]
|
|
return cls
|