Skip to content

Commit 463ed7a

Browse files
authored
Multi host URLs, other URL changes (#325)
* adding multi host urls * linting and more test cases * tweak unicode_string logic to replace range * consume leading whitespace on multihost urls * yet more test cases * improve coverage * add vulnerabilties tests * remove "host_type" from url * fix unicode positioning with multihost urls * uprev * allow default_host, default_port, default_path * fix schema vs. scheme typo * empty hosts() if no host * tweaking url public methods
1 parent e749174 commit 463ed7a

14 files changed

+1462
-208
lines changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pydantic-core"
3-
version = "0.7.0"
3+
version = "0.7.1"
44
edition = "2021"
55
license = "MIT"
66
homepage = "https://github.com/pydantic/pydantic-core"

pydantic_core/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from ._pydantic_core import (
2+
MultiHostUrl,
23
PydanticCustomError,
34
PydanticKnownError,
45
PydanticOmit,
@@ -16,6 +17,7 @@
1617
'CoreSchema',
1718
'SchemaValidator',
1819
'Url',
20+
'MultiHostUrl',
1921
'SchemaError',
2022
'ValidationError',
2123
'PydanticCustomError',

pydantic_core/_pydantic_core.pyi

+19-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ __all__ = (
1414
'build_profile',
1515
'SchemaValidator',
1616
'Url',
17+
'MultiHostUrl',
1718
'SchemaError',
1819
'ValidationError',
1920
'PydanticCustomError',
@@ -44,19 +45,35 @@ class Url:
4445
username: 'str | None'
4546
password: 'str | None'
4647
host: 'str | None'
47-
host_type: Literal['domain', 'punycode_domain', 'ipv4', 'ipv6', None]
4848
port: 'int | None'
4949
path: 'str | None'
5050
query: 'str | None'
5151
fragment: 'str | None'
5252

53-
def __init__(self, raw_url: str) -> None: ...
5453
def unicode_host(self) -> 'str | None': ...
5554
def query_params(self) -> 'list[tuple[str, str]]': ...
5655
def unicode_string(self) -> str: ...
5756
def __str__(self) -> str: ...
5857
def __repr__(self) -> str: ...
5958

59+
class MultiHostHost(TypedDict):
60+
username: 'str | None'
61+
password: 'str | None'
62+
host: str
63+
port: 'int | None'
64+
65+
class MultiHostUrl:
66+
scheme: str
67+
path: 'str | None'
68+
query: 'str | None'
69+
fragment: 'str | None'
70+
71+
def hosts(self) -> 'list[MultiHostHost]': ...
72+
def query_params(self) -> 'list[tuple[str, str]]': ...
73+
def unicode_string(self) -> str: ...
74+
def __str__(self) -> str: ...
75+
def __repr__(self) -> str: ...
76+
6077
class SchemaError(Exception):
6178
pass
6279

pydantic_core/core_schema.py

+55-4
Original file line numberDiff line numberDiff line change
@@ -1023,26 +1023,77 @@ def json_schema(schema: CoreSchema | None = None, *, ref: str | None = None, ext
10231023

10241024
class UrlSchema(TypedDict, total=False):
10251025
type: Required[Literal['url']]
1026-
host_required: bool # default False
10271026
max_length: int
10281027
allowed_schemes: List[str]
1028+
host_required: bool # default False
1029+
default_host: str
1030+
default_port: int
1031+
default_path: str
1032+
strict: bool
10291033
ref: str
10301034
extra: Any
10311035

10321036

10331037
def url_schema(
10341038
*,
1035-
host_required: bool | None = None,
10361039
max_length: int | None = None,
10371040
allowed_schemes: list[str] | None = None,
1041+
host_required: bool | None = None,
1042+
default_host: str | None = None,
1043+
default_port: int | None = None,
1044+
default_path: str | None = None,
1045+
strict: bool | None = None,
10381046
ref: str | None = None,
10391047
extra: Any = None,
10401048
) -> UrlSchema:
10411049
return dict_not_none(
10421050
type='url',
1051+
max_length=max_length,
1052+
allowed_schemes=allowed_schemes,
10431053
host_required=host_required,
1054+
default_host=default_host,
1055+
default_port=default_port,
1056+
default_path=default_path,
1057+
strict=strict,
1058+
ref=ref,
1059+
extra=extra,
1060+
)
1061+
1062+
1063+
class MultiHostUrlSchema(TypedDict, total=False):
1064+
type: Required[Literal['multi-host-url']]
1065+
max_length: int
1066+
allowed_schemes: List[str]
1067+
host_required: bool # default False
1068+
default_host: str
1069+
default_port: int
1070+
default_path: str
1071+
strict: bool
1072+
ref: str
1073+
extra: Any
1074+
1075+
1076+
def multi_host_url_schema(
1077+
*,
1078+
max_length: int | None = None,
1079+
allowed_schemes: list[str] | None = None,
1080+
host_required: bool | None = None,
1081+
default_host: str | None = None,
1082+
default_port: int | None = None,
1083+
default_path: str | None = None,
1084+
strict: bool | None = None,
1085+
ref: str | None = None,
1086+
extra: Any = None,
1087+
) -> MultiHostUrlSchema:
1088+
return dict_not_none(
1089+
type='multi-host-url',
10441090
max_length=max_length,
10451091
allowed_schemes=allowed_schemes,
1092+
host_required=host_required,
1093+
default_host=default_host,
1094+
default_port=default_port,
1095+
default_path=default_path,
1096+
strict=strict,
10461097
ref=ref,
10471098
extra=extra,
10481099
)
@@ -1087,6 +1138,7 @@ def url_schema(
10871138
CustomErrorSchema,
10881139
JsonSchema,
10891140
UrlSchema,
1141+
MultiHostUrlSchema,
10901142
]
10911143

10921144
# used in _pydantic_core.pyi::PydanticKnownError
@@ -1171,6 +1223,5 @@ def url_schema(
11711223
'url_parsing',
11721224
'url_syntax_violation',
11731225
'url_too_long',
1174-
'url_schema',
1175-
'url_host_required',
1226+
'url_scheme',
11761227
]

src/errors/types.rs

+6-8
Original file line numberDiff line numberDiff line change
@@ -334,12 +334,10 @@ pub enum ErrorType {
334334
UrlTooLong {
335335
max_length: usize,
336336
},
337-
#[strum(message = "URL schema should be {expected_schemas}")]
338-
UrlSchema {
339-
expected_schemas: String,
337+
#[strum(message = "URL scheme should be {expected_schemes}")]
338+
UrlScheme {
339+
expected_schemes: String,
340340
},
341-
#[strum(message = "URL host required")]
342-
UrlHostRequired,
343341
}
344342

345343
macro_rules! render {
@@ -475,7 +473,7 @@ impl ErrorType {
475473
Self::UrlParsing { .. } => extract_context!(UrlParsing, ctx, error: String),
476474
Self::UrlSyntaxViolation { .. } => extract_context!(Cow::Owned, UrlSyntaxViolation, ctx, error: String),
477475
Self::UrlTooLong { .. } => extract_context!(UrlTooLong, ctx, max_length: usize),
478-
Self::UrlSchema { .. } => extract_context!(UrlSchema, ctx, expected_schemas: String),
476+
Self::UrlScheme { .. } => extract_context!(UrlScheme, ctx, expected_schemes: String),
479477
_ => {
480478
if ctx.is_some() {
481479
py_err!(PyTypeError; "'{}' errors do not require context", value)
@@ -566,7 +564,7 @@ impl ErrorType {
566564
Self::UrlParsing { error } => render!(self, error),
567565
Self::UrlSyntaxViolation { error } => render!(self, error),
568566
Self::UrlTooLong { max_length } => to_string_render!(self, max_length),
569-
Self::UrlSchema { expected_schemas } => render!(self, expected_schemas),
567+
Self::UrlScheme { expected_schemes } => render!(self, expected_schemes),
570568
_ => Ok(self.message_template().to_string()),
571569
}
572570
}
@@ -619,7 +617,7 @@ impl ErrorType {
619617
Self::UrlParsing { error } => py_dict!(py, error),
620618
Self::UrlSyntaxViolation { error } => py_dict!(py, error),
621619
Self::UrlTooLong { max_length } => py_dict!(py, max_length),
622-
Self::UrlSchema { expected_schemas } => py_dict!(py, expected_schemas),
620+
Self::UrlScheme { expected_schemes } => py_dict!(py, expected_schemes),
623621
_ => Ok(None),
624622
}
625623
}

src/input/input_abstract.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use pyo3::prelude::*;
44
use pyo3::types::{PyString, PyType};
55

66
use crate::errors::{InputValue, LocItem, ValResult};
7-
use crate::PyUrl;
7+
use crate::{PyMultiHostUrl, PyUrl};
88

99
use super::datetime::{EitherDate, EitherDateTime, EitherTime, EitherTimedelta};
1010
use super::return_enums::{EitherBytes, EitherString};
@@ -59,6 +59,10 @@ pub trait Input<'a>: fmt::Debug + ToPyObject {
5959
None
6060
}
6161

62+
fn input_as_multi_host_url(&self) -> Option<PyMultiHostUrl> {
63+
None
64+
}
65+
6266
fn callable(&self) -> bool {
6367
false
6468
}

src/input/input_python.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use pyo3::types::{PyDictItems, PyDictKeys, PyDictValues};
1313
use pyo3::{ffi, intern, AsPyPointer, PyTypeInfo};
1414

1515
use crate::errors::{py_err_string, ErrorType, InputValue, LocItem, ValError, ValLineError, ValResult};
16-
use crate::PyUrl;
16+
use crate::{PyMultiHostUrl, PyUrl};
1717

1818
use super::datetime::{
1919
bytes_as_date, bytes_as_datetime, bytes_as_time, bytes_as_timedelta, date_as_datetime, float_as_datetime,
@@ -113,6 +113,10 @@ impl<'a> Input<'a> for PyAny {
113113
self.extract::<PyUrl>().ok()
114114
}
115115

116+
fn input_as_multi_host_url(&self) -> Option<PyMultiHostUrl> {
117+
self.extract::<PyMultiHostUrl>().ok()
118+
}
119+
116120
fn callable(&self) -> bool {
117121
self.is_callable()
118122
}

src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ mod url;
1818
mod validators;
1919

2020
// required for benchmarks
21-
pub use self::url::PyUrl;
21+
pub use self::url::{PyMultiHostUrl, PyUrl};
2222
pub use build_tools::SchemaError;
2323
pub use errors::{list_all_errors, PydanticCustomError, PydanticKnownError, PydanticOmit, ValidationError};
2424
pub use validators::SchemaValidator;
@@ -44,6 +44,7 @@ fn _pydantic_core(_py: Python, m: &PyModule) -> PyResult<()> {
4444
m.add_class::<PydanticKnownError>()?;
4545
m.add_class::<PydanticOmit>()?;
4646
m.add_class::<self::url::PyUrl>()?;
47+
m.add_class::<self::url::PyMultiHostUrl>()?;
4748
m.add_function(wrap_pyfunction!(list_all_errors, m)?)?;
4849
Ok(())
4950
}

0 commit comments

Comments
 (0)