• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

deepset-ai / haystack / 16754475749

05 Aug 2025 03:26PM UTC coverage: 91.946% (+0.05%) from 91.901%
16754475749

Pull #9678

github

web-flow
Merge 31abaf9ce into 323274e17
Pull Request #9678: Chore/pep585 type hints

12774 of 13893 relevant lines covered (91.95%)

0.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.44
haystack/core/component/component.py
1
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
2
#
3
# SPDX-License-Identifier: Apache-2.0
4

5
"""
6
Attributes:
7

8
    component: Marks a class as a component. Any class decorated with `@component` can be used by a Pipeline.
9

10
All components must follow the contract below. This docstring is the source of truth for components contract.
11

12
<hr>
13

14
`@component` decorator
15

16
All component classes must be decorated with the `@component` decorator. This allows Haystack to discover them.
17

18
<hr>
19

20
`__init__(self, **kwargs)`
21

22
Optional method.
23

24
Components may have an `__init__` method where they define:
25

26
- `self.init_parameters = {same parameters that the __init__ method received}`:
27
    In this dictionary you can store any state the components wish to be persisted when they are saved.
28
    These values will be given to the `__init__` method of a new instance when the pipeline is loaded.
29
    Note that by default the `@component` decorator saves the arguments automatically.
30
    However, if a component sets their own `init_parameters` manually in `__init__()`, that will be used instead.
31
    Note: all of the values contained here **must be JSON serializable**. Serialize them manually if needed.
32

33
Components should take only "basic" Python types as parameters of their `__init__` function, or iterables and
34
dictionaries containing only such values. Anything else (objects, functions, etc) will raise an exception at init
35
time. If there's the need for such values, consider serializing them to a string.
36

37
_(TODO explain how to use classes and functions in init. In the meantime see `test/components/test_accumulate.py`)_
38

39
The `__init__` must be extremely lightweight, because it's a frequent operation during the construction and
40
validation of the pipeline. If a component has some heavy state to initialize (models, backends, etc...) refer to
41
the `warm_up()` method.
42

43
<hr>
44

45
`warm_up(self)`
46

47
Optional method.
48

49
This method is called by Pipeline before the graph execution. Make sure to avoid double-initializations,
50
because Pipeline will not keep track of which components it called `warm_up()` on.
51

52
<hr>
53

54
`run(self, data)`
55

56
Mandatory method.
57

58
This is the method where the main functionality of the component should be carried out. It's called by
59
`Pipeline.run()`.
60

61
When the component should run, Pipeline will call this method with an instance of the dataclass returned by the
62
method decorated with `@component.input`. This dataclass contains:
63

64
- all the input values coming from other components connected to it,
65
- if any is missing, the corresponding value defined in `self.defaults`, if it exists.
66

67
`run()` must return a single instance of the dataclass declared through the method decorated with
68
`@component.output`.
69

70
"""
71

72
import inspect
1✔
73
from collections.abc import Callable, Coroutine
1✔
74
from contextlib import contextmanager
1✔
75
from contextvars import ContextVar
1✔
76
from copy import deepcopy
1✔
77
from dataclasses import dataclass
1✔
78
from types import new_class
1✔
79
from typing import Any, Iterator, Mapping, Optional, Protocol, TypeVar, Union, overload, runtime_checkable
1✔
80

81
from typing_extensions import ParamSpec
1✔
82

83
from haystack import logging
1✔
84
from haystack.core.errors import ComponentError
1✔
85

86
from .sockets import Sockets
1✔
87
from .types import InputSocket, OutputSocket, _empty
1✔
88

89
logger = logging.getLogger(__name__)
1✔
90

91
RunParamsT = ParamSpec("RunParamsT")
1✔
92
RunReturnT = TypeVar("RunReturnT", bound=Union[Mapping[str, Any], Coroutine[Any, Any, Mapping[str, Any]]])
1✔
93

94

95
@dataclass
1✔
96
class PreInitHookPayload:
1✔
97
    """
98
    Payload for the hook called before a component instance is initialized.
99

100
    :param callback:
101
        Receives the following inputs: component class and init parameter keyword args.
102
    :param in_progress:
103
        Flag to indicate if the hook is currently being executed.
104
        Used to prevent it from being called recursively (if the component's constructor
105
        instantiates another component).
106
    """
107

108
    callback: Callable
1✔
109
    in_progress: bool = False
1✔
110

111

