migrate-component-to-uv
Migrate Keboola Component to uv Build System
You are an expert at migrating Keboola Python packages from legacy setup.py + pip to modern pyproject.toml + uv build system. You understand PEP 517/518/639 standards, GitHub Actions workflows, and Keboola's established migration patterns.
When to Use This Skill
Use this skill when:
- Migrating a Keboola Python package from
setup.pytopyproject.toml - Modernizing build system to use uv instead of pip
- Adding deterministic dependency management with
uv.lock - Updating CI/CD workflows to use uv
- Following Keboola's python-http-client and python-component patterns
Prerequisites Check
Before starting, verify:
- Repository uses
setup.pyand/orrequirements.txt - Package has test suite with pytest
- Git repository with clean working tree
- Access to GitHub secrets configuration
- PyPI and Test PyPI accounts available
Migration Philosophy
Flexible Commit Strategy
Guideline (not dogma): Use 3 logical commits
- Linting baseline - Fix linting first to avoid noise in migration diffs
- Package metadata - Migrate to pyproject.toml
- CI/CD workflows - Update to uv + generate lock file
Why this works:
- Clean diffs: Linting fixes don't pollute actual migration changes
- Reviewable: Each commit has clear, focused purpose
- Flexible: Could be 2 commits (combine lint+pyproject) or 4 (separate workflows)
Key principle: Logical, reviewable chunks that make sense independently
Lint-First Principle
Always fix linting BEFORE migrating metadata:
- Establishes clean baseline
- Reveals code quality issues early
- Prevents attribution confusion (linting vs migration changes)
- Makes review significantly easier
Version Strategy
Testing phase: Use next minor version
- Example: Current 1.6.13 → Test as 1.7.0, 1.7.1, 1.7.2
Production release: Use following minor version
- Example: After testing 1.7.x → Release 1.8.0
Flexibility: Could use 1.7.0a1, 1.7.0a2 instead - pattern matters, not exact format
Step-by-Step Migration Guide
Phase 1: Analysis
- Check current state:
# What's in setup.py?
cat setup.py
# What's in requirements.txt?
cat requirements.txt
# What's the current version?
# Check PyPI or setup.py
- Identify Python version support:
# From setup.py python_requires
# Determine: min_version = max(3.8, current_requires_python)
- Check for docs generation:
# Does push_main.yml exist with pdoc?
cat .github/workflows/push_main.yml 2>/dev/null | grep pdoc
Phase 2: Commit 1 - Linting Baseline
Purpose: Establish clean linting baseline
Steps:
- Rename and update flake8 config:
# Rename to standard name
mv flake8.cfg .flake8 # if it exists
# Use cookiecutter template standard:
cat > .flake8 << 'EOF'
[flake8]
exclude = __pycache__, .git, .venv, venv, docs
ignore = E203,W503
max-line-length = 120
EOF
- Run flake8 and fix ALL errors:
# Install flake8
uv add --dev flake8
# Run and fix errors
flake8 src/ tests/
Common fixes:
- F403/F405: Replace star imports with explicit imports
- F841: Remove unused variables
- E501: Break long lines
- E231: Add missing whitespace
- E123: Fix bracket indentation
- CRLF→LF: Normalize line endings (expected, good cleanup)
- Commit:
git add .flake8 src/ tests/
git commit -m "flake8 config consistent with cookiecutter template 🍪"
Phase 3: Commit 2 - Package Metadata
Purpose: Migrate to modern pyproject.toml
Steps:
- Create pyproject.toml (see
templates/pyproject.toml.template):
Key points:
version = "0.0.0"- replaced by git tags in CI- Extract dependencies from setup.py
install_requires - Extract dev dependencies from setup.py
setup_requiresandtests_require - Add
pdoc3to dev if docs workflow exists requires-python = ">=MIN_VERSION"(≥3.8)- Remove
License :: OSI Approved :: MIT Licenseclassifier (PEP 639) - Keep
license = "MIT"field - Add TestPyPI index configuration
- Delete old files:
git rm setup.py requirements.txt
- Update LICENSE copyright year:
# Update to current year (2026)
sed -i 's/Copyright (c) 20[0-9][0-9]/Copyright (c) 2026/' LICENSE
- Commit:
git add pyproject.toml LICENSE
git commit -m "migrate package configuration to pyproject.toml 📦"
Phase 4: Commit 3 - uv Workflows
Purpose: Update CI/CD to use uv
Steps:
- Update all 3-4 workflows (see
references/workflow-templates.md):push_dev.yml- Testing on dev branchesdeploy.yml- Production PyPI deploymentdeploy_to_test.yml- Test PyPI deploymentpush_main.yml- Docs generation (OPTIONAL - only if docs exist)
Key changes per workflow:
- Update action versions:
@v4→@v5,@v6 - Add uv installation:
uses: astral-sh/setup-uv@v6 - Add ruff action:
uses: astral-sh/ruff-action@v3(blocking, no continue-on-error) - Change:
pip install→uv sync --all-groups --frozen - Change:
flake8 --config=...→uv run flake8 - Change:
pytest tests→uv run pytest tests - Add version replacement:
uv version $TAG_VERSION - Update secrets:
UV_PUBLISH_TOKEN,UV_PUBLISH_TOKEN_TEST_PYPI - Python matrix:
[MIN_VERSION, "3.13", "3.14"](min + 2 latest)
- Generate uv.lock:
uv sync --all-groups
- Verify build works:
uv build
# Should create dist/*.tar.gz and dist/*.whl
uv version 1.7.0 --dry-run
# Should show: package-name 0.0.0 => 1.7.0
- Commit:
git add .github/workflows/*.yml uv.lock
git commit -m "uv 💜"
Phase 5: Testing on Test PyPI
- Push branch:
git push origin BRANCH_NAME
- Create test tag:
git tag 1.7.0
git push origin 1.7.0
-
Manually trigger Test PyPI workflow:
- Go to GitHub Actions → "Build & Upload Python Package To Test PyPI"
- Click "Run workflow"
- Select branch or tag
- Click "Run workflow"
-
Verify on Test PyPI:
- Check: https://test.pypi.org/project/PACKAGE_NAME/
- Verify version appears
-
Test installation:
cd /tmp && mkdir test_install && cd test_install
uv init
uv add --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
--index-strategy unsafe-best-match \
PACKAGE_NAME==1.7.0
uv run python -c "import PACKAGE; print('✅ Works!')"
cd .. && rm -rf test_install
Phase 6: Production Release
-
Create PR (see
templates/pr-description.md.template) -
Get approval and merge to main
-
Create production release:
- Go to: https://github.com/ORG/REPO/releases/new
- Tag:
1.8.0 - Target:
main - Title:
1.8.0 - Description: Migration summary
- Click "Publish release"
-
Workflow auto-triggers → Publishes to PyPI
-
Verify production:
cd /tmp && mkdir test_prod && cd test_prod
uv init
uv add PACKAGE_NAME==1.8.0
uv run python -c "import PACKAGE; print('✅ Production works!')"
cd .. && rm -rf test_prod
Python Matrix Strategy
Smart matrix logic:
python-version: [
"MIN_SUPPORTED", # max(3.8, current_requires_python)
"3.13", # Second-latest stable
"3.14" # Latest stable
]
Examples:
- Package supports ≥3.8 →
["3.8", "3.13", "3.14"] - Package supports ≥3.10 →
["3.10", "3.13", "3.14"] - Package supports ≥3.12 →
["3.12", "3.13", "3.14"]
Rationale: Test minimum (compatibility floor) + 2 latest (future-proofing)
Docs Workflow Handling
Detection:
# Check if push_main.yml exists AND contains pdoc
if [ -f .github/workflows/push_main.yml ] && grep -q pdoc .github/workflows/push_main.yml; then
# Include docs workflow in migration
# Add pdoc3 to [dependency-groups] dev
fi
Migration for docs:
- Old:
pip install --user pdoc3(ad-hoc) - New: Add
pdoc3to[dependency-groups] dev+ useuv run pdoc - Improvement: Tracked dependency instead of ad-hoc install
Secret Configuration
Required GitHub secrets:
-
UV_PUBLISH_TOKEN - Production PyPI token
- Create at: https://pypi.org/manage/account/token/
- Scope: Entire account or specific project
- Add at: GitHub repo → Settings → Secrets → UV_PUBLISH_TOKEN
-
UV_PUBLISH_TOKEN_TEST_PYPI - Test PyPI token
- Create at: https://test.pypi.org/manage/account/token/
- Scope: Entire account
- Add at: GitHub repo → Settings → Secrets → UV_PUBLISH_TOKEN_TEST_PYPI
Note: uv automatically uses __token__ as username when UV_PUBLISH_TOKEN env var is set
Workflow Trigger Strategy
Test PyPI: on: workflow_dispatch (manual)
- Allows testing any version without tag naming constraints
- Full control over when to test
Production PyPI: on: push: tags: ['*'] (automatic)
- Triggers automatically when tag pushed to main
- Simpler workflow:
git tag X.Y.Z && git push origin X.Y.Z
Common Issues & Solutions
See references/troubleshooting.md for detailed troubleshooting guide.
Quick fixes:
-
License classifier conflict
- Error:
License classifiers have been superseded - Fix: Remove
License :: OSI Approved :: MIT Licensefrom classifiers
- Error:
-
CRLF line endings
- Symptom: Huge diffs in unchanged files
- Status: Expected and correct (CRLF → LF normalization)
-
Test PyPI installation fails
- Error:
No solution found - Fix: Add
--index-strategy unsafe-best-match
- Error:
-
uv version command not found
- Fix: Ensure uv ≥ 0.5.0
Modern Tooling Requirements
100% modern uv - ZERO pip mentions:
✅ CORRECT:
uv add PACKAGE- Add production dependencyuv add --dev PACKAGE- Add dev dependencyuv sync --all-groups --frozen- Install from lockuv run COMMAND- Run command in environmentuv build- Build packageuv publish- Publish to PyPI
❌ NEVER USE:
pip install- OLDpip- OLDuv pip install- WRONG uv usage
References
references/migration-guide.md- Complete step-by-step guidereferences/workflow-templates.md- All 4 workflow YAML filesreferences/troubleshooting.md- Common issues and solutionsreferences/examples.md- python-http-client and python-component migrationstemplates/pyproject.toml.template- Template pyproject.tomltemplates/flake8.template- Template .flake8templates/pr-description.md.template- PR description template
Questions to Ask User
Before starting:
- What's the current latest version on PyPI?
- Do you have access to configure GitHub secrets?
- What Python versions should we support? (check setup.py)
- Does the package generate HTML docs with pdoc?
- Any custom build requirements or special dependencies?
Success Criteria
Migration complete when:
- ✅ All tests pass with uv locally
- ✅ Package builds successfully (
uv build) - ✅ Test PyPI release installable and functional
- ✅ Production PyPI release installable and functional
- ✅ CI/CD workflows green on main branch
- ✅ Documentation updated (if needed)
- ✅ Team notified
Remember: This is a build system migration, NOT an API change. End users should see no difference except faster installs and more reliable dependency resolution.