-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Description
TL;DR: Replace 7 setup.py files, 18 requirements.txt files, and 46+ scattered pip install commands with a uv workspace, a single uv.lock, and uv sync.
Chore description
Migrate the KFP monorepo's Python dependency management from scattered setup.py + requirements.in/txt files to a unified uv workspace with:
- A root
pyproject.tomldefining workspace members and shared constraints - Per-package
pyproject.tomlfiles replacingsetup.py - A single
uv.locklockfile for reproducible builds - CI workflows updated to use
uv syncinstead of scatteredpip installcommands
Why is this needed?
Current State Analysis
The repository has fragmented Python dependency management that causes multiple problems:
Dependency Files Inventory
| File Type | Count | Locations |
|---|---|---|
setup.py |
7 | See below |
requirements.txt |
18 | Scattered across repo |
requirements.in |
6 | SDK, kubernetes_platform, api, backend |
MANIFEST.in |
3 | SDK, kubernetes_platform, api |
pyproject.toml |
1 | Only components/PyTorch (out of scope) |
Published PyPI Packages (In Scope)
| Package | Location | Version Source | Dependencies |
|---|---|---|---|
kfp |
sdk/python/ |
kfp/version.py |
kfp-pipeline-spec, kfp-server-api, 10+ others |
kfp-kubernetes |
kubernetes_platform/python/ |
kfp/kubernetes/__init__.py |
kfp, protobuf |
kfp-pipeline-spec |
api/v2alpha1/python/ |
Hardcoded in setup.py |
protobuf |
kfp-server-api |
backend/api/v2beta1/python_http_client/ |
Hardcoded in setup.py |
urllib3, six, certifi, python-dateutil |
Out-of-Scope setup.py Files
components/google-cloud/setup.py— managed separatelycomponents/PyTorch/pytorch-kfp-components/setup.py— managed separatelybackend/api/v1beta1/python_http_client/setup.py— deprecated v1beta1 API
CI Workflow Fragmentation
The CI currently has 46+ pip install commands spread across workflows and actions:
| Workflow/Action | pip install commands |
|---|---|
kfp-sdk-tests.yml |
6 commands |
kfp-sdk-unit-tests.yml |
2 commands |
kfp-sdk-client-tests.yml |
6 commands |
gcpc-modules-tests.yml |
5 commands |
publish-packages.yml |
8 commands (2 per package) |
sdk-yapf.yml |
1 command |
sdk-component-yaml.yml |
3 commands |
docs-freshness.yml |
1 command |
readthedocs-builds.yml |
1 command |
.github/actions/kfp-k8s/action.yml |
6 commands |
.github/actions/protobuf/action.yml |
4 commands |
.github/actions/test-and-report/action.yml |
1 command |
.github/actions/check-artifact-exists/action.yml |
2 commands |
Pain Points
- Dependabot chaos: Independent PRs for the same dependency across modules (e.g.,
protobufupdated insdk/pythonandapi/v2alpha1/pythonseparately) - Version drift risk: Comments like
# protobuf version should be identical to the one in kfp-pipeline-specindicate manual coordination is required - CI environment inconsistency: Each workflow installs dependencies independently with no lockfile protection
- Maintenance burden:
hack/update-all-requirements.shexists but only covers 4 of 6requirements.infiles
Labels
/area sdk
/area backend
/area testing
Proposed Solution
Adopt uv workspaces (uv 0.9+) with a unified lockfile.
Target Structure
/pyproject.toml # Workspace root (not published)
/uv.lock # Single lockfile (committed)
/sdk/python/pyproject.toml # kfp package
/kubernetes_platform/python/pyproject.toml # kfp-kubernetes package
/api/v2alpha1/python/pyproject.toml # kfp-pipeline-spec package
/backend/api/v2beta1/python_http_client/pyproject.toml # kfp-server-api package
Root pyproject.toml Template
[project]
name = "kfp-workspace"
version = "0.0.0"
requires-python = ">=3.9"
[tool.uv.workspace]
members = [
"sdk/python",
"kubernetes_platform/python",
"api/v2alpha1/python",
"backend/api/v2beta1/python_http_client",
]
[tool.uv]
# Shared constraints applied during resolution for ALL workspace members
constraint-dependencies = [
"protobuf>=6.31.1,<7.0",
"kubernetes>=8.0.0,<31",
"google-auth>=1.6.1,<3",
"google-api-core>=1.31.5,<3.0.0dev,!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.0",
"google-cloud-storage>=2.2.1,<4",
"PyYAML>=5.3,<7",
"urllib3<3.0.0",
]
[project.optional-dependencies]
# Linting tools (pinned to avoid new lint failures)
lint = [
"docformatter==1.4",
"isort==5.10.1",
"pycln==2.1.1",
"pylint==2.17.7",
"yapf==0.43.0",
]
# Testing tools
test = [
"absl-py",
"docker",
"nbformat",
"pytest",
"pytest-cov",
"pytest-xdist",
]
# Development (lint + test + type checking)
dev = [
"kfp-workspace[lint,test]",
"mypy",
"pre-commit",
"types-protobuf",
"types-PyYAML",
"types-requests",
"types-tabulate",
]
# Documentation building
docs = [
"autodocsumm",
"m2r2",
"sphinx",
"sphinx-click",
"sphinx-immaterial",
"sphinx-rtd-theme",
]
# CI-specific tools (includes lint, test, and docs)
ci = [
"kfp-workspace[lint,test,docs]",
"build",
"junit2html",
"requests",
"ruamel.yaml",
"twine",
]Package pyproject.toml Template (sdk/python)
[project]
name = "kfp"
dynamic = ["version"]
description = "Kubeflow Pipelines SDK"
readme = "README.md"
license = "Apache-2.0"
requires-python = ">=3.9"
authors = [{ name = "The Kubeflow Authors" }]
classifiers = [
"Intended Audience :: Developers",
"Intended Audience :: Education",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: Software Development",
"Topic :: Software Development :: Libraries",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"click>=8.1.8",
"click-option-group==0.5.7",
"docstring-parser>=0.7.3,<1",
# Pin google-api-core version for the bug fixing in 1.31.5
# https://github.com/googleapis/python-api-core/releases/tag/v1.31.5
"google-api-core>=1.31.5,<3.0.0dev,!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.0",
"google-auth>=1.6.1,<3",
# https://github.com/googleapis/python-storage/blob/main/CHANGELOG.md#221-2022-03-15
"google-cloud-storage>=2.2.1,<4",
# Update the upper version whenever a new major version of the
# kfp-pipeline-spec package is released.
# Update the lower version when kfp sdk depends on new apis/fields in
# kfp-pipeline-spec
"kfp-pipeline-spec>=2.15.0,<3",
# Update the upper version whenever a new major version of the
# kfp-server-api package is released.
# Update the lower version when kfp sdk depends on new apis/fields in
# kfp-server-api
"kfp-server-api>=2.15.0,<3",
"kubernetes>=8.0.0,<31",
# protobuf version should be identical to the one in kfp-pipeline-spec
"protobuf>=6.31.1,<7.0",
"PyYAML>=5.3,<7",
"requests-toolbelt>=0.8.0,<2",
"tabulate>=0.8.6,<1",
"urllib3<3.0.0",
]
[project.optional-dependencies]
all = ["kfp[kubernetes,notebooks]", "docker"]
kubernetes = ["kfp-kubernetes>=2.15.0,<3"]
notebooks = ["nbclient>=0.10,<1", "ipykernel>=6,<7", "jupyter_client>=7,<9"]
[project.scripts]
kfp = "kfp.cli.__main__:main"
dsl-compile = "kfp.cli.compile_:main"
[project.urls]
Documentation = "https://kubeflow-pipelines.readthedocs.io/en/stable/"
"Bug Tracker" = "https://github.com/kubeflow/pipelines/issues"
Source = "https://github.com/kubeflow/pipelines/tree/master/sdk"
Changelog = "https://github.com/kubeflow/pipelines/blob/master/sdk/RELEASE.md"
[tool.uv.sources]
kfp-pipeline-spec = { workspace = true }
kfp-server-api = { workspace = true }
kfp-kubernetes = { workspace = true }
[tool.hatch.version]
path = "kfp/version.py"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"Implementation Tasks
Important: This migration should be done atomically in a single PR to avoid confusion about where contributors should update dependencies during a transition period.
Phase 1: Workspace Setup
- Create root
/pyproject.tomlwith workspace configuration - Define
constraint-dependenciesfor shared deps (protobuf, kubernetes, google-auth, etc.) - Define
optional-dependenciesgroups:lint,test,dev,ci,docs - Generate initial
uv.lockfile
Phase 2: Package Conversion
2.1 Convert kfp-pipeline-spec (api/v2alpha1/python/)
- Create
pyproject.tomlwith:name = "kfp-pipeline-spec"version = "2.15.2"(or use dynamic versioning)dependencies = ["protobuf>=6.31.1,<7.0"]- Build system:
hatchling
- Verify proto generation still works (
generate_proto.py) - Remove
setup.py,requirements.in,requirements.txt,MANIFEST.in - Update
api/Makefiletargets (python,python-dev)
2.2 Convert kfp-server-api (backend/api/v2beta1/python_http_client/)
- Create
pyproject.tomlwith:name = "kfp-server-api"version = "2.15.2"(or use dynamic versioning)dependencies = ["urllib3>=1.15", "six>=1.10", "certifi", "python-dateutil"]- Build system:
hatchling
- Note: This package is auto-generated from swagger; ensure regeneration works and templates do not overwrite
pyproject.toml - Remove
setup.py,requirements.txt
2.3 Convert kfp (sdk/python/)
- Create
pyproject.tomlwith:name = "kfp"- Dynamic version from
kfp/version.py - Full dependencies list (see template above)
[tool.uv.sources]for workspace references (kfp-pipeline-spec,kfp-server-api,kfp-kubernetes)- Entry points (
kfp,dsl-compile) - Optional dependencies (
all,kubernetes,notebooks)
- Remove
setup.py,requirements.in,requirements.txt,requirements-dev.txt,MANIFEST.in - Update
sdk/Makefiletarget
2.4 Convert kfp-kubernetes (kubernetes_platform/python/)
- Create
pyproject.tomlwith:name = "kfp-kubernetes"- Dynamic version from
kfp/kubernetes/__init__.py dependencies = ["protobuf>=6.31.1,<7.0", "kfp>=2.15.0,<3"][tool.uv.sources]for workspace reference tokfp
- Verify proto generation still works (
generate_proto.py) - Remove
setup.py,requirements.in,requirements.txt,requirements-dev.txt,MANIFEST.in - Update
kubernetes_platform/Makefiletargets
Phase 3: CI Workflow Updates
3.1 Create setup-python Composite Action
-
Create
.github/actions/setup-python/action.yml:name: "Setup Python with uv" description: "Install Python, uv, and CI dependencies" inputs: python-version: description: "Python version to use" required: true runs: using: "composite" steps: - name: Install uv uses: astral-sh/setup-uv@v7 - name: Install CI dependencies run: uv sync --python ${{ inputs.python-version }} --extra ci shell: bash
-
Add lockfile sync check workflow (runs on PRs that touch
**/pyproject.tomloruv.lock, and all pushes to master and release branches):- name: Check uv.lock is in sync run: uv lock --check
3.2 Update SDK Test Workflows
-
kfp-sdk-tests.yml:- Use
.github/actions/setup-pythonaction - Update test run:
uv run ./test/presubmit-tests-sdk.sh
- Use
-
kfp-sdk-unit-tests.yml:- Use
.github/actions/setup-pythonaction
- Use
-
kfp-sdk-client-tests.yml:- Use
.github/actions/setup-pythonaction
- Use
3.3 Update Other Workflows
-
gcpc-modules-tests.yml:- Use
.github/actions/setup-pythonaction
- Use
-
sdk-yapf.yml:- Use
.github/actions/setup-pythonaction
- Use
-
sdk-component-yaml.yml:- Use
.github/actions/setup-pythonaction
- Use
-
docs-freshness.yml:- Use
.github/actions/setup-pythonaction
- Use
-
readthedocs-builds.yml:- Replace
actions/setup-python@v6with.github/actions/setup-python - Remove
.github/actions/protobufand.github/actions/kfp-k8ssteps (handled byuv sync) - Remove
pip install -r docs/sdk/requirements.txt(docs deps are incigroup) - Update sphinx-build to use
uv run sphinx-build - Generate protos after sync:
make -C api python && make -C kubernetes_platform python
- Replace
- Update
.readthedocs.yml:- Add
build.tools.uv: "latest" - Replace
python.installwithbuild.commandsusinguv sync --extra docs - Add proto generation commands before sphinx build
- Add
-
kfp-kubernetes-native-migration-tests.yaml:- Use
.github/actions/setup-pythonaction
- Use
3.4 Update Publish Workflow
-
publish-packages.yml:- Use
.github/actions/setup-pythonaction - Update build commands to use
uv build --package <name> - Keep
twine checkfor validation
- Use
3.5 Update Composite Actions
-
.github/actions/kfp-k8s/action.yml:- Use
.github/actions/setup-pythonaction - Update wheel building to use
uv build
- Use
-
.github/actions/protobuf/action.yml:- Use
.github/actions/setup-pythonaction - Update proto generation flow
- Use
-
.github/actions/test-and-report/action.yml:- Use
.github/actions/setup-pythonaction
- Use
-
.github/actions/check-artifact-exists/action.yml:- Use
.github/actions/setup-pythonaction
- Use
3.6 Configure Dependabot
-
Create/update
.github/dependabot.ymlto watchuv.lock:version: 2 updates: - package-ecosystem: "uv" directory: "/" schedule: interval: "daily"
Note: The
uvecosystem watchespyproject.tomlanduv.lock. With workspaces, it sees all workspace members' dependencies through the unifieduv.lock— no need to configure each subdirectory separately.
Phase 4: Update Pre-commit to Use uv
-
Update
.pre-commit-config.yamlto uselocalhooks withuv run:# Python linting (uses versions from uv.lock) - repo: local hooks: - id: pycln name: pycln entry: uv run pycln --all language: system types: [python] - id: isort name: isort entry: uv run isort --profile google language: system types: [python] - id: yapf name: yapf entry: uv run yapf -i language: system types: [python] - id: docformatter name: docformatter entry: uv run docformatter -i -r language: system types: [python] exclude: (sdk/python/kfp/compiler/compiler_test.py|kubernetes_platform/python/)
-
Remove old repo-based hooks for pycln, isort, yapf, docformatter
-
Keep non-Python hooks (pre-commit-hooks, flake8, golangci-lint, actionlint) as repo-based
-
Test that
pre-commit runworks on a sample changed file (running--all-filesmay surface pre-existing violations unrelated to this migration)
Phase 5: Documentation & Cleanup
- Update
sdk/CONTRIBUTING.md:- Replace
pip install -r requirements-dev.txtwithuv sync --extra dev - Replace
pip install -e sdk/pythonwithuv sync(auto-installs editable) - Update testing instructions to use
uv run pytest
- Replace
- Update
AGENTS.md:- Update "Local development setup" section
- Update "Local testing" section
- Remove old files:
-
sdk/python/requirements.in -
sdk/python/requirements.txt -
sdk/python/requirements-dev.txt -
sdk/python/setup.py -
sdk/python/MANIFEST.in -
kubernetes_platform/python/requirements.in -
kubernetes_platform/python/requirements.txt -
kubernetes_platform/python/requirements-dev.txt -
kubernetes_platform/python/setup.py -
kubernetes_platform/python/MANIFEST.in -
api/v2alpha1/python/requirements.in -
api/v2alpha1/python/requirements.txt -
api/v2alpha1/python/setup.py -
api/v2alpha1/python/MANIFEST.in -
backend/api/v2beta1/python_http_client/setup.py -
backend/api/v2beta1/python_http_client/requirements.txt
-
- Update or remove scripts that use pip-compile or setup.py:
-
hack/update-all-requirements.sh— remove (uv handles lockfile) -
hack/update-requirements.sh— remove (uv handles lockfile) -
sdk/python/pre-release-requirements-update.sh— remove (uv handles lockfile) -
kubernetes_platform/python/pre-release-requirements-update.sh— remove (uv handles lockfile) -
kubernetes_platform/python/release.sh— update to useuv buildanduv publish -
backend/update_requirements.sh— keep (backend services, out of scope) -
backend/metadata_writer/update_requirements.sh— keep (backend services, out of scope) -
backend/src/apiserver/visualization/update_requirements.sh— keep (backend services, out of scope)
-
Acceptance Criteria
Functional Requirements
- AC1: All 4 packages (
kfp,kfp-kubernetes,kfp-pipeline-spec,kfp-server-api) build successfully withuv build --package <name> - AC2: All 4 packages can be published to PyPI (verify with dry-run)
- AC3:
uv syncat repo root installs all workspace packages in editable mode - AC4:
uv sync --extra ciinstalls CI dependencies (includes test) - AC5:
uv sync --extra devinstalls all development dependencies - AC6: Inter-package dependencies resolve correctly (e.g.,
kfp-kubernetesuses localkfp) - AC7: Proto generation works for
kfp-pipeline-specandkfp-kubernetes
CI Requirements
- AC8: All existing CI workflows pass after migration
- AC9:
uv lock --checkruns in CI to catch lockfile drift - AC10: No
pip installcommands remain in any CI workflow - AC11: Dependabot can create PRs for
uv.lockupdates
Documentation Requirements
- AC12:
sdk/CONTRIBUTING.mdupdated withuvcommands - AC13:
AGENTS.mdupdated withuvworkflow - AC14: No references to removed files (
requirements.in,setup.py, etc.)
Cleanup Requirements
- AC15: All old
setup.pyfiles removed (except out-of-scope components) - AC16: All old
requirements.infiles removed (for in-scope packages) - AC17: All old
requirements.txtfiles removed (for in-scope packages) - AC18:
hack/update-all-requirements.shremoved - AC19: All
MANIFEST.infiles removed (for in-scope packages) - AC20:
.pre-commit-config.yamlPython lint hooks useuv run(versions fromuv.lock)
Implementation Notes for Code Agents
Key Constraints
-
Atomic Migration: Do NOT create separate PRs for each package. The entire migration must be in a single PR to avoid contributor confusion.
-
Version Preservation: Keep existing version numbers. Use
dynamic = ["version"]with[tool.hatch.version]to read from existingversion.pyor__init__.pyfiles. -
Dependency Bounds: The
[project].dependenciesin each package'spyproject.tomlMUST match the bounds from the originalrequirements.inorsetup.py. These are published to PyPI and affect end users. -
Workspace Sources: Use
[tool.uv.sources]withworkspace = truefor inter-package dependencies during development. This does NOT affect published packages. -
Proto Files: The generated
*_pb2.pyfiles are NOT committed. Proto generation must still work after migration:api/v2alpha1/python/generate_proto.pykubernetes_platform/python/generate_proto.py
-
Build Backend: Use
hatchlingas the build backend for consistency. It supports dynamic versioning and works well with uv. -
Workspace Root: Run
uvcommands from the repo root (or pass--project .) so the workspace anduv.lockare used. Running from subdirectories can create an unintended local lockfile. -
Publishing Secrets: Use GitHub Secrets for publish credentials (e.g.,
UV_PUBLISH_TOKEN), and avoid echoing tokens in logs.
Assumptions
uv0.9+ is required for local development (CI usesastral-sh/setup-uvaction).- Python 3.9+ is the minimum supported runtime across all in-scope packages.
- The workspace root is not published to PyPI; only member packages are.
Testing the Migration Locally
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# From repo root, sync all packages (editable mode)
uv sync
# Generate proto files (required before running tests/importing packages)
make -C api python
make -C kubernetes_platform python
# Verify packages are installed
uv pip list | grep kfp
# Run tests
uv run pytest sdk/python/kfp
# Build a specific package
uv build --package kfp
# Verify lockfile is in sync
uv lock --checkCommon Pitfalls
-
Don't add
[build-system]to rootpyproject.toml: The workspace root is not a publishable package. -
Don't forget
[tool.uv.sources]: Without this,uv syncwill try to install inter-package deps from PyPI instead of local source. -
Package data may be missing: When removing
MANIFEST.in, verify any non-Python files (schemas, templates, etc.) are included viatool.hatch.buildsettings.
Related Issues/PRs
- Issue #12245: Use pyproject.toml for python packages
- PR #12275: feat: Adding pyproject.toml to support more modern PEPs — This PR may be superseded by this issue
- KEP-12548: KFP SDK Packaging Consolidation — Future work to merge packages into single
kfp
Love this idea? Give it a 👍.