Skip to content

Commit 492d840

Browse files
authored
Make linter aware of its own requirements (#4159)
1 parent b77726d commit 492d840

File tree

10 files changed

+69
-8
lines changed

10 files changed

+69
-8
lines changed

.config/constraints.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#
55
# pip-compile --all-extras --no-annotate --output-file=.config/constraints.txt --strip-extras --unsafe-package=resolvelib --unsafe-package=ruamel-yaml-clib --unsafe-package=wcmatch pyproject.toml
66
#
7-
ansible-compat==24.5.1a0
7+
ansible-compat==24.5.1
88
ansible-core==2.16.6
99
astroid==3.1.0
1010
attrs==23.2.0
@@ -35,6 +35,7 @@ ghp-import==2.1.0
3535
griffe==0.45.0
3636
htmlmin2==0.1.13
3737
idna==3.7
38+
importlib-metadata==7.1.0
3839
iniconfig==2.0.0
3940
isort==5.13.2
4041
jinja2==3.1.4
@@ -110,6 +111,7 @@ urllib3==2.2.1
110111
watchdog==4.0.0
111112
webencodings==0.5.1
112113
yamllint==1.35.1
114+
zipp==3.18.1
113115

114116
# The following packages are considered to be unsafe in a requirements file:
115117
# resolvelib

.config/requirements-lock.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#
55
# pip-compile --no-annotate --output-file=.config/requirements-lock.txt --strip-extras --unsafe-package=resolvelib --unsafe-package=ruamel-yaml-clib pyproject.toml
66
#
7-
ansible-compat==24.5.1a0
7+
ansible-compat==24.5.1
88
ansible-core==2.16.6
99
attrs==23.2.0
1010
black==24.4.2

.config/requirements.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ ansible-compat>=24.5.0dev0 # GPLv3
55
# alphabetically sorted:
66
black>=24.3.0 # MIT (security)
77
filelock>=3.3.0 # The Unlicense
8+
importlib-metadata # Apache
89
jsonschema>=4.10.0 # MIT, version needed for improved errors
910
packaging>=21.3 # Apache-2.0,BSD-2-Clause
1011
pathspec>=0.10.3 # Mozilla Public License 2.0 (MPL 2.0)

.github/lower-constraints.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# automatically updated by dependabot. This should be kept in sync with
33
# minimal requirements configured inside .config/requirements.in
44
ansible-core==2.13.0
5-
ansible-compat==24.5.1a0 # GPLv3
5+
ansible-compat==24.5.1 # GPLv3
66
black==24.3.0 # MIT (security)
77
filelock==3.3.0 # The Unlicense
88
jsonschema==4.10.0 # MIT, version needed for improved errors

.github/workflows/tox.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ jobs:
7373
env:
7474
# Number of expected test passes, safety measure for accidental skip of
7575
# tests. Update value if you add/remove tests.
76-
PYTEST_REQPASS: 869
76+
PYTEST_REQPASS: 870
7777
steps:
7878
- uses: actions/checkout@v4
7979
with:

.pre-commit-config.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,11 @@ repos:
153153
# empty args needed in order to match mypy cli behavior
154154
args: [--strict]
155155
additional_dependencies:
156-
- ansible-compat>=24.5.1a0
156+
- ansible-compat>=24.5.1
157157
- black>=22.10.0
158158
- cryptography>=39.0.1
159159
- filelock>=3.12.2
160+
- importlib_metadata
160161
- jinja2
161162
- license-expression >= 30.3.0
162163
- pytest-mock
@@ -182,11 +183,12 @@ repos:
182183
args:
183184
- --output-format=colorized
184185
additional_dependencies:
185-
- ansible-compat>=24.5.1a0
186+
- ansible-compat>=24.5.1
186187
- ansible-core>=2.14.0
187188
- black>=22.10.0
188189
- docutils
189190
- filelock>=3.12.2
191+
- importlib_metadata
190192
- jsonschema>=4.20.0
191193
- license-expression >= 30.3.0
192194
- pytest-mock

src/ansiblelint/app.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import itertools
77
import logging
88
import os
9+
import sys
910
from functools import lru_cache
1011
from pathlib import Path
1112
from typing import TYPE_CHECKING, Any
@@ -21,6 +22,7 @@
2122
from ansiblelint.config import options as default_options
2223
from ansiblelint.constants import RC, RULE_DOC_URL
2324
from ansiblelint.loaders import IGNORE_FILE
25+
from ansiblelint.requirements import Reqs
2426
from ansiblelint.stats import SummarizedResults, TagStats
2527

2628
if TYPE_CHECKING:
@@ -53,9 +55,18 @@ def __init__(self, options: Options):
5355
require_module=True,
5456
verbosity=options.verbosity,
5557
)
58+
self.reqs = Reqs("ansible-lint")
59+
package = "ansible-core"
60+
if not self.reqs.matches(
61+
package,
62+
str(self.runtime.version),
63+
): # pragma: no cover
64+
msg = f"ansible-lint requires {package}{','.join(str(x) for x in self.reqs[package])} and current version is {self.runtime.version}"
65+
logging.error(msg)
66+
sys.exit(RC.INVALID_CONFIG)
5667

5768
# pylint: disable=import-outside-toplevel
58-
from ansiblelint.yaml_utils import load_yamllint_config # noqa: 811,I001
69+
from ansiblelint.yaml_utils import load_yamllint_config # noqa: 811, I001
5970

6071
self.yamllint_config = load_yamllint_config()
6172

src/ansiblelint/requirements.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""Utilities for checking python packages requirements."""
2+
3+
import importlib_metadata
4+
from packaging.requirements import Requirement
5+
from packaging.specifiers import SpecifierSet
6+
from packaging.version import Version
7+
8+
9+
class Reqs(dict[str, SpecifierSet]):
10+
"""Utility class for working with package dependencies."""
11+
12+
reqs: dict[str, SpecifierSet]
13+
14+
def __init__(self, name: str = "ansible-lint") -> None:
15+
"""Load linter metadata requirements."""
16+
for req_str in importlib_metadata.metadata(name).json["requires_dist"]:
17+
req = Requirement(req_str)
18+
if req.name:
19+
self[req.name] = req.specifier
20+
21+
def matches(self, req_name: str, req_version: str | Version) -> bool:
22+
"""Verify if given version is matching current metadata dependencies."""
23+
if req_name not in self:
24+
return False
25+
return all(
26+
specifier.contains(str(req_version), prereleases=True)
27+
for specifier in self[req_name]
28+
)

test/test_requirements.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""Tests requirements module."""
2+
3+
from ansible_compat.runtime import Runtime
4+
5+
from ansiblelint.requirements import Reqs
6+
7+
8+
def test_reqs() -> None:
9+
"""Performs basic testing of Reqs class."""
10+
reqs = Reqs()
11+
runtime = Runtime()
12+
assert "ansible-core" in reqs
13+
# checks that this ansible core version is not supported:
14+
assert reqs.matches("ansible-core", "0.0") is False
15+
# assert that invalid package name
16+
assert reqs.matches("this-package-does-not-exist", "0.0") is False
17+
# check the current ansible core version is supported:
18+
assert reqs.matches("ansible-core", runtime.version)

test/test_yaml_utils.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Tests for yaml-related utility functions."""
22

3-
# pylint: disable=too-many-lines
43
from __future__ import annotations
54

65
from io import StringIO

0 commit comments

Comments
 (0)