企业实践与迁移指南
2026/3/20大约 8 分钟
企业实践与迁移指南
团队使用 uv 的最佳实践与从其他工具迁移的完整指南
从 pip 迁移
迁移策略
阶段 1: 直接替换
最简单的迁移,直接用 uv pip 替换 pip:
# 原有命令
pip install flask requests
pip install -r requirements.txt
pip freeze > requirements.txt
# 替换为
uv pip install flask requests
uv pip install -r requirements.txt
uv pip freeze > requirements.txt
创建 shell 别名加速过渡:
# ~/.bashrc 或 ~/.zshrc
alias pip='uv pip'
阶段 2: 使用锁文件
将 requirements.txt 升级为带锁定的工作流:
# 1. 创建 requirements.in(源文件)
cat requirements.txt | grep -v '^#' | cut -d'=' -f1 > requirements.in
# 2. 编译生成锁定的 requirements.txt
uv pip compile requirements.in -o requirements.txt
# 3. 同步安装
uv pip sync requirements.txt
# 4. 升级依赖
uv pip compile requirements.in -o requirements.txt --upgrade
阶段 3: 迁移到项目模式
完整迁移到 pyproject.toml:
# 1. 初始化项目
uv init
# 2. 添加现有依赖
# 方式 A: 手动添加
uv add flask requests sqlalchemy
# 方式 B: 从 requirements.txt 批量添加
while read -r pkg; do
uv add "$pkg" 2>/dev/null || echo "Skipped: $pkg"
done < requirements.in
# 3. 添加开发依赖
uv add --dev pytest ruff mypy
# 4. 验证
uv sync
uv run python -c "import flask; print('OK')"
requirements.txt 转换工具
#!/usr/bin/env python
# /// script
# requires-python = ">=3.10"
# dependencies = ["tomli-w"]
# ///
"""requirements.txt 转 pyproject.toml 工具"""
import re
import sys
import tomli_w
def parse_requirements(file_path):
deps = []
with open(file_path) as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
# 移除版本锁定中的精确版本
pkg = re.split(r'[=<>!~]', line)[0].strip()
if pkg:
deps.append(line if any(c in line for c in '=<>!~') else pkg)
return deps
def main():
req_file = sys.argv[1] if len(sys.argv) > 1 else 'requirements.txt'
deps = parse_requirements(req_file)
pyproject = {
'project': {
'name': 'myproject',
'version': '0.1.0',
'requires-python': '>=3.10',
'dependencies': deps,
}
}
with open('pyproject.toml', 'wb') as f:
tomli_w.dump(pyproject, f)
print(f"Converted {len(deps)} dependencies to pyproject.toml")
if __name__ == '__main__':
main()
从 Poetry 迁移
迁移策略
Poetry 和 uv 都使用 pyproject.toml,迁移相对简单:
# 1. poetry.lock 项目结构
myproject/
├── pyproject.toml # Poetry 格式
├── poetry.lock # Poetry 锁文件
└── src/
# 2. 迁移后结构
myproject/
├── pyproject.toml # 标准 PEP 621 格式
├── uv.lock # uv 锁文件
└── src/
迁移步骤
# 1. 备份原文件
cp pyproject.toml pyproject.toml.bak
cp poetry.lock poetry.lock.bak
# 2. 转换 pyproject.toml
# Poetry 格式:
[tool.poetry.dependencies]
python = "^3.10"
flask = "^3.0"
# 转换为标准格式:
[project]
requires-python = ">=3.10"
dependencies = ["flask>=3.0"]
# 3. 生成 uv.lock
uv lock
# 4. 同步环境
uv sync
# 5. 验证
uv run python -c "import flask"
pyproject.toml 转换对照
# ======= Poetry 格式 =======
[tool.poetry]
name = "myproject"
version = "0.1.0"
description = "My project"
authors = ["Name <email@example.com>"]
[tool.poetry.dependencies]
python = "^3.10"
flask = "^3.0"
requests = {version = "^2.28", optional = true}
[tool.poetry.dev-dependencies]
pytest = "^7.0"
black = "^23.0"
[tool.poetry.extras]
http = ["requests"]
[tool.poetry.scripts]
myapp = "myproject:main"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
# ======= uv/标准格式 =======
[project]
name = "myproject"
version = "0.1.0"
description = "My project"
authors = [{name = "Name", email = "email@example.com"}]
requires-python = ">=3.10"
dependencies = [
"flask>=3.0",
]
[project.optional-dependencies]
http = ["requests>=2.28"]
[project.scripts]
myapp = "myproject:main"
[tool.uv]
dev-dependencies = [
"pytest>=7.0",
"black>=23.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
版本约束转换
| Poetry | 标准格式 |
|---|---|
^3.0 | >=3.0,<4.0 |
~3.0 | >=3.0,<3.1 |
3.0.* | >=3.0,<3.1 |
>=3.0,<4.0 | >=3.0,<4.0 |
3.0 | ==3.0 |
自动转换脚本
#!/usr/bin/env python
# /// script
# requires-python = ">=3.10"
# dependencies = ["tomli", "tomli-w"]
# ///
"""Poetry pyproject.toml 转换为 uv 兼容格式"""
import re
import tomli
import tomli_w
def convert_version(poetry_ver):
"""转换 Poetry 版本约束为 PEP 440"""
if poetry_ver.startswith('^'):
ver = poetry_ver[1:]
parts = ver.split('.')
major = int(parts[0])
return f">={ver},<{major + 1}.0"
elif poetry_ver.startswith('~'):
ver = poetry_ver[1:]
parts = ver.split('.')
return f">={ver},<{parts[0]}.{int(parts[1]) + 1}"
return poetry_ver
def convert_dep(name, spec):
"""转换单个依赖"""
if isinstance(spec, str):
return f"{name}{convert_version(spec)}"
elif isinstance(spec, dict):
version = spec.get('version', '')
if version:
return f"{name}{convert_version(version)}"
return name
return name
def main():
with open('pyproject.toml', 'rb') as f:
data = tomli.load(f)
poetry = data.get('tool', {}).get('poetry', {})
# 转换基本信息
project = {
'name': poetry.get('name', 'myproject'),
'version': poetry.get('version', '0.1.0'),
'description': poetry.get('description', ''),
'requires-python': convert_version(
poetry.get('dependencies', {}).get('python', '^3.10')
),
}
# 转换依赖
deps = poetry.get('dependencies', {})
project['dependencies'] = [
convert_dep(name, spec)
for name, spec in deps.items()
if name != 'python'
]
# 转换开发依赖
dev_deps = poetry.get('dev-dependencies', {})
uv_config = {
'dev-dependencies': [
convert_dep(name, spec)
for name, spec in dev_deps.items()
]
}
# 输出
result = {
'project': project,
'tool': {'uv': uv_config},
'build-system': {
'requires': ['hatchling'],
'build-backend': 'hatchling.build'
}
}
with open('pyproject.uv.toml', 'wb') as f:
tomli_w.dump(result, f)
print("Converted! Review pyproject.uv.toml and rename if satisfied.")
if __name__ == '__main__':
main()
从 PDM 迁移
PDM 也使用标准的 pyproject.toml,迁移更简单:
# 1. PDM 项目结构
myproject/
├── pyproject.toml # PDM 使用标准格式
├── pdm.lock # PDM 锁文件
└── src/
# 2. 迁移命令
# 删除 PDM 锁文件
rm pdm.lock
# 生成 uv 锁文件
uv lock
# 同步
uv sync
主要差异
# PDM 特有配置
[tool.pdm.dev-dependencies]
dev = ["pytest>=7.0"]
# 转换为 uv
[tool.uv]
dev-dependencies = ["pytest>=7.0"]
从 Pipenv 迁移
迁移步骤
# 1. Pipenv 项目结构
myproject/
├── Pipfile
├── Pipfile.lock
└── app.py
# 2. 提取依赖
pipenv requirements > requirements.txt
pipenv requirements --dev > requirements-dev.txt
# 3. 初始化 uv 项目
uv init
# 4. 添加依赖
uv add $(cat requirements.txt | tr '\n' ' ')
uv add --dev $(cat requirements-dev.txt | tr '\n' ' ')
# 5. 清理
rm Pipfile Pipfile.lock requirements.txt requirements-dev.txt
CI/CD 集成
GitHub Actions
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Set up Python
run: uv python install ${{ matrix.python-version }}
- name: Install dependencies
run: uv sync --frozen
- name: Lint
run: uv run ruff check .
- name: Type check
run: uv run mypy src/
- name: Test
run: uv run pytest --cov=src --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
files: ./coverage.xml
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
- run: uv build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
GitLab CI
stages:
- test
- build
- deploy
variables:
UV_CACHE_DIR: .uv-cache
cache:
key: uv-$CI_COMMIT_REF_SLUG
paths:
- .uv-cache/
- .venv/
.uv-base:
before_script:
- pip install uv
- uv sync --frozen
test:
extends: .uv-base
stage: test
script:
- uv run pytest --junitxml=report.xml
artifacts:
reports:
junit: report.xml
lint:
extends: .uv-base
stage: test
script:
- uv run ruff check .
- uv run mypy src/
build:
extends: .uv-base
stage: build
script:
- uv build
artifacts:
paths:
- dist/
deploy:
stage: deploy
script:
- pip install uv
- uv publish --token $PYPI_TOKEN
only:
- tags
Jenkins
pipeline {
agent any
environment {
UV_CACHE_DIR = "${WORKSPACE}/.uv-cache"
}
stages {
stage('Setup') {
steps {
sh 'pip install uv'
sh 'uv sync --frozen'
}
}
stage('Lint') {
steps {
sh 'uv run ruff check .'
}
}
stage('Test') {
steps {
sh 'uv run pytest --junitxml=results.xml'
}
post {
always {
junit 'results.xml'
}
}
}
stage('Build') {
steps {
sh 'uv build'
}
post {
success {
archiveArtifacts artifacts: 'dist/*'
}
}
}
}
}
Docker 集成
# Dockerfile
FROM python:3.12-slim
# 安装 uv
COPY /uv /usr/local/bin/uv
WORKDIR /app
# 复制依赖文件
COPY pyproject.toml uv.lock ./
# 安装依赖(利用缓存)
RUN uv sync --frozen --no-install-project
# 复制源代码
COPY . .
# 安装项目
RUN uv sync --frozen
# 运行
CMD ["uv", "run", "python", "-m", "myapp"]
优化的多阶段构建:
# syntax=docker/dockerfile:1
# 构建阶段
FROM python:3.12-slim as builder
COPY /uv /usr/local/bin/uv
WORKDIR /app
COPY pyproject.toml uv.lock ./
# 创建虚拟环境
RUN uv sync --frozen --no-install-project --no-dev
COPY . .
RUN uv sync --frozen --no-dev
# 运行阶段
FROM python:3.12-slim
WORKDIR /app
COPY /app/.venv /app/.venv
COPY /app/src /app/src
ENV PATH="/app/.venv/bin:$PATH"
CMD ["python", "-m", "myapp"]
团队协作规范
项目结构规范
myproject/
├── .github/
│ └── workflows/
│ └── ci.yml
├── src/
│ └── myproject/
│ ├── __init__.py
│ └── main.py
├── tests/
│ ├── __init__.py
│ └── test_main.py
├── docs/
├── .gitignore
├── .python-version # 固定 Python 版本
├── pyproject.toml # 项目配置
├── uv.lock # 锁文件(必须提交)
├── README.md
└── Makefile # 常用命令
.gitignore 模板
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
dist/
*.egg-info/
*.egg
# 虚拟环境(不提交)
.venv/
venv/
ENV/
# uv 缓存(不提交)
.uv-cache/
# IDE
.idea/
.vscode/
*.swp
# 测试
.pytest_cache/
.coverage
htmlcov/
.tox/
.nox/
# 类型检查
.mypy_cache/
# 其他
*.log
.env
.env.local
Makefile 模板
.PHONY: help install dev test lint format typecheck clean build publish
help:
@echo "Available commands:"
@echo " make install - Install production dependencies"
@echo " make dev - Install development dependencies"
@echo " make test - Run tests"
@echo " make lint - Run linter"
@echo " make format - Format code"
@echo " make typecheck - Run type checker"
@echo " make clean - Clean build artifacts"
@echo " make build - Build package"
@echo " make publish - Publish to PyPI"
install:
uv sync --no-dev --frozen
dev:
uv sync --frozen
test:
uv run pytest -v --cov=src --cov-report=term-missing
lint:
uv run ruff check .
format:
uv run ruff format .
uv run ruff check --fix .
typecheck:
uv run mypy src/
clean:
rm -rf build/ dist/ *.egg-info/
rm -rf .pytest_cache/ .mypy_cache/ .ruff_cache/
rm -rf htmlcov/ .coverage
find . -type d -name __pycache__ -exec rm -rf {} +
build: clean
uv build
publish: build
uv publish
# 依赖更新
update:
uv lock --upgrade
uv sync
# 安全检查
audit:
uv run pip-audit
pre-commit 配置
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: ruff-format
name: ruff format
entry: uv run ruff format
language: system
types: [python]
pass_filenames: true
- id: ruff-check
name: ruff check
entry: uv run ruff check --fix
language: system
types: [python]
pass_filenames: true
- id: mypy
name: mypy
entry: uv run mypy
language: system
types: [python]
pass_filenames: false
args: [src/]
- id: pytest
name: pytest
entry: uv run pytest
language: system
pass_filenames: false
stages: [push]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
安装:
uv add --dev pre-commit
uv run pre-commit install
企业私有仓库
配置私有索引
# pyproject.toml
[tool.uv]
# 主索引使用私有仓库
index-url = "https://pypi.internal.company.com/simple"
# PyPI 作为备用
extra-index-url = ["https://pypi.org/simple"]
认证配置
# 方式 1: .netrc 文件
# ~/.netrc
machine pypi.internal.company.com
login deploy
password ${PYPI_TOKEN}
# 方式 2: 环境变量
export UV_INDEX_URL="https://user:token@pypi.internal.company.com/simple"
# 方式 3: pip.conf 兼容
# ~/.config/pip/pip.conf
[global]
index-url = https://user:token@pypi.internal.company.com/simple
发布到私有仓库
# 配置发布 URL
export UV_PUBLISH_URL=https://pypi.internal.company.com/
export UV_PUBLISH_TOKEN=xxx
# 发布
uv build
uv publish
常见问题与解决方案
迁移过程中的问题
1. 依赖版本冲突
# 查看冲突详情
uv lock --verbose
# 使用依赖覆盖解决
[tool.uv]
override-dependencies = ["problematic-package>=1.0,<2.0"]
2. 私有包找不到
# 确认索引配置正确
uv pip install --index-url https://private/simple private-package
# 使用源配置
[tool.uv.sources]
private-package = { index = "private" }
[[tool.uv.index]]
name = "private"
url = "https://private/simple"
3. 构建依赖缺失
# 安装构建工具
uv pip install build hatchling
# 或在项目配置
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
性能问题
1. 首次安装慢
# 预热缓存
uv sync
# 后续使用缓存
uv sync --frozen
2. CI 缓存配置
- uses: astral-sh/setup-uv@v3
with:
enable-cache: true
cache-dependency-glob: |
**/pyproject.toml
**/uv.lock
总结
迁移检查清单
团队采用建议
- 渐进式迁移 - 从新项目开始
- 保留兼容性 - 过渡期保留旧配置
- 统一规范 - 制定团队使用规范
- 持续集成 - 确保 CI 配置完善
- 文档更新 - 更新项目文档
下一步
- 与其他工具对比分析 - 详细的工具对比