11import datetime
2+ import ipaddress
23import re
34import typing
45import uuid
3031 re .IGNORECASE ,
3132)
3233
34+ IPV4_REGEX = re .compile (
35+ r"(?:0|25[0-5]|2[0-4]\d|1\d?\d?|[1-9]\d?)"
36+ r"(?:\.(?:0|25[0-5]|2[0-4]\d|1\d?\d?|[1-9]\d?)){3}"
37+ )
38+
39+ IPV6_REGEX = re .compile (r"(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}" )
40+
3341
3442class BaseFormat :
3543 errors : typing .Dict [str , str ] = {}
@@ -44,7 +52,7 @@ def is_native_type(self, value: typing.Any) -> bool:
4452 def validate (self , value : typing .Any ) -> typing .Union [typing .Any , ValidationError ]:
4553 raise NotImplementedError () # pragma: no cover
4654
47- def serialize (self , obj : typing .Any ) -> typing .Union [str , None ]:
55+ def serialize (self , obj : typing .Any ) -> typing .Optional [str ]:
4856 raise NotImplementedError () # pragma: no cover
4957
5058
@@ -68,7 +76,7 @@ def validate(self, value: typing.Any) -> datetime.date:
6876 except ValueError :
6977 raise self .validation_error ("invalid" )
7078
71- def serialize (self , obj : typing .Any ) -> typing .Union [str , None ]:
79+ def serialize (self , obj : typing .Optional [ datetime . date ] ) -> typing .Optional [str ]:
7280 if obj is None :
7381 return None
7482
@@ -101,7 +109,7 @@ def validate(self, value: typing.Any) -> datetime.time:
101109 except ValueError :
102110 raise self .validation_error ("invalid" )
103111
104- def serialize (self , obj : typing .Any ) -> typing .Union [str , None ]:
112+ def serialize (self , obj : typing .Optional [ datetime . time ] ) -> typing .Optional [str ]:
105113 if obj is None :
106114 return None
107115
@@ -147,7 +155,9 @@ def validate(self, value: typing.Any) -> datetime.datetime:
147155 except ValueError :
148156 raise self .validation_error ("invalid" )
149157
150- def serialize (self , obj : typing .Any ) -> typing .Union [str , None ]:
158+ def serialize (
159+ self , obj : typing .Optional [datetime .datetime ]
160+ ) -> typing .Optional [str ]:
151161 if obj is None :
152162 return None
153163
@@ -174,7 +184,12 @@ def validate(self, value: typing.Any) -> uuid.UUID:
174184
175185 return uuid .UUID (value )
176186
177- def serialize (self , obj : typing .Any ) -> str :
187+ def serialize (self , obj : typing .Optional [uuid .UUID ]) -> typing .Optional [str ]:
188+ if obj is None :
189+ return None
190+
191+ assert isinstance (obj , uuid .UUID )
192+
178193 return str (obj )
179194
180195
@@ -184,12 +199,48 @@ class EmailFormat(BaseFormat):
184199 def is_native_type (self , value : typing .Any ) -> bool :
185200 return False
186201
187- def validate (self , value : typing . Any ) -> uuid . UUID :
202+ def validate (self , value : str ) -> str :
188203 match = EMAIL_REGEX .match (value )
189204 if not match :
190205 raise self .validation_error ("format" )
191206
192207 return value
193208
194- def serialize (self , obj : typing .Any ) -> str :
209+ def serialize (self , obj : typing .Optional [str ]) -> typing .Optional [str ]:
210+ if obj is None :
211+ return None
212+
213+ return obj
214+
215+
216+ class IPAddressFormat (BaseFormat ):
217+ errors = {
218+ "format" : "Must be a valid IP format." ,
219+ "invalid" : "Must be a real IP." ,
220+ }
221+
222+ def is_native_type (self , value : typing .Any ) -> bool :
223+ return isinstance (value , (ipaddress .IPv4Address , ipaddress .IPv6Address ))
224+
225+ def validate (
226+ self , value : typing .Any
227+ ) -> typing .Union [ipaddress .IPv4Address , ipaddress .IPv6Address ]:
228+ match_ipv4 = IPV4_REGEX .match (value )
229+ match_ipv6 = IPV6_REGEX .match (value )
230+ if not match_ipv4 and not match_ipv6 :
231+ raise self .validation_error ("format" )
232+
233+ try :
234+ return ipaddress .ip_address (value )
235+ except ValueError :
236+ raise self .validation_error ("invalid" )
237+
238+ def serialize (
239+ self , obj : typing .Union [ipaddress .IPv4Address , ipaddress .IPv6Address , None ]
240+ ) -> typing .Optional [str ]:
241+ if obj is None :
242+ return None
243+
244+ assert isinstance (obj , (ipaddress .IPv4Address , ipaddress .IPv6Address ))
245+
195246 return str (obj )
0 commit comments