Skip to content

Commit

Permalink
Add further specifier parsing logic
Browse files Browse the repository at this point in the history
  • Loading branch information
stinodego committed Aug 16, 2022
1 parent 1abf043 commit d5d0d51
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 5 deletions.
38 changes: 34 additions & 4 deletions src/black/files.py
Expand Up @@ -18,7 +18,7 @@
)

from mypy_extensions import mypyc_attr
from packaging.specifiers import InvalidSpecifier, SpecifierSet
from packaging.specifiers import InvalidSpecifier, Specifier, SpecifierSet
from packaging.version import InvalidVersion, Version
from pathspec import PathSpec
from pathspec.patterns.gitwildmatch import GitWildMatchPatternError
Expand Down Expand Up @@ -182,10 +182,10 @@ def parse_req_python_specifier(requires_python: str) -> Optional[TargetVersion]:
If parsing fails, will raise a packaging.specifiers.InvalidSpecifier error.
If the parsed specifier cannot be mapped to a valid TargetVersion, returns None.
"""
if not requires_python:
return None
specifier_set = strip_specifier_set(SpecifierSet(requires_python))

specifier_set = SpecifierSet(requires_python)
if not specifier_set:
return None

target_version_map = {f"3.{v.value}": v for v in TargetVersion}
compatible_versions = specifier_set.filter(target_version_map)
Expand All @@ -195,6 +195,36 @@ def parse_req_python_specifier(requires_python: str) -> Optional[TargetVersion]:
return None


def strip_specifier_set(specifier_set: SpecifierSet) -> SpecifierSet:
"""Strip irrelevant parts of the specifier set.
Drops some specifiers, and strips minor versions for some others.
For background on version specifiers, see PEP 440:
https://peps.python.org/pep-0440/#version-specifiers
"""
specifiers = []
for s in specifier_set:
if "*" in str(s):
specifiers.append(s)
elif s.operator in ["~=", "==", ">=", "==="]:
version = Version(s.version)
stripped = Specifier(f"{s.operator}{version.major}.{version.minor}")
specifiers.append(stripped)
elif s.operator == ">":
version = Version(s.version)
if len(version.release) > 2:
s = Specifier(f">={version.major}.{version.minor}")
specifiers.append(s)
else:
specifiers.append(s)

if all(s.operator in ["<=", "<"] for s in specifiers):
specifiers = []

return SpecifierSet(",".join(str(s) for s in specifiers))


@lru_cache()
def find_user_pyproject_toml() -> Path:
r"""Return the path to the top-level user configuration for black.
Expand Down
6 changes: 5 additions & 1 deletion tests/test_black.py
Expand Up @@ -1370,12 +1370,16 @@ def test_infer_target_version(self) -> None:
(">=3.10", TargetVersion.PY310),
(">3.6,<3.10", TargetVersion.PY37),
("==3.8.*", TargetVersion.PY38),
# (">=3.8.6", TargetVersion.PY38), # Doesn't work yet
(">=3.8.6", TargetVersion.PY38),
("> 3.7.4, != 3.8.8", TargetVersion.PY37),
(">3.7,!=3.8", TargetVersion.PY39),
(None, None),
("", None),
("invalid", None),
("3", None),
("3.2", None),
("<3.11", None),
(">3.10,<3.11", None),
]:
test_toml = {"project": {"requires-python": version}}
result = black.files.infer_target_version(test_toml)
Expand Down

0 comments on commit d5d0d51

Please sign in to comment.