Skip to content

Commit 4e19c63

Browse files
committed
add config
1 parent 5f59c51 commit 4e19c63

File tree

6 files changed

+70
-50
lines changed

6 files changed

+70
-50
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ require_change_file = false
150150
include = ['python/pydantic_core', 'tests/test_typing.py']
151151
reportUnnecessaryTypeIgnoreComment = true
152152
executionEnvironments = [
153-
{ root = "tests", reportPrivateImportUsage = false, reportMissingParameterType = false },
153+
{ root = "tests", reportPrivateImportUsage = false, reportMissingParameterType = false, reportAny = false },
154154
]
155155

156156
[tool.inline-snapshot.shortcuts]

python/pydantic_core/_pydantic_core.pyi

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -522,15 +522,15 @@ class Url(SupportsAllComparisons):
522522
by Mozilla.
523523
"""
524524

525-
def __init__(self, url: str, *, extra_trailing_slash: bool = True) -> None:
525+
def __init__(self, url: str, *, add_trailing_slash: bool = True) -> None:
526526
"""Initialize a new URL object.
527527
528528
Args:
529529
url: The URL string to parse.
530-
extra_trailing_slash: Whether to add an extra trailing slash to the URL, defaults to `True` for
530+
add_trailing_slash: Whether to add an extra trailing slash to some URLs, defaults to `True` for
531531
backward compatibility, default will change to `False` in v3 version.
532532
"""
533-
def __new__(cls, url: str, *, extra_trailing_slash: bool = True) -> Self: ...
533+
def __new__(cls, url: str, *, add_trailing_slash: bool = True) -> Self: ...
534534
@property
535535
def scheme(self) -> str: ...
536536
@property
@@ -575,15 +575,15 @@ class MultiHostUrl(SupportsAllComparisons):
575575
by Mozilla.
576576
"""
577577

578-
def __init__(self, url: str, *, extra_trailing_slash: bool = True) -> None:
578+
def __init__(self, url: str, *, add_trailing_slash: bool = True) -> None:
579579
"""Initialize a new MultiHostUrl object.
580580
581581
Args:
582582
url: The URL string to parse.
583-
extra_trailing_slash: Whether to add an extra trailing slash to the URL, defaults to `True` for
583+
add_trailing_slash: Whether to add an extra trailing slash to some URLs, defaults to `True` for
584584
backward compatibility, default will change to `False` in v3 version.
585585
"""
586-
def __new__(cls, url: str, *, extra_trailing_slash: bool = True) -> Self: ...
586+
def __new__(cls, url: str, *, add_trailing_slash: bool = True) -> Self: ...
587587
@property
588588
def scheme(self) -> str: ...
589589
@property

