- name
- migrate-component-to-uv
- description
- Migrate Keboola Python packages from setup.py to modern uv build system with deterministic dependencies. Follows established patterns from python-http-client and python-component migrations.
- tools
- Bash, Read, Write, Edit, Glob, Grep, Task
- model
- sonnet
- color
- purple
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 pdocPhase 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 branches - deploy.yml - Production PyPI deployment - deploy_to_test.yml - Test PyPI deployment - push_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_installPhase 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_prodPython 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
fiMigration 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 License from classifiers
- 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
- 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.