Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aws
GitHub Repository: aws/aws-cli
Path: blob/develop/tests/dependencies/test_closure.py
1566 views
1
# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License"). You
4
# may not use this file except in compliance with the License. A copy of
5
# the License is located at
6
#
7
# http://aws.amazon.com/apache2.0/
8
#
9
# or in the "license" file accompanying this file. This file is
10
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
# ANY KIND, either express or implied. See the License for the specific
12
# language governing permissions and limitations under the License.
13
import functools
14
import importlib.metadata
15
import json
16
from typing import Dict, Iterator, List, Tuple
17
18
import pytest
19
from packaging.requirements import Requirement
20
21
_NESTED_STR_DICT = Dict[str, "_NESTED_STR_DICT"]
22
23
24
@pytest.fixture()
25
def awscli_package():
26
return Package(name="awscli")
27
28
29
class Package:
30
def __init__(self, name: str) -> None:
31
self.name = name
32
33
@functools.cached_property
34
def runtime_dependencies(self) -> "DependencyClosure":
35
return self._get_runtime_closure()
36
37
def _get_runtime_closure(self) -> "DependencyClosure":
38
closure = DependencyClosure()
39
for requirement in self._get_runtime_requirements():
40
if self._requirement_applies_to_environment(requirement):
41
closure[requirement] = Package(name=requirement.name)
42
return closure
43
44
def _get_runtime_requirements(self) -> List[Requirement]:
45
req_strings = importlib.metadata.distribution(self.name).requires
46
if req_strings is None:
47
return []
48
return [Requirement(req_string) for req_string in req_strings]
49
50
def _requirement_applies_to_environment(
51
self, requirement: Requirement
52
) -> bool:
53
# Do not include any requirements defined as extras as currently
54
# our dependency closure does not use any extras
55
if requirement.extras:
56
return False
57
# Only include requirements where the markers apply to the current
58
# environment.
59
if requirement.marker and not requirement.marker.evaluate():
60
return False
61
return True
62
63
64
class DependencyClosure:
65
def __init__(self) -> None:
66
self._req_to_package: Dict[Requirement, Package] = {}
67
68
def __setitem__(self, key: Requirement, value: Package) -> None:
69
self._req_to_package[key] = value
70
71
def __getitem__(self, key: Requirement) -> Package:
72
return self._req_to_package[key]
73
74
def __delitem__(self, key: Requirement) -> None:
75
del self._req_to_package[key]
76
77
def __iter__(self) -> Iterator[Requirement]:
78
return iter(self._req_to_package)
79
80
def __len__(self) -> int:
81
return len(self._req_to_package)
82
83
def walk(self) -> Iterator[Tuple[Requirement, Package]]:
84
for req, package in self._req_to_package.items():
85
yield req, package
86
yield from package.runtime_dependencies.walk()
87
88
def to_dict(self) -> _NESTED_STR_DICT:
89
reqs = {}
90
for req, package in self._req_to_package.items():
91
reqs[str(req)] = package.runtime_dependencies.to_dict()
92
return reqs
93
94
95
class TestDependencyClosure:
96
def _is_bounded_version_requirement(
97
self, requirement: Requirement
98
) -> bool:
99
for specifier in requirement.specifier:
100
if specifier.operator in ["==", "<=", "<"]:
101
return True
102
return False
103
104
def _pformat_closure(self, closure: DependencyClosure) -> str:
105
return json.dumps(closure.to_dict(), sort_keys=True, indent=2)
106
107
def test_expected_runtime_dependencies(self, awscli_package):
108
expected_dependencies = {
109
"botocore",
110
"colorama",
111
"docutils",
112
"jmespath",
113
"pyasn1",
114
"python-dateutil",
115
"PyYAML",
116
"rsa",
117
"s3transfer",
118
"six",
119
"urllib3",
120
}
121
actual_dependencies = set()
122
for _, package in awscli_package.runtime_dependencies.walk():
123
actual_dependencies.add(package.name)
124
assert actual_dependencies == expected_dependencies, (
125
f"Unexpected dependency found in runtime closure: "
126
f"{self._pformat_closure(awscli_package.runtime_dependencies)}"
127
)
128
129
def test_expected_unbounded_runtime_dependencies(self, awscli_package):
130
expected_unbounded_dependencies = {
131
"pyasn1", # Transitive dependency from rsa
132
"six", # Transitive dependency from python-dateutil
133
}
134
all_dependencies = set()
135
bounded_dependencies = set()
136
for req, package in awscli_package.runtime_dependencies.walk():
137
all_dependencies.add(package.name)
138
if self._is_bounded_version_requirement(req):
139
bounded_dependencies.add(package.name)
140
actual_unbounded_dependencies = all_dependencies - bounded_dependencies
141
assert (
142
actual_unbounded_dependencies == expected_unbounded_dependencies
143
), (
144
f"Unexpected unbounded dependency found in runtime closure: "
145
f"{self._pformat_closure(awscli_package.runtime_dependencies)}"
146
)
147
148