如何从Python包的requires.txt中读取依赖项

时间:2022-09-25 10:32:52

I need the dependencies because I want to add these to my RPM meta-data.

我需要依赖项,因为我想将这些添加到我的RPM元数据中。

To build I use:

要构建我使用:

python setup.py bdist_rpm

When I build the package cryptography-2.2.2 it creates a file /src/cryptography.egg-info/requires.txt

当我构建软件包cryptography-2.2.2时,它会创建一个文件/src/cryptography.egg-info/requires.txt

It contains:

idna>=2.1
asn1crypto>=0.21.0
six>=1.4.1

[:platform_python_implementation != 'PyPy']
cffi>=1.7

[:python_version < '3']
enum34
ipaddress

How can I read all dependencies, evaluating the expression between []?

如何读取所有依赖项,评估[]之间的表达式?

I'm using Python 2.7 (don't ask)

我正在使用Python 2.7(不要问)

I need the following output:

我需要以下输出:

idna>=2.1
asn1crypto>=0.21.0
six>=1.4.1
cffi>=1.7
enum34
ipaddress

I want to omit other sections like [doc], [test] etcetera.

我想省略其他部分,如[doc],[test] etcetera。

3 个解决方案

#1


3  

The requires.txt is part of the dependency metadata, so you can use the same tools easy_install uses when installing the egg. Assuming the file requires.txt is in the current directory:

requires.txt是依赖关系元数据的一部分,因此您可以使用easy_install在安装egg时使用的相同工具。假设文件requires.txt在当前目录中:

In [1]: from pkg_resources import Distribution, PathMetadata

In [2]: dist = Distribution(metadata=PathMetadata('.', '.'))

Now you can filter all dependencies for your current platform with Distribution.requires():

现在,您可以使用Distribution.requires()过滤当前平台的所有依赖项:

In [3]: sys.version
Out[3]: '3.6.4 (v3.6.4:d48ecebad5, Dec 18 2017, 21:07:28) \n[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)]'

In [4]: dist.requires()
Out[4]:
[Requirement.parse('idna>=2.1'),
 Requirement.parse('asn1crypto>=0.21.0'),
 Requirement.parse('six>=1.4.1'),
 Requirement.parse('cffi!=1.11.3,>=1.7'),
 Requirement.parse('cffi>=1.7')]

The list would be different if I used Python 2.7:

如果我使用Python 2.7,列表会有所不同:

In [4]: sys.version
Out[4]: '2.7.10 (default, Oct  6 2017, 22:29:07) \n[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.31)]'

In [5]: dist.requires()
Out[5]:
[Requirement.parse('idna>=2.1'),
 Requirement.parse('asn1crypto>=0.21.0'),
 Requirement.parse('six>=1.4.1'),
 Requirement.parse('cffi!=1.11.3,>=1.7'),
 Requirement.parse('cffi>=1.7'),
 Requirement.parse('enum34'),
 Requirement.parse('ipaddress')]

or PyPy:

In [2]: sys.version
Out[2]: '3.5.3 (fdd60ed87e941677e8ea11acf9f1819466521bf2, Apr 26 2018, 01:25:35)\n[PyPy 6.0.0 with GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.1)]'

In [3]: d.requires()
Out[3]:
[Requirement.parse('idna>=2.1'),
 Requirement.parse('asn1crypto>=0.21.0'),
 Requirement.parse('six>=1.4.1'),
 Requirement.parse('cffi!=1.11.3,>=1.7')]

Now, if you want to generate a list of requirement strings (like when you want to generate a requirements file for pip), convert the requirements to strings:

现在,如果要生成需求字符串列表(例如,当您要为pip生成需求文件时),请将需求转换为字符串:

In [8]: os.linesep.join(str(r) for r in dist.requires())
Out[8]:
'idna>=2.1\nasn1crypto>=0.21.0\nsix>=1.4.1\ncffi!=1.11.3,>=1.7'

PEP 508

If you also want to take PEP 508 environment markers into account independent of the current platform, things can get a bit trickier, but still manageable. First, convert the requirements with env markers:

如果您还想独立于当前平台考虑PEP 508环境标记,事情可能会变得有点棘手,但仍然可以管理。首先,使用env标记转换需求:

In [22]: dep_map_pep508 = {k: v for k, v in dist._build_dep_map().items() if k and k.startswith(':')}

In [24]: reqs_pep508 = [str(r) + ';' + k.lstrip(':') for k, v in dep_map_pep508.items() for r in v]

In [25]: reqs_pep508
Out[25]:
["cffi>=1.7;platform_python_implementation != 'PyPy'",
 "enum34;python_version >= '3'",
 "ipaddress;python_version >= '3'"]

Now handle the platform-independent deps, these house under the None key in dist's dependency map:

现在处理与平台无关的deps,它们位于dist的依赖关系图中的None键下:

In [26]: reqs_no_platform = [str(r) for r in dist._build_dep_map()[None]]

In [27]: reqs_no_platform
Out[27]: ['idna>=2.1', 'asn1crypto>=0.21.0', 'six>=1.4.1', 'cffi!=1.11.3,>=1.7']

Combine both lists to a string ready to be written to requirements file:

将两个列表组合成一个准备写入需求文件的字符串:

In [28]: os.linesep.join(reqs_no_platform + reqs_pep508)
Out[28]: "idna>=2.1\nasn1crypto>=0.21.0\nsix>=1.4.1\ncffi!=1.11.3,>=1.7\ncffi>=1.7;platform_python_implementation != 'PyPy'\nenum34;python_version >= '3'\nipaddress;python_version >= '3'"

#2


0  

So I was able to find one working solution for the same, there might be other possibilities as well, but i think this should work on most versions

所以我能够找到一个相同的工作解决方案,也可能有其他可能性,但我认为这应该适用于大多数版本

import pkg_resources

lines = open("requirements.txt").readlines()

load_packages = True
for line in lines:
    if line.strip().startswith("#"):
        continue

    if line.startswith("[:"):
        # this is marker, let's evaluate
        load_packages = pkg_resources.evaluate_marker(line.strip()[2:-1])
        continue
    elif line.startswith("["):
        # this is a subsection ignore it
        load_packages = False

    if load_packages and line.strip():
        print(line.strip())

The output of the same is below

它的输出如下

idna>=2.1
asn1crypto>=0.21.0
six>=1.4.1
cffi>=1.7
enum34
ipaddress

If I change the requirements.txt like below

如果我更改requirements.txt如下所示

idna>=2.1
asn1crypto>=0.21.0
six>=1.4.1

[:platform_python_implementation == 'PyPy']
cffi>=1.7

[:python_version > '3']
enum34
ipaddress

The output changes to

输出变为

idna>=2.1
asn1crypto>=0.21.0
six>=1.4.1

#3


0  

You could test the beginning of each line to whether or not it is a match to the pip dependency declaration format.

您可以测试每行的开头是否与pip依赖性声明格式匹配。

Regex is probably the best solution to pattern matching:

正则表达式可能是模式匹配的最佳解决方案:

pattern = r'^[\w\d_-]+'

Where:

  • ^ matches to beginning of line/file
  • ^匹配行/文件的开头

  • [\w\d_-]+ matches any word (\w), digit (\d), underscore (_), and dash (-) more than once.
  • [\ w \ d _-] +多次匹配任何单词(\ w),数字(\ d),下划线(_)和短划线( - )。

All in all:

总而言之:

from re import match

pattern = r'^[\w\d_-]+'
with open('requires.txt') as file:
    for line in file:
        if match(pattern, line):
            print(line.strip())

Result:

$ python2.7 test.py
idna>=2.1
asn1crypto>=0.21.0
six>=1.4.1
cffi>=1.7
enum34
ipaddress

#1


3  

The requires.txt is part of the dependency metadata, so you can use the same tools easy_install uses when installing the egg. Assuming the file requires.txt is in the current directory:

requires.txt是依赖关系元数据的一部分,因此您可以使用easy_install在安装egg时使用的相同工具。假设文件requires.txt在当前目录中:

In [1]: from pkg_resources import Distribution, PathMetadata

In [2]: dist = Distribution(metadata=PathMetadata('.', '.'))

Now you can filter all dependencies for your current platform with Distribution.requires():

现在,您可以使用Distribution.requires()过滤当前平台的所有依赖项:

In [3]: sys.version
Out[3]: '3.6.4 (v3.6.4:d48ecebad5, Dec 18 2017, 21:07:28) \n[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)]'

In [4]: dist.requires()
Out[4]:
[Requirement.parse('idna>=2.1'),
 Requirement.parse('asn1crypto>=0.21.0'),
 Requirement.parse('six>=1.4.1'),
 Requirement.parse('cffi!=1.11.3,>=1.7'),
 Requirement.parse('cffi>=1.7')]

The list would be different if I used Python 2.7:

如果我使用Python 2.7,列表会有所不同:

In [4]: sys.version
Out[4]: '2.7.10 (default, Oct  6 2017, 22:29:07) \n[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.31)]'

In [5]: dist.requires()
Out[5]:
[Requirement.parse('idna>=2.1'),
 Requirement.parse('asn1crypto>=0.21.0'),
 Requirement.parse('six>=1.4.1'),
 Requirement.parse('cffi!=1.11.3,>=1.7'),
 Requirement.parse('cffi>=1.7'),
 Requirement.parse('enum34'),
 Requirement.parse('ipaddress')]

or PyPy:

