@@ -43,6 +43,36 @@ class BaseTestUUID:
4343 uuid = None
4444 is_c_uuid = False
4545
46+ def test_generic_setattr (self ):
47+ # Libraries such as Pydantic bypass __init__ and set UUID slots directly.
48+ # Exercise that construction path for the base type and a subclass.
49+ class UUIDSubclass (self .uuid .UUID ):
50+ pass
51+
52+ class IntSubclass (int ):
53+ pass
54+
55+ for cls in (self .uuid .UUID , UUIDSubclass ):
56+ for value in (0 , 1 , 1 << 127 , (1 << 128 ) - 1 ,
57+ 0x123456789abcdef0123456789abcdef0 , IntSubclass (42 )):
58+ with self .subTest (cls = cls , value = value ):
59+ u = cls .__new__ (cls )
60+ object .__setattr__ (u , 'int' , value )
61+ object .__setattr__ (u , 'is_safe' , self .uuid .SafeUUID .safe )
62+ self .assertEqual (u .int , value )
63+ self .assertEqual (u .bytes , value .to_bytes (16 , 'big' ))
64+ self .assertEqual (u , self .uuid .UUID (int = value ))
65+ self .assertIs (u .is_safe , self .uuid .SafeUUID .safe )
66+ # Replacing the integer must invalidate an already cached hash.
67+ hash (u )
68+ object .__setattr__ (u , 'int' , 2 )
69+ self .assertEqual (hash (u ), hash (self .uuid .UUID (int = 2 )))
70+ self .assertEqual (u .int , 2 )
71+ with self .assertRaises (TypeError ):
72+ u .int = value
73+ with self .assertRaises (TypeError ):
74+ u .is_safe = self .uuid .SafeUUID .unknown
75+
4676 def test_nil_uuid (self ):
4777 nil_uuid = self .uuid .NIL
4878
@@ -1763,6 +1793,31 @@ def time_func(self):
17631793
17641794@unittest .skipUnless (c_uuid , "requires the C _uuid module" )
17651795class TestCImplementationCompat (unittest .TestCase ):
1796+ def test_int_setter_errors (self ):
1797+ # Reject invalid integers without changing the UUID's bytes or cached hash,
1798+ # even when an int subclass bypasses the preliminary range comparison.
1799+ import uuid
1800+
1801+ class LargeInt (int ):
1802+ def __gt__ (self , other ):
1803+ return False
1804+
1805+ u = uuid .UUID (int = 42 )
1806+ original_hash = hash (u )
1807+ for value , error in ((None , TypeError ), ('42' , TypeError ),
1808+ (1.5 , TypeError ), (- 1 , ValueError ),
1809+ (1 << 128 , ValueError ),
1810+ (LargeInt (1 << 128 ), OverflowError )):
1811+ with self .subTest (value = value ):
1812+ with self .assertRaises (error ):
1813+ object .__setattr__ (u , 'int' , value )
1814+ self .assertEqual (u .int , 42 )
1815+ self .assertEqual (hash (u ), original_hash )
1816+ for name in ('int' , 'is_safe' ):
1817+ with self .subTest (name = name ):
1818+ with self .assertRaises (AttributeError ):
1819+ object .__delattr__ (u , name )
1820+
17661821 def test_is_safe_lookup_error (self ):
17671822 # Force the lazy SafeUUID lookup to fail before it is cached. The getter
17681823 # must propagate AttributeError and recover when the attribute returns.
0 commit comments