From d4b637753ffd2361c1550efe49aab39d581651a7 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Thu, 8 Dec 2022 22:50:56 +0000 Subject: [PATCH 1/3] Use regular expression from specifiers as-is for requirement parsing Using the exact same patterns as `Specifier` helps ensure that the same parsing behaviour is seen for specifiers on their own as well as specifiers within requirements. --- src/packaging/_parser.py | 12 ++---------- src/packaging/_tokenizer.py | 5 ++++- tests/test_requirements.py | 4 ++-- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/packaging/_parser.py b/src/packaging/_parser.py index 4dcf03d1..020ea716 100644 --- a/src/packaging/_parser.py +++ b/src/packaging/_parser.py @@ -210,20 +210,12 @@ def _parse_specifier(tokenizer: Tokenizer) -> str: def _parse_version_many(tokenizer: Tokenizer) -> str: """ - version_many = (OP VERSION (COMMA OP VERSION)*)? + version_many = (SPECIFIER (WS? COMMA WS? SPECIFIER)*)? """ parsed_specifiers = "" - while tokenizer.check("OP"): + while tokenizer.check("SPECIFIER"): parsed_specifiers += tokenizer.read().text - - # We intentionally do not consume whitespace here, since the regular expression - # for `VERSION` uses a lookback for the operator, to determine what - # corresponding syntax is permitted. - - version_token = tokenizer.expect("VERSION", expected="version after operator") - parsed_specifiers += version_token.text tokenizer.consume("WS") - if not tokenizer.check("COMMA"): break parsed_specifiers += tokenizer.read().text diff --git a/src/packaging/_tokenizer.py b/src/packaging/_tokenizer.py index 17a47a11..b1fb207c 100644 --- a/src/packaging/_tokenizer.py +++ b/src/packaging/_tokenizer.py @@ -71,7 +71,10 @@ def __str__(self) -> str: """, re.VERBOSE, ), - "VERSION": re.compile(Specifier._version_regex_str, re.VERBOSE | re.IGNORECASE), + "SPECIFIER": re.compile( + Specifier._operator_regex_str + Specifier._version_regex_str, + re.VERBOSE | re.IGNORECASE, + ), "AT": r"\@", "URL": r"[^ \t]+", "IDENTIFIER": r"\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b", diff --git a/tests/test_requirements.py b/tests/test_requirements.py index 36ea474b..547a56b3 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -492,9 +492,9 @@ def test_error_on_missing_version_after_op(self) -> None: # THEN assert ctx.exconly() == ( "packaging.requirements.InvalidRequirement: " - "Expected version after operator\n" + "Expected end or semicolon (after name and no valid version specifier)\n" " name==\n" - " ^" + " ^" ) def test_error_on_missing_op_after_name(self) -> None: From bee120fbf55a6c4166bbbbc618c245dce948c3b9 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Thu, 8 Dec 2022 22:51:39 +0000 Subject: [PATCH 2/3] Reorder specifier matching to try longer names first This ensures that a non-greedy match still preferentially matches the longer format. --- src/packaging/specifiers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/packaging/specifiers.py b/src/packaging/specifiers.py index 645214ae..b8ab9277 100644 --- a/src/packaging/specifiers.py +++ b/src/packaging/specifiers.py @@ -140,7 +140,7 @@ class Specifier(BaseSpecifier): | (?: # pre release [-_\.]? - (a|b|c|rc|alpha|beta|pre|preview) + (alpha|beta|preview|pre|a|b|c|rc) [-_\.]? [0-9]* )? @@ -163,7 +163,7 @@ class Specifier(BaseSpecifier): [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *) (?: # pre release [-_\.]? - (a|b|c|rc|alpha|beta|pre|preview) + (alpha|beta|preview|pre|a|b|c|rc) [-_\.]? [0-9]* )? @@ -188,7 +188,7 @@ class Specifier(BaseSpecifier): [0-9]+(?:\.[0-9]+)* # release (?: # pre release [-_\.]? - (a|b|c|rc|alpha|beta|pre|preview) + (alpha|beta|preview|pre|a|b|c|rc) [-_\.]? [0-9]* )? From 946989c4260c4f69f89a0794a57d3607a0238d41 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Thu, 8 Dec 2022 22:52:43 +0000 Subject: [PATCH 3/3] Add a test case for non-normalised specifiers This validates that the specifier is parsed correctly. --- tests/test_requirements.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_requirements.py b/tests/test_requirements.py index 547a56b3..0a597be7 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -82,6 +82,7 @@ (None, "({ws}==={ws}arbitrarystring{ws})"), (None, "=={ws}1.0"), (None, "({ws}=={ws}1.0{ws})"), + (None, "=={ws}1.0-alpha"), (None, "<={ws}1!3.0.0.rc2"), (None, ">{ws}2.2{ws},{ws}<{ws}3"), (None, "(>{ws}2.2{ws},{ws}<{ws}3)"),