migrate-to-uv

Migrate to uv Build System

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "migrate-to-uv" with this command: npx skills add keboola/ai-kit/keboola-ai-kit-migrate-to-uv

Migrate to uv Build System

You are an expert at migrating Keboola Python projects to modern pyproject.toml

  • uv build system with ruff linting. You handle two types of migrations:
  • Python Packages - Published to PyPI, installed by other projects (setup.py → pyproject.toml with build system)

  • Keboola Components - Docker-based applications deployed to ECR (requirements.txt → pyproject.toml, no build system)

Phase 0: Determine Migration Type

Always start by detecting or asking the migration type.

Auto-Detection Heuristics

Run these checks:

Check for package indicators

[ -f setup.py ] && echo "PACKAGE"

Check for component indicators

[ -f Dockerfile ] && [ ! -f setup.py ] && echo "COMPONENT"

Check CI deployment target

grep -q "pypi|PyPI" .github/workflows/.yml 2>/dev/null && echo "PACKAGE" grep -q "ECR|DEVELOPERPORTAL" .github/workflows/.yml 2>/dev/null && echo "COMPONENT"

Ask the User

If detection is ambiguous or you want to confirm:

Question: Is this a Python package (published to PyPI) or a Keboola component (Docker-based, deployed to ECR)?

  • Package → Follow Package Migration Path

  • Component → Follow Component Migration Path

Prerequisites Check

Both Types

  • Git repository with clean working tree

  • Existing Python source code in src/ or similar

  • Test suite exists

Package Only

  • setup.py exists with package metadata

  • PyPI and Test PyPI accounts available

  • GitHub secrets configured (UV_PUBLISH_TOKEN)

Component Only

  • Dockerfile exists

  • requirements.txt exists

  • Keboola Developer Portal credentials available

Migration Philosophy

Ruff-Only Linting

Modern best practice: Use ruff exclusively, no flake8.

  • Ruff is faster, more comprehensive, and actively maintained

  • Covers all flake8 checks + pyflakes + isort + pyupgrade

  • Single tool instead of multiple linters

  • Built-in formatting support

Flexible Commit Strategy

Guideline: Use logical commits for reviewability

  • Linting baseline - Add ruff config, fix all linting issues

  • Metadata migration - Create pyproject.toml, delete old files

  • CI/CD updates - Update workflows/Dockerfile to use uv

Key principle: Each commit should make sense independently

Dependency Pinning Strategy

Package dependencies: Use >= (minimum version)

  • Example: keboola-component>=1.6.13

  • uv.lock provides determinism, >= in pyproject.toml allows flexibility

Python version:

  • Packages: requires-python = ">=3.N" (range, test matrix covers multiple versions)

  • Components: requires-python = "~=3.N.0" (pin to major.minor from Dockerfile base image)

Version Strategy [Package Only]

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

Package Migration Path

Use this path when migrating a Python package published to PyPI.

Phase 1: Analysis [Package]

  • Check current state:

cat setup.py # Extract dependencies, python_requires, version cat requirements.txt # May have additional deps ls .github/workflows/ # Check for PyPI deployment workflows

  • Identify Python version:

From setup.py python_requires

Use this as minimum in pyproject.toml

  • Check for docs:

