From 89ab70daea0e1bf77eb8310a42fb7312a8076d0f Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Fri, 19 Jan 2024 10:24:56 +0000 Subject: [PATCH 1/2] Allow creating a SpecifierSet from a list of specifiers --- CHANGELOG.rst | 1 + src/packaging/specifiers.py | 21 +++++++++++++++------ tests/test_specifiers.py | 6 ++++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 153c2af6..73568358 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ Changelog have not been provided (:issue:`733`) * Fix a bug preventing the use of the built in ``ExceptionGroup`` on versions of Python that support it (:issue:`725`) +* Support creating a ``SpecifierSet`` from an iterable of ``Specifier`` objects (:issue:`775`) 23.2 - 2023-10-01 ~~~~~~~~~~~~~~~~~ diff --git a/src/packaging/specifiers.py b/src/packaging/specifiers.py index 2d015bab..3f88fd12 100644 --- a/src/packaging/specifiers.py +++ b/src/packaging/specifiers.py @@ -701,13 +701,17 @@ class SpecifierSet(BaseSpecifier): """ def __init__( - self, specifiers: str = "", prereleases: Optional[bool] = None + self, + specifiers: Union[str, Iterable[Specifier]] = "", + prereleases: Optional[bool] = None, ) -> None: """Initialize a SpecifierSet instance. :param specifiers: The string representation of a specifier or a comma-separated list of specifiers which will be parsed and normalized before use. + May also be an iterable of ``Specifier`` instances, which will be used + as is. :param prereleases: This tells the SpecifierSet if it should accept prerelease versions if applicable or not. The default of ``None`` will autodetect it from the @@ -718,12 +722,17 @@ def __init__( raised. """ - # Split on `,` to break each individual specifier into it's own item, and - # strip each item to remove leading/trailing whitespace. - split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] + if isinstance(specifiers, str): + # Split on `,` to break each individual specifier into its own item, and + # strip each item to remove leading/trailing whitespace. + split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] - # Make each individual specifier a Specifier and save in a frozen set for later. - self._specs = frozenset(map(Specifier, split_specifiers)) + # Make each individual specifier a Specifier and save in a frozen set + # for later. + self._specs = frozenset(map(Specifier, split_specifiers)) + else: + # Save the supplied specifiers in a frozen set. + self._specs = frozenset(specifiers) # Store our prereleases value so we can use it later to determine if # we accept prereleases or not. diff --git a/tests/test_specifiers.py b/tests/test_specifiers.py index a79b860e..de360981 100644 --- a/tests/test_specifiers.py +++ b/tests/test_specifiers.py @@ -650,6 +650,12 @@ def test_empty_specifier(self, version): assert parse(version) in spec assert spec.contains(parse(version)) + def test_create_from_specifiers(self): + spec_strs = [">=1.0", "!=1.1", "!=1.2", "<2.0"] + specs = [Specifier(s) for s in spec_strs] + spec = SpecifierSet(iter(specs)) + assert set(spec) == set(specs) + def test_specifier_prereleases_explicit(self): spec = SpecifierSet() assert not spec.prereleases From 805bf15ddf954101208d281bb2d20b7116d896e7 Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Thu, 8 Aug 2024 09:28:52 +0100 Subject: [PATCH 2/2] Change from Union to | in type annotation --- src/packaging/specifiers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packaging/specifiers.py b/src/packaging/specifiers.py index 54a12238..5fe042b3 100644 --- a/src/packaging/specifiers.py +++ b/src/packaging/specifiers.py @@ -696,7 +696,7 @@ class SpecifierSet(BaseSpecifier): def __init__( self, - specifiers: Union[str, Iterable[Specifier]] = "", + specifiers: str | Iterable[Specifier] = "", prereleases: bool | None = None, ) -> None: """Initialize a SpecifierSet instance.