Skip to content

Commit 0dca0f6

Browse files
committed
[feature]:Include Kubeflow SDK in KFP SDK
1 parent c6604f3 commit 0dca0f6

File tree

4 files changed

+392
-2
lines changed

4 files changed

+392
-2
lines changed

sdk/python/kfp/dsl/component_decorator.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def component(
3030
pip_index_urls: Optional[List[str]] = None,
3131
output_component_file: Optional[str] = None,
3232
install_kfp_package: bool = True,
33+
install_kubeflow_package: bool = True,
3334
kfp_package_path: Optional[str] = None,
3435
pip_trusted_hosts: Optional[List[str]] = None,
3536
use_venv: bool = False,
@@ -88,6 +89,10 @@ def component(
8889
This flag is ignored when ``target_image`` is specified, which implies
8990
a choice to build a containerized component. Containerized components
9091
will always install KFP as part of the build process.
92+
install_kubeflow_package: If True (default), automatically detects kubeflow
93+
imports in the component function and adds 'kubeflow' to packages_to_install.
94+
Set to False if kubeflow is pre-installed in your base image to avoid
95+
duplicate installation.
9196
kfp_package_path: Specifies the location from which to install KFP. By
9297
default, this will try to install from PyPI using the same version
9398
as that used when this component was created. Component authors can
@@ -156,6 +161,7 @@ def pipeline():
156161
pip_index_urls=pip_index_urls,
157162
output_component_file=output_component_file,
158163
install_kfp_package=install_kfp_package,
164+
install_kubeflow_package=install_kubeflow_package,
159165
kfp_package_path=kfp_package_path,
160166
pip_trusted_hosts=pip_trusted_hosts,
161167
use_venv=use_venv,
@@ -171,6 +177,7 @@ def pipeline():
171177
pip_index_urls=pip_index_urls,
172178
output_component_file=output_component_file,
173179
install_kfp_package=install_kfp_package,
180+
install_kubeflow_package=install_kubeflow_package,
174181
kfp_package_path=kfp_package_path,
175182
pip_trusted_hosts=pip_trusted_hosts,
176183
use_venv=use_venv,

sdk/python/kfp/dsl/component_factory.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
import ast
1415
import base64
1516
import dataclasses
1617
import gzip
@@ -47,6 +48,97 @@
4748
SINGLE_OUTPUT_NAME = 'Output'
4849

4950

51+
def _detect_kubeflow_imports_in_function(func: Callable) -> bool:
52+
"""Detects if the function imports kubeflow package using AST parsing.
53+
54+
Args:
55+
func: The function to analyze for kubeflow imports.
56+
57+
Returns:
58+
bool: True if any kubeflow import is found, False otherwise.
59+
60+
Detects these import patterns:
61+
- import kubeflow
62+
- import kubeflow.training (any submodule)
63+
- from kubeflow import X
64+
- from kubeflow.submodule import X
65+
"""
66+
try:
67+
# Get function source code
68+
source = inspect.getsource(func)
69+
70+
# Remove leading indentation to handle nested/indented functions
71+
source = textwrap.dedent(source)
72+
73+
# Parse the source into an AST
74+
tree = ast.parse(source)
75+
76+
for node in ast.walk(tree):
77+
if isinstance(node, ast.Import):
78+
# Handle "import kubeflow" or "import kubeflow.submodule"
79+
for alias in node.names:
80+
if alias.name == 'kubeflow' or alias.name.startswith(
81+
'kubeflow.'):
82+
return True
83+
elif isinstance(node, ast.ImportFrom):
84+
# Handle "from kubeflow import X" or "from kubeflow.submodule import X"
85+
if node.module and (node.module == 'kubeflow' or
86+
node.module.startswith('kubeflow.')):
87+
return True
88+
89+
return False
90+
91+
except (OSError, TypeError, SyntaxError, AttributeError):
92+
# Handle cases where:
93+
# - Source is not available (OSError)
94+
# - Function is built-in or C extension (TypeError)
95+
# - Source has syntax errors (SyntaxError)
96+
# - inspect module issues (AttributeError)
97+
return False
98+
99+
100+
def _parse_package_name(package_spec: str) -> str:
101+
"""Extract base package name from package specification.
102+
103+
Args:
104+
package_spec: Package specification like 'kubeflow==2.0.0', 'kubeflow>=1.5.0',
105+
'kubeflow[extras]', 'git+https://github.com/kubeflow/sdk.git', etc.
106+
107+
Returns:
108+
str: The base package name without version/extras/operators.
109+
110+
Examples:
111+
'kubeflow==2.0.0' -> 'kubeflow'
112+
'kubeflow>=1.5.0' -> 'kubeflow'
113+
'kubeflow[extras]' -> 'kubeflow'
114+
'git+https://github.com/kubeflow/sdk.git' -> 'kubeflow' (special case for kubeflow detection)
115+
"""
116+
# Handle VCS URLs (git+https://, git+ssh://, etc.)
117+
if '+' in package_spec and '://' in package_spec:
118+
# Special case: if it's a kubeflow URL, return 'kubeflow' for auto-detection logic
119+
if 'kubeflow' in package_spec.lower():
120+
return 'kubeflow'
121+
# For other VCS URLs, extract package name from URL path
122+
# e.g., 'git+https://github.com/org/package.git' -> 'package'
123+
match = re.search(r'/([^/]+?)(?:\.git)?(?:[#@].*)?$', package_spec)
124+
if match:
125+
return match.group(1)
126+
return package_spec # fallback
127+
128+
# Handle standard package specs: package[extras]==version, package>=version, etc.
129+
package_name = package_spec
130+
131+
# Remove extras in brackets: package[extras] -> package
132+
if '[' in package_name:
133+
package_name = package_name.split('[')[0]
134+
135+
# Remove version operators: package>=1.0 -> package, package==2.0 -> package
136+
package_name = re.split(r'[<>=!~]', package_name)[0]
137+
138+
# Remove any remaining whitespace
139+
return package_name.strip()
140+
141+
50142
@dataclasses.dataclass
51143
class ComponentInfo():
52144
"""A dataclass capturing registered components.
@@ -152,15 +244,32 @@ def make_pip_install_command(
152244

153245

154246
def _get_packages_to_install_command(
247+
func: Optional[Callable] = None,
155248
kfp_package_path: Optional[str] = None,
156249
pip_index_urls: Optional[List[str]] = None,
157250
packages_to_install: Optional[List[str]] = None,
158251
install_kfp_package: bool = True,
252+
install_kubeflow_package: bool = True,
159253
target_image: Optional[str] = None,
160254
pip_trusted_hosts: Optional[List[str]] = None,
161255
use_venv: bool = False,
162256
) -> List[str]:
163257
packages_to_install = packages_to_install or []
258+
259+
# Auto-detect and add kubeflow if needed
260+
if install_kubeflow_package and func is not None:
261+
detected_kubeflow = _detect_kubeflow_imports_in_function(func)
262+
263+
if detected_kubeflow:
264+
# Parse existing packages to check for kubeflow
265+
existing_package_names = [
266+
_parse_package_name(pkg) for pkg in packages_to_install
267+
]
268+
269+
# Only add if not already specified
270+
if 'kubeflow' not in existing_package_names:
271+
packages_to_install.append('kubeflow')
272+
164273
kfp_in_user_pkgs = any(pkg.startswith('kfp') for pkg in packages_to_install)
165274
# if the user doesn't say "don't install", they aren't building a
166275
# container component, and they haven't already specified a KFP dep
@@ -645,6 +754,7 @@ def create_notebook_component_from_func(
645754
pip_index_urls: Optional[List[str]] = None,
646755
output_component_file: Optional[str] = None,
647756
install_kfp_package: bool = True,
757+
install_kubeflow_package: bool = True,
648758
kfp_package_path: Optional[str] = None,
649759
pip_trusted_hosts: Optional[List[str]] = None,
650760
use_venv: bool = False,
@@ -709,6 +819,7 @@ def create_notebook_component_from_func(
709819
pip_index_urls=pip_index_urls,
710820
output_component_file=output_component_file,
711821
install_kfp_package=install_kfp_package,
822+
install_kubeflow_package=install_kubeflow_package,
712823
kfp_package_path=kfp_package_path,
713824
pip_trusted_hosts=pip_trusted_hosts,
714825
use_venv=use_venv,
@@ -738,6 +849,7 @@ def create_component_from_func(
738849
pip_index_urls: Optional[List[str]] = None,
739850
output_component_file: Optional[str] = None,
740851
install_kfp_package: bool = True,
852+
install_kubeflow_package: bool = True,
741853
kfp_package_path: Optional[str] = None,
742854
pip_trusted_hosts: Optional[List[str]] = None,
743855
use_venv: bool = False,
@@ -752,7 +864,9 @@ def create_component_from_func(
752864
"""
753865

754866
packages_to_install_command = _get_packages_to_install_command(
867+
func=func,
755868
install_kfp_package=install_kfp_package,
869+
install_kubeflow_package=install_kubeflow_package,
756870
target_image=target_image,
757871
kfp_package_path=kfp_package_path,
758872
packages_to_install=packages_to_install,

0 commit comments

Comments
 (0)