# pip 换源(Windows/Linux/MacOS通用):
方法一:使用脚本(支持Python2和Python3)
wget /waketzheng/carstino/main/pip_conf.py
# 下载太慢的话,可以用这个加速:
#wget //waketzheng/carstino/main/pip_conf.py
python pip_conf.py # 不带参数默认为腾讯云
python pip_conf.py hw # 华为
python pip_conf.py ali # 阿里
python pip_conf.py douban # 豆瓣
方法二:手动在命令行修改:
# 升级pip版本(可跳过)
python -m pip install -i /pypi/simple/ --upgrade --user pip
# 配置镜像为腾讯云
pip config set -url /pypi/simple/
## 2022-05-11 补充
如果是云服务器,可以这样配置:
sudo python pip_conf.py --etc
会配置到/etc目录下,root用户和普通用户都会用这个源
## poetry换源
python pip_conf.py --poetry
将自动加载插件poetry-plugin-pypi-mirror并将镜像源URL写入到文件
## 2025-04-01 补充
### uv换源
python pip_conf.py --tool=uv --source=qinghua
### pdm换源
python pip_conf.py tx --pdm
* 更多用法见:./pip_conf.py --help
完整脚本内容如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
This script is for pip source config.
Worked both Windows and Linux/Mac, support python2.7 and python3.5+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Usage:
$ ./pip_conf.py # default to tencent source
Or:
$ python pip_conf.py tx
$ python pip_conf.py huawei
$ python pip_conf.py aliyun
$ python pip_conf.py douban
$ python pip_conf.py qinghua
$ python pip_conf.py /simple # conf with full url
$ python pip_conf.py --list # show choices
$ python pip_conf.py --poetry # set mirrors in poetry's
$ python pip_conf.py --pdm # set for pdm
$ python pip_conf.py --uv # set mirror for uv
$ python pip_conf.py --tool=auto # find out manage tool at current directory and set mirror for it
$ sudo python pip_conf.py --etc # Set conf to /etc/pip.[conf|ini]
If there is any bug or feature request, report it to:
/waketzheng/carstino/issues
"""
__author__ = "waketzheng@"
__updated_at__ = "2025.04.01"
__version__ = "0.7.0"
import contextlib
import functools
import os
import platform
import pprint
import re
import socket
import subprocess
import sys
try:
import typing
except ImportError:
pass
else:
if typing.TYPE_CHECKING:
from typing import Optional # NOQA:F401
"""
A sample of the /:
[global]
index-url = /pypi/simple/
trusted-host =
"""
TEMPLATE = """
[global]
index-url = {}
trusted-host = {}
""".lstrip()
DEFAULT = "tx"
SOURCES = {
"ali": "/pypi",
"db": "",
"qh": "",
"tx": "/pypi",
"tx_ecs": "/pypi",
"hw": "/repository/pypi",
"ali_ecs": "/pypi",
"pypi": "",
}
SOURCES["tencent"] = SOURCES["tengxun"] = SOURCES["tx"]
SOURCES["aliyun"] = SOURCES["ali"]
SOURCES["douban"] = SOURCES["db"]
SOURCES["qinghua"] = SOURCES["qh"]
SOURCES["huawei"] = SOURCES["hw"]
SOURCES["hw_inner"] = SOURCES["hw_ecs"] = (
SOURCES["hw"]
.replace("cloud", "")
.replace("/repository", "")
.replace("repo", "")
)
CONF_PIP = "pip config set -url "
INDEX_URL = "https://{}/simple/"
(5)
class ParamError(Exception):
"""Invalid parameters"""
class System:
_system = None # type: Optional[str]
@classmethod
def get_system(cls):
# type: () -> str
system = cls._system
if system is None:
system = cls._system = ()
return system
@classmethod
def is_win(cls):
# type: () -> bool
return cls.get_system() == "Windows"
@classmethod
def is_mac(cls):
# type: () -> bool
return cls.get_system() == "Darwin"
def is_mac():
# type: () -> bool
return System.is_mac()
def check_mirror_by_pip_download(domain, tmp=False):
# type: (str, bool) -> bool
if "/" not in domain:
domain = "http://{0}/pypi/simple/ --trusted-host {0}".format(domain)
elif "https:" not in domain:
if not ("http"):
domain = "http://" + domain
if ("pypi"):
domain += "/simple/"
domain += " --trusted-host " + parse_host(domain)
elif not ("http"):
domain = (
"http://"
+ ("/")
+ " --trusted-host "
+ ensure_domain_name(domain)
)
cmd = "python -m pip download -i {} --isolated six".format(domain)
if tmp:
cmd += " -d /tmp"
if (cmd) == 0:
dirname = "/tmp" if tmp else "."
for name in (dirname):
if ("six-") and (".whl"):
((dirname, name))
return True
return False
def ensure_domain_name(host):
# type: (str) -> str
if "/" not in host:
return host
domain = ("://", 1)[-1].split("/", 1)[0]
return domain
def is_pip_ready():
# type: () -> bool
return ("python -m pip --version") == 0
def check_url_reachable(url):
# type: (str) -> bool
try:
from import urlopen as _urlopen
urlopen = (_urlopen, timeout=5)
except ImportError:
from urllib import urlopen as _urlopen # type:ignore
class Response(object):
def __init__(self, status):
= status
@
def urlopen(url): # type:ignore
f = _urlopen(url)
r = ()
status = 200 if r else 400
yield Response(status)
try:
with urlopen(url) as response:
if == 200:
return True
except Exception as e:
print(e)
pass
return False
def build_mirror_url(host):
# type: (str) -> str
if ("http"):
url = host
elif "/" not in host:
url = "http://" + host
if "pypi" not in host:
url += "/pypi"
url += "/simple/"
else:
url = "http://" + host
if "simple" not in host:
url += ("/") + "/simple/"
return url
def is_pingable(host="", is_windows=False, domain=""):
# type: (str, bool,str) -> bool
host = host or domain
if is_windows:
# 2024.12.23 Windows may need administrator to run `ping -c 1 xxx`
# So use `pip download ...` instead.
if is_pip_ready():
return check_mirror_by_pip_download(host)
else:
url = build_mirror_url(host)
return check_url_reachable(url)
domain = ensure_domain_name(host)
try:
(domain)
except Exception:
return False
else:
if is_pip_ready():
return check_mirror_by_pip_download(host, tmp=True)
return check_url_reachable(build_mirror_url(host))
# Cost about 11 seconds to ping
cmd = "ping -c 1 {}".format(domain)
return (cmd) == 0
def load_bool(name):
# type: (str) -> bool
v = (name)
if not v:
return False
return () in ("1", "yes", "on", "true", "y")
def is_tx_cloud_server(is_windows=False):
# type: (bool) -> bool
return is_pingable(SOURCES["tx_ecs"], is_windows=is_windows)
def is_ali_cloud_server(is_windows=False):
# type: (bool) -> bool
return is_pingable(SOURCES["ali_ecs"], is_windows=is_windows)
def is_hw_inner(is_windows=False):
# type: (bool) -> bool
return is_pingable(SOURCES["hw_inner"], is_windows=is_windows)
def parse_host(url):
# type: (str) -> str
return ("://", 1)[-1].split("/", 1)[0]
def run_and_echo(cmd, dry=False):
# type: (str, bool) -> int
print("--> " + cmd)
()
if dry:
return 1
return (cmd)
def capture_output(cmd):
# type: (str) -> str
try:
r = (cmd, shell=True, capture_output=True)
except (TypeError, AttributeError): # For python<=3.6
with (cmd) as p:
return ().strip()
else:
return ().strip()
def config_by_cmd(url, is_windows=False):
# type: (str, bool) -> None
if not is_windows and is_mac():
# MacOS need sudo to avoid PermissionError
_config_by_cmd(url, sudo=True)
else:
_config_by_cmd(url, is_windows=is_windows)
def _config_by_cmd(url, sudo=False, is_windows=False):
# type: (str, bool, bool) -> int
cmd = CONF_PIP + url
if not ("https"):
print("cmd = {}".format(repr(cmd)))
host = parse_host(url)
if host in SOURCES["hw_inner"]:
extra_host = ("mirrors", "socapi").replace("tools", "cloudbu")
try:
(ensure_domain_name(extra_host))
except :
print("Ignore {} as it's not pingable".format(extra_host))
else:
extra_index = SOURCES["hw_inner"].replace(host, extra_host)
extra_index_url = INDEX_URL.replace("https", "http").format(extra_index)
cmd += " && pip config set -index-url " + extra_index_url
host = '"{host} {extra_host}"'.format(host=host, extra_host=extra_host)
cmd += " && pip config set -host " + host
if sudo:
cmd = " && ".join("sudo " + () for i in ("&&"))
return run_and_echo(cmd, dry="--dry" in )
def smart_detect(source, is_windows):
# type: (str, bool) -> tuple[str, bool]
if is_windows:
if is_hw_inner(True):
return "hw_inner", True
elif not is_mac():
mirror_map = {
"huawei": (is_hw_inner, "hw_inner"),
"tencent": (is_tx_cloud_server, "tx_ecs"),
"aliyun": (is_ali_cloud_server, "ali_ecs"),
}
mirrors = []
welcome_file = "/etc/motd"
if (welcome_file):
with open(welcome_file) as f:
msg = ().strip()
else:
msg = capture_output("python -m pip config list")
if msg:
mirrors = [v for k, v in mirror_map.items() if k in ()]
if not mirrors:
mirrors = list(mirror_map.values())
for detect_func, source_name in mirrors:
if detect_func(False):
return source_name, True
return source, False
def detect_inner_net(source, verbose=False, is_windows=False):
# type: (str, bool, bool) -> str
args = [1:]
inner = False
if not args or all(("-") for i in args):
source, inner = smart_detect(source, is_windows=is_windows)
elif source in ("huawei", "hw"):
if is_hw_inner(is_windows):
source = "hw_inner"
inner = True
elif "ali" in source:
if is_ali_cloud_server(is_windows):
source = "ali_ecs"
inner = True
elif "tx" in source or "ten" in source:
inner = is_tx_cloud_server(is_windows)
source = "tx_ecs" if inner else "tx"
if verbose and inner:
print("Use {} as it's pingable".format(source))
return source
def build_index_url(source, force, verbose=False, strict=False, is_windows=False):
# type: (str, bool, bool, bool, bool) -> str
if ("http"):
return source
if not force:
source = detect_inner_net(source, verbose, is_windows=is_windows)
if strict:
try:
host = SOURCES[source]
except KeyError:
raise ParamError( # noqa: B904
"Unknown source: {}\nAvailables: {}".format(repr(source), list(SOURCES))
)
else:
host = (source, SOURCES[DEFAULT])
url = INDEX_URL.format(host)
if source in ("hw_inner", "hw_ecs", "tx_ecs", "ali_ecs"):
url = ("https", "http")
return url
def get_parent_path(path):
# type: (str) -> str
return (("/").rstrip("\\"))
def get_conf_path(is_windows, at_etc):
# type: (bool, bool) -> str
if is_windows:
_pip_conf = ("pip", "")
conf_file = (("~"), *_pip_conf)
else:
if at_etc:
conf_file = ("/etc", "")
else:
_pip_conf = (".pip", "")
conf_file = (("~"), *_pip_conf)
parent = get_parent_path(conf_file)
if not (parent):
(parent)
return conf_file
class PdmMirror:
@staticmethod
def set(url):
# type: (str) -> int
cmd = "pdm config " + url
if ("http:"):
cmd += " && pdm config pypi.verify_ssl false"
return run_and_echo(cmd, dry="--dry" in )
class Mirror:
def __init__(self, url, is_windows, replace):
# type: (str, bool, bool) -> None
= url
self.is_windows = is_windows
= replace
self._version = ""
@property
def tool(self):
# type: () -> str
return self.__class__.__name__.replace("Mirror", "").lower()
def prompt_y(self, filename, content):
print("The {}'s {} content:".format(, filename))
print(content)
print('If you want to replace it, rerun with "-y" in args.')
print("Exit!")
def check_installed(self):
# type: () -> Optional[bool]
if == "poetry":
not_install = run_and_echo( + " check --quiet") > 256
else:
not_install = run_and_echo( + " --version") != 0
if not_install:
msg = (
"{0} not found!\n"
"You can install it by:\n"
" pip install --user --upgrade pipx\n"
" pipx install {0}\n"
)
print(())
return True
class UvMirror(Mirror):
def build_content(self, url=None):
# type: (Optional[str]) -> str
if url is None:
url =
text = 'index-url="{}"'.format(url)
if not ("https"):
text += '\nallow-insecure-host=["{}"]'.format(parse_host(url))
return text
def set(self):
# type: () -> Optional[int]
filename = ""
dirpath = self.get_dirpath(self.is_windows, , filename)
if not dirpath:
return 1
config_toml_path = (dirpath, filename)
text = self.build_content()
if (config_toml_path):
with open(config_toml_path) as f:
content = ().strip()
if text in content:
print("uv mirror set as expected. Skip!")
return None
if "index-url" in content:
pattern = r'index-url\s*=\s*"([^"]*)"'
m = (pattern, content, )
if m:
already = (1)
if not :
self.prompt_y(filename, ())
return None
if ("https"):
text = (already, )
elif not (dirpath):
parent = get_parent_path(dirpath)
if not (parent):
(parent)
(dirpath)
do_write(config_toml_path, text)
def _get_dirpath(self, is_windows, filename, is_etc=False):
# type: (bool, str, bool) -> str
if is_etc:
parent = (
(["SYSTEMDRIVE"], "ProgramData")
if is_windows
else ("XDG_CONFIG_DIRS", "/etc")
)
else:
default = "~/.config"
env_key = "APPDATA" if is_windows else "XDG_CONFIG_HOME"
parent = ((env_key, default))
return (parent, "uv")
def get_dirpath(self, is_windows, url, filename):
# type: (bool, str, str) -> Optional[str]
if self.check_installed():
return None
return self._get_dirpath(is_windows, filename)
class PoetryMirror(Mirror):
plugin_name = "poetry-plugin-pypi-mirror"
extra_plugins = [ # You can set PIP_CONF_NO_EXTRA_POETRY_PLUGINS=1 to skip install extra plugins
"poetry-dotenv-plugin",
"poetry-plugin-i",
"poetry-plugin-version",
]
def fix_poetry_v1_6_error(self, version):
# type: (str) -> None
if version >= "1.6":
self.fix_v1_6_error()
@classmethod
def fix_v1_6_error(cls):
# type: () -> None
pipx_envs = capture_output("pipx environment")
env_key = "PIPX_LOCAL_VENVS"
key = "PIPX_HOME"
module = cls.plugin_name.replace("-", "_")
filename = ""
if env_key in pipx_envs:
path = pipx_envs.split(env_key + "=")[1].splitlines()[0]
lib = (path, "poetry/lib")
ds = (lib)
file = (lib, ds[0], "site-packages", module, filename)
elif key in pipx_envs:
path = pipx_envs.split(key + "=")[1].splitlines()[0]
lib = (path, "venvs/poetry/lib")
ds = (lib)
file = (lib, ds[0], "site-packages", module, filename)
else:
code = "import {} as m;print(m.__file__)".format(module)
path = capture_output("python -c {}".format(repr(code)))
file = (get_parent_path(path), filename)
if not (file):
print("WARNING: plugin file not found {}".format(file))
return
s = "semver"
with open(file) as f:
text = ()
if s in text:
text = (s, "constraints")
with open(file, "w") as f:
(text)
print("pypi mirror plugin error fixed.")
@property
def poetry_version(self):
# type: () -> str
if not self._version:
self._version = self.get_poetry_version()
self.fix_poetry_v1_6_error(self._version)
return self._version
@staticmethod
def get_poetry_version():
# type: () -> str
v = capture_output("poetry --version")
return ("Poetry (version ", "")
@staticmethod
def unset():
# type: () -> None
print("By pipx:\n", "pipx runpip poetry uninstall <plugin-name>")
print("By poetry self:\n", "poetry self remove <plugin-name>")
def set_self_pypi_mirror(self, is_windows, url):
# type: (bool, str) -> None
config_path = self._get_dirpath(is_windows)
if not ((config_path, "")):
try:
from .self_command import SelfCommand
except ImportError:
pass
else:
SelfCommand().generate_system_pyproject()
cmds = [
"cd {}".format(config_path),
"poetry source add -v --priority=default pypi_mirror {}".format(url),
]
run_and_echo(" && ".join(cmds), dry="--dry" in )
def get_dirpath(self, is_windows, url):
# type: (bool, str) -> Optional[str]
if self.check_installed():
return None
plugins = capture_output("poetry self show plugins")
mirror_plugin = self.plugin_name
if mirror_plugin not in plugins:
if run_and_echo("pipx --version") == 0:
install_plugin = "pipx inject poetry "
else:
self.set_self_pypi_mirror(is_windows, url)
install_plugin = "poetry self add "
dry = "--dry" in
if run_and_echo(install_plugin + mirror_plugin, dry=dry) != 0:
print("Failed to install plugin: {}".format(repr(mirror_plugin)))
return None
if not load_bool("PIP_CONF_NO_EXTRA_POETRY_PLUGINS"):
cmd = install_plugin + " ".join(self.extra_plugins)
run_and_echo(cmd, dry=dry)
return self._get_dirpath(is_windows)
def _get_dirpath(self, is_windows):
# type: (bool) -> str
dirpath = "~/Library/Application Support/pypoetry/"
if is_windows:
dirpath = ("APPDATA", "") + "/pypoetry/"
elif not is_mac():
dirpath = "~/.config/pypoetry/"
elif self.poetry_version < "1.5":
dirpath = "~/Library/Preferences/pypoetry/"
return (dirpath)
def set(self):
# type: () -> Optional[int]
filename = ""
dirpath = self.get_dirpath(self.is_windows, )
if not dirpath:
return 1
config_toml_path = (dirpath, filename)
item = "[plugins.pypi_mirror]"
text = item + '{}url = "{}"'.format(, )
if (config_toml_path):
with open(config_toml_path) as f:
content = ().strip()
if text in content:
print("poetry mirror set as expected. Skip!")
return None
if item in content:
pattern = r'\[plugins\.pypi_mirror\].url = "([^"]*)"'
m = (pattern, content, )
if m:
already = (1)
if not :
self.prompt_y(filename, ())
return None
text = (already, )
else:
text = content + + text
elif not (dirpath):
parent = get_parent_path(dirpath)
if not (parent):
(parent)
(dirpath)
do_write(config_toml_path, text)
def init_pip_conf(
url,
replace=False,
at_etc=False,
write=False,
poetry=False,
pdm=False,
verbose=False,
uv=False,
is_windows=False,
):
# type: (str, bool, bool, bool, bool, bool, bool, bool, bool) -> Optional[int]
if poetry:
return PoetryMirror(url, is_windows, replace).set()
poetry_set_env = "SET_POETRY"
if load_bool(poetry_set_env):
env_name = "PIP_CONF_POETRY_MIRROR"
v = (env_name)
if v:
tip = "Poetry mirror source will be set to {}"
print((repr(v)))
url = build_index_url(
v, force=True, verbose=True, strict=True, is_windows=is_windows
)
replace = True
elif verbose:
tip = "Going to configure poetry mirror source, because {} was set to {}"
print((repr(poetry_set_env), (poetry_set_env)))
return PoetryMirror(url, is_windows, replace).set()
if pdm or load_bool("PIP_CONF_SET_PDM"):
return (url)
if uv or load_bool("PIP_CONF_SET_UV"):
return UvMirror(url, is_windows, replace).set()
if not write and (not at_etc or is_windows) and can_set_global():
config_by_cmd(url, is_windows)
return None
text = (url, parse_host(url))
conf_file = get_conf_path(is_windows, at_etc)
if (conf_file):
with open(conf_file) as fp:
s = ()
if text in s:
print("Pip source already be configured as expected.\nSkip!")
return None
if not replace:
print("The pip file {} exists! content:".format(conf_file))
print(s)
print('If you want to replace it, rerun with "-y" in args.\nExit!')
return None
do_write(conf_file, text)
def do_write(conf_file, text):
# type: (str, str) -> None
with open(conf_file, "w") as fp:
(text + "\n")
print("Write lines to `{}` as below:\n{}\n".format(conf_file, text))
print("Done.")
def can_set_global():
# type: () -> bool
s = capture_output("pip --version")
m = (r"^pip (\d+)\.(\d+)", s)
if not m:
return False
return [int(i) for i in ()] >= [10, 1]
def read_lines(filename):
# type: (str) -> list[str]
with open(filename) as f:
s = ()
return ()
def auto_detect_tool(args):
if == "pip":
return args
if in ("uv", "poetry", "pdm"):
setattr(args, , True)
elif != "auto":
raise ValueError("Unknown tool: " + repr())
else:
files = (".")
pyproject = ""
locks = {"", "", ""} - set(files)
if len(locks) == 1:
tool = list(locks)[0].split(".")[0]
elif pyproject not in files:
return args # Same as == 'pip'
else:
for line in read_lines(pyproject):
if not line:
continue
m = (r"\[\[?tool\.(uv|pdm|poetry)[.\]]", line)
if m:
tool = (1)
break
setattr(args, tool, True)
return args
def main():
# type: () -> Optional[int]
from argparse import ArgumentParser
parser = ArgumentParser()
source_help = "the source of pip, ali/douban/hw/qinghua or tx(default)"
parser.add_argument("name", nargs="?", default="", help=source_help)
parser.add_argument("files", nargs="*", default="", help="Add for pre-commit")
# Be compatible with old version
parser.add_argument("-s", "--source", default=DEFAULT, help=source_help)
parser.add_argument(
"-l", "--list", action="store_true", help="Show available mirrors"
)
parser.add_argument("-y", action="store_true", help="whether replace existing file")
parser.add_argument("--etc", action="store_true", help="Set conf file to /etc")
parser.add_argument("-f", action="store_true", help="Force to skip ecs cloud check")
parser.add_argument("--write", action="store_true", help="Conf by write file")
parser.add_argument("--poetry", action="store_true", help="Set mirrors for poetry")
parser.add_argument("--pdm", action="store_true", help="Set for pdm")
parser.add_argument("--uv", action="store_true", help="Set index url for uv")
parser.add_argument(
"-t", "--tool", default="auto", help="Choices: pip/uv/pdm/poetry"
)
parser.add_argument("--url", action="store_true", help="Show mirrors url")
parser.add_argument(
"--dry",
action="store_true",
help="Display cmd command without actually executing",
)
parser.add_argument("--verbose", action="store_true", help="Print more info")
parser.add_argument(
"--fix", action="store_true", help="Fix poetry pypi mirror plugin error"
)
if not [1:]:
# In case of runing by curl result, try to get args from ENV
env = ("PIP_CONF_ARGS")
if env:
(())
args = parser.parse_args()
if :
print("There are several mirrors that can be used for pip/poetry:")
(SOURCES)
elif :
PoetryMirror.fix_v1_6_error()
else:
source = or
is_windows = System.is_win()
url = build_index_url(source, , , is_windows=is_windows)
if : # Only display prefer source url, but not config
print(url)
return None
if not and not and not :
args = auto_detect_tool(args)
if init_pip_conf(
url,
replace=,
at_etc=,
write=,
poetry=,
pdm=,
verbose=,
uv=,
is_windows=is_windows,
):
return 1
if __name__ == "__main__":
if "--url" not in :
try:
from asynctor import timeit
except (ImportError, SyntaxError, AttributeError):
pass
else:
main = timeit(main)
(main())