11import re
2+ from collections .abc import Iterable
23from dataclasses import dataclass
34from typing import Any , Literal , Protocol , Self
45
@@ -81,7 +82,7 @@ def from_string(cls, string: str) -> Self:
8182 operator = next (filter (string .startswith , _OPERATORS ), None )
8283 if operator :
8384 version_string = string .removeprefix (operator ).lstrip ()
84- if not _SEMVER_PATTERN .match (string ):
85+ if not _SEMVER_PATTERN .match (version_string ):
8586 msg = f"Comparison version '{ version_string } ' of clause '{ string } ' does not conform to SemVer."
8687 raise ValueError (msg )
8788
@@ -91,7 +92,7 @@ def from_string(cls, string: str) -> Self:
9192 if not _SEMVER_PATTERN .match (string ):
9293 msg = (
9394 f"Version specifier clause '{ string } ' does not start with a valid operator and isn't a "
94- f"version itself. Valid operators are { ', ' .join (_OPERATORS )} ."
95+ f"version itself. Valid operators are { ", " .join (_OPERATORS )} ."
9596 )
9697 raise ValueError (msg )
9798
@@ -103,10 +104,18 @@ def from_string(cls, string: str) -> Self:
103104 def __str__ (self ) -> str :
104105 return f"{ self .operator } { self .operand } "
105106
106- clauses : tuple [Clause , ...]
107+ # Dict because we want to preserve order (for readability) but not compare order or allow dupes.
108+ _clauses : dict [Clause , None ]
109+
110+ def __init__ (self , clauses : Iterable [Clause ]) -> None :
111+ super ().__setattr__ ("_clauses" , dict .fromkeys (clauses ))
112+
113+ @property
114+ def clauses (self ) -> tuple [Clause , ...]:
115+ return tuple (self ._clauses )
107116
108117 def __str__ (self ) -> str :
109- return ", " .join (map (str , self .clauses ))
118+ return ", " .join (map (str , self ._clauses ))
110119
111120 @classmethod
112121 def from_string (cls , string : str ) -> Self :
@@ -116,7 +125,7 @@ def from_string(cls, string: str) -> Self:
116125
117126 def allows (self , version : VersionProtocol ) -> bool :
118127 """Checks if _all_ clauses allow the given version."""
119- return all (clause .allows (version ) for clause in self .clauses )
128+ return all (clause .allows (version ) for clause in self ._clauses )
120129
121130 @classmethod
122131 def __get_pydantic_core_schema__ (cls , source_type : Any , handler : GetCoreSchemaHandler ) -> CoreSchema :
0 commit comments