前言
参考:
https://zhuanlan.zhihu.com/p/704988918
https://www.cnblogs.com/aozhejin/p/19255701
https://www.trae.cn/article/660503298
https://www.xmirror.cn/page/particulars?id=3136&type=dynamic
基础概念
Python Package Index(PyPI)是 Python 编程语言的主要软件包索引和分发平台。它是一个集中的存储库,供开发者和用户查找、分享和安装 Python 软件包。
功能与特点:
- 包的托管与发布:开发者可以将他们开发的 Python 包上传到 PyPI,供全球的 Python 社区下载和使用。这个过程通过
setuptools和twine等工具实现,其中 twine 负责将构建好的包上传到 PyPI。 - 包的搜索与浏览:PyPI 提供了一个强大的搜索引擎,用户可以通过关键词、包名或作者名来查找感兴趣的软件包。每个包都有详细的描述、版本历史和文档链接,帮助用户了解和选择适合的包。
- 版本控制与依赖管理:PyPI 对包的版本进行了有效管理,用户可以根据需求选择特定的版本进行安装。同时,PyPI 还处理了包的依赖关系,确保安装时能正确地解决依赖和版本冲突。
- 社区与贡献:PyPI 作为 Python 社区的中心平台,鼓励开发者和用户参与到包的贡献和改进中来。开源社区的特性使得每个人都能为 PyPI 上的包提供反馈、报告问题或贡献代码。
包的安全验证:
- 包签名验证:支持 GPG 签名验证包的完整性
- 双因素认证:保护开发者账号安全
- 恶意代码扫描:自动检测已知的恶意模式
- 命名空间保护:防止包名抢注和混淆攻击
可以使用 pip-audit 来检查已安装包的安全漏洞
相关命令
pip install requests # 安装 requests 包
pip install django==4.2.0 # 安装特定版本
pip install -r requirements.txt # 从 requirements.txt 批量安装
pip install --upgrade package-name
pip uninstall package-name
pip list
pip show package-name
pip freeze > requirements.txt # 导出依赖列表
pip search numpy # 查找 numpy 包(此功能已被弃用)
构建自己的包并发布:
使用 setuptools
python setup.py sdist
twine upload dist/*
使用 twine build:
# 1. 安装发布工具
pip install twine build
# 2. 构建分发包
python -m build
# 3. 检查包的完整性
twine check dist/*
# 4. 上传到 PyPI(需要注册账号)
twine upload dist/*
项目结构示例:
my_package/
├── src/
│ └── my_package/
│ ├── __init__.py
│ └── main.py
├── tests/
│ └── test_main.py
├── setup.py # 构建配置与元数据,可以使用 pyproject.toml 替代
├── README.md
└── LICENSE
镜像源
常用的国内镜像源:
https://pypi.tuna.tsinghua.edu.cn/simple
https://mirrors.aliyun.com/pypi/simple
https://pypi.douban.com/simple
命令:
# 临时使用清华镜像
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple package-name
# 永久配置镜像源
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
自建 pypi
pypiserver
pypiserver 是一个极简的 PyPI 兼容服务器,支持 pip 或 easy_install
这里选择在 docker 上进行构建
docker-compose.yml
version: '3.7'
services:
pypi-server:
image: pypiserver/pypiserver:latest
ports:
- 8080:8080
volumes:
- type: volume
source: pypi-server
target: /data/packages
command: -P . -a . /data/packages
restart: always
volumes:
pypi-server:
访问 localhost:8080

接下来创建一个项目进行测试,项目结构仿照上面提过的样式
hellopypi
├── setup.py
└── src
└── hellopypi
├── __init__.py
└── main.py
setup.py
from setuptools import setup, find_packages
setup(
name='hellopypi', #应用名
package_dir={"": "src"},
author='0w0', #作者
version='1.0', #版本号
description="这是一个测试模块",
author_email="1@1.com",
packages=find_packages(where="src"), #包括在安装包内的Python包
include_package_data=True, #启用清单文件MANIFEST.in,包含数据文件
#排除文件
exclude_package_data={'docs': ['hellopypi.txt']},
#自动安装依赖
install_requires=[
'Flask>=0.10',
],
entry_points={"console_scripts": [
"hellopypi=hellopypi.main:main",
]},
)
然后打包,生成分发包

把这个 tar.gz 包放到容器内的 /data/packages 目录下

此时就上传成功了
如果要 pip 下载的话就
pip3 install http://localhost:8080/packages/hellopypi-1.0.tar.gz
导入常见依赖
pypi:
image: pypiserver/pypiserver:latest
container_name: pypi
ports:
- "8080:8080"
volumes:
- pypi-data:/data/packages
command: -P . -a . /data/packages
restart: always
pypi-init:
image: python:3.10-slim
container_name: pypi-init
depends_on:
- pypi
volumes:
- ./pypi/init:/init:ro
- pypi-data:/data/packages
environment:
PYPI_REPO_URL: http://pypi:8080
PYPI_PACKAGE_DIR: /data/packages
PUBLIC_INDEX_URL: http://mirrors.cloud.aliyuncs.com/pypi/simple
REAL_PACKAGES: >-
requests==2.31.0 flask==3.0.3 pyyaml==6.0.2 rich==13.7.1 urllib3==2.2.1
jinja2==3.1.4 sqlalchemy==2.0.29 fastapi==0.110.1 pydantic==2.7.1 redis==5.0.4
celery==5.4.0 boto3==1.34.103 numpy==1.26.4 pandas==2.2.2 pytest==8.2.0
DOWNLOAD_WITH_DEPS: "false"
PYPI_WAIT_SECONDS: "180"
entrypoint:
- /bin/bash
- /init/setup.sh
restart: "no"
真实包的导入脚本:
#!/usr/bin/env bash
set -euo pipefail
TARGET_DIR="${PYPI_PACKAGE_DIR:-/data/packages}"
PUBLIC_INDEX_URL="${PUBLIC_INDEX_URL:-http://mirrors.cloud.aliyuncs.com/pypi/simple}"
DOWNLOAD_WITH_DEPS="${DOWNLOAD_WITH_DEPS:-false}"
REAL_PACKAGES="${REAL_PACKAGES:-requests==2.31.0 flask==3.0.3 pyyaml==6.0.2 rich==13.7.1 urllib3==2.2.1 jinja2==3.1.4 sqlalchemy==2.0.29 fastapi==0.110.1 pydantic==2.7.1 redis==5.0.4 celery==5.4.0 boto3==1.34.103 numpy==1.26.4 pandas==2.2.2 pytest==8.2.0}"
WORK_DIR="$(mktemp -d)"
DOWNLOAD_DIR="${WORK_DIR}/downloads"
trap 'rm -rf "${WORK_DIR}"' EXIT
if ! command -v python3 >/dev/null 2>&1; then
echo "python3 未安装,请先安装 Python 3"
exit 1
fi
PYTHON_BIN="python3"
mkdir -p "${DOWNLOAD_DIR}"
mkdir -p "${TARGET_DIR}"
read -r -a package_specs <<<"${REAL_PACKAGES}"
if [[ ${#package_specs[@]} -eq 0 ]]; then
echo "未配置 REAL_PACKAGES,跳过真实包导入"
exit 0
fi
download_from_index() {
local index_url="$1"
local host
host="$(python3 - "${index_url}" <<'PY'
import sys
from urllib.parse import urlparse
print(urlparse(sys.argv[1]).hostname or "")
PY
)"
download_cmd=(
"${PYTHON_BIN}" -m pip download
--dest "${DOWNLOAD_DIR}"
--index-url "${index_url}"
--disable-pip-version-check
--retries 5
--timeout 30
)
if [[ "${DOWNLOAD_WITH_DEPS}" != "true" ]]; then
download_cmd+=(--no-deps)
fi
if [[ "${index_url}" == http://* && -n "${host}" ]]; then
download_cmd+=(--trusted-host "${host}")
fi
"${download_cmd[@]}" "${package_specs[@]}"
}
index_candidates=(
"${PUBLIC_INDEX_URL}"
"http://mirrors.cloud.aliyuncs.com/pypi/simple"
"https://pypi.org/simple"
)
download_ok="false"
for index_url in "${index_candidates[@]}"; do
rm -rf "${DOWNLOAD_DIR}"/*
if download_from_index "${index_url}"; then
download_ok="true"
echo "下载源可用: ${index_url}"
break
fi
echo "下载源失败,尝试下一个: ${index_url}"
done
if [[ "${download_ok}" != "true" ]]; then
echo "真实包下载失败,所有镜像源均不可用" >&2
exit 1
fi
for artifact in "${DOWNLOAD_DIR}"/*; do
if [[ -f "${artifact}" ]]; then
target_file="${TARGET_DIR}/$(basename "${artifact}")"
if [[ -f "${target_file}" ]]; then
echo "已存在,跳过: $(basename "${artifact}")"
else
cp "${artifact}" "${target_file}"
echo "导入成功: $(basename "${artifact}")"
fi
fi
done
echo
echo "真实包导入完成,共处理 ${#package_specs[@]} 个包,目录: ${TARGET_DIR}"