@@ -43,7 +43,14 @@ def safezip(*args):
4343 return zip (* args )
4444
4545
46- def is_uuid (u ):
46+ UUID_PATTERN = re .compile (r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" , re .I )
47+
48+
49+ def is_uuid (u : str ) -> bool :
50+ # E.g., hashlib.md5(b'hello') is a 32-letter hex number, but not an UUID.
51+ # It would fail UUID-like comparison (< & >) because of casing and dashes.
52+ if not UUID_PATTERN .fullmatch (u ):
53+ return False
4754 try :
4855 UUID (u )
4956 except ValueError :
@@ -128,23 +135,75 @@ def range(self, other: "ArithString", count: int) -> List[Self]:
128135 return [self .new (int = i ) for i in checkpoints ]
129136
130137
131- # @attrs.define # not as long as it inherits from UUID
132- class ArithUUID (UUID , ArithString ):
138+ def _any_to_uuid (v : Union [str , int , UUID , "ArithUUID" ]) -> UUID :
139+ if isinstance (v , ArithUUID ):
140+ return v .uuid
141+ elif isinstance (v , UUID ):
142+ return v
143+ elif isinstance (v , str ):
144+ return UUID (v )
145+ elif isinstance (v , int ):
146+ return UUID (int = v )
147+ else :
148+ raise ValueError (f"Cannot convert a value to UUID: { v !r} " )
149+
150+
151+ @attrs .define (frozen = True , eq = False , order = False )
152+ class ArithUUID (ArithString ):
133153 "A UUID that supports basic arithmetic (add, sub)"
134154
155+ uuid : UUID = attrs .field (converter = _any_to_uuid )
156+ lowercase : Optional [bool ] = None
157+ uppercase : Optional [bool ] = None
158+
159+ def range (self , other : "ArithUUID" , count : int ) -> List [Self ]:
160+ assert isinstance (other , ArithUUID )
161+ checkpoints = split_space (self .uuid .int , other .uuid .int , count )
162+ return [attrs .evolve (self , uuid = i ) for i in checkpoints ]
163+
135164 def __int__ (self ):
136- return self .int
165+ return self .uuid . int
137166
138167 def __add__ (self , other : int ) -> Self :
139168 if isinstance (other , int ):
140- return self . new ( int = self .int + other )
169+ return attrs . evolve ( self , uuid = self . uuid .int + other )
141170 return NotImplemented
142171
143- def __sub__ (self , other : Union [UUID , int ]):
172+ def __sub__ (self , other : Union ["ArithUUID" , int ]):
144173 if isinstance (other , int ):
145- return self .new (int = self .int - other )
146- elif isinstance (other , UUID ):
147- return self .int - other .int
174+ return attrs .evolve (self , uuid = self .uuid .int - other )
175+ elif isinstance (other , ArithUUID ):
176+ return self .uuid .int - other .uuid .int
177+ return NotImplemented
178+
179+ def __eq__ (self , other : object ) -> bool :
180+ if isinstance (other , ArithUUID ):
181+ return self .uuid == other .uuid
182+ return NotImplemented
183+
184+ def __ne__ (self , other : object ) -> bool :
185+ if isinstance (other , ArithUUID ):
186+ return self .uuid != other .uuid
187+ return NotImplemented
188+
189+ def __gt__ (self , other : object ) -> bool :
190+ if isinstance (other , ArithUUID ):
191+ return self .uuid > other .uuid
192+ return NotImplemented
193+
194+ def __lt__ (self , other : object ) -> bool :
195+ if isinstance (other , ArithUUID ):
196+ return self .uuid < other .uuid
197+ return NotImplemented
198+
199+ def __ge__ (self , other : object ) -> bool :
200+ if isinstance (other , ArithUUID ):
201+ return self .uuid >= other .uuid
202+ return NotImplemented
203+
204+ def __le__ (self , other : object ) -> bool :
205+ if isinstance (other , ArithUUID ):
206+ return self .uuid <= other .uuid
148207 return NotImplemented
149208
150209
0 commit comments