目录

  1. 1. 前言
  2. 2. 基础概念
  3. 3. 相关命令
  4. 4. 镜像源
  5. 5. 自建 pypi
    1. 5.1. pypiserver
  6. 6. 导入常见依赖

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

Pypi

2026/2/9 Dev
  |     |   总文章阅读量:

前言

参考:

https://pypi.org/

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 社区下载和使用。这个过程通过 setuptoolstwine 等工具实现,其中 twine 负责将构建好的包上传到 PyPI。
  • 包的搜索与浏览:PyPI 提供了一个强大的搜索引擎,用户可以通过关键词、包名或作者名来查找感兴趣的软件包。每个包都有详细的描述、版本历史和文档链接,帮助用户了解和选择适合的包。
  • 版本控制与依赖管理:PyPI 对包的版本进行了有效管理,用户可以根据需求选择特定的版本进行安装。同时,PyPI 还处理了包的依赖关系,确保安装时能正确地解决依赖和版本冲突。
  • 社区与贡献:PyPI 作为 Python 社区的中心平台,鼓励开发者和用户参与到包的贡献和改进中来。开源社区的特性使得每个人都能为 PyPI 上的包提供反馈、报告问题或贡献代码。

包的安全验证:

  1. 包签名验证:支持 GPG 签名验证包的完整性
  2. 双因素认证:保护开发者账号安全
  3. 恶意代码扫描:自动检测已知的恶意模式
  4. 命名空间保护:防止包名抢注和混淆攻击

可以使用 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}"