如何获取在特定日期之前发布的 python 个软件包的版本号列表?

How to get a list of version numbers for python packages released up until a specific date?

考虑有一个 python requirements.txt 文件,其中包含(未版本化的)依赖项列表(python 包)。安装它们后(例如 pip install -r requirements.txt),您可以调用 pip freeze 并获得所有已安装 python 软件包的( 版本)列表。

这将是当时可用的 python 软件包版本(及其依赖项)的快照。我需要生成的是同一个列表,但是是过去的日期(假设 2018-06-12)。

我想从技术上讲,我只需要找到 requirements.txt 文件中包含的所有包的发布版本。

理想情况下,会有一个命令 pip install -r requirements.txt --before 2018-06-21,然后只需调用 pip freeze,但我在 pip install --help 中没有看到类似的东西。我确实看到了一种指定另一个 --index-url 的方法,我可以想象如果从那个日期开始有一个 archived 索引,我可以指向 pip 并且它应该工作?

还有一个--constraint选项,即:

Constrain versions using the given constraints file

但我猜在那种情况下我已经必须有日期限制版本了?

好吧,一个可能的答案(虽然不是很好)是手动检查 requirements.txt 中的每个依赖项,在 https://pypi.org and then visit the release history (e.g. https://pypi.org/project/requests/#history). From there it's easy enough to see which version had been released at what date (e.g. https://pypi.org/project/requests/2.19.0/ 上查找 requests 的包,当包括 2018-06-12) 然后将其用作版本 (requests==2.19.0).

更好的答案可能是以编程方式从 pypi 中提取该信息(可能通过 curl),提取所有版本信息(包括日期),对其进行排序并选择正确的。

根据你的问题,如果我没猜错的话,你想使用以下命令安装依赖项:

pip install -r requirements.txt --before 2018-06-21

这需要修补 pip 本身,以便添加 --before 选项来提供目标日期。

它下面的代码是第二好的东西。目前它是一个粗略的草图,但它几乎可以满足您的需求,而不是生成 requirements.txt,它会将最新版本的包输出到控制台,直到提供的日期,格式为:

$ pipenv run python <script_name>.py django click --before 2018-06-21
pip install django==2.0.6 click==6.7

这与您的想法并不完全相同,但非常接近。随意根据您的需要更改它,通过添加(或不添加)-r 选项并在新行上输出每个依赖项,然后通过重定向输出,它看起来像这样:

$ pipenv run python <script_name>.py django click --before 2018-06-21 >> requirements.txt

代码(或者直接使用link到gist):

import sys
import requests
from bs4 import BeautifulSoup
from datetime import datetime
import click

PYPI_URL = "https://pypi.org/project/{project_name}/#history"

def get_releases(request):

    soup = BeautifulSoup(request, 'html.parser')
    releases = list()

    for release in soup.find_all('div', class_='release'):
        release_version = release.find('p', class_='release__version').text.strip()
        if not is_numeric(release_version):
            continue
        release_date = try_parsing_date(release.find('time').text.strip())
        releases.append({'version': release_version, 'date': release_date})

    sorted_packages = sorted(releases, key=lambda s: list(map(int, s['version'].split('.'))))

    return sorted_packages


def is_numeric(s):
    for char in s:
        if not char.isdigit() and char not in [" ", ".", ","]:
            return False

    return True


def try_parsing_date(text):
    for fmt in ('%d.%m.%Y', '%d/%m/%Y', '%b %d, %Y', '%Y-%m-%d'):
        try:
            return datetime.strptime(text, fmt)
        except ValueError:
            pass
    click.echo('Not valid date format. Try to use one of this: <31.12.2018>, <31/12/2019> or <2018-12-31>')
    sys.exit(0)


@click.command(context_settings=dict(help_option_names=['-h', '--help']))
@click.option('-b', '--before', help='Get latest package before specified date')
@click.argument('packages', nargs=-1, type=click.UNPROCESSED)
def cli(before, packages):
    target_date = try_parsing_date(before) if before else datetime.today()

    required_packages = list()
    not_found = list()

    for package in packages:
        project_url = PYPI_URL.format(project_name=package)
        r = requests.get(project_url)
        if r.status_code is not 200:
            not_found.append(package)
            continue

        releases = get_releases(r.text)
        last_release = None
        for idx, release in enumerate(releases):
            release_date = release['date']
            if release_date > target_date:
                if last_release and last_release['date'] <= release_date:
                    continue
            last_release = release

        required_packages.append({'package': package,
                                  'release_date': last_release['date'],
                                  'release_version': last_release['version']})


    print('pip install ' + ' '.join('{}=={}'.format(p['package'], str(p['release_version'])) for p in required_packages))
    if len(not_found) > 0:
        print('\nCould not find the following packages: {}'.format(' '.join(p for p in not_found)))

if __name__ == '__main__':
    cli()

所需的依赖项(Python3):

beautifulsoup4==4.7.1
Click==7.0
requests==2.21.0

我找到了一个似乎可以满足您需求的工具(仍处于测试阶段): https://pypi.org/project/pypi-timemachine/

正如我从它的自述文件中读到的,它创建了一个 pypi.org 的代理,它使用了日期过滤器。