Repository: kubeflow/pipelines

Issue:
[sdk] Cannot execute containerized python component - AttributeError: 'PythonComponent' object has no attribute '__annotations__'
### Environment

*  KFP version:
2.0.0-alpha.5
*  KFP SDK version: 
2.0.0b9
*  All dependencies version:
kfp                      2.0.0b9
kfp-pipeline-spec        0.1.16
kfp-server-api           2.0.0a6


### Steps to reproduce

I am following these [instructions](https://www.kubeflow.org/docs/components/pipelines/v2/author-a-pipeline/components/#2-containerized-python-components) to create containerized python component. I am able to build the component and push the image using kfp cli.
I am not quite sure how to use the component in pipeline, this is what I am doing:
Component file (components/mycomp.py):
```python
@component(
    base_image='python:3.11-slim',
    target_image='...',
    packages_to_install=['pandas >=1.5.3']
)
def do_stuff(some_id: str, output_df: Output[Artifact]):
    print(f'Hello {some_id} from containerized component')
    df = pd.DataFrame(np.random.default_rng().random((4, 3)))
    with open(output_df.path, 'w') as fp:
        df.to_csv(fp, index=False)
```
Pipeline definition file
```python
import components.mycomp

@dsl.pipeline(name='cont-pipeline')
def cont_pipelinesome_id: str):
    task1 = components.mycomp.do_stuff(some_id=some_id)
```
Then I can either run the pipeline or compile, upload and run. During run the image is downloaded and executed, but fails with following error:
```
[KFP Executor 2023-02-03 13:41:08,810 INFO]: --component_module_path is not specified. Looking for component `do_stuff` in config file `kfp_config.ini` instead
[KFP Executor 2023-02-03 13:41:08,810 INFO]: Loading KFP component "do_stuff" from mycomp.py (directory "." and module name "mycomp")
[KFP Executor 2023-02-03 13:41:09,067 INFO]: Got executor_input:
{
  "inputs": {
    "parameterValues": {
      "some_id": "asd"
    }
  },
  "outputs": {
    "artifacts": {
      "output_df": {
        "artifacts": [
          {
            "type": {
              "schemaTitle": "system.Artifact",
              "schemaVersion": "0.0.1"
            },
            "uri": "minio://mlpipeline/v2/artifacts/pipeline/cont-pipeline/88d0c5ea-b5c0-4fc2-b461-6aa2d4233c5c/do_stuff/output_df"
          }
        ]
      }
    },
    "outputFile": "/tmp/kfp_outputs/output_metadata.json"
  }
}
Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "/usr/local/lib/python3.11/site-packages/kfp/components/executor_main.py", line 105, in <module>
executor_main()
File "/usr/local/lib/python3.11/site-packages/kfp/components/executor_main.py", line 98, in executor_main
executor = component_executor.Executor(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/kfp/components/executor.py", line 63, in __init__
output_artifact = self.make_artifact(
^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/kfp/components/executor.py", line 81, in make_artifact
annotation = func.__annotations__.get(name)
^^^^^^^^^^^^^^^^^^^^
AttributeError: 'PythonComponent' object has no attribute '__annotations__'
F0203 13:41:09.239538 36 main.go:49] failed to execute component: exit status 1
Error: exit status 1
```
Why I think it is an sdk problem - looking at executor_main.py, this is how the function is loaded:
```
function_to_execute = getattr(module, func_name)
```
and later in [Executor.__init__](https://github.com/kubeflow/pipelines/blob/master/sdk/python/kfp/components/executor.py#L81) the __annotations__ are accessed and the error is raised.
The `func` (or `function_to_execute` above) is not the actual function `do_stuff` (as the code seems to expect), but it is the PythonComponent because `do_stuff `is decorated with @component, that's why the annotations are not there.

Am I creating the pipeline incorrectly (and what would be the correct way?) perhaps or is it a bug?

### Expected result

Pipeline runs successfully.

---

<!-- Don't delete message below to encourage users to support your issue! -->
Impacted by this bug? Give it a 👍. 


Discussion:
I've managed to find a workaround for this. The point is that the @component decorator is necessary  for `kfp component build ...` and later when compiling/running the pipeline. But the target image and the code in it should not have it (at least not with current sdk version). Steps:
1. run `kfp component build ...` - this will generate few files (ini, Dockerfile, yaml, requirements.txt), build the image and push it. This is the image which contains the decorator and will fail with missing __annotations__
2. comment out the decorator
3. build & push the image directly with docker (Dockerfile and other meta files are already there from step 1) - now image with non-decorated function will be pushed
4. uncomment the decorator
5. compile/run the pipeline using the component
I'm experiencing the same issue. Is this something that would be easy to fix for the next release?


Resolving patch:
diff --git a/sdk/python/kfp/components/executor.py b/sdk/python/kfp/components/executor.py
--- a/sdk/python/kfp/components/executor.py
+++ b/sdk/python/kfp/components/executor.py
@@ -16,6 +16,7 @@
 import os
 from typing import Any, Callable, Dict, List, Optional, Union
 
+from kfp.components import python_component
 from kfp.components import task_final_status
 from kfp.components.types import artifact_types
 from kfp.components.types import type_annotations
@@ -24,8 +25,13 @@
 class Executor():
     """Executor executes v2-based Python function components."""
 
-    def __init__(self, executor_input: Dict, function_to_execute: Callable):
-        self._func = function_to_execute
+    def __init__(self, executor_input: Dict,
+                 function_to_execute: Union[Callable,
+                                            python_component.PythonComponent]):
+        if hasattr(function_to_execute, 'python_func'):
+            self._func = function_to_execute.python_func
+        else:
+            self._func = function_to_execute
 
         self._input = executor_input
         self._input_artifacts: Dict[str,


Test patch: