This is an important bug/linting violation to fix because it can lead to unexpected behavior.
Quoted from the Ruff website:
Any function call that's used in a default argument will only be performed once, at definition time. The returned value will then be reused by all calls to the function, which can lead to unexpected behaviour.
Describe the expected behavior
We should be able to apply the diffs below without experiencing test collection errors (also shown below)
Here are the errors that the above diffs cause when collecting tests with pytest:
_____________________________________________________________ ERROR collecting tfx/dsl/component/experimental/decorators_test.py ______________________________________________________________
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for tfx.dsl.components.base.decorators."""
from __future__ import annotations
import pytest
import os
from typing import Any, Dict, List, Optional
import apache_beam as beam
import tensorflow as tf
from tfx import types
from tfx.dsl.component.experimental.annotations import BeamComponentParameter
from tfx.dsl.component.experimental.annotations import InputArtifact
from tfx.dsl.component.experimental.annotations import OutputArtifact
from tfx.dsl.component.experimental.annotations import OutputDict
from tfx.dsl.component.experimental.annotations import Parameter
from tfx.dsl.component.experimental.decorators import _SimpleBeamComponent
from tfx.dsl.component.experimental.decorators import _SimpleComponent
from tfx.dsl.component.experimental.decorators import BaseFunctionalComponent
from tfx.dsl.component.experimental.decorators import component
from tfx.dsl.components.base import base_beam_executor
from tfx.dsl.components.base import base_executor
from tfx.dsl.components.base import executor_spec
from tfx.dsl.io import fileio
from tfx.orchestration import metadata
from tfx.orchestration import pipeline
from tfx.orchestration.beam import beam_dag_runner
from tfx.types import component_spec
from tfx.types import standard_artifacts
from tfx.types.channel_utils import union
from tfx.types.system_executions import SystemExecution
_TestBeamPipelineArgs = ['--my_testing_beam_pipeline_args=foo']
class _InputArtifact(types.Artifact):
TYPE_NAME = '_InputArtifact'
class _OutputArtifact(types.Artifact):
TYPE_NAME = '_OutputArtifact'
class _BasicComponentSpec(component_spec.ComponentSpec):
PARAMETERS = {
'folds': component_spec.ExecutionParameter(type=int),
}
INPUTS = {
'input': component_spec.ChannelParameter(type=_InputArtifact),
}
OUTPUTS = {
'output': component_spec.ChannelParameter(type=_OutputArtifact),
}
class _InjectorAnnotation(SystemExecution):
MLMD_SYSTEM_BASE_TYPE = 1
class _SimpleComponentAnnotation(SystemExecution):
MLMD_SYSTEM_BASE_TYPE = 2
class _VerifyAnnotation(SystemExecution):
MLMD_SYSTEM_BASE_TYPE = 3
def no_op():
pass
_decorated_no_op = component(no_op)
_decorated_with_arg_no_op = component()(no_op)
@component
> def injector_1(
foo: Parameter[int], bar: Parameter[str]
) -> OutputDict(a=int, b=int, c=str, d=bytes): # pytype: disable=invalid-annotation,wrong-arg-types
tfx/dsl/component/experimental/decorators_test.py:94:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
func = <function injector_1 at 0x77aa8d575d80>
def component(
func: Optional[types.FunctionType] = None,
/,
*,
component_annotation: Optional[
Type[system_executions.SystemExecution]
] = None,
use_beam: bool = False,
) -> Union[
BaseFunctionalComponentFactory,
Callable[[types.FunctionType], BaseFunctionalComponentFactory],
]:
'''Decorator: creates a component from a typehint-annotated Python function.
This decorator creates a component based on typehint annotations specified for
the arguments and return value for a Python function. The decorator can be
supplied with a parameter `component_annotation` to specify the annotation for
this component decorator. This annotation hints which system execution type
this python function-based component belongs to.
Specifically, function arguments can be annotated with the following types and
associated semantics:
* `Parameter[T]` where `T` is `int`, `float`, `str`, or `bool`:
indicates that a primitive type execution parameter, whose value is known at
pipeline construction time, will be passed for this argument. These
parameters will be recorded in ML Metadata as part of the component's
execution record. Can be an optional argument.
* `int`, `float`, `str`, `bytes`, `bool`, `Dict`, `List`: indicates that a
primitive type value will be passed for this argument. This value is tracked
as an `Integer`, `Float`, `String`, `Bytes`, `Boolean` or `JsonValue`
artifact (see `tfx.types.standard_artifacts`) whose value is read and passed
into the given Python component function. Can be an optional argument.
* `InputArtifact[ArtifactType]`: indicates that an input artifact object of
type `ArtifactType` (deriving from `tfx.types.Artifact`) will be passed for
this argument. This artifact is intended to be consumed as an input by this
component (possibly reading from the path specified by its `.uri`). Can be
an optional argument by specifying a default value of `None`.
* `OutputArtifact[ArtifactType]`: indicates that an output artifact object of
type `ArtifactType` (deriving from `tfx.types.Artifact`) will be passed for
this argument. This artifact is intended to be emitted as an output by this
component (and written to the path specified by its `.uri`). Cannot be an
optional argument.
The return value typehint should be either empty or `None`, in the case of a
component function that has no return values, or a `TypedDict` of primitive
value types (`int`, `float`, `str`, `bytes`, `bool`, `dict` or `list`; or
`Optional[T]`, where T is a primitive type value, in which case `None` can be
returned), to indicate that the return value is a dictionary with specified
keys and value types.
Note that output artifacts should not be included in the return value
typehint; they should be included as `OutputArtifact` annotations in the
function inputs, as described above.
The function to which this decorator is applied must be at the top level of
its Python module (it may not be defined within nested classes or function
closures).
This is example usage of component definition using this decorator:
``` python
from tfx import v1 as tfx
InputArtifact = tfx.dsl.components.InputArtifact
OutputArtifact = tfx.dsl.components.OutputArtifact
Parameter = tfx.dsl.components.Parameter
Examples = tfx.types.standard_artifacts.Examples
Model = tfx.types.standard_artifacts.Model
class MyOutput(TypedDict):
loss: float
accuracy: float
@component(component_annotation=tfx.dsl.standard_annotations.Train)
def MyTrainerComponent(
training_data: InputArtifact[Examples],
model: OutputArtifact[Model],
dropout_hyperparameter: float,
num_iterations: Parameter[int] = 10,
) -> MyOutput:
"""My simple trainer component."""
records = read_examples(training_data.uri)
model_obj = train_model(records, num_iterations, dropout_hyperparameter)
model_obj.write_to(model.uri)
return {"loss": model_obj.loss, "accuracy": model_obj.accuracy}
# Example usage in a pipeline graph definition:
# ...
trainer = MyTrainerComponent(
training_data=example_gen.outputs["examples"],
dropout_hyperparameter=other_component.outputs["dropout"],
num_iterations=1000,
)
pusher = Pusher(model=trainer.outputs["model"])
# ...
When the parameter `component_annotation` is not supplied, the default value
is None. This is another example usage with `component_annotation` = None:
``` python
@component
def MyTrainerComponent(
training_data: InputArtifact[standard_artifacts.Examples],
model: OutputArtifact[standard_artifacts.Model],
dropout_hyperparameter: float,
num_iterations: Parameter[int] = 10,
) -> Output:
"""My simple trainer component."""
records = read_examples(training_data.uri)
model_obj = train_model(records, num_iterations, dropout_hyperparameter)
model_obj.write_to(model.uri)
return {"loss": model_obj.loss, "accuracy": model_obj.accuracy}
```
When the parameter `use_beam` is True, one of the parameters of the decorated
function type-annotated by BeamComponentParameter[beam.Pipeline] and the
default value can only be None. It will be replaced by a beam Pipeline made
with the tfx pipeline's beam_pipeline_args that's shared with other beam-based
components:
``` python
@component(use_beam=True)
def DataProcessingComponent(
input_examples: InputArtifact[standard_artifacts.Examples],
output_examples: OutputArtifact[standard_artifacts.Examples],
beam_pipeline: BeamComponentParameter[beam.Pipeline] = None,
) -> None:
"""My simple trainer component."""
records = read_examples(training_data.uri)
with beam_pipeline as p:
...
```
Experimental: no backwards compatibility guarantees.
Args:
func: Typehint-annotated component executor function.
component_annotation: used to annotate the python function-based component.
It is a subclass of SystemExecution from
third_party/py/tfx/types/system_executions.py; it can be None.
use_beam: Whether to create a component that is a subclass of
BaseBeamComponent. This allows a beam.Pipeline to be made with
tfx-pipeline-wise beam_pipeline_args.
Returns:
An object that:
1. you can call like the initializer of a subclass of [`base_component.BaseComponent`][tfx.v1.types.BaseChannel] (or [`base_component.BaseBeamComponent`][tfx.v1.types.BaseBeamComponent]).
2. has a test_call() member function for unit testing the inner implementation of the component.
Today, the returned object is literally a subclass of [BaseComponent][tfx.v1.types.BaseChannel], so it can be used as a `Type` e.g. in isinstance() checks. But you must not rely on this, as we reserve the right to reserve a different kind of object in the future, which _only_ satisfies the two criteria (1.) and (2.) above without being a `Type` itself.
Raises:
EnvironmentError: if the current Python interpreter is not Python 3.
'''
if func is None:
# Python decorators with arguments in parentheses result in two function
# calls. The first function call supplies the kwargs and the second supplies
# the decorated function. Here we forward the kwargs to the second call.
return functools.partial(
component,
component_annotation=component_annotation,
use_beam=use_beam,
)
utils.assert_is_top_level_func(func)
(inputs, outputs, parameters, arg_formats, arg_defaults, returned_values,
json_typehints, return_json_typehints) = (
def parse_typehint_component_function(
func: types.FunctionType,
) -> ParsedSignature:
"""Parses the given component executor function.
This method parses a typehinted-annotated Python function that is intended to
be used as a component and returns the information needed about the interface
(inputs / outputs / returned output values) about that components, as well as
a list of argument names and formats for determining the parameters that
should be passed when calling `func(*args)`.
Args:
func: A component executor function to be parsed.
Returns:
A ParsedSignature.
"""
utils.assert_is_functype(func)
# Inspect the component executor function.
typehints = func.__annotations__ # pytype: disable=attribute-error
argspec = inspect.getfullargspec(func) # pytype: disable=module-attr
subject_message = 'Component declared as a typehint-annotated function'
def _validate_signature(
func: types.FunctionType,
argspec: inspect.FullArgSpec, # pytype: disable=module-attr
typehints: Dict[str, Any],
subject_message: str,
) -> None:
"""Validates signature of a typehint-annotated component executor function."""
utils.assert_no_varargs_varkw(argspec, subject_message)
# Validate argument type hints.
for arg in argspec.args:
if isinstance(arg, list):
# Note: this feature was removed in Python 3:
# https://www.python.org/dev/peps/pep-3113/.
raise ValueError('%s does not support nested input arguments.' %
subject_message)
if arg not in typehints:
raise ValueError('%s must have all arguments annotated with typehints.' %
subject_message)
# Validate return type hints.
if return_kwargs := _parse_return_type_kwargs(func, typehints):
def _parse_return_type_kwargs(
func: types.FunctionType, typehints: Dict[str, Any]
) -> Optional[Dict[str, Any]]:
"""Parse function return type which should be TypedDict or OutputDict."""
return_annotation = typehints.get('return')
if return_annotation is None:
return None
elif _is_typeddict(return_annotation):
return typing.get_type_hints(return_annotation)
elif isinstance(
return_annotation, annotations.OutputDict
): # For backward compatibility.
return return_annotation.kwargs
else:
raise ValueError(
f'Return type annotation of @component {func.name} should be'
' TypedDict or None.'
)
E ValueError: Return type annotation of @component injector_1 should be TypedDict or None.
```python
________________________________________________________ ERROR collecting tfx/dsl/component/experimental/decorators_typeddict_test.py _________________________________________________________
# Copyright 2023 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for tfx.dsl.components.base.decorators."""
from __future__ import annotations
import pytest
import os
from typing import Any, Dict, List, Optional, TypedDict
import apache_beam as beam
import tensorflow as tf
from tfx import types
from tfx.dsl.component.experimental.annotations import BeamComponentParameter
from tfx.dsl.component.experimental.annotations import InputArtifact
from tfx.dsl.component.experimental.annotations import OutputArtifact
from tfx.dsl.component.experimental.annotations import Parameter
from tfx.dsl.component.experimental.decorators import _SimpleBeamComponent
from tfx.dsl.component.experimental.decorators import _SimpleComponent
from tfx.dsl.component.experimental.decorators import component
from tfx.dsl.components.base import base_beam_executor
from tfx.dsl.components.base import base_executor
from tfx.dsl.components.base import executor_spec
from tfx.dsl.io import fileio
from tfx.orchestration import metadata
from tfx.orchestration import pipeline
from tfx.orchestration.beam import beam_dag_runner
from tfx.types import component_spec
from tfx.types import standard_artifacts
from tfx.types.channel_utils import union
from tfx.types.system_executions import SystemExecution
_TestBeamPipelineArgs = ['--my_testing_beam_pipeline_args=foo']
class _InputArtifact(types.Artifact):
TYPE_NAME = '_InputArtifact'
class _OutputArtifact(types.Artifact):
TYPE_NAME = '_OutputArtifact'
class _BasicComponentSpec(component_spec.ComponentSpec):
PARAMETERS = {
'folds': component_spec.ExecutionParameter(type=int),
}
INPUTS = {
'input': component_spec.ChannelParameter(type=_InputArtifact),
}
OUTPUTS = {
'output': component_spec.ChannelParameter(type=_OutputArtifact),
}
class _InjectorAnnotation(SystemExecution):
MLMD_SYSTEM_BASE_TYPE = 1
class _SimpleComponentAnnotation(SystemExecution):
MLMD_SYSTEM_BASE_TYPE = 2
class _VerifyAnnotation(SystemExecution):
MLMD_SYSTEM_BASE_TYPE = 3
def no_op():
pass
_decoratedno_op = component(no_op)
_decorated_with_argno_op = component()(no_op)
@component
> def injector_1(
foo: Parameter[int], bar: Parameter[str]
) -> TypedDict('Output1', dict(a=int, b=int, c=str, d=bytes)): # pytype: disable=wrong-arg-types
tfx/dsl/component/experimental/decorators_typeddict_test.py:88:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
func = <function injector_1 at 0x77aa7d421360>
def component(
func: Optional[types.FunctionType] = None,
/,
*,
component_annotation: Optional[
Type[system_executions.SystemExecution]
] = None,
use_beam: bool = False,
) -> Union[
BaseFunctionalComponentFactory,
Callable[[types.FunctionType], BaseFunctionalComponentFactory],
]:
'''Decorator: creates a component from a typehint-annotated Python function.
This decorator creates a component based on typehint annotations specified for
the arguments and return value for a Python function. The decorator can be
supplied with a parameter `component_annotation` to specify the annotation for
this component decorator. This annotation hints which system execution type
this python function-based component belongs to.
Specifically, function arguments can be annotated with the following types and
associated semantics:
* `Parameter[T]` where `T` is `int`, `float`, `str`, or `bool`:
indicates that a primitive type execution parameter, whose value is known at
pipeline construction time, will be passed for this argument. These
parameters will be recorded in ML Metadata as part of the component's
execution record. Can be an optional argument.
* `int`, `float`, `str`, `bytes`, `bool`, `Dict`, `List`: indicates that a
primitive type value will be passed for this argument. This value is tracked
as an `Integer`, `Float`, `String`, `Bytes`, `Boolean` or `JsonValue`
artifact (see `tfx.types.standard_artifacts`) whose value is read and passed
into the given Python component function. Can be an optional argument.
* `InputArtifact[ArtifactType]`: indicates that an input artifact object of
type `ArtifactType` (deriving from `tfx.types.Artifact`) will be passed for
this argument. This artifact is intended to be consumed as an input by this
component (possibly reading from the path specified by its `.uri`). Can be
an optional argument by specifying a default value of `None`.
* `OutputArtifact[ArtifactType]`: indicates that an output artifact object of
type `ArtifactType` (deriving from `tfx.types.Artifact`) will be passed for
this argument. This artifact is intended to be emitted as an output by this
component (and written to the path specified by its `.uri`). Cannot be an
optional argument.
The return value typehint should be either empty or `None`, in the case of a
component function that has no return values, or a `TypedDict` of primitive
value types (`int`, `float`, `str`, `bytes`, `bool`, `dict` or `list`; or
`Optional[T]`, where T is a primitive type value, in which case `None` can be
returned), to indicate that the return value is a dictionary with specified
keys and value types.
Note that output artifacts should not be included in the return value
typehint; they should be included as `OutputArtifact` annotations in the
function inputs, as described above.
The function to which this decorator is applied must be at the top level of
its Python module (it may not be defined within nested classes or function
closures).
This is example usage of component definition using this decorator:
``` python
from tfx import v1 as tfx
InputArtifact = tfx.dsl.components.InputArtifact
OutputArtifact = tfx.dsl.components.OutputArtifact
Parameter = tfx.dsl.components.Parameter
Examples = tfx.types.standard_artifacts.Examples
Model = tfx.types.standard_artifacts.Model
class MyOutput(TypedDict):
loss: float
accuracy: float
@component(component_annotation=tfx.dsl.standard_annotations.Train)
def MyTrainerComponent(
training_data: InputArtifact[Examples],
model: OutputArtifact[Model],
dropout_hyperparameter: float,
num_iterations: Parameter[int] = 10,
) -> MyOutput:
"""My simple trainer component."""
records = read_examples(training_data.uri)
model_obj = train_model(records, num_iterations, dropout_hyperparameter)
model_obj.write_to(model.uri)
return {"loss": model_obj.loss, "accuracy": model_obj.accuracy}
# Example usage in a pipeline graph definition:
# ...
trainer = MyTrainerComponent(
training_data=example_gen.outputs["examples"],
dropout_hyperparameter=other_component.outputs["dropout"],
num_iterations=1000,
)
pusher = Pusher(model=trainer.outputs["model"])
# ...
When the parameter `component_annotation` is not supplied, the default value
is None. This is another example usage with `component_annotation` = None:
``` python
@component
def MyTrainerComponent(
training_data: InputArtifact[standard_artifacts.Examples],
model: OutputArtifact[standard_artifacts.Model],
dropout_hyperparameter: float,
num_iterations: Parameter[int] = 10,
) -> Output:
"""My simple trainer component."""
records = read_examples(training_data.uri)
model_obj = train_model(records, num_iterations, dropout_hyperparameter)
model_obj.write_to(model.uri)
return {"loss": model_obj.loss, "accuracy": model_obj.accuracy}
```
When the parameter `use_beam` is True, one of the parameters of the decorated
function type-annotated by BeamComponentParameter[beam.Pipeline] and the
default value can only be None. It will be replaced by a beam Pipeline made
with the tfx pipeline's beam_pipeline_args that's shared with other beam-based
components:
``` python
@component(use_beam=True)
def DataProcessingComponent(
input_examples: InputArtifact[standard_artifacts.Examples],
output_examples: OutputArtifact[standard_artifacts.Examples],
beam_pipeline: BeamComponentParameter[beam.Pipeline] = None,
) -> None:
"""My simple trainer component."""
records = read_examples(training_data.uri)
with beam_pipeline as p:
...
```
Experimental: no backwards compatibility guarantees.
Args:
func: Typehint-annotated component executor function.
component_annotation: used to annotate the python function-based component.
It is a subclass of SystemExecution from
third_party/py/tfx/types/system_executions.py; it can be None.
use_beam: Whether to create a component that is a subclass of
BaseBeamComponent. This allows a beam.Pipeline to be made with
tfx-pipeline-wise beam_pipeline_args.
Returns:
An object that:
1. you can call like the initializer of a subclass of [`base_component.BaseComponent`][tfx.v1.types.BaseChannel] (or [`base_component.BaseBeamComponent`][tfx.v1.types.BaseBeamComponent]).
2. has a test_call() member function for unit testing the inner implementation of the component.
Today, the returned object is literally a subclass of [BaseComponent][tfx.v1.types.BaseChannel], so it can be used as a `Type` e.g. in isinstance() checks. But you must not rely on this, as we reserve the right to reserve a different kind of object in the future, which _only_ satisfies the two criteria (1.) and (2.) above without being a `Type` itself.
Raises:
EnvironmentError: if the current Python interpreter is not Python 3.
'''
if func is None:
# Python decorators with arguments in parentheses result in two function
# calls. The first function call supplies the kwargs and the second supplies
# the decorated function. Here we forward the kwargs to the second call.
return functools.partial(
component,
component_annotation=component_annotation,
use_beam=use_beam,
)
utils.assert_is_top_level_func(func)
(inputs, outputs, parameters, arg_formats, arg_defaults, returned_values,
json_typehints, return_json_typehints) = (
def parse_typehint_component_function(
func: types.FunctionType,
) -> ParsedSignature:
"""Parses the given component executor function.
This method parses a typehinted-annotated Python function that is intended to
be used as a component and returns the information needed about the interface
(inputs / outputs / returned output values) about that components, as well as
a list of argument names and formats for determining the parameters that
should be passed when calling `func(*args)`.
Args:
func: A component executor function to be parsed.
Returns:
A ParsedSignature.
"""
utils.assert_is_functype(func)
# Inspect the component executor function.
typehints = func.__annotations__ # pytype: disable=attribute-error
argspec = inspect.getfullargspec(func) # pytype: disable=module-attr
subject_message = 'Component declared as a typehint-annotated function'
def _validate_signature(
func: types.FunctionType,
argspec: inspect.FullArgSpec, # pytype: disable=module-attr
typehints: Dict[str, Any],
subject_message: str,
) -> None:
"""Validates signature of a typehint-annotated component executor function."""
utils.assert_no_varargs_varkw(argspec, subject_message)
# Validate argument type hints.
for arg in argspec.args:
if isinstance(arg, list):
# Note: this feature was removed in Python 3:
# https://www.python.org/dev/peps/pep-3113/.
raise ValueError('%s does not support nested input arguments.' %
subject_message)
if arg not in typehints:
raise ValueError('%s must have all arguments annotated with typehints.' %
subject_message)
# Validate return type hints.
if return_kwargs := _parse_return_type_kwargs(func, typehints):
def _parse_return_type_kwargs(
func: types.FunctionType, typehints: Dict[str, Any]
) -> Optional[Dict[str, Any]]:
"""Parse function return type which should be TypedDict or OutputDict."""
return_annotation = typehints.get('return')
if return_annotation is None:
return None
elif _is_typeddict(return_annotation):
return typing.get_type_hints(return_annotation)
elif isinstance(
return_annotation, annotations.OutputDict
): # For backward compatibility.
return return_annotation.kwargs
else:
raise ValueError(
f'Return type annotation of @component {func.name} should be'
' TypedDict or None.'
)
E ValueError: Return type annotation of @component injector_1 should be TypedDict or None.
If the bug is related to a specific library below, please raise an issue in the respective repo directly: TFX
System information
pip freeze
output):Describe the current behavior
Fixing Ruff rule B008 causes test collection failures in the files
This is an important bug/linting violation to fix because it can lead to unexpected behavior.
Quoted from the Ruff website:
Describe the expected behavior
We should be able to apply the diffs below without experiencing test collection errors (also shown below)
Standalone code to reproduce the issue
Other info / logs
Here are the errors that the above diffs cause when collecting tests with pytest:
tfx/dsl/component/experimental/decorators.py:489:
func = <function injector_1 at 0x77aa8d575d80>
tfx/dsl/component/experimental/function_parser.py:320:
func = <function injector_1 at 0x77aa8d575d80> argspec = FullArgSpec(args=['foo', 'bar'], varargs=None, varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={'return': 'OutputDict(a=int, b=int, c=str, d=bytes)', 'foo': 'Parameter[int]', 'bar': 'Parameter[str]'}) typehints = {'bar': 'Parameter[str]', 'foo': 'Parameter[int]', 'return': 'OutputDict(a=int, b=int, c=str, d=bytes)'}, subject_message = 'Component declared as a typehint-annotated function'
tfx/dsl/component/experimental/function_parser.py:108:
func = <function injector_1 at 0x77aa8d575d80>, typehints = {'bar': 'Parameter[str]', 'foo': 'Parameter[int]', 'return': 'OutputDict(a=int, b=int, c=str, d=bytes)'}
tfx/dsl/component/experimental/function_parser.py:81: ValueError
tfx/dsl/component/experimental/decorators.py:489:
func = <function injector_1 at 0x77aa7d421360>
tfx/dsl/component/experimental/function_parser.py:320:
func = <function injector_1 at 0x77aa7d421360> argspec = FullArgSpec(args=['foo', 'bar'], varargs=None, varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotati...return': "TypedDict('Output1', dict(a=int, b=int, c=str, d=bytes))", 'foo': 'Parameter[int]', 'bar': 'Parameter[str]'}) typehints = {'bar': 'Parameter[str]', 'foo': 'Parameter[int]', 'return': "TypedDict('Output1', dict(a=int, b=int, c=str, d=bytes))"} subject_message = 'Component declared as a typehint-annotated function'
tfx/dsl/component/experimental/function_parser.py:108:
func = <function injector_1 at 0x77aa7d421360>, typehints = {'bar': 'Parameter[str]', 'foo': 'Parameter[int]', 'return': "TypedDict('Output1', dict(a=int, b=int, c=str, d=bytes))"}
tfx/dsl/component/experimental/function_parser.py:81: ValueError