dependabot-python 0.368.0 → 0.369.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/helpers/build +4 -0
- data/helpers/requirements.txt +1 -0
- data/helpers/test/fixtures/no_dependencies.toml +3 -0
- data/helpers/test/fixtures/pep621_arbitrary_equality.toml +7 -0
- data/helpers/test/fixtures/pep621_dependencies.toml +21 -0
- data/helpers/test/fixtures/pep621_empty_deps.toml +8 -0
- data/helpers/test/fixtures/pep621_extras.toml +8 -0
- data/helpers/test/fixtures/pep621_markers.toml +7 -0
- data/helpers/test/fixtures/pep621_multiple_extras.toml +7 -0
- data/helpers/test/fixtures/pep621_no_version.toml +8 -0
- data/helpers/test/fixtures/pep621_only_build_system.toml +3 -0
- data/helpers/test/fixtures/pep735_cycle.toml +13 -0
- data/helpers/test/fixtures/pep735_dependency_groups.toml +18 -0
- data/helpers/test/fixtures/requirements/constraints.txt +1 -0
- data/helpers/test/fixtures/requirements/markers.txt +1 -0
- data/helpers/test/fixtures/requirements/requirements-dev.txt +2 -0
- data/helpers/test/fixtures/requirements/requirements.txt +5 -0
- data/helpers/test/fixtures/requirements/with_constraints.txt +2 -0
- data/helpers/test/fixtures/requirements_empty/.gitkeep +0 -0
- data/helpers/test/fixtures/setup_cfg/setup.cfg +16 -0
- data/helpers/test/fixtures/setup_py/setup.py +20 -0
- data/helpers/test/fixtures/setup_py_comments/setup.py +9 -0
- data/helpers/test/test_hasher.py +114 -0
- data/helpers/test/test_parse_requirements.py +103 -0
- data/helpers/test/test_parse_setup.py +127 -0
- data/helpers/test/test_parser.py +184 -0
- data/helpers/test/test_run.py +49 -0
- data/lib/dependabot/python/file_updater/poetry_file_updater.rb +16 -11
- data/lib/dependabot/python/file_updater/pyproject_preparer.rb +109 -0
- data/lib/dependabot/python/update_checker/latest_version_finder.rb +4 -2
- metadata +29 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 64cdd01e07bc2f6472a1027884006af4c7df3648168f04135c7b91cdf5530f14
|
|
4
|
+
data.tar.gz: b5610dc0420858f42cb23a8462abcd7ebd4b9b1aab8be3ca28233a99c239982c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f4cb556708bba8d54dc3d04495d021e4808cf5651184ed9fe2d1b65d6b1114c688f92d9be8ab8fae6e96d818643ce4c4824db5971dc2fabf55370b6ffff26f39
|
|
7
|
+
data.tar.gz: 64514c6fad289e34af6bdbaeecc25f8ff815ba193d77088a86fa3399c9379ab1cad740ff0f18ae0ac176e85375714e15f36ceae638b96d6e2714f425ce86cd96
|
data/helpers/build
CHANGED
|
@@ -17,6 +17,10 @@ cp -r \
|
|
|
17
17
|
"$helpers_dir/requirements.txt" \
|
|
18
18
|
"$install_dir"
|
|
19
19
|
|
|
20
|
+
if [ -d "$helpers_dir/test" ]; then
|
|
21
|
+
cp -r "$helpers_dir/test" "$install_dir"
|
|
22
|
+
fi
|
|
23
|
+
|
|
20
24
|
cd "$install_dir"
|
|
21
25
|
PYENV_VERSION=$1 pyenv exec pip3 --disable-pip-version-check install --use-pep517 -r "requirements.txt"
|
|
22
26
|
|
data/helpers/requirements.txt
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "myapp"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
requires-python = ">=3.10"
|
|
5
|
+
|
|
6
|
+
dependencies = [
|
|
7
|
+
"requests>=2.13.0,<3.0",
|
|
8
|
+
"urllib3==1.26.0",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
[project.optional-dependencies]
|
|
12
|
+
socks = [
|
|
13
|
+
"PySocks >= 1.5.6, != 1.5.7, < 2",
|
|
14
|
+
]
|
|
15
|
+
tests = [
|
|
16
|
+
"pytest >= 7.0, < 8",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[build-system]
|
|
20
|
+
requires = ["setuptools>=68.0"]
|
|
21
|
+
build-backend = "setuptools.build_meta"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "myapp"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
requires-python = ">=3.10"
|
|
5
|
+
|
|
6
|
+
dependencies = [
|
|
7
|
+
"requests>=2.13.0",
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
[dependency-groups]
|
|
11
|
+
dev = [
|
|
12
|
+
"pytest==7.1.3",
|
|
13
|
+
"black==22.10.0",
|
|
14
|
+
]
|
|
15
|
+
lint = [
|
|
16
|
+
{include-group = "dev"},
|
|
17
|
+
"flake8>=5.0",
|
|
18
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests<3.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pywin32>=1.0; sys_platform == "win32"
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from setuptools import setup
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="myapp",
|
|
5
|
+
version="1.0.0",
|
|
6
|
+
install_requires=[
|
|
7
|
+
"requests>=2.13.0",
|
|
8
|
+
"urllib3==1.26.0",
|
|
9
|
+
],
|
|
10
|
+
setup_requires=[
|
|
11
|
+
"setuptools>=68.0",
|
|
12
|
+
],
|
|
13
|
+
tests_require=[
|
|
14
|
+
"pytest>=7.0",
|
|
15
|
+
],
|
|
16
|
+
extras_require={
|
|
17
|
+
"socks": ["PySocks>=1.5.6"],
|
|
18
|
+
"dev": ["black==22.10.0"],
|
|
19
|
+
},
|
|
20
|
+
)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import ssl
|
|
4
|
+
import sys
|
|
5
|
+
from unittest.mock import MagicMock, patch
|
|
6
|
+
from urllib.error import URLError
|
|
7
|
+
|
|
8
|
+
sys.path.insert(
|
|
9
|
+
0, os.path.join(os.path.dirname(__file__), os.pardir, "lib")
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
import hasher # noqa: E402
|
|
13
|
+
import hashin as hashin_mod # noqa: E402
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestGetDependencyHash:
|
|
17
|
+
@patch("hasher.hashin.get_package_hashes")
|
|
18
|
+
def test_returns_hashes(self, mock_get):
|
|
19
|
+
mock_get.return_value = {
|
|
20
|
+
"hashes": [
|
|
21
|
+
{"hash": "abc123", "platform": "linux"},
|
|
22
|
+
{"hash": "def456", "platform": "macos"},
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
result = json.loads(hasher.get_dependency_hash(
|
|
27
|
+
"requests", "2.28.0", "sha256"
|
|
28
|
+
))
|
|
29
|
+
|
|
30
|
+
assert "result" in result
|
|
31
|
+
assert len(result["result"]) == 2
|
|
32
|
+
assert result["result"][0]["hash"] == "abc123"
|
|
33
|
+
mock_get.assert_called_once()
|
|
34
|
+
|
|
35
|
+
@patch("hasher.hashin.get_package_hashes")
|
|
36
|
+
def test_custom_index_url(self, mock_get):
|
|
37
|
+
mock_get.return_value = {"hashes": []}
|
|
38
|
+
|
|
39
|
+
hasher.get_dependency_hash(
|
|
40
|
+
"requests", "2.28.0", "sha256",
|
|
41
|
+
index_url="https://custom.registry/simple/"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
mock_get.assert_called_once_with(
|
|
45
|
+
"requests",
|
|
46
|
+
version="2.28.0",
|
|
47
|
+
algorithm="sha256",
|
|
48
|
+
index_url="https://custom.registry/simple/"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@patch("hasher.hashin.get_package_hashes")
|
|
52
|
+
def test_package_not_found(self, mock_get):
|
|
53
|
+
mock_get.side_effect = hashin_mod.PackageNotFoundError(
|
|
54
|
+
"no-such-package"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
result = json.loads(hasher.get_dependency_hash(
|
|
58
|
+
"no-such-package", "1.0.0", "sha256"
|
|
59
|
+
))
|
|
60
|
+
|
|
61
|
+
assert "error" in result
|
|
62
|
+
|
|
63
|
+
@patch("hasher.hashin.get_package_hashes")
|
|
64
|
+
def test_ssl_certificate_error(self, mock_get):
|
|
65
|
+
ssl_error = ssl.SSLError(
|
|
66
|
+
"CERTIFICATE_VERIFY_FAILED: unable to get local issuer"
|
|
67
|
+
)
|
|
68
|
+
mock_get.side_effect = URLError(ssl_error)
|
|
69
|
+
|
|
70
|
+
result = json.loads(hasher.get_dependency_hash(
|
|
71
|
+
"requests", "2.28.0", "sha256"
|
|
72
|
+
))
|
|
73
|
+
|
|
74
|
+
assert "error" in result
|
|
75
|
+
assert "CERTIFICATE_VERIFY_FAILED" in result["error"]
|
|
76
|
+
|
|
77
|
+
@patch("hasher.hashin.get_package_hashes")
|
|
78
|
+
def test_non_ssl_url_error_raises(self, mock_get):
|
|
79
|
+
mock_get.side_effect = URLError("Connection refused")
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
hasher.get_dependency_hash("requests", "2.28.0", "sha256")
|
|
83
|
+
assert False, "Expected URLError to be raised"
|
|
84
|
+
except URLError:
|
|
85
|
+
pass # expected
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class TestGetPipfileHash:
|
|
89
|
+
@patch("builtins.open")
|
|
90
|
+
@patch("hasher.plette")
|
|
91
|
+
def test_returns_hash(self, mock_plette, mock_open):
|
|
92
|
+
mock_pipfile = MagicMock()
|
|
93
|
+
mock_pipfile.get_hash.return_value.value = "abc123hash"
|
|
94
|
+
mock_plette.Pipfile.load.return_value = mock_pipfile
|
|
95
|
+
|
|
96
|
+
result = json.loads(hasher.get_pipfile_hash("/tmp/project"))
|
|
97
|
+
|
|
98
|
+
assert result["result"] == "abc123hash"
|
|
99
|
+
mock_open.assert_called_once_with("/tmp/project/Pipfile")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class TestGetPyprojectHash:
|
|
103
|
+
@patch("hasher.Factory")
|
|
104
|
+
def test_returns_hash(self, mock_factory_cls):
|
|
105
|
+
mock_poetry = MagicMock()
|
|
106
|
+
mock_poetry.locker._get_content_hash.return_value = "xyz789hash"
|
|
107
|
+
mock_factory_cls.return_value.create_poetry.return_value = mock_poetry
|
|
108
|
+
|
|
109
|
+
result = json.loads(hasher.get_pyproject_hash("/tmp/project"))
|
|
110
|
+
|
|
111
|
+
assert result["result"] == "xyz789hash"
|
|
112
|
+
mock_factory_cls.return_value.create_poetry.assert_called_once_with(
|
|
113
|
+
"/tmp/project"
|
|
114
|
+
)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
sys.path.insert(
|
|
6
|
+
0, os.path.join(os.path.dirname(__file__), os.pardir, "lib")
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from parser import parse_requirements # noqa: E402
|
|
10
|
+
|
|
11
|
+
FIXTURES = os.path.join(os.path.dirname(__file__), "fixtures")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def parse(fixture_dir):
|
|
15
|
+
path = os.path.join(FIXTURES, fixture_dir)
|
|
16
|
+
result = json.loads(parse_requirements(path))
|
|
17
|
+
return result
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def find_dep(deps, name):
|
|
21
|
+
return next((d for d in deps if d["name"] == name), None)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def find_dep_in_file(deps, name, file_substring):
|
|
25
|
+
return next(
|
|
26
|
+
(d for d in deps
|
|
27
|
+
if d["name"] == name and file_substring in d["file"]),
|
|
28
|
+
None
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TestParseRequirementsTxt:
|
|
33
|
+
def test_parses_basic_requirements(self):
|
|
34
|
+
result = parse("requirements")
|
|
35
|
+
deps = result["result"]
|
|
36
|
+
requests = find_dep_in_file(deps, "requests", "requirements.txt")
|
|
37
|
+
assert requests is not None
|
|
38
|
+
assert requests["requirement"] == "<3.0,>=2.13.0"
|
|
39
|
+
|
|
40
|
+
def test_parses_pinned_version(self):
|
|
41
|
+
result = parse("requirements")
|
|
42
|
+
deps = result["result"]
|
|
43
|
+
urllib3 = find_dep(deps, "urllib3")
|
|
44
|
+
assert urllib3 is not None
|
|
45
|
+
assert urllib3["version"] == "1.26.0"
|
|
46
|
+
assert urllib3["requirement"] == "==1.26.0"
|
|
47
|
+
|
|
48
|
+
def test_parses_extras(self):
|
|
49
|
+
result = parse("requirements")
|
|
50
|
+
deps = result["result"]
|
|
51
|
+
flask = find_dep(deps, "Flask")
|
|
52
|
+
assert flask is not None
|
|
53
|
+
assert flask["extras"] == ["async"]
|
|
54
|
+
|
|
55
|
+
def test_strips_inline_comments(self):
|
|
56
|
+
result = parse("requirements")
|
|
57
|
+
deps = result["result"]
|
|
58
|
+
boto3 = find_dep(deps, "boto3")
|
|
59
|
+
assert boto3 is not None
|
|
60
|
+
# boto3 has no version specifier, just a comment
|
|
61
|
+
assert boto3["requirement"] is None or boto3["requirement"] == ""
|
|
62
|
+
|
|
63
|
+
def test_file_path_is_relative(self):
|
|
64
|
+
result = parse("requirements")
|
|
65
|
+
deps = result["result"]
|
|
66
|
+
for dep in deps:
|
|
67
|
+
assert not os.path.isabs(dep["file"])
|
|
68
|
+
|
|
69
|
+
def test_parses_dev_requirements(self):
|
|
70
|
+
result = parse("requirements")
|
|
71
|
+
deps = result["result"]
|
|
72
|
+
black = find_dep(deps, "black")
|
|
73
|
+
assert black is not None
|
|
74
|
+
assert black["version"] == "22.10.0"
|
|
75
|
+
assert "requirements-dev.txt" in black["file"]
|
|
76
|
+
|
|
77
|
+
def test_parses_markers(self):
|
|
78
|
+
result = parse("requirements")
|
|
79
|
+
deps = result["result"]
|
|
80
|
+
pywin32 = find_dep(deps, "pywin32")
|
|
81
|
+
assert pywin32 is not None
|
|
82
|
+
assert pywin32["markers"] == 'sys_platform == "win32"'
|
|
83
|
+
|
|
84
|
+
def test_empty_directory_returns_empty(self):
|
|
85
|
+
result = parse("requirements_empty")
|
|
86
|
+
deps = result["result"]
|
|
87
|
+
assert deps == []
|
|
88
|
+
|
|
89
|
+
def test_constraint_file_deps(self):
|
|
90
|
+
"""Requirements with -c constraints should still parse the deps."""
|
|
91
|
+
result = parse("requirements")
|
|
92
|
+
deps = result["result"]
|
|
93
|
+
# with_constraints.txt has requests
|
|
94
|
+
req_files = [d["file"] for d in deps if d["name"] == "requests"]
|
|
95
|
+
assert any("with_constraints" in f for f in req_files)
|
|
96
|
+
|
|
97
|
+
def test_multiple_files_parsed(self):
|
|
98
|
+
"""All .txt files in the directory are parsed."""
|
|
99
|
+
result = parse("requirements")
|
|
100
|
+
deps = result["result"]
|
|
101
|
+
files = {d["file"] for d in deps}
|
|
102
|
+
assert any("requirements.txt" in f for f in files)
|
|
103
|
+
assert any("requirements-dev.txt" in f for f in files)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
sys.path.insert(
|
|
6
|
+
0, os.path.join(os.path.dirname(__file__), os.pardir, "lib")
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from parser import parse_setup # noqa: E402
|
|
10
|
+
|
|
11
|
+
FIXTURES = os.path.join(os.path.dirname(__file__), "fixtures")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def parse(fixture_dir):
|
|
15
|
+
path = os.path.join(FIXTURES, fixture_dir)
|
|
16
|
+
result = json.loads(parse_setup(path))
|
|
17
|
+
return result
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def find_dep(deps, name):
|
|
21
|
+
return next((d for d in deps if d["name"] == name), None)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# setup.py parsing
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
class TestSetupPy:
|
|
28
|
+
def test_parses_install_requires(self):
|
|
29
|
+
result = parse("setup_py")
|
|
30
|
+
deps = result["result"]
|
|
31
|
+
requests = find_dep(deps, "requests")
|
|
32
|
+
assert requests is not None
|
|
33
|
+
assert requests["requirement"] == ">=2.13.0"
|
|
34
|
+
assert requests["requirement_type"] == "install_requires"
|
|
35
|
+
assert requests["file"] == "setup.py"
|
|
36
|
+
|
|
37
|
+
def test_parses_pinned_version(self):
|
|
38
|
+
result = parse("setup_py")
|
|
39
|
+
deps = result["result"]
|
|
40
|
+
urllib3 = find_dep(deps, "urllib3")
|
|
41
|
+
assert urllib3 is not None
|
|
42
|
+
assert urllib3["version"] == "1.26.0"
|
|
43
|
+
|
|
44
|
+
def test_parses_setup_requires(self):
|
|
45
|
+
result = parse("setup_py")
|
|
46
|
+
deps = result["result"]
|
|
47
|
+
setuptools = find_dep(deps, "setuptools")
|
|
48
|
+
assert setuptools is not None
|
|
49
|
+
assert setuptools["requirement_type"] == "setup_requires"
|
|
50
|
+
|
|
51
|
+
def test_parses_tests_require(self):
|
|
52
|
+
result = parse("setup_py")
|
|
53
|
+
deps = result["result"]
|
|
54
|
+
pytest = find_dep(deps, "pytest")
|
|
55
|
+
assert pytest is not None
|
|
56
|
+
assert pytest["requirement_type"] == "tests_require"
|
|
57
|
+
|
|
58
|
+
def test_parses_extras_require(self):
|
|
59
|
+
result = parse("setup_py")
|
|
60
|
+
deps = result["result"]
|
|
61
|
+
pysocks = find_dep(deps, "PySocks")
|
|
62
|
+
assert pysocks is not None
|
|
63
|
+
assert pysocks["requirement_type"] == "extras_require:socks"
|
|
64
|
+
|
|
65
|
+
def test_parses_multiple_extras_groups(self):
|
|
66
|
+
result = parse("setup_py")
|
|
67
|
+
deps = result["result"]
|
|
68
|
+
extras = [d for d in deps if d["requirement_type"].startswith(
|
|
69
|
+
"extras_require:"
|
|
70
|
+
)]
|
|
71
|
+
groups = {d["requirement_type"] for d in extras}
|
|
72
|
+
assert "extras_require:socks" in groups
|
|
73
|
+
assert "extras_require:dev" in groups
|
|
74
|
+
|
|
75
|
+
def test_strips_comments(self):
|
|
76
|
+
result = parse("setup_py_comments")
|
|
77
|
+
deps = result["result"]
|
|
78
|
+
requests = find_dep(deps, "requests")
|
|
79
|
+
assert requests is not None
|
|
80
|
+
assert requests["requirement"] == ">=2.13.0"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
# setup.cfg parsing
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
class TestSetupCfg:
|
|
87
|
+
def test_parses_install_requires(self):
|
|
88
|
+
result = parse("setup_cfg")
|
|
89
|
+
deps = result["result"]
|
|
90
|
+
requests = find_dep(deps, "requests")
|
|
91
|
+
assert requests is not None
|
|
92
|
+
assert requests["requirement"] == ">=2.13.0"
|
|
93
|
+
assert requests["requirement_type"] == "install_requires"
|
|
94
|
+
assert requests["file"] == "setup.cfg"
|
|
95
|
+
|
|
96
|
+
def test_parses_pinned_version(self):
|
|
97
|
+
result = parse("setup_cfg")
|
|
98
|
+
deps = result["result"]
|
|
99
|
+
urllib3 = find_dep(deps, "urllib3")
|
|
100
|
+
assert urllib3 is not None
|
|
101
|
+
assert urllib3["version"] == "1.26.0"
|
|
102
|
+
|
|
103
|
+
def test_parses_setup_requires(self):
|
|
104
|
+
result = parse("setup_cfg")
|
|
105
|
+
deps = result["result"]
|
|
106
|
+
setuptools = find_dep(deps, "setuptools")
|
|
107
|
+
assert setuptools is not None
|
|
108
|
+
assert setuptools["requirement_type"] == "setup_requires"
|
|
109
|
+
|
|
110
|
+
def test_parses_tests_require(self):
|
|
111
|
+
result = parse("setup_cfg")
|
|
112
|
+
deps = result["result"]
|
|
113
|
+
pytest = find_dep(deps, "pytest")
|
|
114
|
+
assert pytest is not None
|
|
115
|
+
assert pytest["requirement_type"] == "tests_require"
|
|
116
|
+
|
|
117
|
+
def test_parses_extras_require(self):
|
|
118
|
+
result = parse("setup_cfg")
|
|
119
|
+
deps = result["result"]
|
|
120
|
+
pysocks = find_dep(deps, "PySocks")
|
|
121
|
+
assert pysocks is not None
|
|
122
|
+
assert pysocks["requirement_type"] == "extras_require:socks"
|
|
123
|
+
|
|
124
|
+
def test_empty_directory_returns_empty(self):
|
|
125
|
+
result = parse("requirements_empty")
|
|
126
|
+
deps = result["result"]
|
|
127
|
+
assert deps == []
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
# Add the helpers lib directory to the Python path so we can import parser
|
|
6
|
+
sys.path.insert(
|
|
7
|
+
0, os.path.join(os.path.dirname(__file__), os.pardir, "lib")
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from parser import parse_pep621_pep735_dependencies # noqa: E402
|
|
11
|
+
|
|
12
|
+
FIXTURES = os.path.join(os.path.dirname(__file__), "fixtures")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def parse(fixture_name):
|
|
16
|
+
path = os.path.join(FIXTURES, fixture_name)
|
|
17
|
+
result = json.loads(parse_pep621_pep735_dependencies(path))
|
|
18
|
+
return result["result"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def find_dep(deps, name):
|
|
22
|
+
return next((d for d in deps if d["name"] == name), None)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# PEP 621 project.dependencies
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
class TestPep621Dependencies:
|
|
29
|
+
def test_parses_runtime_dependencies(self):
|
|
30
|
+
deps = parse("pep621_dependencies.toml")
|
|
31
|
+
requests = find_dep(deps, "requests")
|
|
32
|
+
assert requests is not None
|
|
33
|
+
# packaging normalises specifier order alphabetically by operator
|
|
34
|
+
assert requests["requirement"] == "<3.0,>=2.13.0"
|
|
35
|
+
assert requests["requirement_type"] == "dependencies"
|
|
36
|
+
|
|
37
|
+
def test_parses_exact_version(self):
|
|
38
|
+
deps = parse("pep621_dependencies.toml")
|
|
39
|
+
urllib3 = find_dep(deps, "urllib3")
|
|
40
|
+
assert urllib3 is not None
|
|
41
|
+
assert urllib3["version"] == "1.26.0"
|
|
42
|
+
assert urllib3["requirement"] == "==1.26.0"
|
|
43
|
+
|
|
44
|
+
def test_parses_optional_dependencies(self):
|
|
45
|
+
deps = parse("pep621_dependencies.toml")
|
|
46
|
+
pysocks = find_dep(deps, "PySocks")
|
|
47
|
+
assert pysocks is not None
|
|
48
|
+
assert pysocks["requirement_type"] == "socks"
|
|
49
|
+
|
|
50
|
+
def test_optional_dependency_specifiers(self):
|
|
51
|
+
deps = parse("pep621_dependencies.toml")
|
|
52
|
+
pysocks = find_dep(deps, "PySocks")
|
|
53
|
+
# packaging normalises specifiers: sorted, no spaces
|
|
54
|
+
assert "!=" in pysocks["requirement"]
|
|
55
|
+
assert ">=" in pysocks["requirement"]
|
|
56
|
+
|
|
57
|
+
def test_parses_multiple_optional_groups(self):
|
|
58
|
+
deps = parse("pep621_dependencies.toml")
|
|
59
|
+
group_types = {d["requirement_type"] for d in deps}
|
|
60
|
+
assert "socks" in group_types
|
|
61
|
+
assert "tests" in group_types
|
|
62
|
+
|
|
63
|
+
def test_parses_build_system_requires(self):
|
|
64
|
+
deps = parse("pep621_dependencies.toml")
|
|
65
|
+
setuptools = find_dep(deps, "setuptools")
|
|
66
|
+
assert setuptools is not None
|
|
67
|
+
assert setuptools["requirement_type"] == "build-system.requires"
|
|
68
|
+
assert setuptools["requirement"] == ">=68.0"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
# PEP 621 extras
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
class TestPep621Extras:
|
|
75
|
+
def test_parses_extras(self):
|
|
76
|
+
deps = parse("pep621_extras.toml")
|
|
77
|
+
cc = find_dep(deps, "cachecontrol")
|
|
78
|
+
assert cc is not None
|
|
79
|
+
assert cc["extras"] == ["filecache"]
|
|
80
|
+
|
|
81
|
+
def test_extras_requirement(self):
|
|
82
|
+
deps = parse("pep621_extras.toml")
|
|
83
|
+
cc = find_dep(deps, "cachecontrol")
|
|
84
|
+
assert cc["requirement"] == ">=0.14.0"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
# PEP 735 dependency-groups
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
class TestPep735DependencyGroups:
|
|
91
|
+
def test_parses_dependency_group(self):
|
|
92
|
+
deps = parse("pep735_dependency_groups.toml")
|
|
93
|
+
pytest_dep = find_dep(deps, "pytest")
|
|
94
|
+
assert pytest_dep is not None
|
|
95
|
+
assert pytest_dep["requirement_type"] == "dev"
|
|
96
|
+
assert pytest_dep["version"] == "7.1.3"
|
|
97
|
+
|
|
98
|
+
def test_include_group_resolves(self):
|
|
99
|
+
deps = parse("pep735_dependency_groups.toml")
|
|
100
|
+
# "lint" group includes "dev" via include-group, plus flake8
|
|
101
|
+
lint_deps = [d for d in deps if d["requirement_type"] == "lint"]
|
|
102
|
+
lint_names = {d["name"] for d in lint_deps}
|
|
103
|
+
assert "flake8" in lint_names
|
|
104
|
+
# include-group pulls dev deps into lint but they keep their
|
|
105
|
+
# original group name, so they appear under "dev" requirement_type
|
|
106
|
+
all_names = {d["name"] for d in deps}
|
|
107
|
+
assert "pytest" in all_names
|
|
108
|
+
assert "black" in all_names
|
|
109
|
+
|
|
110
|
+
def test_include_group_lists_all_resolved_deps(self):
|
|
111
|
+
deps = parse("pep735_dependency_groups.toml")
|
|
112
|
+
# pytest appears twice: once from direct "dev" processing and
|
|
113
|
+
# once from "lint" including "dev" (both with requirement_type="dev"
|
|
114
|
+
# because included deps keep their original group name)
|
|
115
|
+
pytests = [d for d in deps if d["name"] == "pytest"]
|
|
116
|
+
assert len(pytests) == 2
|
|
117
|
+
assert all(d["requirement_type"] == "dev" for d in pytests)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
# Markers
|
|
122
|
+
# ---------------------------------------------------------------------------
|
|
123
|
+
class TestMarkers:
|
|
124
|
+
def test_parses_markers(self):
|
|
125
|
+
deps = parse("pep621_markers.toml")
|
|
126
|
+
requests = find_dep(deps, "requests")
|
|
127
|
+
assert requests is not None
|
|
128
|
+
assert requests["markers"] == 'python_version >= "3.8"'
|
|
129
|
+
|
|
130
|
+
def test_requirement_with_markers(self):
|
|
131
|
+
deps = parse("pep621_markers.toml")
|
|
132
|
+
requests = find_dep(deps, "requests")
|
|
133
|
+
assert requests["requirement"] == ">=2.13.0"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
# Edge cases
|
|
138
|
+
# ---------------------------------------------------------------------------
|
|
139
|
+
class TestEdgeCases:
|
|
140
|
+
def test_no_dependencies(self):
|
|
141
|
+
deps = parse("no_dependencies.toml")
|
|
142
|
+
assert deps == []
|
|
143
|
+
|
|
144
|
+
def test_only_build_system(self):
|
|
145
|
+
deps = parse("pep621_only_build_system.toml")
|
|
146
|
+
assert len(deps) == 2
|
|
147
|
+
names = {d["name"] for d in deps}
|
|
148
|
+
assert "setuptools" in names
|
|
149
|
+
assert "wheel" in names
|
|
150
|
+
assert all(
|
|
151
|
+
d["requirement_type"] == "build-system.requires" for d in deps
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def test_empty_dependency_lists(self):
|
|
155
|
+
deps = parse("pep621_empty_deps.toml")
|
|
156
|
+
assert deps == []
|
|
157
|
+
|
|
158
|
+
def test_multiple_extras_on_single_dep(self):
|
|
159
|
+
deps = parse("pep621_multiple_extras.toml")
|
|
160
|
+
boto3 = find_dep(deps, "boto3")
|
|
161
|
+
assert boto3 is not None
|
|
162
|
+
assert boto3["extras"] == ["crt", "s3"]
|
|
163
|
+
assert boto3["requirement"] == ">=1.28.0"
|
|
164
|
+
|
|
165
|
+
def test_arbitrary_equality_operator(self):
|
|
166
|
+
deps = parse("pep621_arbitrary_equality.toml")
|
|
167
|
+
numpy = find_dep(deps, "numpy")
|
|
168
|
+
assert numpy is not None
|
|
169
|
+
assert numpy["version"] == "1.24.0rc1"
|
|
170
|
+
assert numpy["requirement"] == "===1.24.0rc1"
|
|
171
|
+
|
|
172
|
+
def test_no_version_specifier(self):
|
|
173
|
+
deps = parse("pep621_no_version.toml")
|
|
174
|
+
requests = find_dep(deps, "requests")
|
|
175
|
+
assert requests is not None
|
|
176
|
+
assert requests["version"] is None
|
|
177
|
+
assert requests["requirement"] == ""
|
|
178
|
+
|
|
179
|
+
def test_cyclic_include_group_does_not_loop(self):
|
|
180
|
+
deps = parse("pep735_cycle.toml")
|
|
181
|
+
names = [d["name"] for d in deps]
|
|
182
|
+
# Both groups should be processed without infinite recursion
|
|
183
|
+
assert "requests" in names
|
|
184
|
+
assert "flask" in names
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
FIXTURES = os.path.join(os.path.dirname(__file__), "fixtures")
|
|
7
|
+
HELPERS_DIR = os.path.join(os.path.dirname(__file__), os.pardir)
|
|
8
|
+
RUN_PY = os.path.join(HELPERS_DIR, "run.py")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run_helper(function, args):
|
|
12
|
+
input_json = json.dumps({"function": function, "args": args})
|
|
13
|
+
result = subprocess.run(
|
|
14
|
+
[sys.executable, RUN_PY],
|
|
15
|
+
input=input_json,
|
|
16
|
+
capture_output=True,
|
|
17
|
+
text=True,
|
|
18
|
+
cwd=HELPERS_DIR,
|
|
19
|
+
)
|
|
20
|
+
assert result.returncode == 0, (
|
|
21
|
+
f"run.py failed: {result.stderr}"
|
|
22
|
+
)
|
|
23
|
+
return json.loads(result.stdout)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TestRunRouting:
|
|
27
|
+
def test_parse_pep621_routing(self):
|
|
28
|
+
fixture = os.path.join(FIXTURES, "pep621_dependencies.toml")
|
|
29
|
+
result = run_helper("parse_pep621_pep735_dependencies", [fixture])
|
|
30
|
+
|
|
31
|
+
assert "result" in result
|
|
32
|
+
names = {d["name"] for d in result["result"]}
|
|
33
|
+
assert "requests" in names
|
|
34
|
+
|
|
35
|
+
def test_parse_setup_routing(self):
|
|
36
|
+
fixture_dir = os.path.join(FIXTURES, "setup_py")
|
|
37
|
+
result = run_helper("parse_setup", [fixture_dir])
|
|
38
|
+
|
|
39
|
+
assert "result" in result
|
|
40
|
+
names = {d["name"] for d in result["result"]}
|
|
41
|
+
assert "requests" in names
|
|
42
|
+
|
|
43
|
+
def test_parse_requirements_routing(self):
|
|
44
|
+
fixture_dir = os.path.join(FIXTURES, "requirements")
|
|
45
|
+
result = run_helper("parse_requirements", [fixture_dir])
|
|
46
|
+
|
|
47
|
+
assert "result" in result
|
|
48
|
+
names = {d["name"] for d in result["result"]}
|
|
49
|
+
assert "requests" in names
|
|
@@ -124,8 +124,10 @@ module Dependabot
|
|
|
124
124
|
declaration_match = content.match(declaration_regex)
|
|
125
125
|
if declaration_match
|
|
126
126
|
declaration = declaration_match[:declaration]
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
if T.must(declaration).include?(old_req)
|
|
128
|
+
new_declaration = T.must(declaration).sub(old_req, new_req)
|
|
129
|
+
return content.sub(T.must(declaration), new_declaration)
|
|
130
|
+
end
|
|
129
131
|
end
|
|
130
132
|
|
|
131
133
|
# Try Poetry table format
|
|
@@ -220,9 +222,8 @@ module Dependabot
|
|
|
220
222
|
|
|
221
223
|
sig { params(pyproject_content: String).returns(String) }
|
|
222
224
|
def freeze_other_dependencies(pyproject_content)
|
|
223
|
-
PyprojectPreparer
|
|
224
|
-
|
|
225
|
-
.freeze_top_level_dependencies_except(dependencies)
|
|
225
|
+
PyprojectPreparer.new(pyproject_content: pyproject_content, lockfile: lockfile)
|
|
226
|
+
.freeze_top_level_dependencies_except(dependencies)
|
|
226
227
|
end
|
|
227
228
|
|
|
228
229
|
sig { params(pyproject_content: String).returns(String) }
|
|
@@ -244,14 +245,18 @@ module Dependabot
|
|
|
244
245
|
end
|
|
245
246
|
end
|
|
246
247
|
|
|
248
|
+
# Freeze PEP 621 project.dependencies and project.optional-dependencies
|
|
249
|
+
PyprojectPreparer.freeze_pep621_deps!(pyproject_object, dependencies) do |dep|
|
|
250
|
+
!git_dependency_being_updated?(dep)
|
|
251
|
+
end
|
|
252
|
+
|
|
247
253
|
TomlRB.dump(pyproject_object)
|
|
248
254
|
end
|
|
249
255
|
|
|
250
256
|
sig { params(pyproject_content: String).returns(String) }
|
|
251
257
|
def update_python_requirement(pyproject_content)
|
|
252
|
-
PyprojectPreparer
|
|
253
|
-
|
|
254
|
-
.update_python_requirement(language_version_manager.python_version)
|
|
258
|
+
PyprojectPreparer.new(pyproject_content: pyproject_content)
|
|
259
|
+
.update_python_requirement(language_version_manager.python_version)
|
|
255
260
|
end
|
|
256
261
|
|
|
257
262
|
sig { params(poetry_object: T::Hash[String, T.untyped], dep: Dependabot::Dependency).returns(T::Array[String]) }
|
|
@@ -262,6 +267,8 @@ module Dependabot
|
|
|
262
267
|
next unless pkg_name
|
|
263
268
|
|
|
264
269
|
if poetry_object[type][pkg_name].is_a?(Hash)
|
|
270
|
+
next unless poetry_object[type][pkg_name].key?("version") # skip enrichment-only entries
|
|
271
|
+
|
|
265
272
|
poetry_object[type][pkg_name]["version"] = dep.version
|
|
266
273
|
else
|
|
267
274
|
poetry_object[type][pkg_name] = dep.version
|
|
@@ -284,9 +291,7 @@ module Dependabot
|
|
|
284
291
|
|
|
285
292
|
sig { params(pyproject_content: String).returns(String) }
|
|
286
293
|
def sanitize(pyproject_content)
|
|
287
|
-
PyprojectPreparer
|
|
288
|
-
.new(pyproject_content: pyproject_content)
|
|
289
|
-
.sanitize
|
|
294
|
+
PyprojectPreparer.new(pyproject_content: pyproject_content).sanitize
|
|
290
295
|
end
|
|
291
296
|
|
|
292
297
|
sig { params(pyproject_content: String).returns(String) }
|
|
@@ -16,6 +16,80 @@ module Dependabot
|
|
|
16
16
|
class PyprojectPreparer
|
|
17
17
|
extend T::Sig
|
|
18
18
|
|
|
19
|
+
# Builds a regex pattern that matches a PEP 508 package name,
|
|
20
|
+
# treating hyphens, underscores, and dots as interchangeable per PEP 508.
|
|
21
|
+
sig { params(name: String).returns(String) }
|
|
22
|
+
def self.pep508_name_pattern(name)
|
|
23
|
+
Regexp.escape(name).gsub("\\-", "[-_.]").gsub("_", "[-_.]").gsub("\\.", "[-_.]")
|
|
24
|
+
end
|
|
25
|
+
private_class_method :pep508_name_pattern
|
|
26
|
+
|
|
27
|
+
# Pins a single PEP 508 dependency entry string to a specific version,
|
|
28
|
+
# preserving extras and environment markers.
|
|
29
|
+
sig { params(entry: String, name_pattern: String, version: String).returns(String) }
|
|
30
|
+
def self.pin_pep508_entry(entry, name_pattern, version)
|
|
31
|
+
if entry.match?(/\A#{name_pattern}(\[.*?\])?\s*[><=!~]/i)
|
|
32
|
+
entry.sub(
|
|
33
|
+
/(?<pre>#{name_pattern}(?:\[.*?\])?)\s*[><=!~][^;]*?(?=\s*;|\s*\z)/i,
|
|
34
|
+
"\\k<pre>==#{version}"
|
|
35
|
+
)
|
|
36
|
+
else
|
|
37
|
+
entry.sub(
|
|
38
|
+
/(?<pre>#{name_pattern}(?:\[.*?\])?)(?<rest>\s*(?:;.*)?)/i,
|
|
39
|
+
"\\k<pre>==#{version}\\k<rest>"
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
private_class_method :pin_pep508_entry
|
|
44
|
+
|
|
45
|
+
# Freezes PEP 621 dependencies in-place within a parsed pyproject object.
|
|
46
|
+
# Replaces version specifiers with ==dep.version for each matching dep.
|
|
47
|
+
# Accepts an optional block to filter which dependencies to freeze.
|
|
48
|
+
sig do
|
|
49
|
+
params(
|
|
50
|
+
pyproject_object: T::Hash[String, T.untyped],
|
|
51
|
+
deps: T::Array[Dependabot::Dependency],
|
|
52
|
+
blk: T.nilable(T.proc.params(dep: Dependabot::Dependency).returns(T::Boolean))
|
|
53
|
+
).void
|
|
54
|
+
end
|
|
55
|
+
def self.freeze_pep621_deps!(pyproject_object, deps, &blk)
|
|
56
|
+
dep_arrays = collect_pep621_dep_arrays(pyproject_object)
|
|
57
|
+
return if dep_arrays.empty?
|
|
58
|
+
|
|
59
|
+
deps.each do |dep|
|
|
60
|
+
next if blk && !yield(dep)
|
|
61
|
+
next unless dep.version
|
|
62
|
+
|
|
63
|
+
pin_pep621_dep_in_arrays!(dep_arrays, dep)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
sig { params(pyproject_object: T::Hash[String, T.untyped]).returns(T::Array[T::Array[String]]) }
|
|
68
|
+
def self.collect_pep621_dep_arrays(pyproject_object)
|
|
69
|
+
project_object = pyproject_object["project"]
|
|
70
|
+
return [] unless project_object
|
|
71
|
+
|
|
72
|
+
dep_arrays = [project_object["dependencies"]]
|
|
73
|
+
project_object["optional-dependencies"]&.each_value { |opt| dep_arrays << opt }
|
|
74
|
+
dep_arrays.compact!
|
|
75
|
+
dep_arrays.select! { |arr| arr.is_a?(Array) && arr.all?(String) }
|
|
76
|
+
dep_arrays
|
|
77
|
+
end
|
|
78
|
+
private_class_method :collect_pep621_dep_arrays
|
|
79
|
+
|
|
80
|
+
sig { params(dep_arrays: T::Array[T::Array[String]], dep: Dependabot::Dependency).void }
|
|
81
|
+
def self.pin_pep621_dep_in_arrays!(dep_arrays, dep)
|
|
82
|
+
name_pattern = pep508_name_pattern(dep.name)
|
|
83
|
+
dep_arrays.each do |arr|
|
|
84
|
+
arr.each_with_index do |entry, i|
|
|
85
|
+
next unless entry.match?(/\A#{name_pattern}(\[.*?\])?\s*(\z|[><=!~;,])/i)
|
|
86
|
+
|
|
87
|
+
arr[i] = pin_pep508_entry(entry, name_pattern, T.must(dep.version))
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
private_class_method :pin_pep621_dep_in_arrays!
|
|
92
|
+
|
|
19
93
|
sig { params(pyproject_content: String, lockfile: T.nilable(Dependabot::DependencyFile)).void }
|
|
20
94
|
def initialize(pyproject_content:, lockfile: nil)
|
|
21
95
|
@pyproject_content = pyproject_content
|
|
@@ -109,6 +183,9 @@ module Dependabot
|
|
|
109
183
|
end
|
|
110
184
|
end
|
|
111
185
|
|
|
186
|
+
# Freeze PEP 621 project.dependencies and project.optional-dependencies
|
|
187
|
+
freeze_pep621_top_level_deps!(pyproject_object, excluded_names)
|
|
188
|
+
|
|
112
189
|
TomlRB.dump(pyproject_object)
|
|
113
190
|
end
|
|
114
191
|
# rubocop:enable Metrics/AbcSize
|
|
@@ -122,6 +199,38 @@ module Dependabot
|
|
|
122
199
|
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
|
123
200
|
attr_reader :lockfile
|
|
124
201
|
|
|
202
|
+
sig { params(pyproject_object: T::Hash[String, T.untyped], excluded_names: T::Array[String]).void }
|
|
203
|
+
def freeze_pep621_top_level_deps!(pyproject_object, excluded_names)
|
|
204
|
+
project_object = pyproject_object["project"]
|
|
205
|
+
return unless project_object
|
|
206
|
+
|
|
207
|
+
freeze_pep621_dep_array!(project_object["dependencies"], excluded_names)
|
|
208
|
+
|
|
209
|
+
project_object["optional-dependencies"]&.each_value do |opt_deps|
|
|
210
|
+
freeze_pep621_dep_array!(opt_deps, excluded_names)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
sig { params(dep_array: T.nilable(T::Array[String]), excluded_names: T::Array[String]).void }
|
|
215
|
+
def freeze_pep621_dep_array!(dep_array, excluded_names)
|
|
216
|
+
return unless dep_array
|
|
217
|
+
|
|
218
|
+
dep_array.each_with_index do |entry, index|
|
|
219
|
+
# Extract dependency name from PEP 508 string
|
|
220
|
+
match = entry.match(/\A([a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?)/i)
|
|
221
|
+
next unless match
|
|
222
|
+
|
|
223
|
+
dep_name = normalise(T.must(match[1]))
|
|
224
|
+
next if excluded_names.include?(dep_name)
|
|
225
|
+
|
|
226
|
+
locked_details = locked_details(dep_name)
|
|
227
|
+
next unless (locked_version = locked_details&.fetch("version"))
|
|
228
|
+
|
|
229
|
+
name_pattern = self.class.send(:pep508_name_pattern, T.must(match[1]))
|
|
230
|
+
dep_array[index] = self.class.send(:pin_pep508_entry, entry, name_pattern, locked_version)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
125
234
|
sig { params(dep_name: String).returns(T.nilable(T::Hash[String, T.untyped])) }
|
|
126
235
|
def locked_details(dep_name)
|
|
127
236
|
parsed_lockfile.fetch("package")
|
|
@@ -9,6 +9,7 @@ require "sorbet-runtime"
|
|
|
9
9
|
require "dependabot/dependency"
|
|
10
10
|
require "dependabot/git_commit_checker"
|
|
11
11
|
require "dependabot/python/update_checker"
|
|
12
|
+
require "dependabot/update_checkers/cooldown_calculation"
|
|
12
13
|
require "dependabot/update_checkers/version_filters"
|
|
13
14
|
require "dependabot/registry_client"
|
|
14
15
|
require "dependabot/python/authed_url_builder"
|
|
@@ -110,8 +111,9 @@ module Dependabot
|
|
|
110
111
|
new_version = version_class.new(tag_version_str)
|
|
111
112
|
days = cooldown_days_for(current_version, new_version)
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
release_time = Time.at(release_date_to_seconds(tag_with_detail.release_date))
|
|
115
|
+
Dependabot::UpdateCheckers::CooldownCalculation
|
|
116
|
+
.within_cooldown_window?(release_time, days)
|
|
115
117
|
end
|
|
116
118
|
|
|
117
119
|
sig do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dependabot-python
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.369.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dependabot
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.369.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 0.369.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: debug
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -247,6 +247,31 @@ files:
|
|
|
247
247
|
- helpers/lib/parser.py
|
|
248
248
|
- helpers/requirements.txt
|
|
249
249
|
- helpers/run.py
|
|
250
|
+
- helpers/test/fixtures/no_dependencies.toml
|
|
251
|
+
- helpers/test/fixtures/pep621_arbitrary_equality.toml
|
|
252
|
+
- helpers/test/fixtures/pep621_dependencies.toml
|
|
253
|
+
- helpers/test/fixtures/pep621_empty_deps.toml
|
|
254
|
+
- helpers/test/fixtures/pep621_extras.toml
|
|
255
|
+
- helpers/test/fixtures/pep621_markers.toml
|
|
256
|
+
- helpers/test/fixtures/pep621_multiple_extras.toml
|
|
257
|
+
- helpers/test/fixtures/pep621_no_version.toml
|
|
258
|
+
- helpers/test/fixtures/pep621_only_build_system.toml
|
|
259
|
+
- helpers/test/fixtures/pep735_cycle.toml
|
|
260
|
+
- helpers/test/fixtures/pep735_dependency_groups.toml
|
|
261
|
+
- helpers/test/fixtures/requirements/constraints.txt
|
|
262
|
+
- helpers/test/fixtures/requirements/markers.txt
|
|
263
|
+
- helpers/test/fixtures/requirements/requirements-dev.txt
|
|
264
|
+
- helpers/test/fixtures/requirements/requirements.txt
|
|
265
|
+
- helpers/test/fixtures/requirements/with_constraints.txt
|
|
266
|
+
- helpers/test/fixtures/requirements_empty/.gitkeep
|
|
267
|
+
- helpers/test/fixtures/setup_cfg/setup.cfg
|
|
268
|
+
- helpers/test/fixtures/setup_py/setup.py
|
|
269
|
+
- helpers/test/fixtures/setup_py_comments/setup.py
|
|
270
|
+
- helpers/test/test_hasher.py
|
|
271
|
+
- helpers/test/test_parse_requirements.py
|
|
272
|
+
- helpers/test/test_parse_setup.py
|
|
273
|
+
- helpers/test/test_parser.py
|
|
274
|
+
- helpers/test/test_run.py
|
|
250
275
|
- lib/dependabot/python.rb
|
|
251
276
|
- lib/dependabot/python/authed_url_builder.rb
|
|
252
277
|
- lib/dependabot/python/dependency_grapher.rb
|
|
@@ -294,7 +319,7 @@ licenses:
|
|
|
294
319
|
- MIT
|
|
295
320
|
metadata:
|
|
296
321
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
|
297
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
|
322
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.369.0
|
|
298
323
|
rdoc_options: []
|
|
299
324
|
require_paths:
|
|
300
325
|
- lib
|