In [2]: sys.version
Out[2]: '3.5.3 (fdd60ed87e941677e8ea11acf9f1819466521bf2, Apr 26 2018, 01:25:35)\n[PyPy 6.0.0 with GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.1)]'

In [3]: d.requires()
Out[3]:
[Requirement.parse('idna>=2.1'),
 Requirement.parse('asn1crypto>=0.21.0'),
 Requirement.parse('six>=1.4.1'),
 Requirement.parse('cffi!=1.11.3,>=1.7')]

Now, if you want to generate a list of requirement strings (like when you want to generate a requirements file for pip), convert the requirements to strings:

现在,如果要生成需求字符串列表(例如,当您要为pip生成需求文件时),请将需求转换为字符串:

In [8]: os.linesep.join(str(r) for r in dist.requires())
Out[8]:
'idna>=2.1\nasn1crypto>=0.21.0\nsix>=1.4.1\ncffi!=1.11.3,>=1.7'

PEP 508

If you also want to take PEP 508 environment markers into account independent of the current platform, things can get a bit trickier, but still manageable. First, convert the requirements with env markers:

如果您还想独立于当前平台考虑PEP 508环境标记,事情可能会变得有点棘手,但仍然可以管理。首先,使用env标记转换需求:

In [22]: dep_map_pep508 = {k: v for k, v in dist._build_dep_map().items() if k and k.startswith(':')}

In [24]: reqs_pep508 = [str(r) + ';' + k.lstrip(':') for k, v in dep_map_pep508.items() for r in v]

In [25]: reqs_pep508
Out[25]:
["cffi>=1.7;platform_python_implementation != 'PyPy'",
 "enum34;python_version >= '3'",
 "ipaddress;python_version >= '3'"]

Now handle the platform-independent deps, these house under the None key in dist's dependency map:

现在处理与平台无关的deps,它们位于dist的依赖关系图中的None键下:

In [26]: reqs_no_platform = [str(r) for r in dist._build_dep_map()[None]]

In [27]: reqs_no_platform
Out[27]: ['idna>=2.1', 'asn1crypto>=0.21.0', 'six>=1.4.1', 'cffi!=1.11.3,>=1.7']

Combine both lists to a string ready to be written to requirements file:

将两个列表组合成一个准备写入需求文件的字符串:

In [28]: os.linesep.join(reqs_no_platform + reqs_pep508)
Out[28]: "idna>=2.1\nasn1crypto>=0.21.0\nsix>=1.4.1\ncffi!=1.11.3,>=1.7\ncffi>=1.7;platform_python_implementation != 'PyPy'\nenum34;python_version >= '3'\nipaddress;python_version >= '3'"

#2


0  

So I was able to find one working solution for the same, there might be other possibilities as well, but i think this should work on most versions

所以我能够找到一个相同的工作解决方案,也可能有其他可能性,但我认为这应该适用于大多数版本

import pkg_resources

lines = open("requirements.txt").readlines()

load_packages = True
for line in lines:
    if line.strip().startswith("#"):
        continue

    if line.startswith("[:"):
        # this is marker, let's evaluate
        load_packages = pkg_resources.evaluate_marker(line.strip()[2:-1])
        continue
    elif line.startswith("["):
        # this is a subsection ignore it
        load_packages = False

    if load_packages and line.strip():
        print(line.strip())

The output of the same is below

它的输出如下

idna>=2.1
asn1crypto>=0.21.0
six>=1.4.1
cffi>=1.7
enum34
ipaddress

If I change the requirements.txt like below

如果我更改requirements.txt如下所示

idna>=2.1
asn1crypto>=0.21.0
six>=1.4.1

[:platform_python_implementation == 'PyPy']
cffi>=1.7

[:python_version > '3']
enum34
ipaddress

The output changes to

输出变为

idna>=2.1
asn1crypto>=0.21.0
six>=1.4.1

#3


0  

You could test the beginning of each line to whether or not it is a match to the pip dependency declaration format.

您可以测试每行的开头是否与pip依赖性声明格式匹配。

Regex is probably the best solution to pattern matching:

正则表达式可能是模式匹配的最佳解决方案:

pattern = r'^[\w\d_-]+'

Where:

  • ^ matches to beginning of line/file
  • ^匹配行/文件的开头

  • [\w\d_-]+ matches any word (\w), digit (\d), underscore (_), and dash (-) more than once.
  • [\ w \ d _-] +多次匹配任何单词(\ w),数字(\ d),下划线(_)和短划线( - )。

All in all:

总而言之:

from re import match

pattern = r'^[\w\d_-]+'
with open('requires.txt') as file:
    for line in file:
        if match(pattern, line):
            print(line.strip())

Result:

$ python2.7 test.py
idna>=2.1
asn1crypto>=0.21.0
six>=1.4.1
cffi>=1.7
enum34
ipaddress