diff --git a/etc/scripts/utils_requirements.py b/etc/scripts/utils_requirements.py index b9b2c0e7..100ca88c 100644 --- a/etc/scripts/utils_requirements.py +++ b/etc/scripts/utils_requirements.py @@ -15,7 +15,7 @@ """ Utilities to manage requirements files and call pip. NOTE: this should use ONLY the standard library and not import anything else -because this is used for boostrapping with no requirements installed. +because this is used for bootstrapping with no requirements installed. """ @@ -31,7 +31,7 @@ def load_requirements(requirements_file="requirements.txt", with_unpinned=False) def get_required_name_versions(requirement_lines, with_unpinned=False): """ - Yield required (name, version) tuples given a`requirement_lines` iterable of + Yield required (name, version) tuples given a `requirement_lines` iterable of requirement text lines. Only accept requirements pinned to an exact version. """ @@ -47,21 +47,26 @@ def get_required_name_versions(requirement_lines, with_unpinned=False): def get_required_name_version(requirement, with_unpinned=False): """ - Return a (name, version) tuple given a`requirement` specifier string. + Return a (name, version) tuple given a `requirement` specifier string. Requirement version must be pinned. If ``with_unpinned`` is True, unpinned requirements are accepted and only the name portion is returned. - For example: - >>> assert get_required_name_version("foo==1.2.3") == ("foo", "1.2.3") - >>> assert get_required_name_version("fooA==1.2.3.DEV1") == ("fooa", "1.2.3.dev1") - >>> assert get_required_name_version("foo==1.2.3", with_unpinned=False) == ("foo", "1.2.3") - >>> assert get_required_name_version("foo", with_unpinned=True) == ("foo", "") - >>> expected = ("foo", ""), get_required_name_version("foo>=1.2") - >>> assert get_required_name_version("foo>=1.2", with_unpinned=True) == expected + Examples: + >>> get_required_name_version("foo==1.2.3") + ('foo', '1.2.3') + >>> get_required_name_version("fooA==1.2.3.DEV1") + ('fooa', '1.2.3.dev1') + >>> get_required_name_version("foo==1.2.3", with_unpinned=False) + ('foo', '1.2.3') + >>> get_required_name_version("foo", with_unpinned=True) + ('foo', '') + >>> get_required_name_version("foo>=1.2", with_unpinned=True) + ('foo', '') >>> try: - ... assert not get_required_name_version("foo", with_unpinned=False) + ... get_required_name_version("foo", with_unpinned=False) ... except Exception as e: - ... assert "Requirement version must be pinned" in str(e) + ... "Requirement version must be pinned" in str(e) + True """ requirement = requirement and "".join(requirement.lower().split()) if not requirement: @@ -95,9 +100,7 @@ def lock_dev_requirements( """ Freeze and lock current installed development-only requirements and save this to the `dev_requirements_file` requirements file. Development-only is - achieved by subtracting requirements from the `main_requirements_file` - requirements file from the current requirements using package names (and - ignoring versions). + achieved by subtracting requirements from the main requirements using names only. """ main_names = {n for n, _v in load_requirements(main_requirements_file)} all_reqs = get_installed_reqs(site_packages_dir=site_packages_dir) @@ -112,13 +115,11 @@ def lock_dev_requirements( def get_installed_reqs(site_packages_dir): """ - Return the installed pip requirements as text found in `site_packages_dir` - as a text. + Return the installed pip requirements as text found in `site_packages_dir`. """ if not os.path.exists(site_packages_dir): raise Exception(f"site_packages directory: {site_packages_dir!r} does not exists") - # Also include these packages in the output with --all: wheel, distribute, - # setuptools, pip + args = ["pip", "freeze", "--exclude-editable", "--all", "--path", site_packages_dir] return subprocess.check_output(args, encoding="utf-8") # noqa: S603 @@ -140,23 +141,29 @@ def get_installed_reqs(site_packages_dir): def split_req(req): """ - Return a three-tuple of (name, comparator, version) given a ``req`` - requirement specifier string. Each segment may be empty. Spaces are removed. - - For example: - >>> assert split_req("foo==1.2.3") == ("foo", "==", "1.2.3"), split_req("foo==1.2.3") - >>> assert split_req("foo") == ("foo", "", ""), split_req("foo") - >>> assert split_req("==1.2.3") == ("", "==", "1.2.3"), split_req("==1.2.3") - >>> assert split_req("foo >= 1.2.3 ") == ("foo", ">=", "1.2.3"), split_req("foo >= 1.2.3 ") - >>> assert split_req("foo>=1.2") == ("foo", ">=", "1.2"), split_req("foo>=1.2") + Return a three-tuple of (name, comparator, version) given a requirement + specifier string. + + Examples: + >>> split_req("foo==1.2.3") + ('foo', '==', '1.2.3') + >>> split_req("foo") + ('foo', '', '') + >>> split_req("==1.2.3") + ('', '==', '1.2.3') + >>> split_req("foo >= 1.2.3 ") + ('foo', '>=', '1.2.3') + >>> split_req("foo>=1.2") + ('foo', '>=', '1.2') """ if not req: raise ValueError("req is required") - # do not allow multiple constraints and tags if not any(c in req for c in ",;"): raise Exception(f"complex requirements with : or ; not supported: {req}") + req = "".join(req.split()) if not any(c in req for c in comparators): return req, "", "" + segments = version_splitter.split(req, maxsplit=1) return tuple(segments)