Skip to content

Commit 89bdee1

Browse files
Revert changes to url schemas that support cls, the 2x validation isn't worth it (and is breaking) (#1514)
1 parent 9217019 commit 89bdee1

File tree

6 files changed

+15
-100
lines changed

6 files changed

+15
-100
lines changed

python/pydantic_core/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ class ErrorTypeInfo(_TypedDict):
124124
"""Example of context values."""
125125

126126

127-
class MultiHostHost(_TypedDict, total=False):
127+
class MultiHostHost(_TypedDict):
128128
"""
129129
A host part of a multi-host URL.
130130
"""

python/pydantic_core/_pydantic_core.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ class Url(SupportsAllComparisons):
583583
scheme: str,
584584
username: str | None = None,
585585
password: str | None = None,
586-
host: str | None = None,
586+
host: str,
587587
port: int | None = None,
588588
path: str | None = None,
589589
query: str | None = None,
@@ -596,7 +596,7 @@ class Url(SupportsAllComparisons):
596596
scheme: The scheme part of the URL.
597597
username: The username part of the URL, or omit for no username.
598598
password: The password part of the URL, or omit for no password.
599-
host: The host part of the URL, or omit for no host.
599+
host: The host part of the URL.
600600
port: The port part of the URL, or omit for no port.
601601
path: The path part of the URL, or omit for no path.
602602
query: The query part of the URL, or omit for no query.

python/pydantic_core/core_schema.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3655,7 +3655,6 @@ class MyModel:
36553655

36563656
class UrlSchema(TypedDict, total=False):
36573657
type: Required[Literal['url']]
3658-
cls: Type[Any]
36593658
max_length: int
36603659
allowed_schemes: List[str]
36613660
host_required: bool # default False
@@ -3670,7 +3669,6 @@ class UrlSchema(TypedDict, total=False):
36703669