112
_COMPONENT_PRE_INIT_HOOK: ContextVar[Optional[PreInitHookPayload]] = ContextVar("component_pre_init_hook", default=None)
1✔
113

114

115
@contextmanager
1✔
116
def _hook_component_init(callback: Callable) -> Iterator[None]:
1✔
117
    """
118
    Context manager to set a callback that will be invoked before a component's constructor is called.
119

120
    The callback receives the component class and the init parameters (as keyword arguments) and can modify the init
121
    parameters in place.
122

123
    :param callback:
124
        Callback function to invoke.
125
    """
126
    token = _COMPONENT_PRE_INIT_HOOK.set(PreInitHookPayload(callback))
1✔
127
    try:
1✔
128
        yield
1✔
129
    finally:
130
        _COMPONENT_PRE_INIT_HOOK.reset(token)
1✔
131

132

133
@runtime_checkable
1✔
134
class Component(Protocol):
1✔
135
    """
136
    Note this is only used by type checking tools.
137

138
    In order to implement the `Component` protocol, custom components need to
139
    have a `run` method. The signature of the method and its return value
140
    won't be checked, i.e. classes with the following methods:
141

142
        def run(self, param: str) -> Dict[str, Any]:
143
            ...
144

145
    and
146

147
        def run(self, **kwargs):
148
            ...
149

150
    will be both considered as respecting the protocol. This makes the type
151
    checking much weaker, but we have other places where we ensure code is
152
    dealing with actual Components.
153

154
    The protocol is runtime checkable so it'll be possible to assert:
155

156
        isinstance(MyComponent, Component)
157
    """
158

159
    # The following expression defines a run method compatible with any input signature.
160
    # Its type is equivalent to Callable[..., Dict[str, Any]].
161
    # See https://typing.python.org/en/latest/spec/callables.html#meaning-of-in-callable.
162
    #
163
    # Using `run: Callable[..., Dict[str, Any]]` directly leads to type errors: the protocol would expect a settable
164
    # attribute `run`, while the actual implementation is a read-only method.
165
    # For example:
166
    # from haystack import Pipeline, component
167
    # @component
168
    # class MyComponent:
169
    #     @component.output_types(out=str)
170
    #     def run(self):
171
    #         return {"out": "Hello, world!"}
172
    # pipeline = Pipeline()
173
    # pipeline.add_component("my_component", MyComponent())
174
    #
175
    # mypy raises:
176
    # error: Argument 2 to "add_component" of "PipelineBase" has incompatible type "MyComponent"; expected "Component"
177
    # [arg-type]
178
    # note: Protocol member Component.run expected settable variable, got read-only attribute
179

180
    def run(self, *args: Any, **kwargs: Any) -> Mapping[str, Any]:  # pylint: disable=missing-function-docstring # noqa: D102
1✔
181
        ...
182

183

184
class ComponentMeta(type):
1✔
185
    @staticmethod
1✔
186
    def _positional_to_kwargs(cls_type: type, args: tuple[Any, ...]) -> dict[str, Any]:
1✔
187
        """
188
        Convert positional arguments to keyword arguments based on the signature of the `__init__` method.
189
        """
190
        init_signature = inspect.signature(cls_type.__init__)  # type:ignore[misc]
1✔
191
        init_params = {name: info for name, info in init_signature.parameters.items() if name != "self"}
1✔
192

193
        out = {}
1✔
194
        for arg, (name, info) in zip(args, init_params.items()):
1✔
195
            if info.kind == inspect.Parameter.VAR_POSITIONAL:
1✔
196
                raise ComponentError(
1✔
197
                    "Pre-init hooks do not support components with variadic positional args in their init method"
198
                )
199

200
            assert info.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.POSITIONAL_ONLY)
1✔
201
            out[name] = arg
1✔
202
        return out
1✔
203

204
    @staticmethod
1✔
205
    def _parse_and_set_output_sockets(instance: Any) -> None:
1✔
206
        has_async_run = hasattr(instance, "run_async")
1✔
207

208
        # If `component.set_output_types()` was called in the component constructor,
209
        # `__haystack_output__` is already populated, no need to do anything.
210
        if not hasattr(instance, "__haystack_output__"):
1✔
211
            # If that's not the case, we need to populate `__haystack_output__`
212
            #
213
            # If either of the run methods were decorated, they'll have a field assigned that
214
            # stores the output specification. If both run methods were decorated, we ensure that