python/pydantic_core/core_schema.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ class CoreConfig(TypedDict, total=False):
7575
validate_by_alias: Whether to use the field's alias when validating against the provided input data. Default is `True`.
7676
validate_by_name: Whether to use the field's name when validating against the provided input data. Default is `False`. Replacement for `populate_by_name`.
7777
serialize_by_alias: Whether to serialize by alias. Default is `False`, expected to change to `True` in V3.
78+
url_add_trailing_slash: Whether to add an extra trailing slash to some URLs, defaults to `True` for
79+
backward compatibility, default will change to `False` in v3 version.
7880
"""
7981

8082
title: str
@@ -114,6 +116,7 @@ class CoreConfig(TypedDict, total=False):
114116
validate_by_alias: bool # default: True
115117
validate_by_name: bool # default: False
116118
serialize_by_alias: bool # default: False
119+
url_add_trailing_slash: bool # default: True
117120

118121

119122
IncExCall: TypeAlias = 'set[int | str] | dict[int | str, IncExCall] | None'
@@ -3824,7 +3827,7 @@ class UrlSchema(TypedDict, total=False):
38243827
default_host: str
38253828
default_port: int
38263829
default_path: str
3827-
extra_trailing_slash: bool
3830+
add_trailing_slash: bool
38283831
strict: bool
38293832
ref: str
38303833
metadata: dict[str, Any]
@@ -3839,7 +3842,7 @@ def url_schema(
38393842
default_host: str | None = None,
38403843
default_port: int | None = None,
38413844
default_path: str | None = None,
3842-
extra_trailing_slash: bool | None = None,
3845+
add_trailing_slash: bool | None = None,
38433846
strict: bool | None = None,
38443847
ref: str | None = None,
38453848
metadata: dict[str, Any] | None = None,
@@ -3864,7 +3867,7 @@ def url_schema(
38643867
default_host: The default host to use if the URL does not have a host
38653868
default_port: The default port to use if the URL does not have a port
38663869
default_path: The default path to use if the URL does not have a path
3867-
extra_trailing_slash: Whether to add an extra trailing slash to the URL, defaults to `True` for
3870+
add_trailing_slash: Whether to add an extra trailing slash to some URLs, defaults to `True` for
38683871
backward compatibility, default will change to `False` in v3 version.
38693872
strict: Whether to use strict URL parsing
38703873
ref: optional unique identifier of the schema, used to reference the schema in other places
@@ -3883,7 +3886,7 @@ def url_schema(
38833886
ref=ref,
38843887
metadata=metadata,
38853888
serialization=serialization,
3886-
extra_trailing_slash=extra_trailing_slash,
3889+
add_trailing_slash=add_trailing_slash,
38873890
)
38883891

38893892

@@ -3895,7 +3898,7 @@ class MultiHostUrlSchema(TypedDict, total=False):
38953898
default_host: str
38963899
default_port: int
38973900
default_path: str
3898-
extra_trailing_slash: bool
3901+
add_trailing_slash: bool
38993902
strict: bool
39003903
ref: str
39013904
metadata: dict[str, Any]
@@ -3910,7 +3913,7 @@ def multi_host_url_schema(
39103913
default_host: str | None = None,
39113914
default_port: int | None = None,
39123915
default_path: str | None = None,
3913-
extra_trailing_slash: bool | None = None,
3916+
add_trailing_slash: bool | None = None,
39143917
strict: bool | None = None,
39153918
ref: str | None = None,
39163919
metadata: dict[str, Any] | None = None,
@@ -3935,7 +3938,7 @@ def multi_host_url_schema(
39353938
default_host: The default host to use if the URL does not have a host
39363939
default_port: The default port to use if the URL does not have a port
39373940
default_path: The default path to use if the URL does not have a path
3938-
extra_trailing_slash: Whether to add an extra trailing slash to the URL, defaults to `True` for
3941+
add_trailing_slash: Whether to add an extra trailing slash to some URLs, defaults to `True` for
39393942
backward compatibility, default will change to `False` in v3 version.
39403943
strict: Whether to use strict URL parsing
39413944
ref: optional unique identifier of the schema, used to reference the schema in other places
@@ -3950,7 +3953,7 @@ def multi_host_url_schema(
39503953
default_host=default_host,
39513954
default_port=default_port,
39523955
default_path=default_path,
3953-
extra_trailing_slash=extra_trailing_slash,
3956+
add_trailing_slash=add_trailing_slash,
39543957
strict=strict,
39553958
ref=ref,
39563959
metadata=metadata,

src/url.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,28 +50,28 @@ static SCHEMA_URL_SINGLE_FALSE: GILOnceCell<SchemaValidator> = GILOnceCell::new(
5050
static SCHEMA_URL_MULTI_TRUE: GILOnceCell<SchemaValidator> = GILOnceCell::new();
5151
static SCHEMA_URL_MULTI_FALSE: GILOnceCell<SchemaValidator> = GILOnceCell::new();
5252

53-
fn get_schema_validator(py: Python<'_>, multi_host: bool, extra_trailing_slash: bool) -> &SchemaValidator {
54-
match (multi_host, extra_trailing_slash) {
53+
fn get_schema_validator(py: Python<'_>, multi_host: bool, add_trailing_slash: bool) -> &SchemaValidator {
54+
match (multi_host, add_trailing_slash) {
5555
(false, true) => SCHEMA_URL_SINGLE_TRUE.get_or_init(py, || build_schema_validator(py, "url", true)),
5656
(false, false) => SCHEMA_URL_SINGLE_FALSE.get_or_init(py, || build_schema_validator(py, "url", false)),
5757
(true, true) => SCHEMA_URL_MULTI_TRUE.get_or_init(py, || build_schema_validator(py, "multi-host-url", true)),
5858
(true, false) => SCHEMA_URL_MULTI_FALSE.get_or_init(py, || build_schema_validator(py, "multi-host-url", false)),
5959
}
6060
}
6161

62-
fn build_schema_validator(py: Python, schema_type: &str, extra_trailing_slash: bool) -> SchemaValidator {
62+
fn build_schema_validator(py: Python, schema_type: &str, add_trailing_slash: bool) -> SchemaValidator {
6363
let schema = PyDict::new(py);
6464
schema.set_item("type", schema_type).unwrap();
65-
schema.set_item("extra_trailing_slash", extra_trailing_slash).unwrap();
65+
schema.set_item("add_trailing_slash", add_trailing_slash).unwrap();
6666
SchemaValidator::py_new(py, &schema, None).unwrap()
6767
}
6868

6969
#[pymethods]
7070
impl PyUrl {
7171
#[new]
72-
#[pyo3(signature = (url, *, extra_trailing_slash=true))]
73-
pub fn py_new(py: Python, url: &Bound<'_, PyAny>, extra_trailing_slash: bool) -> PyResult<Self> {
74-
let schema_validator = get_schema_validator(py, false, extra_trailing_slash);
72+
#[pyo3(signature = (url, *, add_trailing_slash=true))]
73+
pub fn py_new(py: Python, url: &Bound<'_, PyAny>, add_trailing_slash: bool) -> PyResult<Self> {
74+
let schema_validator = get_schema_validator(py, false, add_trailing_slash);
7575
let schema_obj = schema_validator.validate_python(py, url, None, None, None, None, false.into(), None, None)?;
7676
schema_obj.extract(py)
7777
}
@@ -248,9 +248,9 @@ impl PyMultiHostUrl {
248248
#[pymethods]
249249
impl PyMultiHostUrl {
250250
#[new]
251-
#[pyo3(signature = (url, *, extra_trailing_slash=true))]
252-
pub fn py_new(py: Python, url: &Bound<'_, PyAny>, extra_trailing_slash: bool) -> PyResult<Self> {
253-
let schema_validator = get_schema_validator(py, true, extra_trailing_slash);
251+
#[pyo3(signature = (url, *, add_trailing_slash=true))]
252+
pub fn py_new(py: Python, url: &Bound<'_, PyAny>, add_trailing_slash: bool) -> PyResult<Self> {
253+
let schema_validator = get_schema_validator(py, true, add_trailing_slash);
254254
let schema_obj = schema_validator.validate_python(py, url, None, None, None, None, false.into(), None, None)?;
255255
schema_obj.extract(py)
256256
}

src/validators/url.rs

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use ahash::AHashSet;
1010
use pyo3::IntoPyObjectExt;
1111
use url::{ParseError, SyntaxViolation, Url};
1212

13-
use crate::build_tools::{is_strict, py_schema_err};
13+
use crate::build_tools::{is_strict, py_schema_err, schema_or_config};
1414
use crate::errors::ToErrorValue;
1515
use crate::errors::{ErrorType, ErrorTypeDefaults, ValError, ValResult};
1616
use crate::input::downcast_python_input;
@@ -34,7 +34,17 @@ pub struct UrlValidator {
3434
default_port: Option<u16>,
3535
default_path: Option<String>,
3636
name: String,
37-
extra_trailing_slash: bool,
37+
add_trailing_slash: bool,
38+
}
39+
40+
fn get_add_trailing_slash(schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>) -> PyResult<bool> {
41+
schema_or_config(
42+
schema,
43+
config,
44+
intern!(schema.py(), "add_trailing_slash"),
45+
intern!(schema.py(), "url_add_trailing_slash"),
46+
)
47+
.map(|v| v.unwrap_or(true))
3848
}
3949

4050
impl BuildValidator for UrlValidator {
@@ -56,9 +66,7 @@ impl BuildValidator for UrlValidator {
5666
default_path: schema.get_as(intern!(schema.py(), "default_path"))?,
5767
allowed_schemes,
5868
name,
59-
extra_trailing_slash: schema
60-
.get_as(intern!(schema.py(), "extra_trailing_slash"))?
61-
.unwrap_or(true),
69+
add_trailing_slash: get_add_trailing_slash(schema, config)?,
6270
}
6371
.into())
6472
}
@@ -73,7 +81,7 @@ impl Validator for UrlValidator {
7381
input: &(impl Input<'py> + ?Sized),
7482
state: &mut ValidationState<'_, 'py>,
7583
) -> ValResult<PyObject> {
76-
let mut either_url = self.get_url(input, state.strict_or(self.strict), self.extra_trailing_slash)?;
84+
let mut either_url = self.get_url(input, state.strict_or(self.strict), self.add_trailing_slash)?;
7785

7886
if let Some((ref allowed_schemes, ref expected_schemes_repr)) = self.allowed_schemes {
7987
if !allowed_schemes.contains(either_url.url().scheme()) {
@@ -114,7 +122,7 @@ impl UrlValidator {
114122
&self,
115123
input: &(impl Input<'py> + ?Sized),
116124
strict: bool,
117-
extra_trailing_slash: bool,
125+
add_trailing_slash: bool,
118126
) -> ValResult<EitherUrl<'py>> {
119127
match input.validate_str(strict, false) {
120128
Ok(val_match) => {
@@ -124,7 +132,7 @@ impl UrlValidator {
124132

125133
self.check_length(input, url_str)?;
126134

127-
parse_url(url_str, input, strict, extra_trailing_slash).map(EitherUrl::Owned)
135+
parse_url(url_str, input, strict, add_trailing_slash).map(EitherUrl::Owned)
128136
}
129137
Err(_) => {
130138
// we don't need to worry about whether the url was parsed in strict mode before,
@@ -136,7 +144,7 @@ impl UrlValidator {
136144
let url_str = multi_host_url.get().__str__();
137145
self.check_length(input, &url_str)?;
138146

139-
parse_url(&url_str, input, strict, extra_trailing_slash).map(EitherUrl::Owned)
147+
parse_url(&url_str, input, strict, add_trailing_slash).map(EitherUrl::Owned)
140148
} else {
141149
Err(ValError::new(ErrorTypeDefaults::UrlType, input))
142150
}
@@ -208,7 +216,7 @@ pub struct MultiHostUrlValidator {
208216
default_port: Option<u16>,
209217
default_path: Option<String>,
210218
name: String,
211-
extra_trailing_slash: bool,
219+
add_trailing_slash: bool,
212220
}
213221

214222
impl BuildValidator for MultiHostUrlValidator {
@@ -236,9 +244,7 @@ impl BuildValidator for MultiHostUrlValidator {
236244
default_port: schema.get_as(intern!(schema.py(), "default_port"))?,
237245
default_path: schema.get_as(intern!(schema.py(), "default_path"))?,
238246
name,
239-
extra_trailing_slash: schema
240-
.get_as(intern!(schema.py(), "extra_trailing_slash"))?
241-
.unwrap_or(true),
247+
add_trailing_slash: get_add_trailing_slash(schema, config)?,
242248
}
243249
.into())
244250
}
@@ -253,7 +259,7 @@ impl Validator for MultiHostUrlValidator {
253259
input: &(impl Input<'py> + ?Sized),
254260
state: &mut ValidationState<'_, 'py>,
255261
) -> ValResult<PyObject> {
256-
let mut multi_url = self.get_url(input, state.strict_or(self.strict), self.extra_trailing_slash)?;
262+
let mut multi_url = self.get_url(input, state.strict_or(self.strict), self.add_trailing_slash)?;
257263

258264
if let Some((ref allowed_schemes, ref expected_schemes_repr)) = self.allowed_schemes {
259265
if !allowed_schemes.contains(multi_url.url().scheme()) {
@@ -293,7 +299,7 @@ impl MultiHostUrlValidator {
293299
&self,
294300
input: &(impl Input<'py> + ?Sized),
295301
strict: bool,
296-
extra_trailing_slash: bool,
302+
add_trailing_slash: bool,
297303
) -> ValResult<EitherMultiHostUrl<'py>> {
298304
match input.validate_str(strict, false) {
299305
Ok(val_match) => {
@@ -303,7 +309,7 @@ impl MultiHostUrlValidator {
303309

304310
self.check_length(input, || url_str.len())?;
305311

306-
parse_multihost_url(url_str, input, strict, extra_trailing_slash).map(EitherMultiHostUrl::Rust)
312+
parse_multihost_url(url_str, input, strict, add_trailing_slash).map(EitherMultiHostUrl::Rust)
307313
}
308314
Err(_) => {
309315
// we don't need to worry about whether the url was parsed in strict mode before,
@@ -384,7 +390,7 @@ fn parse_multihost_url<'py>(
384390
url_str: &str,
385391
input: &(impl Input<'py> + ?Sized),
386392
strict: bool,
387-
extra_trailing_slash: bool,
393+
add_trailing_slash: bool,
388394
) -> ValResult<PyMultiHostUrl> {
389395
macro_rules! parsing_err {
390396
($parse_error:expr) => {
@@ -474,7 +480,7 @@ fn parse_multihost_url<'py>(
474480
// with just one host, for consistent behaviour, we parse the URL the same as with multiple hosts
475481

476482
let reconstructed_url = format!("{prefix}{}", &url_str[start..]);
477-
let ref_url = parse_url(&reconstructed_url, input, strict, extra_trailing_slash)?;
483+
let ref_url = parse_url(&reconstructed_url, input, strict, add_trailing_slash)?;
478484

479485
if hosts.is_empty() {
480486
// if there's no one host (e.g. no `,`), we allow it to be empty to allow for default hosts
@@ -488,7 +494,7 @@ fn parse_multihost_url<'py>(
488494
.iter()
489495
.map(|host| {
490496
let reconstructed_url = format!("{prefix}{host}");
491-
parse_url(&reconstructed_url, input, strict, extra_trailing_slash).map(Into::into)
497+
parse_url(&reconstructed_url, input, strict, add_trailing_slash).map(Into::into)
492498
})
493499
.collect::<ValResult<_>>()?;
494500

@@ -500,7 +506,7 @@ fn parse_multihost_url<'py>(
500506
}
501507
}
502508

503-
fn parse_url(url_str: &str, input: impl ToErrorValue, strict: bool, extra_trailing_slash: bool) -> ValResult<PyUrl> {
509+
fn parse_url(url_str: &str, input: impl ToErrorValue, strict: bool, add_trailing_slash: bool) -> ValResult<PyUrl> {
504510
if url_str.is_empty() {
505511
return Err(ValError::new(
506512
ErrorType::UrlParsing {
@@ -510,7 +516,7 @@ fn parse_url(url_str: &str, input: impl ToErrorValue, strict: bool, extra_traili
510516
input,
511517
));
512518
}
513-
let remove_trailing_slash = !extra_trailing_slash && !url_str.ends_with('/');
519+
let remove_trailing_slash = !add_trailing_slash && !url_str.ends_with('/');
514520

515521
// if we're in strict mode, we consider a syntax violation as an error
516522
if strict {

0 commit comments

Comments
 (0)