├── MANIFEST.in ├── .gitignore ├── psm ├── README.md ├── Readme_CN.md ├── LICENSE ├── setup.py └── psm.py /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by venv; see https://docs.python.org/3/library/venv.html 2 | * 3 | -------------------------------------------------------------------------------- /psm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # -*- coding: utf-8 -*- 4 | import re 5 | import sys 6 | 7 | from psm import main 8 | 9 | if __name__ == '__main__': 10 | sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) 11 | sys.exit(main()) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # psm 2 | 3 | Pypi Source Manager: fast switch between different Pypi Source: pypi, double, aliyun, tsu,. 4 | 5 | 6 | This package is inspired by [Pana/nrm](https://github.com/Pana/nrm). 7 | 8 | ## [中文文档](Readme_CN.md) 9 | 10 | ## Installation 11 | 12 | ``` 13 | pip install psm 14 | ``` 15 | 16 | 17 | ## Usage 18 | 19 | ### Unix 20 | 21 | ##### list all pypi source 22 | 23 | ``` 24 | psm ls 25 | ``` 26 | 27 | ##### change pypi source 28 | 29 | ``` 30 | psm use douban 31 | ``` 32 | 33 | or 'aliyun' and 'thu' 34 | 35 | ##### show current source 36 | 37 | ``` 38 | psm current 39 | ``` 40 | 41 | ##### add a new source 42 | 43 | ``` 44 | psm add my_source https://pypi.python.org/simple 45 | ``` 46 | 47 | ##### delete a source 48 | 49 | ``` 50 | psm delete my_source 51 | ``` 52 | 53 | ### Windows 54 | 55 | ##### list all pypi source 56 | 57 | ``` 58 | python -m psm ls 59 | ``` 60 | 61 | ## LICENSE 62 | 63 | [MIT](LICENSE) 64 | -------------------------------------------------------------------------------- /Readme_CN.md: -------------------------------------------------------------------------------- 1 | # Python 笔记九:打造pypi换源加速神器psm 2 | 3 | > 源码github地址在此,记得点星: 4 | https://github.com/brandonxiang/psm 5 | 6 | 其实这是一个很简单的repo,是参考了[使用国内镜像源来加速python pypi包的安装](http://topmanopensource.iteye.com/blog/2004853),对pypi下载源的配置文件进行修改。该repo受到nodejs的[nrm](https://github.com/Pana/nrm)的启发。 7 | 8 | pypi换源加速神器,又名psm(即pypi source manager)。如今,包含了douban和aliyun的pypi同步源,我觉得这两个大网站的资源已经很够用了。如果你想加更多的第三方源,请给我发issue或者pr。 9 | 10 | ## 安装 11 | 12 | ``` 13 | pip install psm 14 | ``` 15 | 16 | ## 使用 17 | 18 | ### Unix 系 19 | 20 | ##### 列出所有源 21 | 22 | ``` 23 | psm ls 24 | ``` 25 | 26 | ##### 更换pypi源 27 | 28 | ``` 29 | psm use douban 30 | ``` 31 | 32 | ##### 显示当前源 33 | 34 | ``` 35 | psm current 36 | ``` 37 | 38 | ##### 添加当前源 39 | 40 | ``` 41 | psm add my_source https://pypi.python.org/simple 42 | ``` 43 | 44 | ##### 删除当前源 45 | 46 | ``` 47 | psm delete my_source 48 | ``` 49 | 50 | ### Windows 系 51 | 52 | ##### 列出所有源 53 | 54 | ``` 55 | python -m psm ls 56 | ``` 57 | 58 | ### 路书 59 | 60 | 现在支持linux平台,windows,希望在windows系统的兼容方面要进一步提升。 61 | 62 | 如果大家想加源或者修复一些bug,可以随时issue或者pr。 63 | 64 | 转载,请表明出处。[总目录Awesome GIS](http://www.jianshu.com/p/3b3efa92dd6d) 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 brandonxiang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import os 3 | import sys 4 | 5 | from setuptools import setup, find_packages 6 | 7 | def read(fname): 8 | return codecs.open(os.path.join(os.path.dirname(__file__), fname)).read() 9 | 10 | 11 | 12 | NAME = "psm" 13 | PACKAGES = ["psm"] 14 | DESCRIPTION = "Pypi Source Manager: fast switch between different Pypi Source: pypi, double, aliyun." 15 | LONG_DESCRIPTION = open('README.md').read() 16 | KEYWORDS = "python source package" 17 | AUTHOR = "brandonxiang" 18 | AUTHOR_EMAIL = "1542453460@qq.com" 19 | URL = "http://www.jianshu.com/users/64467c788eb7" 20 | VERSION = "0.3.0" 21 | LICENSE = "MIT" 22 | 23 | setup( 24 | name = NAME, 25 | version = VERSION, 26 | description = DESCRIPTION, 27 | long_description = LONG_DESCRIPTION, 28 | classifiers = [ 29 | 'License :: OSI Approved :: MIT License', 30 | 'Programming Language :: Python', 31 | 'Programming Language :: Python :: 3.5', 32 | 'Intended Audience :: Developers', 33 | 'Operating System :: OS Independent', 34 | ], 35 | python_requires='>=3.5', 36 | py_modules=['psm'], 37 | keywords = KEYWORDS, 38 | author = AUTHOR, 39 | author_email = AUTHOR_EMAIL, 40 | url = URL, 41 | license = LICENSE, 42 | install_requires=[ 43 | 'docopt>=0.6.2', 44 | 'configparser>=7.1.0' 45 | ], 46 | scripts=['psm'], 47 | # packages = PACKAGES, 48 | include_package_data=True, 49 | zip_safe=True, 50 | ) -------------------------------------------------------------------------------- /psm.py: -------------------------------------------------------------------------------- 1 | """Naval Fate. 2 | Usage: 3 | psm ls 4 | psm use 5 | psm current 6 | psm add 7 | psm delete 8 | psm test [] 9 | psm (-h | --help) 10 | psm (-v | --version) 11 | Options: 12 | -h --help Show this screen. 13 | -v --version Show version. 14 | """ 15 | 16 | from __future__ import print_function 17 | import json 18 | import os 19 | from re import A 20 | import time 21 | from typing import Dict 22 | from docopt import docopt 23 | import configparser 24 | import urllib.request 25 | 26 | 27 | PSMRC = os.path.expanduser("~/.psmrc") 28 | 29 | sources = { 30 | "pypi":"https://pypi.python.org/simple/", 31 | "douban":"http://pypi.douban.com/simple/", 32 | "aliyun":"http://mirrors.aliyun.com/pypi/simple/", 33 | "thu1":"https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/", 34 | "thu2": 'https://pypi.tuna.tsinghua.edu.cn/simple/', 35 | "ustc":"http://pypi.mirrors.ustc.edu.cn/simple/", 36 | } 37 | 38 | def list_source(): 39 | _sources = _read_config() 40 | print("\n") 41 | for key in _sources: 42 | print(key,"\t",_sources[key]) 43 | print("\n") 44 | 45 | def use(name: str): 46 | _sources = _read_config() 47 | if name not in _sources.keys(): 48 | print("\nSource name is not in the list.\n") 49 | else: 50 | _write_pip_conf(name) 51 | print("\nSource is changed to %s.\n"%(name)) 52 | 53 | def add(name: str, url: str): 54 | _sources = _read_config() 55 | if name in _sources.keys(): 56 | print("\nSource name is already in the list.\n") 57 | else: 58 | _sources[name] = url 59 | _write_config(_sources) 60 | print("\nSource is added.\n") 61 | return _sources 62 | 63 | def delete(name: str): 64 | _sources = _read_config() 65 | if name not in _sources.keys(): 66 | print("\nSource name is not in the list.\n") 67 | else: 68 | del _sources[name] 69 | _write_config(_sources) 70 | print("\nSource is deleted.\n") 71 | 72 | def test(name: str | None): 73 | print("\n") 74 | _sources = _read_config() 75 | if name is None: 76 | for key in _sources: 77 | url = _sources[key] + 'pip' 78 | res = _test_url(url, key) 79 | print(f"{res.get('name')}\t{res.get('time')}") 80 | 81 | else: 82 | url = _sources[name] + 'pip' 83 | res = _test_url(url, name) 84 | print(f"{res.get('name')}\t{res.get('time')}") 85 | print("\n") 86 | 87 | 88 | 89 | def _test_url(url: str, name: str): 90 | try: 91 | start_time = time.time() 92 | with urllib.request.urlopen(url, timeout = 5) as response: 93 | response.read() 94 | end_time = time.time() 95 | duration = end_time - start_time 96 | return {"name": name, "time": f"{duration* 1000:.2f} ms"} 97 | except Exception as e: 98 | return {"name": name, "time": "Fetch failed"} 99 | 100 | 101 | 102 | def _write_pip_conf(name: str): 103 | _sources = _read_config() 104 | path = os.path.expanduser("~/.pip/pip.conf") 105 | file = os.path.dirname(path) 106 | if not os.path.exists(file): 107 | os.mkdir(file) 108 | 109 | conf = configparser.ConfigParser() 110 | conf.read(path) 111 | if not conf.has_section('global'): 112 | conf.add_section('global') 113 | if not conf.has_section('install'): 114 | conf.add_section('install') 115 | conf.set("global", "index-url", _sources[name]) 116 | conf.set("install", "trusted-host", _sources[name].split('/')[2]) 117 | with open(path,'w') as fp: 118 | conf.write(fp) 119 | 120 | def _read_config() -> Dict[str, str]: 121 | path = os.path.expanduser(PSMRC) 122 | conf = configparser.ConfigParser() 123 | conf.read(path) 124 | if conf.has_section('global'): 125 | config = conf.get("global", "config") 126 | if config is not None: 127 | return json.loads(config) 128 | return sources 129 | 130 | def _write_config(_sources: Dict[str, str]): 131 | path = os.path.expanduser(PSMRC) 132 | conf = configparser.ConfigParser() 133 | conf.read(path) 134 | 135 | if not conf.has_section('global'): 136 | conf.add_section('global') 137 | 138 | conf.set("global", "config", json.dumps(_sources)) 139 | with open(path, 'w') as fp: 140 | conf.write(fp) 141 | 142 | def current(): 143 | conf = configparser.ConfigParser() 144 | path = os.path.expanduser("~/.pip/pip.conf") 145 | conf.read(path) 146 | index_url = conf.get("global", "index-url") 147 | _sources = _read_config() 148 | for key in _sources: 149 | if index_url == _sources[key]: 150 | print("\nCurrent source is %s"%key) 151 | print("Current url is %s\n"%index_url) 152 | break 153 | else: 154 | print("\nUnknown source\n") 155 | 156 | def main(): 157 | arguments = docopt(__doc__, version='0.3.0') 158 | 159 | if arguments['ls']: 160 | list_source() 161 | if arguments['use']: 162 | use(arguments['']) 163 | if arguments['current']: 164 | current() 165 | if arguments['add']: 166 | add(arguments[''], arguments['']) 167 | if arguments['delete']: 168 | delete(arguments['']) 169 | if arguments['test']: 170 | test(arguments['']) 171 | 172 | if __name__ == '__main__': 173 | main() 174 | --------------------------------------------------------------------------------