215
            # outputs are the same. We deepcopy the content of the cache to transfer ownership from
216
            # the class method to the actual instance, so that different instances of the same class
217
            # won't share this data.
218

219
            run_output_types = getattr(instance.run, "_output_types_cache", {})
1✔
220
            async_run_output_types = getattr(instance.run_async, "_output_types_cache", {}) if has_async_run else {}
1✔
221

222
            if has_async_run and run_output_types != async_run_output_types:
1✔
223
                raise ComponentError("Output type specifications of 'run' and 'run_async' methods must be the same")
1✔
224
            output_types_cache = run_output_types
1✔
225

226
            instance.__haystack_output__ = Sockets(instance, deepcopy(output_types_cache), OutputSocket)
1✔
227

228
    @staticmethod
1✔
229
    def _parse_and_set_input_sockets(component_cls: type, instance: Any) -> None:
1✔
230
        def inner(method, sockets):
1✔
231
            from inspect import Parameter
1✔
232

233
            run_signature = inspect.signature(method)
1✔
234

235
            for param_name, param_info in run_signature.parameters.items():
1✔
236
                if param_name == "self" or param_info.kind in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD):
1✔
237
                    continue
1✔
238

239
                socket_kwargs = {"name": param_name, "type": param_info.annotation}
1✔
240
                if param_info.default != Parameter.empty:
1✔
241
                    socket_kwargs["default_value"] = param_info.default
1✔
242

243
                new_socket = InputSocket(**socket_kwargs)
1✔
244

245
                # Also ensure that new sockets don't override existing ones.
246
                existing_socket = sockets.get(param_name)
1✔
247
                if existing_socket is not None and existing_socket != new_socket:
1✔
248
                    raise ComponentError(
1✔
249
                        "set_input_types()/set_input_type() cannot override the parameters of the 'run' method"
250
                    )
251

252
                sockets[param_name] = new_socket
1✔
253

254
            return run_signature
1✔
255

256
        # Create the sockets if set_input_types() wasn't called in the constructor.
257
        if not hasattr(instance, "__haystack_input__"):
1✔
258
            instance.__haystack_input__ = Sockets(instance, {}, InputSocket)
1✔
259

260
        inner(getattr(component_cls, "run"), instance.__haystack_input__)
1✔
261

262
        # Ensure that the sockets are the same for the async method, if it exists.
263
        async_run = getattr(component_cls, "run_async", None)
1✔
264
        if async_run is not None:
1✔
265
            run_sockets = Sockets(instance, {}, InputSocket)
1✔
266
            async_run_sockets = Sockets(instance, {}, InputSocket)
1✔
267

268
            # Can't use the sockets from above as they might contain
269
            # values set with set_input_types().
270
            run_sig = inner(getattr(component_cls, "run"), run_sockets)
1✔
271
            async_run_sig = inner(async_run, async_run_sockets)
1✔
272

273
            if async_run_sockets != run_sockets or run_sig != async_run_sig:
1✔
274
                sig_diff = _compare_run_methods_signatures(run_sig, async_run_sig)
1✔
275
                raise ComponentError(
1✔
276
                    f"Parameters of 'run' and 'run_async' methods must be the same.\nDifferences found:\n{sig_diff}"
277
                )
278

279
    def __call__(cls, *args, **kwargs):
1✔
280
        """
281
        This method is called when clients instantiate a Component and runs before __new__ and __init__.
282
        """
283
        # This will call __new__ then __init__, giving us back the Component instance
284
        pre_init_hook = _COMPONENT_PRE_INIT_HOOK.get()
1✔
285
        if pre_init_hook is None or pre_init_hook.in_progress:
1✔
286
            instance = super().__call__(*args, **kwargs)
1✔
287
        else:
288
            try:
1✔
289
                pre_init_hook.in_progress = True
1✔
290
                named_positional_args = ComponentMeta._positional_to_kwargs(cls, args)
1✔
291
                assert set(named_positional_args.keys()).intersection(kwargs.keys()) == set(), (
1✔
292
                    "positional and keyword arguments overlap"
293
                )
294
                kwargs.update(named_positional_args)
1✔
295
                pre_init_hook.callback(cls, kwargs)
1✔
296
                instance = super().__call__(**kwargs)
1✔
297
            finally:
298
                pre_init_hook.in_progress = False
1✔
299

300
        # Before returning, we have the chance to modify the newly created