36713670
def url_schema(
36723671
*,
3673-
cls: Type[Any] | None = None,
36743672
max_length: int | None = None,
36753673
allowed_schemes: list[str] | None = None,
36763674
host_required: bool | None = None,
@@ -3695,7 +3693,6 @@ def url_schema(
36953693
```
36963694
36973695
Args:
3698-
cls: The class to use for the URL build (a subclass of `pydantic_core.Url`)
36993696
max_length: The maximum length of the URL
37003697
allowed_schemes: The allowed URL schemes
37013698
host_required: Whether the URL must have a host
@@ -3709,7 +3706,6 @@ def url_schema(
37093706
"""
37103707
return _dict_not_none(
37113708
type='url',
3712-
cls=cls,
37133709
max_length=max_length,
37143710
allowed_schemes=allowed_schemes,
37153711
host_required=host_required,
@@ -3725,7 +3721,6 @@ def url_schema(
37253721

37263722
class MultiHostUrlSchema(TypedDict, total=False):
37273723
type: Required[Literal['multi-host-url']]
3728-
cls: Type[Any]
37293724
max_length: int
37303725
allowed_schemes: List[str]
37313726
host_required: bool # default False
@@ -3740,7 +3735,6 @@ class MultiHostUrlSchema(TypedDict, total=False):
37403735

37413736
def multi_host_url_schema(
37423737
*,
3743-
cls: Type[Any] | None = None,
37443738
max_length: int | None = None,
37453739
allowed_schemes: list[str] | None = None,
37463740
host_required: bool | None = None,
@@ -3765,7 +3759,6 @@ def multi_host_url_schema(
37653759
```
37663760
37673761
Args:
3768-
cls: The class to use for the URL build (a subclass of `pydantic_core.MultiHostUrl`)
37693762
max_length: The maximum length of the URL
37703763
allowed_schemes: The allowed URL schemes
37713764
host_required: Whether the URL must have a host
@@ -3779,7 +3772,6 @@ def multi_host_url_schema(
37793772
"""
37803773
return _dict_not_none(
37813774
type='multi-host-url',
3782-
cls=cls,
37833775
max_length=max_length,
37843776
allowed_schemes=allowed_schemes,
37853777
host_required=host_required,

src/url.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,12 @@ impl PyUrl {
156156
}
157157

158158
#[classmethod]
159-
#[pyo3(signature=(*, scheme, host=None, username=None, password=None, port=None, path=None, query=None, fragment=None))]
159+
#[pyo3(signature=(*, scheme, host, username=None, password=None, port=None, path=None, query=None, fragment=None))]
160160
#[allow(clippy::too_many_arguments)]
161161
pub fn build<'py>(
162162
cls: &Bound<'py, PyType>,
163163
scheme: &str,
164-
host: Option<&str>,
164+
host: &str,
165165
username: Option<&str>,
166166
password: Option<&str>,
167167
port: Option<u16>,
@@ -172,7 +172,7 @@ impl PyUrl {
172172
let url_host = UrlHostParts {
173173
username: username.map(Into::into),
174174
password: password.map(Into::into),
175-
host: host.map(Into::into),
175+
host: Some(host.into()),
176176
port,
177177
};
178178
let mut url = format!("{scheme}://{url_host}");
@@ -423,7 +423,6 @@ impl PyMultiHostUrl {
423423
}
424424
}
425425

426-
#[cfg_attr(debug_assertions, derive(Debug))]
427426
pub struct UrlHostParts {
428427
username: Option<String>,
429428
password: Option<String>,
@@ -441,12 +440,11 @@ impl FromPyObject<'_> for UrlHostParts {
441440
fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
442441
let py = ob.py();
443442
let dict = ob.downcast::<PyDict>()?;
444-
445443
Ok(UrlHostParts {
446-
username: dict.get_as::<Option<_>>(intern!(py, "username"))?.flatten(),
447-
password: dict.get_as::<Option<_>>(intern!(py, "password"))?.flatten(),
448-
host: dict.get_as::<Option<_>>(intern!(py, "host"))?.flatten(),
449-
port: dict.get_as::<Option<_>>(intern!(py, "port"))?.flatten(),
444+
username: dict.get_as(intern!(py, "username"))?,
445+
password: dict.get_as(intern!(py, "password"))?,
446+
host: dict.get_as(intern!(py, "host"))?,
447+
port: dict.get_as(intern!(py, "port"))?,
450448
})
451449
}
452450
}

src/validators/url.rs

Lines changed: 5 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::str::Chars;
44

55
use pyo3::intern;
66
use pyo3::prelude::*;
7-
use pyo3::types::{PyDict, PyList, PyType};
7+
use pyo3::types::{PyDict, PyList};
88

99
use ahash::AHashSet;
1010
use url::{ParseError, SyntaxViolation, Url};
@@ -26,7 +26,6 @@ type AllowedSchemas = Option<(AHashSet<String>, String)>;
2626
#[derive(Debug, Clone)]
2727
pub struct UrlValidator {
2828
strict: bool,
29-
cls: Option<Py<PyType>>,
3029
max_length: Option<usize>,
3130
allowed_schemes: AllowedSchemas,
3231
host_required: bool,
@@ -48,7 +47,6 @@ impl BuildValidator for UrlValidator {
4847

4948
Ok(Self {
5049
strict: is_strict(schema, config)?,
51-
cls: schema.get_as(intern!(schema.py(), "cls"))?,
5250
max_length: schema.get_as(intern!(schema.py(), "max_length"))?,
5351
host_required: schema.get_as(intern!(schema.py(), "host_required"))?.unwrap_or(false),
5452
default_host: schema.get_as(intern!(schema.py(), "default_host"))?,
@@ -61,7 +59,7 @@ impl BuildValidator for UrlValidator {
6159
}
6260
}
6361

64-
impl_py_gc_traverse!(UrlValidator { cls });
62+
impl_py_gc_traverse!(UrlValidator {});
6563

6664
impl Validator for UrlValidator {
6765
fn validate<'py>(
@@ -95,31 +93,7 @@ impl Validator for UrlValidator {
9593
Ok(()) => {
9694
// Lax rather than strict to preserve V2.4 semantic that str wins over url in union
9795
state.floor_exactness(Exactness::Lax);
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-
}
96+
Ok(either_url.into_py(py))
12397
}
12498
Err(error_type) => Err(ValError::new(error_type, input)),
12599
}
@@ -212,7 +186,6 @@ impl CopyFromPyUrl for EitherUrl<'_> {
212186
#[derive(Debug, Clone)]
213187
pub struct MultiHostUrlValidator {
214188
strict: bool,
215-
cls: Option<Py<PyType>>,
216189
max_length: Option<usize>,
217190
allowed_schemes: AllowedSchemas,
218191
host_required: bool,
@@ -240,7 +213,6 @@ impl BuildValidator for MultiHostUrlValidator {
240213
}
241214
Ok(Self {
242215
strict: is_strict(schema, config)?,
243-
cls: schema.get_as(intern!(schema.py(), "cls"))?,
244216
max_length: schema.get_as(intern!(schema.py(), "max_length"))?,
245217
allowed_schemes,
246218
host_required: schema.get_as(intern!(schema.py(), "host_required"))?.unwrap_or(false),
@@ -253,7 +225,7 @@ impl BuildValidator for MultiHostUrlValidator {
253225
}
254226
}
255227

256-
impl_py_gc_traverse!(MultiHostUrlValidator { cls });
228+
impl_py_gc_traverse!(MultiHostUrlValidator {});
257229

258230
impl Validator for MultiHostUrlValidator {
259231
fn validate<'py>(
@@ -286,38 +258,7 @@ impl Validator for MultiHostUrlValidator {
286258
Ok(()) => {
287259
// Lax rather than strict to preserve V2.4 semantic that str wins over url in union
288260
state.floor_exactness(Exactness::Lax);
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-
}
261+
Ok(multi_url.into_py(py))
321262
}
322263
Err(error_type) => Err(ValError::new(error_type, input)),
323264
}

tests/validators/test_url.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,19 +1305,3 @@ def test_url_build() -> None:
13051305
)
13061306
assert url == Url('postgresql://testuser:[email protected]:5432/database?sslmode=require#test')
13071307
assert str(url) == 'postgresql://testuser:[email protected]:5432/database?sslmode=require#test'
1308-
1309-
1310-
def test_url_subclass() -> None:
1311-
class UrlSubclass(Url):
1312-
pass
1313-
1314-
validator = SchemaValidator(core_schema.url_schema(cls=UrlSubclass))
1315-
assert isinstance(validator.validate_python('http://example.com'), UrlSubclass)
1316-
1317-
1318-
def test_multi_host_url_subclass() -> None:
1319-
class MultiHostUrlSubclass(MultiHostUrl):
1320-
pass
1321-
1322-
validator = SchemaValidator(core_schema.multi_host_url_schema(cls=MultiHostUrlSubclass))
1323-
assert isinstance(validator.validate_python('http://example.com'), MultiHostUrlSubclass)

0 commit comments

Comments
 (0)