def test_packaging_req_to_rez_req(self): """ """ self.assertEqual( rez.utils.pip.packaging_req_to_rez_req( packaging_Requirement("package>1")), Requirement("package-1.1+")) self.assertEqual( rez.utils.pip.packaging_req_to_rez_req( packaging_Requirement("package")), Requirement("package")) self.assertEqual( rez.utils.pip.packaging_req_to_rez_req( packaging_Requirement("package[extra]")), Requirement("package"))
def reconstruct(req, marker_str=None, conditional_extras=None): new_req_str = req.name if req.specifier: new_req_str += " (%s)" % str(req.specifier) if marker_str is None and req.marker: marker_str = str(req.marker) if marker_str: new_req_str += " ; " + marker_str new_req = packaging_Requirement(new_req_str) setattr(new_req, "conditional_extras", conditional_extras) return new_req
def test_normalize_requirement(self): """ """ def assertRequirements(requirement, expected, conditional_extras): """ """ result = rez.utils.pip.normalize_requirement(requirement) self.assertEqual([str(req) for req in result], [str(req) for req in expected]) for index, req in enumerate(result): self.assertEqual(req.conditional_extras, conditional_extras[index]) assertRequirements("packageA", [packaging_Requirement("packageA")], [None]) assertRequirements("mypkg ; extra == 'dev'", [packaging_Requirement("mypkg")], [set(["dev"])]) assertRequirements( 'win-inet-pton ; (sys_platform == "win32" and python_version == "2.7") and extra == \'socks\'', [ packaging_Requirement( 'win-inet-pton; (sys_platform == "win32" and python_version == "2.7")' ) ], [set(["socks"])]) # PySocks (!=1.5.7,<2.0,>=1.5.6) ; extra == 'socks' assertRequirements( "PySocks (!=1.5.7,<2.0,>=1.5.6) ; extra == 'socks'", [packaging_Requirement("PySocks!=1.5.7,<2.0,>=1.5.6")], [set(["socks"])]) # certifi ; extra == 'secure' assertRequirements("certifi ; extra == 'secure'", [packaging_Requirement("certifi")], [set(["secure"])]) # coverage (>=4.4) assertRequirements('coverage (>=4.4)', [packaging_Requirement("coverage (>=4.4)")], [None]) # colorama ; sys_platform == "win32" assertRequirements( 'colorama ; sys_platform == "win32"', [packaging_Requirement('colorama ; sys_platform == "win32"')], [None]) # pathlib2-2.3.4: {u'environment': u'python_version<"3.5"', u'requires': [u'scandir']}, {u'requires': [u'six']} assertRequirements( { u"environment": u'python_version<"3.5"', u"requires": [u"scandir"] }, [packaging_Requirement('scandir; python_version < "3.5"')], [None]) # bleach-3.1.0: {u'requires': [u'six (>=1.9.0)', u'webencodings']} assertRequirements({u"requires": [u"six (>=1.9.0)", u"webencodings"]}, [ packaging_Requirement("six (>=1.9.0)"), packaging_Requirement("webencodings") ], [None, None]) assertRequirements( { u"requires": [u"six (>=1.9.0)", u'webencodings; sys_platform == "win32"'] }, [ packaging_Requirement("six (>=1.9.0)"), packaging_Requirement('webencodings; sys_platform == "win32"') ], [None, None]) assertRequirements({ u"requires": [u"packageA"], u"extra": "doc" }, [packaging_Requirement("packageA")], [set(["doc"])]) assertRequirements("mypkg ; extra == 'dev' or extra == 'doc'", [packaging_Requirement("mypkg")], [set(["dev", "doc"])]) assertRequirements( 'mypkg ; extra == "dev" and sys_platform == "win32"', [packaging_Requirement('mypkg; sys_platform == "win32"')], [set(["dev"])]) assertRequirements( 'mypkg ; sys_platform == "win32" and extra == "test"', [packaging_Requirement('mypkg; sys_platform == "win32"')], [set(["test"])])
def normalize_requirement(requirement): """Normalize a package requirement. Requirements from distlib packages can be a mix of string- or dict- based formats, as shown here: * https://www.python.org/dev/peps/pep-0508/#environment-markers * https://legacy.python.org/dev/peps/pep-0426/#environment-markers There's another confusing case that this code deals with. Consider these two requirements: # means: reportlab is a requirement of this package when the 'pdf' extra is requested Requires-Dist: reportlab; extra == 'pdf' means: this package requires libexample, with its 'test' extras Requires-Dist: libexample[test] See https://packaging.python.org/specifications/core-metadata/#provides-extra-multiple-use The packaging lib doesn't do a good job of expressing this - the first form of extras use just gets embedded in the environment marker. This function parses the extra from the marker, and stores it onto the resulting `packaging.Requirement` object in a 'conditional_extras' attribute. It also removes the extra from the marker (otherwise the marker cannot evaluate). Even though you can specify `environment` in `packaging.Marker.evaluate`, you can only supply a single 'extra' key in the env, so this can't be used to correctly evaluate if multiple extras were requested. Args: requirement (str or dict): Requirement, for eg from `distlib.database.InstalledDistribution.run_requires`. Returns: List of `packaging.requirements.Requirement`: Normalized requirements. Note that a list is returned, because the PEP426 format can define multiple requirements. """ def reconstruct(req, marker_str=None, conditional_extras=None): new_req_str = req.name if req.specifier: new_req_str += " (%s)" % str(req.specifier) if marker_str is None and req.marker: marker_str = str(req.marker) if marker_str: new_req_str += " ; " + marker_str new_req = packaging_Requirement(new_req_str) setattr(new_req, "conditional_extras", conditional_extras) return new_req # PEP426 dict syntax # So only metadata that are of version 2.0 will be in dict. The other versions # (1.0, 1.1, 1.2, 2.1) will be strings. if isinstance(requirement, dict): result = [] requires = requirement["requires"] extra = requirement.get("extra") marker_str = requirement.get("environment") # conditional extra, equivalent to: 'foo ; extra = "doc"' if extra: conditional_extras = set([extra]) else: conditional_extras = None for req_str in requires: req = packaging_Requirement(req_str) new_req = reconstruct(req, marker_str, conditional_extras) result.append(new_req) return result # string-based syntax req = packaging_Requirement(requirement) # detect case: "mypkg ; extra == 'dev'" # note: packaging lib already delimits with whitespace marker_str = str(req.marker) marker_parts = marker_str.split() # already in PEP508, packaging lib- friendly format if "extra" not in marker_parts: setattr(req, "conditional_extras", None) return [req] # Parse conditional extras out of marker conditional_extras = set() marker_str = marker_str.replace(" and ", " \nand ") marker_str = marker_str.replace(" or ", " \nor ") lines = marker_str.split('\n') lines = [x.strip() for x in lines] new_marker_lines = [] for line in lines: if "extra" in line.split(): extra = line.split()[-1] extra = extra.replace('"', '') extra = extra.replace("'", '') conditional_extras.add(extra) else: new_marker_lines.append(line) # reconstruct requirement in new syntax if new_marker_lines: new_marker_parts = ' '.join(new_marker_lines).split() if new_marker_parts[0] in ("and", "or"): new_marker_parts = new_marker_parts[1:] new_marker_str = ' '.join(new_marker_parts) else: new_marker_str = '' new_req = reconstruct(req, new_marker_str, conditional_extras) return [new_req]