diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9b824d6c..3484dda7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -24,6 +24,7 @@ No unreleased changes. 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 2fa75f7a..5fe042b3 100644 --- a/src/packaging/specifiers.py +++ b/src/packaging/specifiers.py @@ -694,12 +694,18 @@ class SpecifierSet(BaseSpecifier): specifiers (``>=3.0,!=3.1``), or no specifier at all. """ - def __init__(self, specifiers: str = "", prereleases: bool | None = None) -> None: + def __init__( + self, + specifiers: str | Iterable[Specifier] = "", + prereleases: bool | None = 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 @@ -710,12 +716,17 @@ def __init__(self, specifiers: str = "", prereleases: bool | None = None) -> Non 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 bce215b2..af89928d 100644 --- a/tests/test_specifiers.py +++ b/tests/test_specifiers.py @@ -646,6 +646,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