考虑使用一个包含(未版本控制的)依赖项(python 包)列表的 python
requirements.txt
文件。安装它们后(例如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
选项,其中:
使用给定的约束文件约束版本
但我猜在这种情况下我已经必须拥有日期约束版本了?
从你的问题来看,如果我猜对了,你想使用以下命令安装依赖项:
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
我找到了一个似乎可以满足您需求的工具(仍处于测试阶段): https://pypi.org/project/pypi-timemachine/
正如我从其自述文件中读到的那样,它创建了一个使用日期过滤器的 pypi.org 代理。
好吧,一个可能的答案(尽管不是一个很好的答案)是手动浏览
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
),提取所有版本信息(包括日期),对其进行排序并选择正确的。
uv
的巧妙解决方案。
您可以使用 pip install uv
安装它。
假设您想要安装软件包
rvc3python
,其依赖项不晚于 2023 年 11 月 13 日。
就像运行这个一样简单:
uv pip install --exclude-newer 2023-11-13T10:00:00Z rvc3python
如果您有需求文件,请查看
uv pip install -r ...
。