@@ -4,7 +4,7 @@ use std::str::Chars;
4
4
5
5
use pyo3:: intern;
6
6
use pyo3:: prelude:: * ;
7
- use pyo3:: types:: { PyDict , PyList } ;
7
+ use pyo3:: types:: { PyDict , PyList , PyType } ;
8
8
9
9
use ahash:: AHashSet ;
10
10
use url:: { ParseError , SyntaxViolation , Url } ;
@@ -26,6 +26,7 @@ type AllowedSchemas = Option<(AHashSet<String>, String)>;
26
26
#[ derive( Debug , Clone ) ]
27
27
pub struct UrlValidator {
28
28
strict : bool ,
29
+ cls : Option < Py < PyType > > ,
29
30
max_length : Option < usize > ,
30
31
allowed_schemes : AllowedSchemas ,
31
32
host_required : bool ,
@@ -47,6 +48,7 @@ impl BuildValidator for UrlValidator {
47
48
48
49
Ok ( Self {
49
50
strict : is_strict ( schema, config) ?,
51
+ cls : schema. get_as ( intern ! ( schema. py( ) , "cls" ) ) ?,
50
52
max_length : schema. get_as ( intern ! ( schema. py( ) , "max_length" ) ) ?,
51
53
host_required : schema. get_as ( intern ! ( schema. py( ) , "host_required" ) ) ?. unwrap_or ( false ) ,
52
54
default_host : schema. get_as ( intern ! ( schema. py( ) , "default_host" ) ) ?,
@@ -59,7 +61,7 @@ impl BuildValidator for UrlValidator {
59
61
}
60
62
}
61
63
62
- impl_py_gc_traverse ! ( UrlValidator { } ) ;
64
+ impl_py_gc_traverse ! ( UrlValidator { cls } ) ;
63
65
64
66
impl Validator for UrlValidator {
65
67
fn validate < ' py > (
@@ -93,7 +95,31 @@ impl Validator for UrlValidator {
93
95
Ok ( ( ) ) => {
94
96
// Lax rather than strict to preserve V2.4 semantic that str wins over url in union
95
97
state. floor_exactness ( Exactness :: Lax ) ;
96
- Ok ( either_url. into_py ( py) )
98
+
99
+ if let Some ( url_subclass) = & self . cls {
100
+ // TODO: we do an extra build for a subclass here, we should avoid this
101
+ // in v2.11 for perf reasons, but this is a worthwhile patch for now
102
+ // given that we want isinstance to work properly for subclasses of Url
103
+ let py_url = match either_url {
104
+ EitherUrl :: Py ( py_url) => py_url. get ( ) . clone ( ) ,
105
+ EitherUrl :: Rust ( rust_url) => PyUrl :: new ( rust_url) ,
106
+ } ;
107
+
108
+ let py_url = PyUrl :: build (
109
+ url_subclass. bind ( py) ,
110
+ py_url. scheme ( ) ,
111
+ py_url. host ( ) ,
112
+ py_url. username ( ) ,
113
+ py_url. password ( ) ,
114
+ py_url. port ( ) ,
115
+ py_url. path ( ) . filter ( |path| * path != "/" ) ,
116
+ py_url. query ( ) ,
117
+ py_url. fragment ( ) ,
118
+ ) ?;
119
+ Ok ( py_url. into_py ( py) )
120
+ } else {
121
+ Ok ( either_url. into_py ( py) )
122
+ }
97
123
}
98
124
Err ( error_type) => Err ( ValError :: new ( error_type, input) ) ,
99
125
}
@@ -186,6 +212,7 @@ impl CopyFromPyUrl for EitherUrl<'_> {
186
212
#[ derive( Debug , Clone ) ]
187
213
pub struct MultiHostUrlValidator {
188
214
strict : bool ,
215
+ cls : Option < Py < PyType > > ,
189
216
max_length : Option < usize > ,
190
217
allowed_schemes : AllowedSchemas ,
191
218
host_required : bool ,
@@ -213,6 +240,7 @@ impl BuildValidator for MultiHostUrlValidator {
213
240
}
214
241
Ok ( Self {
215
242
strict : is_strict ( schema, config) ?,
243
+ cls : schema. get_as ( intern ! ( schema. py( ) , "cls" ) ) ?,
216
244
max_length : schema. get_as ( intern ! ( schema. py( ) , "max_length" ) ) ?,
217
245
allowed_schemes,
218
246
host_required : schema. get_as ( intern ! ( schema. py( ) , "host_required" ) ) ?. unwrap_or ( false ) ,
@@ -225,7 +253,7 @@ impl BuildValidator for MultiHostUrlValidator {
225
253
}
226
254
}
227
255
228
- impl_py_gc_traverse ! ( MultiHostUrlValidator { } ) ;
256
+ impl_py_gc_traverse ! ( MultiHostUrlValidator { cls } ) ;
229
257
230
258
impl Validator for MultiHostUrlValidator {
231
259
fn validate < ' py > (
@@ -258,7 +286,38 @@ impl Validator for MultiHostUrlValidator {
258
286
Ok ( ( ) ) => {
259
287
// Lax rather than strict to preserve V2.4 semantic that str wins over url in union
260
288
state. floor_exactness ( Exactness :: Lax ) ;
261
- Ok ( multi_url. into_py ( py) )
289
+
290
+ if let Some ( url_subclass) = & self . cls {
291
+ // TODO: we do an extra build for a subclass here, we should avoid this
292
+ // in v2.11 for perf reasons, but this is a worthwhile patch for now
293
+ // given that we want isinstance to work properly for subclasses of Url
294
+ let py_url = match multi_url {
295
+ EitherMultiHostUrl :: Py ( py_url) => py_url. get ( ) . clone ( ) ,
296
+ EitherMultiHostUrl :: Rust ( rust_url) => rust_url,
297
+ } ;
298
+
299
+ let hosts = py_url
300
+ . hosts ( py) ?
301
+ . into_iter ( )
302
+ . map ( |host| host. extract ( ) . expect ( "host should be a valid UrlHostParts" ) )
303
+ . collect ( ) ;
304
+
305
+ let py_url = PyMultiHostUrl :: build (
306
+ url_subclass. bind ( py) ,
307
+ py_url. scheme ( ) ,
308
+ Some ( hosts) ,
309
+ py_url. path ( ) . filter ( |path| * path != "/" ) ,
310
+ py_url. query ( ) ,
311
+ py_url. fragment ( ) ,
312
+ None ,
313
+ None ,
314
+ None ,
315
+ None ,
316
+ ) ?;
317
+ Ok ( py_url. into_py ( py) )
318
+ } else {
319
+ Ok ( multi_url. into_py ( py) )
320
+ }
262
321
}
263
322
Err ( error_type) => Err ( ValError :: new ( error_type, input) ) ,
264
323
}
0 commit comments