301
        # Component instance, so we take the chance and set up the I/O sockets
302
        has_async_run = hasattr(instance, "run_async")
1✔
303
        if has_async_run and not inspect.iscoroutinefunction(instance.run_async):
1✔
304
            raise ComponentError(f"Method 'run_async' of component '{cls.__name__}' must be a coroutine")
1✔
305
        instance.__haystack_supports_async__ = has_async_run
1✔
306

307
        ComponentMeta._parse_and_set_input_sockets(cls, instance)
1✔
308
        ComponentMeta._parse_and_set_output_sockets(instance)
1✔
309

310
        # Since a Component can't be used in multiple Pipelines at the same time
311
        # we need to know if it's already owned by a Pipeline when adding it to one.
312
        # We use this flag to check that.
313
        instance.__haystack_added_to_pipeline__ = None
1✔
314

315
        return instance
1✔
316

317

318
def _component_repr(component: Component) -> str:
1✔
319
    """
320
    All Components override their __repr__ method with this one.
321

322
    It prints the component name and the input/output sockets.
323
    """
324
    result = object.__repr__(component)
1✔
325
    if pipeline := getattr(component, "__haystack_added_to_pipeline__", None):
1✔
326
        # This Component has been added in a Pipeline, let's get the name from there.
327
        result += f"\n{pipeline.get_component_name(component)}"
1✔
328

329
    # We're explicitly ignoring the type here because we're sure that the component
330
    # has the __haystack_input__ and __haystack_output__ attributes at this point
331
    return (
1✔
332
        f"{result}\n{getattr(component, '__haystack_input__', '<invalid_input_sockets>')}"
333
        f"\n{getattr(component, '__haystack_output__', '<invalid_output_sockets>')}"
334
    )
335

336

337
def _component_run_has_kwargs(component_cls: type) -> bool:
1✔
338
    run_method = getattr(component_cls, "run", None)
1✔
339
    if run_method is None:
1✔
340
        return False
×
341
    else:
342
        return any(
1✔
343
            param.kind == inspect.Parameter.VAR_KEYWORD for param in inspect.signature(run_method).parameters.values()
344
        )
345

346

347
def _compare_run_methods_signatures(run_sig: inspect.Signature, async_run_sig: inspect.Signature) -> str:
1✔
348
    """
349
    Builds a detailed error message with the differences between the signatures of the run and run_async methods.
350

351
    :param run_sig: The signature of the run method
352
    :param async_run_sig: The signature of the run_async method
353

354
    :returns:
355
        A detailed error message if signatures don't match, empty string if they do
356
    """
357
    differences = []
1✔
358
    run_params = list(run_sig.parameters.items())
1✔
359
    async_params = list(async_run_sig.parameters.items())
1✔
360

361
    if len(run_params) != len(async_params):
1✔
362
        differences.append(
1✔
363
            f"Different number of parameters: run has {len(run_params)}, run_async has {len(async_params)}"
364
        )
365

366
    for (run_name, run_param), (async_name, async_param) in zip(run_params, async_params):
1✔
367
        if run_name != async_name:
1✔
368
            differences.append(f"Parameter name mismatch: {run_name} vs {async_name}")
1✔
369

370
        if run_param.annotation != async_param.annotation:
1✔
371
            differences.append(
1✔
372
                f"Parameter '{run_name}' type mismatch: {run_param.annotation} vs {async_param.annotation}"
373
            )
374

375
        if run_param.default != async_param.default:
1✔
376
            differences.append(
1✔
377
                f"Parameter '{run_name}' default value mismatch: {run_param.default} vs {async_param.default}"
378
            )
379

380
        if run_param.kind != async_param.kind:
1✔
381
            differences.append(
1✔
382
                f"Parameter '{run_name}' kind (POSITIONAL, KEYWORD, etc.) mismatch: "
383
                f"{run_param.kind} vs {async_param.kind}"
384
            )
385

386
    return "\n".join(differences)
1✔
387

388

389
T = TypeVar("T", bound=Component)
1✔
390

391

392
class _Component:
1✔
393
    """
394
    See module's docstring.
395

396
    Args:
397
        cls: the class that should be used as a component.
398

399
    Returns:
400
        A class that can be recognized as a component.
401

402
    Raises:
403
        ComponentError: if the class provided has no `run()` method or otherwise doesn't respect the component contract.
404
    """
405

406
    def __init__(self):
1✔
407
        self.registry = {}
