【问题标题】:How to get a list of version numbers for python packages released up until a specific date?如何获取在特定日期之前发布的 python 包的版本号列表?
【发布时间】:2019-01-17 06:37:24
【问题描述】:

考虑有一个 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 的方法,我可以想象如果从那个日期起有一个 归档 索引,我可以将 pip 指向它,它应该可以工作吗?

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

使用给定的约束文件约束版本

但我猜在这种情况下我已经必须拥有日期约束版本?

【问题讨论】:

    标签: python pip dependencies python-packaging


    【解决方案1】:

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

    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
    

    代码(或只使用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
    

    【讨论】:

    • 谢谢,我还没试过这个脚本,但我希望它对有同样问题的人有所帮助。
    • 使用 PyPI API 应该可以提供更简洁的解决方案,例如 pypi.org/pypi/numpy/json
    【解决方案2】:

    好的,一个可能的答案(虽然不是一个很好的答案)是手动检查requirements.txt 中的每个依赖项,在https://pypi.org 上查看该包,然后访问发布历史记录(例如https://pypi.org/project/requests/#history)。从那里可以很容易地查看哪个版本是在什么日期发布的(例如,https://pypi.org/project/requests/2.19.0/ 代表 requests,包括 2018-06-12),然后将其用作版本 (requests==2.19.0)。

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

    【讨论】:

      【解决方案3】:

      我找到了一个似乎可以满足您需求的工具(仍然是 alpha 版本): https://pypi.org/project/pypi-timemachine/

      当我从它的 README 中阅读时,它创建了一个使用日期过滤器的 pypi.org 代理。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-03-30
        • 1970-01-01
        • 2021-09-20
        • 1970-01-01
        • 1970-01-01
        • 2015-05-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多