Migrate Keboola Python packages and components to modern uv build system with deterministic dependencies and ruff linting.
Migrates Python packages and components to uv build system with pyproject.toml and ruff linting.
npx claudepluginhub keboola/ai-kitThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/examples.mdreferences/migration-guide.mdreferences/troubleshooting.mdreferences/workflow-templates.mdtemplates/Dockerfile.templatetemplates/pr-description.md.templatetemplates/pyproject.toml.component.templatetemplates/pyproject.toml.templateYou are an expert at migrating Keboola Python projects to modern pyproject.toml + uv build system with ruff linting. You handle two types of migrations:
Always start by detecting or asking the migration type.
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"
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)?
src/ or similarsetup.py exists with package metadataDockerfile existsrequirements.txt existsModern best practice: Use ruff exclusively, no flake8.
Guideline: Use logical commits for reviewability
Key principle: Each commit should make sense independently
Package dependencies: Use >= (minimum version)
keboola-component>=1.6.13Python version:
requires-python = ">=3.N" (range, test matrix covers multiple versions)requires-python = "~=3.N.0" (pin to major.minor from Dockerfile base image)Testing phase: Use next minor version
Production release: Use following minor version
Use this path when migrating a Python package published to PyPI.
cat setup.py # Extract dependencies, python_requires, version
cat requirements.txt # May have additional deps
ls .github/workflows/ # Check for PyPI deployment workflows
# From setup.py python_requires
# Use this as minimum in pyproject.toml
grep -q pdoc .github/workflows/*.yml && echo "HAS_DOCS"
# We'll add ruff config to pyproject.toml in next phase
# For now, just ensure ruff is available
uv tool install ruff
ruff check --fix src/ tests/
ruff format src/ tests/
git add src/ tests/
git commit -m "ruff linting baseline 🎨"
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
git rm setup.py requirements.txt
[ -f .flake8 ] && git rm .flake8
[ -f flake8.cfg ] && git rm flake8.cfg
sed -i 's/Copyright (c) 20[0-9][0-9]/Copyright (c) 2026/' LICENSE
git add pyproject.toml LICENSE
git commit -m "migrate to pyproject.toml 📦"
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
uv sync --all-groups
uv build
uv version 1.0.0 --dry-run # Test version replacement
git add .github/workflows/*.yml uv.lock
git commit -m "uv 💜"
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('✅')"
Use this path when migrating a Keboola component (Docker-based).
grep "FROM python:" Dockerfile # e.g., FROM python:3.13-slim
cat requirements.txt
cat .github/workflows/push.yml
[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
uv tool install ruff
ruff check --fix src/ tests/
ruff format src/ tests/
git add pyproject.toml src/ tests/
git commit -m "ruff linting baseline 🎨"
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:
[build-system] - components are not installable packagesdynamic = ["version"] - version managed elsewhere~=3.N.0 - pins to major.minor, allows patch updatesgit rm requirements.txt
[ -f .flake8 ] && git rm .flake8
[ -f flake8.cfg ] && git rm flake8.cfg
.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.
git add pyproject.toml .gitignore
git commit -m "migrate to pyproject.toml 📦"
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:
UV_PROJECT_ENVIRONMENT="/usr/local/" installs to system Pythonuv sync --all-groups --frozen installs all deps including dev (for tests)pip install, no uv run at runtimescripts/build_n_test.sh (if exists):#!/bin/sh
set -e
ruff check .
python -m unittest discover
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")))
.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 .
uv sync --all-groups
git add Dockerfile scripts/ tests/ .github/workflows/ uv.lock
git commit -m "uv 💜"
# 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
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
From requirements.txt:
keboola.component==1.4.4
To pyproject.toml:
dependencies = [
"keboola-component>=1.4.4", # Note: dot → dash in name, == → >=
]
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 subsetsF - pyflakesWe add:
I - isort (import sorting)uv build succeedsError: COPY uv.lock . fails
Fix: Run uv sync --all-groups locally to generate uv.lock before building Docker image
Error: Cannot write to /usr/local/
Fix: Ensure ENV UV_PROJECT_ENVIRONMENT="/usr/local/" is set before RUN uv sync
Error: hatchling can't find package files
Fix: Add to pyproject.toml:
[tool.hatch.build.targets.wheel]
packages = ["src/package_name"]
Status: Expected and good! Ruff is more comprehensive than flake8.
Action: Fix the issues. They were always problems, just not caught before.
Packages:
Components:
Remember: This is a build system migration. End users should see no difference except faster dependency resolution and more consistent environments.
Activates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Search, retrieve, and install Agent Skills from the prompts.chat registry using MCP tools. Use when the user asks to find skills, browse skill catalogs, install a skill for Claude, or extend Claude's capabilities with reusable AI agent components.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.