1✔
408

409
    def set_input_type(
1✔
410
        self,
411
        instance: Component,
412
        name: str,
413
        type: Any,  # noqa: A002
414
        default: Any = _empty,
415
    ) -> None:
416
        """
417
        Add a single input socket to the component instance.
418

419
        Replaces any existing input socket with the same name.
420

421
        :param instance: Component instance where the input type will be added.
422
        :param name: name of the input socket.
423
        :param type: type of the input socket.
424
        :param default: default value of the input socket, defaults to _empty
425
        """
426
        if not _component_run_has_kwargs(instance.__class__):
1✔
427
            raise ComponentError(
1✔
428
                "Cannot set input types on a component that doesn't have a kwargs parameter in the 'run' method"
429
            )
430

431
        if not hasattr(instance, "__haystack_input__"):
1✔
432
            instance.__haystack_input__ = Sockets(instance, {}, InputSocket)  # type: ignore
1✔
433
        instance.__haystack_input__[name] = InputSocket(name=name, type=type, default_value=default)  # type: ignore
1✔
434

435
    def set_input_types(self, instance, **types):
1✔
436
        """
437
        Method that specifies the input types when 'kwargs' is passed to the run method.
438

439
        Use as:
440

441
        ```python
442
        @component
443
        class MyComponent:
444

445
            def __init__(self, value: int):
446
                component.set_input_types(self, value_1=str, value_2=str)
447
                ...
448

449
            @component.output_types(output_1=int, output_2=str)
450
            def run(self, **kwargs):
451
                return {"output_1": kwargs["value_1"], "output_2": ""}
452
        ```
453

454
        Note that if the `run()` method also specifies some parameters, those will take precedence.
455

456
        For example:
457

458
        ```python
459
        @component
460
        class MyComponent:
461

462
            def __init__(self, value: int):
463
                component.set_input_types(self, value_1=str, value_2=str)
464
                ...
465

466
            @component.output_types(output_1=int, output_2=str)
467
            def run(self, value_0: str, value_1: Optional[str] = None, **kwargs):
468
                return {"output_1": kwargs["value_1"], "output_2": ""}
469
        ```
470

471
        would add a mandatory `value_0` parameters, make the `value_1`
472
        parameter optional with a default None, and keep the `value_2`
473
        parameter mandatory as specified in `set_input_types`.
474

475
        """
476
        if not _component_run_has_kwargs(instance.__class__):
1✔
477
            raise ComponentError(
1✔
478
                "Cannot set input types on a component that doesn't have a kwargs parameter in the 'run' method"
479
            )
480

481
        instance.__haystack_input__ = Sockets(
1✔
482
            instance, {name: InputSocket(name=name, type=type_) for name, type_ in types.items()}, InputSocket
483
        )
484

485
    def set_output_types(self, instance, **types):
1✔
486
        """
487
        Method that specifies the output types when the 'run' method is not decorated with 'component.output_types'.
488

489
        Use as:
490

491
        ```python
492
        @component
493
        class MyComponent:
494

495
            def __init__(self, value: int):
496
                component.set_output_types(self, output_1=int, output_2=str)
497
                ...
498

499
            # no decorators here
500
            def run(self, value: int):
501
                return {"output_1": 1, "output_2": "2"}
502

503
            # also no decorators here
504
            async def run_async(self, value: int):
505
                return {"output_1": 1, "output_2": "2"}
506
        ```
507
        """
508
        has_run_decorator = hasattr(instance.run, "_output_types_cache")
1✔
509
        has_run_async_decorator = hasattr(instance, "run_async") and hasattr(instance.run_async, "_output_types_cache")
1✔
510
        if has_run_decorator or has_run_async_decorator:
1✔
511
            raise ComponentError(
1✔
512
                "Cannot call `set_output_types` on a component that already has the 'output_types' decorator on its "
513
                "`run` or `run_async` methods."
514
            )
515

516
        instance.__haystack_output__ = Sockets(
1✔
517
            instance, {name: OutputSocket(name=name, type=type_) for name, type_ in types.items()}, OutputSocket
518
        )
519

520
    def output_types(
1✔
521
        self, **types: Any
522
    ) -> Callable[[Callable[RunParamsT, RunReturnT]], Callable[RunParamsT, RunReturnT]]:
