diff --git a/backports.txt b/backports.txt new file mode 100644 index 0000000000..d0437a41db --- /dev/null +++ b/backports.txt @@ -0,0 +1 @@ +importlib-metadata diff --git a/openstack_requirements/check.py b/openstack_requirements/check.py index e12802d924..5066a79686 100644 --- a/openstack_requirements/check.py +++ b/openstack_requirements/check.py @@ -25,9 +25,6 @@ from openstack_requirements import requirement MIN_PY_VERSION = '3.5' PY3_SPECIFIER_RE = re.compile(r'python_version(==|>=|>)[\'"]3\.\d+[\'"]') -BACKPORTS = { - 'importlib-metadata', -} class RequirementsList(object): @@ -90,7 +87,12 @@ def _get_exclusions(req): ) -def _is_requirement_in_global_reqs(local_req, global_reqs, allow_3_only=False): +def _is_requirement_in_global_reqs( + local_req, + global_reqs, + backports, + allow_3_only=False, +): req_exclusions = _get_exclusions(local_req) for global_req in global_reqs: @@ -118,7 +120,7 @@ def _is_requirement_in_global_reqs(local_req, global_reqs, allow_3_only=False): allow_3_only and matching and aname == 'markers' and - local_req.package in BACKPORTS + local_req.package in backports ): if ( PY3_SPECIFIER_RE.match(global_req_val) or @@ -195,8 +197,16 @@ def _get_python3_reqs(reqs): return results -def _validate_one(name, reqs, blacklist, global_reqs, allow_3_only=False): +def _validate_one( + name, + reqs, + blacklist, + global_reqs, + backports, + allow_3_only=False, +): """Returns True if there is a failure.""" + if name in blacklist: # Blacklisted items are not synced and are managed # by project teams as they see fit, so no further @@ -213,7 +223,8 @@ def _validate_one(name, reqs, blacklist, global_reqs, allow_3_only=False): else: counts[''] = counts.get('', 0) + 1 if not _is_requirement_in_global_reqs( - req, global_reqs[name], allow_3_only): + req, global_reqs[name], backports, allow_3_only, + ): return True # check for minimum being defined min = [s for s in req.specifiers.split(',') if '>' in s] @@ -245,7 +256,13 @@ def _validate_one(name, reqs, blacklist, global_reqs, allow_3_only=False): return False -def validate(head_reqs, blacklist, global_reqs, allow_3_only=False): +def validate( + head_reqs, + blacklist, + global_reqs, + backports, + allow_3_only=False, +): failed = False # iterate through the changing entries and see if they match the global # equivalents we want enforced @@ -258,6 +275,7 @@ def validate(head_reqs, blacklist, global_reqs, allow_3_only=False): reqs, blacklist, global_reqs, + backports, allow_3_only, ) or failed diff --git a/openstack_requirements/tests/test_check.py b/openstack_requirements/tests/test_check.py index 694f42a28b..cf514b0e2b 100644 --- a/openstack_requirements/tests/test_check.py +++ b/openstack_requirements/tests/test_check.py @@ -26,6 +26,7 @@ class TestIsReqInGlobalReqs(testtools.TestCase): self._stdout_fixture = fixtures.StringStream('stdout') self.stdout = self.useFixture(self._stdout_fixture).stream + self.backports = dict() self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.stdout)) self.global_reqs = check.get_global_reqs(textwrap.dedent(""" @@ -41,6 +42,7 @@ class TestIsReqInGlobalReqs(testtools.TestCase): check._is_requirement_in_global_reqs( req, self.global_reqs['name'], + self.backports, ) ) @@ -52,6 +54,7 @@ class TestIsReqInGlobalReqs(testtools.TestCase): check._is_requirement_in_global_reqs( req, self.global_reqs['withmarker'], + self.backports, ) ) @@ -63,6 +66,7 @@ class TestIsReqInGlobalReqs(testtools.TestCase): check._is_requirement_in_global_reqs( req, self.global_reqs['withmarker'], + self.backports, allow_3_only=True ) ) @@ -73,6 +77,7 @@ class TestIsReqInGlobalReqs(testtools.TestCase): check._is_requirement_in_global_reqs( req, self.global_reqs['name'], + self.backports, ) ) @@ -82,6 +87,7 @@ class TestIsReqInGlobalReqs(testtools.TestCase): check._is_requirement_in_global_reqs( req, self.global_reqs['name'], + self.backports, ) ) @@ -91,6 +97,7 @@ class TestIsReqInGlobalReqs(testtools.TestCase): check._is_requirement_in_global_reqs( req, self.global_reqs['name'], + self.backports, ) ) @@ -100,6 +107,7 @@ class TestIsReqInGlobalReqs(testtools.TestCase): check._is_requirement_in_global_reqs( req, self.global_reqs['name'], + self.backports, ) ) @@ -135,6 +143,7 @@ class TestValidateOne(testtools.TestCase): self._stdout_fixture = fixtures.StringStream('stdout') self.stdout = self.useFixture(self._stdout_fixture).stream self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.stdout)) + self.backports = dict() def test_unchanged(self): # If the line matches the value in the branch list everything @@ -149,6 +158,7 @@ class TestValidateOne(testtools.TestCase): 'name', reqs=reqs, blacklist=requirement.parse(''), + backports=self.backports, global_reqs=global_reqs, ) ) @@ -165,6 +175,7 @@ class TestValidateOne(testtools.TestCase): 'name', reqs=reqs, blacklist=requirement.parse('name'), + backports=self.backports, global_reqs=global_reqs, ) ) @@ -182,6 +193,7 @@ class TestValidateOne(testtools.TestCase): 'name', reqs=reqs, blacklist=requirement.parse('name'), + backports=self.backports, global_reqs=global_reqs, ) ) @@ -198,6 +210,7 @@ class TestValidateOne(testtools.TestCase): 'name', reqs=reqs, blacklist=requirement.parse(''), + backports=self.backports, global_reqs=global_reqs, ) ) @@ -214,6 +227,7 @@ class TestValidateOne(testtools.TestCase): 'name', reqs=reqs, blacklist=requirement.parse(''), + backports=self.backports, global_reqs=global_reqs, ) ) @@ -231,6 +245,7 @@ class TestValidateOne(testtools.TestCase): 'name', reqs=reqs, blacklist=requirement.parse(''), + backports=self.backports, global_reqs=global_reqs, ) ) @@ -248,6 +263,7 @@ class TestValidateOne(testtools.TestCase): 'name', reqs=reqs, blacklist=requirement.parse(''), + backports=self.backports, global_reqs=global_reqs, ) ) @@ -265,6 +281,7 @@ class TestValidateOne(testtools.TestCase): 'name', reqs=reqs, blacklist=requirement.parse(''), + backports=self.backports, global_reqs=global_reqs, ) ) @@ -290,6 +307,7 @@ class TestValidateOne(testtools.TestCase): 'name', reqs=reqs, blacklist=requirement.parse(''), + backports=self.backports, global_reqs=global_reqs, ) ) @@ -314,6 +332,7 @@ class TestValidateOne(testtools.TestCase): 'name', reqs=reqs, blacklist=requirement.parse(''), + backports=self.backports, global_reqs=global_reqs, ) ) @@ -339,6 +358,7 @@ class TestValidateOne(testtools.TestCase): 'name', reqs=reqs, blacklist=requirement.parse(''), + backports=self.backports, global_reqs=global_reqs, ) ) @@ -365,6 +385,7 @@ class TestValidateOne(testtools.TestCase): 'name', reqs=reqs, blacklist=requirement.parse(''), + backports=self.backports, global_reqs=global_reqs, allow_3_only=True, ) @@ -392,6 +413,7 @@ class TestValidateOne(testtools.TestCase): 'name', reqs=reqs, blacklist=requirement.parse(''), + backports=self.backports, global_reqs=global_reqs, allow_3_only=True, ) @@ -418,6 +440,7 @@ class TestValidateOne(testtools.TestCase): 'name', reqs=reqs, blacklist=requirement.parse(''), + backports=self.backports, global_reqs=global_reqs, allow_3_only=True, ) @@ -442,6 +465,7 @@ class TestValidateOne(testtools.TestCase): 'name', reqs=reqs, blacklist=requirement.parse(''), + backports=self.backports, global_reqs=global_reqs, allow_3_only=True, ) @@ -696,3 +720,44 @@ class TestValidateLowerConstraints(testtools.TestCase): blacklist=requirement.parse(''), ) ) + + +class TestBackportPythonMarkers(testtools.TestCase): + + def setUp(self): + super(TestBackportPythonMarkers, self).setUp() + self._stdout_fixture = fixtures.StringStream('stdout') + self.stdout = self.useFixture(self._stdout_fixture).stream + self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.stdout)) + + self.req = requirement.parse(textwrap.dedent(""" + name>=1.5;python_version=='3.11' + """))['name'][0][0] + self.global_reqs = check.get_global_reqs(textwrap.dedent(""" + name>=1.5;python_version=='3.10' + """)) + + def test_notmatching_no_backport(self): + backports = requirement.parse("") + self.assertFalse( + check._is_requirement_in_global_reqs( + self.req, + self.global_reqs["name"], + list(backports.keys()), + allow_3_only=True, + ) + ) + + def test_notmatching_with_backport(self): + b_content = textwrap.dedent(""" + name + """) + backports = requirement.parse(b_content) + self.assertTrue( + check._is_requirement_in_global_reqs( + self.req, + self.global_reqs["name"], + list(backports.keys()), + allow_3_only=True, + ) + ) diff --git a/playbooks/files/project-requirements-change.py b/playbooks/files/project-requirements-change.py index 232fcfd200..2525ada8cc 100755 --- a/playbooks/files/project-requirements-change.py +++ b/playbooks/files/project-requirements-change.py @@ -113,6 +113,11 @@ def main(): global_reqs = check.get_global_reqs(f.read()) blacklist = requirement.parse( open(reqdir + '/blacklist.txt', 'rt').read()) + backports_file = reqdir + '/backports.txt' + if os.path.exists(backports_file): + backports = requirement.parse(open(backports_file, 'rt').read()) + else: + backports = {} cwd = os.getcwd() # build a list of requirements in the proposed change, # and check them for style violations while doing so @@ -136,6 +141,7 @@ def main(): head_reqs, blacklist, global_reqs, + list(backports.keys()), allow_3_only=python_3_branch, )