# -*- encoding: utf-8 -*-
# @Author: SWHL
# @Contact: liekkaskono@163.com
import argparse
import re
import subprocess
from typing import Dict, List, Optional, Tuple, Union
import requests
from html5lib.html5parser import parse
REQUESTS_TIMEOUT = 15
[docs]class GetPyPiLatestVersion():
"""Get the latest version of the specified python package name in the pypi.
"""
[docs] def __init__(self, url: str = "https://pypi.org/",) -> None:
self._base_url = url
self.pip_versions = []
[docs] def __call__(self, package_name: str,
return_all_versions: bool = False) -> Union[str, Tuple[str, List]]:
"""_summary_
Args:
package_name (str): The name of the package you want to get the latest version.
return_all_versions (bool, optional): Whether to return all release versions. Default is :code:`False` .
Raises:
ValueError: The exception is thrown when no valid package information is found.
Returns:
Union[str, Tuple[str, List]]: the latest version. When :code:`return_all_versions=True` , Return Tuple: :code:`[latest_version_str, all_release_list]`
"""
try:
all_versions_web = self.get_by_spider_web(package_name)
except ValueError as e:
raise e
latest_ver_web = all_versions_web[0]
latest_ver_pip = self.get_by_pip_index(package_name)
latest_ver = latest_ver_web or latest_ver_pip or ''
if return_all_versions:
return latest_ver, all_versions_web
return latest_ver
[docs] def get_by_spider_web(self, package_name: str) -> str:
search = {"q": package_name}
response = requests.session().get(
self._base_url + "search", params=search, timeout=REQUESTS_TIMEOUT
)
content = parse(response.content, namespaceHTMLElements=False)
search_packages = content.findall(".//*[@class='package-snippet']")
package_info = {}
for result in search_packages:
name_element = result.find("h3/*[@class='package-snippet__name']")
version_element = result.find(
"h3/*[@class='package-snippet__version']")
package_href = result.attrib.get('href')
if (
name_element is None
or version_element is None
or not name_element.text
or not version_element.text
):
continue
name = name_element.text
version = version_element.text
if name == package_name.replace('_', '-'):
if package_href:
package_info['href'] = self._base_url + package_href
package_info['latest'] = version
break
if not package_info:
raise ValueError(f'No version information of {package_name}. '
'Please check if the package name correct.')
all_versions = self.get_all_release_versions(package_info)
return all_versions
[docs] def get_all_release_versions(self, package_info: Dict):
history_url = package_info['href'] + '#history'
response = requests.session().get(history_url,
timeout=REQUESTS_TIMEOUT)
content = parse(response.content, namespaceHTMLElements=False)
a_elements = content.findall(
'.//*[@class="release"]/a[@class="card release__card"]')
a_elemnets_old = content.findall(
'.//*[@class="release release--oldest"]/a[@class="card release__card"]')
a_elements.extend(a_elemnets_old)
release_list = [package_info['latest']]
for a_ele in a_elements:
p_ele = a_ele.find('p[@class="release__version"]')
# 查看是否是yanked 版本
yanked_ele = p_ele.find('span[@class="badge badge--danger"]')
if yanked_ele is not None:
# 有值,则说明是yanked
continue
cur_version = [v.strip() for v in p_ele.text.split('\n')
if v.strip()][0]
release_list.append(cur_version)
return release_list
[docs] def get_by_pip_index(self, package_name: str) -> List:
output = subprocess.run(["pip", "index", "versions", package_name],
capture_output=True)
output = output.stdout.decode('utf-8')
if output:
return self.extract_version(output)
return ''
[docs] @staticmethod
def version_add_one(version: Optional[str], add_loc: int = -1) -> str:
"""Add one for direct version.
Args:
version (Optional[str]): current version num
add_loc (int, optional): Where to add one from the back to front. Default is -1. e.g. 4.0.7, when `add_loc=-1` → 4.0.8, `add_loc=-2` → 4.1.0, `add_loc=-3` → 5.0.0.
Returns:
str: the version after adding one.
"""
if not version:
return '1.0.0'
version_list = version.split('.')
if abs(add_loc) > len(version_list):
raise ValueError(
f'add_loc must be between [-{len(version_list)}, -1]. But now add_loc is {add_loc}.')
add_one_ver_num = str(int(version_list[add_loc]) + 1)
if add_loc != -1:
version_list[add_loc+1:] = ['0'] * len(version_list[add_loc+1:])
version_list[add_loc] = add_one_ver_num
new_version = '.'.join(version_list)
return new_version
[docs]def main():
parser = argparse.ArgumentParser()
parser.add_argument('package_name', type=str,
help='The specified python package name. e.g. opencv-python.')
parser.add_argument('-a', '--all_versions',
default=False, action="store_true",
help="Whether to return all release versions. Default is False.")
args = parser.parse_args()
obtainer = GetPyPiLatestVersion()
latest_version = obtainer(args.package_name, args.all_versions)
if args.all_versions:
print(latest_version[0])
print(latest_version[1])
else:
print(latest_version)
if __name__ == '__main__':
main()