Use importlib_metadata rather than pip freeze

This commit is contained in:
Petr Viktorin 2019-08-10 14:34:24 +02:00
parent 23901d999a
commit d262d909f5
5 changed files with 90 additions and 70 deletions

View File

@ -24,7 +24,6 @@ fi
%{-e:%{expand:%global toxenv %{-e*}}} %{-e:%{expand:%global toxenv %{-e*}}}
echo 'python3-devel' echo 'python3-devel'
echo 'python3dist(packaging)' echo 'python3dist(packaging)'
echo 'python3dist(pip) >= 19'
echo 'python3dist(pytoml)' echo 'python3dist(pytoml)'
# setuptools assumes no pre-existing dist-info # setuptools assumes no pre-existing dist-info
rm -rfv *.dist-info/ rm -rfv *.dist-info/

View File

@ -21,17 +21,28 @@ URL: https://src.fedoraproject.org/rpms/pyproject-rpm-macros
BuildArch: noarch BuildArch: noarch
# We keep them here for now to avoid one loop of %%generate_buildrequires
# And to allow the other macros without %%pyproject_buildrequires (e.g. on Fedora 30)
# But those are also always in the output of %%generate_buildrequires
# in order to be removable in the future
Requires: python3-pip >= 19 Requires: python3-pip >= 19
Requires: python3-devel Requires: python3-devel
# We keep these here for now to avoid one loop of %%generate_buildrequires
# But those are also always in the output of %%generate_buildrequires
# in order to be removable in the future
Requires: python3dist(packaging)
Requires: python3dist(pytoml)
# This is not output from %%generate_buildrequires to work around:
# https://github.com/rpm-software-management/mock/issues/336
Requires: (python3dist(importlib-metadata) if python3 < 3.8)
%if %{with tests} %if %{with tests}
BuildRequires: python3dist(pytest) BuildRequires: python3dist(pytest)
BuildRequires: python3dist(pyyaml) BuildRequires: python3dist(pyyaml)
BuildRequires: python3dist(packaging) BuildRequires: python3dist(packaging)
%if 0%{fedora} < 32
# The %%if should not be needed, it works around:
# https://github.com/rpm-software-management/mock/issues/336
BuildRequires: (python3dist(importlib-metadata) if python3 < 3.8)
%endif
BuildRequires: python3dist(pytoml) BuildRequires: python3dist(pytoml)
BuildRequires: python3dist(pip) BuildRequires: python3dist(pip)
BuildRequires: python3dist(setuptools) BuildRequires: python3dist(setuptools)

View File

@ -26,7 +26,10 @@ try:
from packaging.requirements import Requirement, InvalidRequirement from packaging.requirements import Requirement, InvalidRequirement
from packaging.version import Version from packaging.version import Version
from packaging.utils import canonicalize_name, canonicalize_version from packaging.utils import canonicalize_name, canonicalize_version
import pip try:
import importlib.metadata as importlib_metadata
except ImportError:
import importlib_metadata
except ImportError as e: except ImportError as e:
print_err('Import error:', e) print_err('Import error:', e)
# already echoed by the %pyproject_buildrequires macro # already echoed by the %pyproject_buildrequires macro
@ -44,14 +47,8 @@ def hook_call():
class Requirements: class Requirements:
"""Requirement printer""" """Requirement printer"""
def __init__(self, freeze_output, extras=''): def __init__(self, get_installed_version, extras=''):
self.installed_packages = {} self.get_installed_version = get_installed_version
for line in freeze_output.splitlines():
line = line.strip()
if line.startswith('#'):
continue
name, version = line.split('==')
self.installed_packages[name.strip()] = Version(version)
self.marker_env = {'extra': extras} self.marker_env = {'extra': extras}
@ -77,7 +74,11 @@ class Requirements:
print_err(f'Ignoring alien requirement:', requirement_str) print_err(f'Ignoring alien requirement:', requirement_str)
return return
installed = self.installed_packages.get(requirement.name) try:
installed = self.get_installed_version(requirement.name)
except importlib_metadata.PackageNotFoundError:
print_err(f'Requirement not satisfied: {requirement_str}')
installed = None
if installed and installed in requirement.specifier: if installed and installed in requirement.specifier:
print_err(f'Requirement satisfied: {requirement_str}') print_err(f'Requirement satisfied: {requirement_str}')
print_err(f' (installed: {requirement.name} {installed})') print_err(f' (installed: {requirement.name} {installed})')
@ -203,9 +204,14 @@ def python3dist(name, op=None, version=None):
def generate_requires( def generate_requires(
freeze_output, *, include_runtime=False, toxenv=None, extras='', *, include_runtime=False, toxenv=None, extras='',
get_installed_version=importlib_metadata.version, # for dep injection
): ):
requirements = Requirements(freeze_output, extras=extras) """Generate the BuildRequires for the project in the current directory
This is the main Python entry point.
"""
requirements = Requirements(get_installed_version, extras=extras)
try: try:
backend = get_backend(requirements) backend = get_backend(requirements)
@ -259,16 +265,8 @@ def main(argv):
print_err('-x (--extras) are only useful with -r (--runtime)') print_err('-x (--extras) are only useful with -r (--runtime)')
exit(1) exit(1)
freeze_output = subprocess.run(
[sys.executable, '-I', '-m', 'pip', 'freeze', '--all'],
encoding='utf-8',
stdout=subprocess.PIPE,
check=True,
).stdout
try: try:
generate_requires( generate_requires(
freeze_output,
include_runtime=args.runtime, include_runtime=args.runtime,
toxenv=args.toxenv, toxenv=args.toxenv,
extras=args.extras, extras=args.extras,

View File

@ -6,6 +6,11 @@ import yaml
from pyproject_buildrequires import generate_requires from pyproject_buildrequires import generate_requires
try:
import importlib.metadata as importlib_metadata
except ImportError:
import importlib_metadata
testcases = {} testcases = {}
with Path(__file__).parent.joinpath('testcases.yaml').open() as f: with Path(__file__).parent.joinpath('testcases.yaml').open() as f:
testcases = yaml.safe_load(f) testcases = yaml.safe_load(f)
@ -26,9 +31,17 @@ def test_data(case_name, capsys, tmp_path, monkeypatch):
if filename in case: if filename in case:
cwd.joinpath(filename).write_text(case[filename]) cwd.joinpath(filename).write_text(case[filename])
def get_installed_version(dist_name):
try:
return str(case['installed'][dist_name])
except (KeyError, TypeError):
raise importlib_metadata.PackageNotFoundError(
f'info not found for {dist_name}'
)
try: try:
generate_requires( generate_requires(
case['freeze_output'], get_installed_version=get_installed_version,
include_runtime=case.get('include_runtime', False), include_runtime=case.get('include_runtime', False),
extras=case.get('extras', ''), extras=case.get('extras', ''),
toxenv=case.get('toxenv', None), toxenv=case.get('toxenv', None),

View File

@ -1,5 +1,5 @@
No pyproject.toml, nothing installed: No pyproject.toml, nothing installed:
freeze_output: | installed:
# empty # empty
expected: | expected: |
python3dist(setuptools) >= 40.8 python3dist(setuptools) >= 40.8
@ -7,7 +7,7 @@ No pyproject.toml, nothing installed:
result: 0 result: 0
Nothing installed yet: Nothing installed yet:
freeze_output: | installed:
# empty # empty
pyproject.toml: | pyproject.toml: |
# empty # empty
@ -17,9 +17,9 @@ Nothing installed yet:
result: 0 result: 0
Insufficient version of setuptools: Insufficient version of setuptools:
freeze_output: | installed:
setuptools==5 setuptools: 5
wheel==1 wheel: 1
pyproject.toml: | pyproject.toml: |
# empty # empty
expected: | expected: |
@ -28,9 +28,9 @@ Insufficient version of setuptools:
result: 0 result: 0
Empty pyproject.toml, empty setup.py: Empty pyproject.toml, empty setup.py:
freeze_output: | installed:
setuptools==50 setuptools: 50
wheel==1 wheel: 1
setup.py: | setup.py: |
expected: | expected: |
python3dist(setuptools) >= 40.8 python3dist(setuptools) >= 40.8
@ -39,9 +39,9 @@ Empty pyproject.toml, empty setup.py:
result: 0 result: 0
Default build system, empty setup.py: Default build system, empty setup.py:
freeze_output: | installed:
setuptools==50 setuptools: 50
wheel==1 wheel: 1
pyproject.toml: | pyproject.toml: |
# empty # empty
setup.py: | setup.py: |
@ -52,24 +52,24 @@ Default build system, empty setup.py:
result: 0 result: 0
Erroring setup.py: Erroring setup.py:
freeze_output: | installed:
setuptools==50 setuptools: 50
wheel==1 wheel: 1
setup.py: | setup.py: |
exit(77) exit(77)
result: 77 result: 77
Bad character in version: Bad character in version:
freeze_output: | installed: {}
pyproject.toml: | pyproject.toml: |
[build-system] [build-system]
requires = ["pkg == 0.$.^.*"] requires = ["pkg == 0.$.^.*"]
except: ValueError except: ValueError
Build system dependencies in pyproject.toml: Build system dependencies in pyproject.toml:
freeze_output: | installed:
setuptools==50 setuptools: 50
wheel==1 wheel: 1
pyproject.toml: | pyproject.toml: |
[build-system] [build-system]
requires = [ requires = [
@ -100,9 +100,9 @@ Build system dependencies in pyproject.toml:
result: 0 result: 0
Default build system, build dependencies in setup.py: Default build system, build dependencies in setup.py:
freeze_output: | installed:
setuptools==50 setuptools: 50
wheel==1 wheel: 1
setup.py: | setup.py: |
from setuptools import setup from setuptools import setup
setup( setup(
@ -120,10 +120,10 @@ Default build system, build dependencies in setup.py:
result: 0 result: 0
Default build system, run dependencies in setup.py: Default build system, run dependencies in setup.py:
freeze_output: | installed:
setuptools==50 setuptools: 50
wheel==1 wheel: 1
pyyaml==1 pyyaml: 1
include_runtime: true include_runtime: true
setup.py: | setup.py: |
from setuptools import setup from setuptools import setup
@ -143,10 +143,10 @@ Default build system, run dependencies in setup.py:
result: 0 result: 0
Run dependencies with extras (not selected): Run dependencies with extras (not selected):
freeze_output: | installed:
setuptools==50 setuptools: 50
wheel==1 wheel: 1
pyyaml==1 pyyaml: 1
include_runtime: true include_runtime: true
setup.py: &pytest_setup_py | setup.py: &pytest_setup_py |
# slightly abriged copy of pytest's setup.py # slightly abriged copy of pytest's setup.py
@ -200,10 +200,10 @@ Run dependencies with extras (not selected):
result: 0 result: 0
Run dependencies with extras (selected): Run dependencies with extras (selected):
freeze_output: | installed:
setuptools==50 setuptools: 50
wheel==1 wheel: 1
pyyaml==1 pyyaml: 1
include_runtime: true include_runtime: true
extras: testing extras: testing
setup.py: *pytest_setup_py setup.py: *pytest_setup_py
@ -227,10 +227,10 @@ Run dependencies with extras (selected):
Run dependencies with multiple extras: Run dependencies with multiple extras:
xfail: requirement.marker.evaluate seems to not support multiple extras xfail: requirement.marker.evaluate seems to not support multiple extras
freeze_output: | installed:
setuptools==50 setuptools: 50
wheel==1 wheel: 1
pyyaml==1 pyyaml: 1
include_runtime: true include_runtime: true
extras: testing,more-testing, even-more-testing , cool-feature extras: testing,more-testing, even-more-testing , cool-feature
setup.py: | setup.py: |
@ -246,19 +246,18 @@ Run dependencies with multiple extras:
expected: | expected: |
python3dist(setuptools) >= 40.8 python3dist(setuptools) >= 40.8
python3dist(wheel) python3dist(wheel)
python3dist(wheel)
python3dist(dep1) python3dist(dep1)
python3dist(dep2) python3dist(dep2)
python3dist(dep3) python3dist(dep3)
python3dist(dep4) python3dist(dep4)
result: 0 result: 0
Tox depndencies: Tox dependencies:
freeze_output: | installed:
setuptools==50 setuptools: 50
wheel==1 wheel: 1
tox==3.5.3 tox: 3.5.3
tox-current-env==0.0.2 tox-current-env: 0.0.2
toxenv: py3 toxenv: py3
setup.py: | setup.py: |
from setuptools import setup from setuptools import setup