grep -q pdoc .github/workflows/*.yml && echo "HAS_DOCS"

Phase 2: Linting Baseline [Package]

  • Create pyproject.toml with ruff config:

We'll add ruff config to pyproject.toml in next phase

For now, just ensure ruff is available

uv tool install ruff

  • Run ruff and fix issues:

ruff check --fix src/ tests/ ruff format src/ tests/

  • Commit:

git add src/ tests/ git commit -m "ruff linting baseline 🎨"

Phase 3: Package Metadata [Package]

  • Create pyproject.toml :

[project] name = "package-name" version = "0.0.0" # Replaced by git tags in CI description = "Short description" readme = "README.md" requires-python = ">=3.N" license = "MIT" authors = [ { name = "Keboola", email = "support@keboola.com" } ] classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.N", # Add supported versions ]

dependencies = [ "package>=x.y.z", # From setup.py install_requires ]

[dependency-groups] dev = [ "ruff>=0.15.0", "pytest>=8.0.0", # If using pytest # Add other dev deps from setup_requires, tests_require ]

[project.urls] Homepage = "https://github.com/keboola/REPO" Repository = "https://github.com/keboola/REPO"

[build-system] requires = ["hatchling"] build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel] packages = ["src/package_name"]

[tool.ruff] line-length = 120

[tool.ruff.lint] extend-select = ["I"] # Add isort to default ruff rules

[[tool.uv.index]] name = "test-pypi" url = "https://test.pypi.org/simple" explicit = true

  • Delete old files:

git rm setup.py requirements.txt [ -f .flake8 ] && git rm .flake8 [ -f flake8.cfg ] && git rm flake8.cfg

  • Update LICENSE year:

sed -i 's/Copyright (c) 20[0-9][0-9]/Copyright (c) 2026/' LICENSE

  • Commit:

git add pyproject.toml LICENSE git commit -m "migrate to pyproject.toml 📦"

Phase 4: CI/CD Workflows [Package]

  • Update workflows (typically push_dev.yml , deploy.yml , deploy_to_test.yml ):

Key changes:

Add uv setup (after checkout and python setup)

  • name: Set up uv uses: astral-sh/setup-uv@v6

Replace all pip install → uv sync

  • name: Install dependencies run: uv sync --all-groups --frozen

Replace pytest → uv run pytest

  • name: Run tests run: uv run pytest tests/

Add ruff linting

  • name: Lint with ruff uses: astral-sh/ruff-action@v3

For version replacement in deploy workflows

  • name: Set package version run: uv version ${{ env.TAG_VERSION }}

For publishing

  • name: Build package run: uv build

  • name: Publish to PyPI env: UV_PUBLISH_TOKEN: ${{ secrets.UV_PUBLISH_TOKEN }} run: uv publish

Update Python matrix:

strategy: matrix: python-version: ["3.N", "3.13", "3.14"] # min + 2 latest

  • Generate uv.lock:

uv sync --all-groups

  • Verify build:

uv build uv version 1.0.0 --dry-run # Test version replacement

  • Commit:

git add .github/workflows/*.yml uv.lock git commit -m "uv 💜"

Phase 5: Test on Test PyPI [Package]

  • Push branch and create test tag

  • Manually trigger Test PyPI workflow

  • Verify installation:

uv init --name test-install uv add --index-url https://test.pypi.org/simple/
--extra-index-url https://pypi.org/simple/
--index-strategy unsafe-best-match
PACKAGE==1.0.0 uv run python -c "import PACKAGE; print('✅')"

Phase 6: Production Release [Package]

  • Create PR, get approval, merge to main

  • Create release tag

  • Verify on PyPI

Component Migration Path

Use this path when migrating a Keboola component (Docker-based).

Phase 1: Analysis [Component]

  • Check Dockerfile Python version:

grep "FROM python:" Dockerfile # e.g., FROM python:3.13-slim

  • Check current dependencies:

cat requirements.txt

  • Check CI workflow:

cat .github/workflows/push.yml

Phase 2: Linting Baseline [Component]

  • Create pyproject.toml with ruff config (we'll complete it in next phase):

[project] name = "component-name" dynamic = ["version"] requires-python = "~=3.N.0" # Match Dockerfile FROM python:3.N-slim

[tool.ruff] line-length = 120

[tool.ruff.lint] extend-select = ["I"] # Add isort to default ruff rules

  • Run ruff locally:

uv tool install ruff ruff check --fix src/ tests/ ruff format src/ tests/

  • Commit:

git add pyproject.toml src/ tests/ git commit -m "ruff linting baseline 🎨"

Phase 3: Metadata [Component]

  • Complete pyproject.toml :

[project] name = "component-name" dynamic = ["version"] requires-python = "~=3.N.0" dependencies = [ "keboola-component>=1.6.13", "package>=x.y.z", # From requirements.txt, converted to >= ]

[dependency-groups] dev = [ "ruff>=0.15.0", ]

[tool.ruff] line-length = 120

[tool.ruff.lint] extend-select = ["I"]

Note:

  • No [build-system]

  • components are not installable packages

  • No classifiers - not published to PyPI

  • dynamic = ["version"]

  • version managed elsewhere

  • ~=3.N.0

  • pins to major.minor, allows patch updates

  • Delete old files:

git rm requirements.txt [ -f .flake8 ] && git rm .flake8 [ -f flake8.cfg ] && git rm flake8.cfg

  • Update .gitignore :

Add to .gitignore if not already present:

echo "*.egg-info/" >> .gitignore echo ".venv/" >> .gitignore

Note: *.egg-info/ is created by uv due to dynamic = ["version"] . It should be gitignored, not committed.

  • Commit:

git add pyproject.toml .gitignore git commit -m "migrate to pyproject.toml 📦"

Phase 4: Docker and CI [Component]

  • Update Dockerfile :

FROM python:3.N-slim COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

WORKDIR /code/

Copy dependency files first (layer caching)

COPY pyproject.toml . COPY uv.lock .

Install dependencies into system Python (no venv in Docker)

ENV UV_PROJECT_ENVIRONMENT="/usr/local/" RUN uv sync --all-groups --frozen

Copy source code

COPY src/ src COPY tests/ tests COPY scripts/ scripts COPY deploy.sh .

CMD ["python", "-u", "src/component.py"]

Key changes:

  • Install uv from official image

  • Copy pyproject.toml + uv.lock before source (layer caching)

  • UV_PROJECT_ENVIRONMENT="/usr/local/" installs to system Python

  • uv sync --all-groups --frozen installs all deps including dev (for tests)

  • No pip install , no uv run at runtime

  • Update scripts/build_n_test.sh (if exists):

#!/bin/sh set -e

ruff check . python -m unittest discover

  • Modernize tests/init.py to use pathlib:

Before (old os.path pattern):

import sys import os sys.path.append(os.path.dirname(os.path.realpath(file)) + "/../src")

After (modern pathlib pattern from cookiecutter):

import sys from pathlib import Path

sys.path.append(str((Path(file).resolve().parent.parent / "src")))

  • Update .github/workflows/push.yml :

Modernize workflow trigger:

Before (old whitelist pattern):

on: push: branches: - feature/* - bug/* - fix/* - SUPPORT-* tags: - "*"

After (modern blacklist pattern from cookiecutter):

on: push: # skip the workflow on the main branch without tags branches-ignore: - main tags: - "*"

Change test commands:

Before:

docker run ${{ env.KBC_DEVELOPERPORTAL_APP }}:latest flake8 . --config=flake8.cfg

After:

docker run ${{ env.KBC_DEVELOPERPORTAL_APP }}:latest ruff check .

  • Generate uv.lock:

uv sync --all-groups

  • Commit:

git add Dockerfile scripts/ tests/ .github/workflows/ uv.lock git commit -m "uv 💜"

Phase 5: Test Locally [Component]

Build Docker image

docker build -t test-component .

Run linting

docker run test-component ruff check .

Run tests

docker run test-component python -m unittest discover

Run component

docker run test-component python -u src/component.py

Common Patterns

Python Version Selection

Packages: Use range for broad compatibility

requires-python = ">=3.9"

Components: Pin to Dockerfile base image major.minor

requires-python = "~=3.13.0" # FROM python:3.13-slim

Dependency Conversion

From requirements.txt:

keboola.component==1.4.4

To pyproject.toml:

dependencies = [ "keboola-component>=1.4.4", # Note: dot → dash in name, == → >= ]

Ruff Configuration

Minimal standard config for all Keboola projects:

[tool.ruff] line-length = 120

[tool.ruff.lint] extend-select = ["I"] # Add isort to default ruff rules

Ruff defaults (enabled automatically):

  • E4, E7, E9

  • pycodestyle error subsets

  • F

  • pyflakes

We add:

  • I
  • isort (import sorting)

Success Criteria

Package Success

  • ✅ All tests pass with uv locally

  • ✅ uv build succeeds

  • ✅ Test PyPI release installable

  • ✅ Production PyPI release installable

  • ✅ CI/CD workflows green

Component Success

  • ✅ Docker build succeeds

  • ✅ Linting passes in Docker

  • ✅ Tests pass in Docker

  • ✅ Component runs successfully

  • ✅ CI/CD workflow green

Troubleshooting

Component: uv.lock not found in Docker build

Error: COPY uv.lock . fails

Fix: Run uv sync --all-groups locally to generate uv.lock before building Docker image

Component: Permission errors with UV_PROJECT_ENVIRONMENT

Error: Cannot write to /usr/local/

Fix: Ensure ENV UV_PROJECT_ENVIRONMENT="/usr/local/" is set before RUN uv sync

Package: Build fails with "no files to ship"

Error: hatchling can't find package files

Fix: Add to pyproject.toml:

[tool.hatch.build.targets.wheel] packages = ["src/package_name"]

Ruff finding issues flake8 missed

Status: Expected and good! Ruff is more comprehensive than flake8.

Action: Fix the issues. They were always problems, just not caught before.

Reference Examples

Packages:

  • keboola/python-http-client

  • keboola/python-component

Components:

  • keboola/component-bingads-ex (commit b72a98b)

Remember: This is a build system migration. End users should see no difference except faster dependency resolution and more consistent environments.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

get-started

No summary provided by upstream source.

Repository SourceNeeds Review
General

gh-process-review

No summary provided by upstream source.

Repository SourceNeeds Review
General

tester

No summary provided by upstream source.

Repository SourceNeeds Review
General

component-builder

No summary provided by upstream source.

Repository SourceNeeds Review