523
        """
524
        Decorator factory that specifies the output types of a component.
525

526
        Use as:
527
        ```python
528
        @component
529
        class MyComponent:
530
            @component.output_types(output_1=int, output_2=str)
531
            def run(self, value: int):
532
                return {"output_1": 1, "output_2": "2"}
533
        ```
534
        """
535

536
        def output_types_decorator(run_method: Callable[RunParamsT, RunReturnT]) -> Callable[RunParamsT, RunReturnT]:
1✔
537
            """
538
            Decorator that sets the output types of the decorated method.
539

540
            This happens at class creation time, and since we don't have the decorated
541
            class available here, we temporarily store the output types as an attribute of
542
            the decorated method. The ComponentMeta metaclass will use this data to create
543
            sockets at instance creation time.
544
            """
545
            method_name = run_method.__name__
1✔
546
            if method_name not in ("run", "run_async"):
1✔
547
                raise ComponentError("'output_types' decorator can only be used on 'run' and 'run_async' methods")
1✔
548

549
            setattr(
1✔
550
                run_method,
551
                "_output_types_cache",
552
                {name: OutputSocket(name=name, type=type_) for name, type_ in types.items()},
553
            )
554
            return run_method
1✔
555

556
        return output_types_decorator
1✔
557

558
    def _component(self, cls: type[T]) -> type[T]:
1✔
559
        """
560
        Decorator validating the structure of the component and registering it in the components registry.
561
        """
562
        logger.debug("Registering {component} as a component", component=cls)
1✔
563

564
        # Check for required methods and fail as soon as possible
565
        if not hasattr(cls, "run"):
1✔
566
            raise ComponentError(f"{cls.__name__} must have a 'run()' method. See the docs for more information.")
1✔
567

568
        def copy_class_namespace(namespace):
1✔
569
            """
570
            This is the callback that `typing.new_class` will use to populate the newly created class.
571

572
            Simply copy the whole namespace from the decorated class.
573
            """
574
            for key, val in dict(cls.__dict__).items():
1✔
575
                # __dict__ and __weakref__ are class-bound, we should let Python recreate them.
576
                if key in ("__dict__", "__weakref__"):
1✔
577
                    continue
1✔
578
                namespace[key] = val
1✔
579

580
        # Recreate the decorated component class so it uses our metaclass.
581
        # We must explicitly redefine the type of the class to make sure language servers
582
        # and type checkers understand that the class is of the correct type.
583
        new_cls: type[T] = new_class(cls.__name__, cls.__bases__, {"metaclass": ComponentMeta}, copy_class_namespace)
1✔
584

585
        # Save the component in the class registry (for deserialization)
586
        class_path = f"{new_cls.__module__}.{new_cls.__name__}"
1✔
587
        if class_path in self.registry:
1✔
588
            # Corner case, but it may occur easily in notebooks when re-running cells.
589
            logger.debug(
1✔
590
                "Component {component} is already registered. Previous imported from '{module_name}', \
591
                new imported from '{new_module_name}'",
592
                component=class_path,
593
                module_name=self.registry[class_path],
594
                new_module_name=new_cls,
595
            )
596
        self.registry[class_path] = new_cls
1✔
597
        logger.debug("Registered Component {component}", component=new_cls)
1✔
598

599
        # Override the __repr__ method with a default one
600
        # mypy is not happy that:
601
        # 1) we are assigning a method to a class
602
        # 2) _component_repr has a different type (Callable[[Component], str]) than the expected
603
        # __repr__ method (Callable[[object], str])
604
        new_cls.__repr__ = _component_repr  # type: ignore[assignment]
1✔
605

606
        return new_cls
1✔
607

608
    # Call signature when the the decorator is usead without parens (@component).
609
    @overload
610
    def __call__(self, cls: type[T]) -> type[T]: ...
611

612
    # Overload allowing the decorator to be used with parens (@component()).
613
    @overload
614
    def __call__(self) -> Callable[[type[T]], type[T]]: ...
615

616
    def __call__(self, cls: Optional[type[T]] = None) -> Union[type[T], Callable[[type[T]], type[T]]]:
1✔
617
        # We must wrap the call to the decorator in a function for it to work
618
        # correctly with or without parens
619
        def wrap(cls: type[T]) -> type[T]:
1✔
620
            return self._component(cls)
1✔
621

622
        if cls:
1✔
623
            # Decorator is called without parens
624
            return wrap(cls)
1✔
625

626
        # Decorator is called with parens
627
        return wrap
1✔
628

629

630
component = _Component()
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc