Skip to content

Commit 2a191d2

Browse files
joelbreton2pre-commit-ci[bot]gmuloc
authored
CI: Use PDM to extract requirements from pyproject.toml (#6501)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: gmuloc <gmulocher@arista.com>
1 parent b53df25 commit 2a191d2

7 files changed

Lines changed: 61 additions & 56 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ dist/
3737
.python-version
3838
## direnv
3939
.envrc
40+
## pyenv
41+
uv.lock
4042
## .vscode/*
4143
.vscode/
4244

.pre-commit-config.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,16 @@ repos:
198198
docs/plugins/.* | # Excluded auto generated doc for Ansible plugins
199199
)$
200200
201+
- repo: https://github.com/pdm-project/pdm
202+
rev: "2.26.6"
203+
hooks:
204+
- id: pdm-export
205+
name: Extract requirements from pyavd/pyproject.toml to requirements.txt
206+
entry: pdm export -p python-avd -o ansible_collections/arista/avd/requirements.txt --without-hashes -G ansible -G self --pyproject
207+
language: python
208+
files: python-avd/pyproject.toml
209+
pass_filenames: false
210+
201211
- repo: local
202212
hooks:
203213

ansible_collections/arista/avd/plugins/action/verify_requirements.py

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
# Copyright (c) 2023-2026 Arista Networks, Inc.
22
# Use of this source code is governed by the Apache License 2.0
33
# that can be found in the LICENSE file.
4+
from __future__ import annotations
45

56
import json
67
import sys
78
import warnings
89
from importlib import import_module
9-
from importlib.metadata import Distribution, PackageNotFoundError, metadata, version
10+
from importlib.metadata import Distribution, PackageNotFoundError, version
1011
from logging import getLogger
1112
from pathlib import Path
1213
from subprocess import PIPE, Popen
13-
from typing import Any
14+
from typing import TYPE_CHECKING, Any
1415

1516
import yaml
1617
from ansible import constants as C # noqa: N812
@@ -20,16 +21,19 @@
2021
from ansible_collections.arista.avd.plugins import PYTHON_AVD_PATH, RUNNING_FROM_SOURCE
2122
from ansible_collections.arista.avd.plugins.plugin_utils.utils.avd_action_plugin import AvdActionPlugin, AvdLoggingConfig
2223

24+
if TYPE_CHECKING:
25+
# Relying on packaging installed by ansible
26+
from packaging.requirements import Requirement
27+
from packaging.specifiers import SpecifierSet
28+
2329
try:
2430
# Relying on packaging installed by ansible
25-
from packaging.requirements import InvalidRequirement, Requirement
31+
from packaging.requirements import Requirement
2632
from packaging.specifiers import SpecifierSet
2733

2834
HAS_PACKAGING = True
2935
except ImportError:
3036
HAS_PACKAGING = False
31-
# Making ansible-test sanity happy
32-
Requirement = object
3337

3438
LOGGER = getLogger("ansible_collections.arista.avd")
3539
DISPLAY = Display()
@@ -81,24 +85,6 @@ def _validate_python_version(info: dict[str, Any]) -> bool:
8185
return True
8286

8387

84-
def _parse_requirements(req_str: str) -> tuple[Requirement, list[str]]:
85-
"""Parse a requirement string and return the parsed object and a list of extras requirements to parse if any."""
86-
try:
87-
req = Requirement(req_str)
88-
except InvalidRequirement as exc:
89-
msg = f"Wrong format for requirement {req_str}"
90-
raise ValueError(msg) from exc
91-
92-
extras = []
93-
if req.extras:
94-
for subreq_name in metadata(req.name).get_all("Requires-Dist"):
95-
subreq = Requirement(subreq_name)
96-
if subreq.marker:
97-
extras.extend([subreq_name for marker in subreq.marker._markers if str(marker[0]) == "extra" and str(marker[2]) in req.extras])
98-
99-
return req, extras
100-
101-
10288
def _check_requirement(req: Requirement, requirements_dict: dict[str, Any]) -> bool:
10389
"""
10490
Check one requirement and in-place update requirement_dict.
@@ -195,19 +181,17 @@ def _validate_python_requirements(requirements: list[str], info: dict[str, Any])
195181
}
196182

197183
# Remove the comments including inline comments
198-
requirements = [req.split(" #", maxsplit=1)[0] for req in requirements if req[0] != "#"]
184+
requirements = [req.split(" #", maxsplit=1)[0] for req in requirements if req != "" and req[0] != "#"]
199185
for raw_req in requirements:
200-
req, extras = _parse_requirements(raw_req)
186+
req = Requirement(raw_req)
201187
if RUNNING_FROM_SOURCE and req.name == "pyavd":
202-
LOGGER.debug("AVD is running from source, *not* checking pyavd version nor any extra.")
188+
LOGGER.debug("AVD is running from source, *not* checking pyavd version.")
203189
requirements_dict["valid"][req.name] = {
204190
"installed": "running from source",
205191
"required_version": str(req.specifier) if len(req.specifier) > 0 else None,
206192
}
207193
continue
208194

209-
requirements.extend(extras)
210-
211195
valid = valid and _check_requirement(req, requirements_dict)
212196

213197
info["python_requirements"] = requirements_dict
Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,17 @@
1-
# PyAVD must follow the exact same version as the Ansible collection.
2-
pyavd[ansible-collection]==6.0.0.dev8
1+
# This file is @generated by PDM.
2+
# Please do not edit it manually.
3+
4+
ansible-core<2.21.0,>=2.16.0
5+
anta>=1.7.0
6+
aristaproto>=0.1.1
7+
cryptography>=43.0.0
8+
deepmerge>=1.1.0
9+
distlib>=0.3.9
10+
grpclib==0.4.9
11+
jinja2>=3.0
12+
netaddr>=0.7.19
13+
pyavd==6.0.0.dev8
14+
pyavd-utils==0.0.2
15+
python-socks[asyncio]>=2.7.2
16+
pyyaml>=6.0.0
17+
requests>=2.27.0

ansible_collections/arista/avd/tests/unit/action/test_verify_requirements.py

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -136,18 +136,16 @@ def test__validate_python_requirements(n_reqs: int, mocked_version: str | None,
136136

137137

138138
@pytest.mark.parametrize(
139-
("extras", "running_from_source", "expected_return"),
139+
("running_from_source", "expected_return"),
140140
[
141-
pytest.param(False, False, True, id="pyavd - no extra - not running from source"),
142-
pytest.param(True, False, True, id="pyavd - extra - not running from source"),
143-
pytest.param(False, True, True, id="pyavd - no extra - running from source"),
144-
pytest.param(False, True, True, id="pyavd - extra - running from source"),
141+
pytest.param(False, True, id="pyavd - not running from source"),
142+
pytest.param(True, True, id="pyavd - running from source"),
145143
],
146144
)
147-
def test__validate_python_requirements_pyavd(extras: bool, running_from_source: bool, expected_return: bool) -> None:
145+
def test__validate_python_requirements_pyavd(running_from_source: bool, expected_return: bool) -> None:
148146
"""Testing behavior of the function for pyavd when running from source or not."""
149147
result = {}
150-
req = f"pyavd{'[ansible-collection]' if extras else ''}==5.3.0"
148+
req = "pyavd==5.3.0"
151149

152150
requirements = [req]
153151

@@ -159,24 +157,14 @@ def test__validate_python_requirements_pyavd(extras: bool, running_from_source:
159157
ret = _validate_python_requirements(requirements, result)
160158
assert ret == expected_return
161159
python_req_result = result["python_requirements"]
160+
assert (
161+
len(python_req_result["valid"]) + len(python_req_result["mismatched"]) + len(python_req_result["not_found"]) + len(python_req_result["parsing_failed"])
162+
== 1
163+
)
162164
if running_from_source:
163165
assert python_req_result["valid"]["pyavd"]["installed"] == "running from source"
164-
# only pyavd is expected for this test when running from source with or without extra
165-
assert (
166-
len(python_req_result["valid"])
167-
+ len(python_req_result["mismatched"])
168-
+ len(python_req_result["not_found"])
169-
+ len(python_req_result["parsing_failed"])
170-
== 1
171-
)
172-
elif extras:
173-
assert (
174-
len(python_req_result["valid"])
175-
+ len(python_req_result["mismatched"])
176-
+ len(python_req_result["not_found"])
177-
+ len(python_req_result["parsing_failed"])
178-
> 1
179-
)
166+
else:
167+
assert python_req_result["valid"]["pyavd"]["installed"] == "5.3.0"
180168

181169

182170
@pytest.mark.parametrize(

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,9 +292,9 @@ replace = "version: {new_version}"
292292
### Bumping version using PEP440 style like x.y.z.dev1
293293

294294
[[tool.bumpversion.files]]
295-
filename = "ansible_collections/arista/avd/requirements.txt"
296-
search = "pyavd[ansible-collection]=={current_version}"
297-
replace = "pyavd[ansible-collection]=={new_version}"
295+
filename = "python-avd/pyproject.toml"
296+
search = 'pyavd=="{current_version}"'
297+
replace = 'pyavd=="{new_version}"'
298298
parse = """(?x)
299299
(?P<major>0|[1-9]\\d*)\\.
300300
(?P<minor>0|[1-9]\\d*)\\.

python-avd/pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ include = ["pyavd*"]
8181
[tool.setuptools.dynamic]
8282
version = {attr = "pyavd.__version__"}
8383

84+
[dependency-groups]
85+
# helping pdm generate the flat list of requirements in pre-commit
86+
self = [
87+
"pyavd==6.0.0.dev8"
88+
]
89+
8490
[tool.black]
8591
# Black has been replaced with Ruff.
8692
# This section should be removed later, but kept here to avoid IDE extensions from messing up the code.

0 commit comments

Comments
 (0)