Skip to content

Commit

Permalink
Url type
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin committed Nov 1, 2022
1 parent b82b041 commit 40033d4
Show file tree
Hide file tree
Showing 12 changed files with 413 additions and 99 deletions.
2 changes: 2 additions & 0 deletions pydantic_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
PydanticOmit,
SchemaError,
SchemaValidator,
Url,
ValidationError,
__version__,
)
Expand All @@ -14,6 +15,7 @@
'CoreConfig',
'CoreSchema',
'SchemaValidator',
'Url',
'SchemaError',
'ValidationError',
'PydanticCustomError',
Expand Down
19 changes: 18 additions & 1 deletion pydantic_core/_pydantic_core.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import decimal
import sys
from typing import Any, TypedDict
from typing import Any, Literal, TypedDict

from pydantic_core.core_schema import CoreConfig, CoreSchema, ErrorType

Expand All @@ -13,6 +13,7 @@ __all__ = (
'__version__',
'build_profile',
'SchemaValidator',
'Url',
'SchemaError',
'ValidationError',
'PydanticCustomError',
Expand All @@ -38,6 +39,22 @@ class SchemaValidator:
self, field: str, input: Any, data: 'dict[str, Any]', strict: 'bool | None' = None, context: Any = None
) -> 'dict[str, Any]': ...

class Url:
scheme: str
username: 'str | None'
password: 'str | None'
host: 'str | None'
host_type: Literal['domain', 'international_domain', 'ipv4', 'ipv6', None]
port: 'int | None'
path: 'str | None'
query: 'str | None'
fragment: 'str | None'

def __init__(self, raw_url: str) -> None: ...
def query_params(self) -> 'list[tuple[str, str]]': ...
def __str__(self) -> str: ...
def __repr__(self) -> str: ...

class SchemaError(Exception):
pass

Expand Down
32 changes: 32 additions & 0 deletions pydantic_core/core_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,33 @@ def json_schema(schema: CoreSchema | None = None, *, ref: str | None = None, ext
return dict_not_none(type='json', schema=schema, ref=ref, extra=extra)


class UrlSchema(TypedDict, total=False):
type: Required[Literal['url']]
host_required: bool # default False
max_length: int
allowed_schemes: List[str]
ref: str
extra: Any


def url_schema(
*,
host_required: bool | None = None,
max_length: int | None = None,
allowed_schemes: list[str] | None = None,
ref: str | None = None,
extra: Any = None,
) -> UrlSchema:
return dict_not_none(
type='url',
host_required=host_required,
max_length=max_length,
allowed_schemes=allowed_schemes,
ref=ref,
extra=extra,
)


CoreSchema = Union[
AnySchema,
NoneSchema,
Expand Down Expand Up @@ -1059,6 +1086,7 @@ def json_schema(schema: CoreSchema | None = None, *, ref: str | None = None, ext
RecursiveReferenceSchema,
CustomErrorSchema,
JsonSchema,
UrlSchema,
]

# used in _pydantic_core.pyi::PydanticKnownError
Expand Down Expand Up @@ -1139,4 +1167,8 @@ def json_schema(schema: CoreSchema | None = None, *, ref: str | None = None, ext
'unexpected_positional_argument',
'missing_positional_argument',
'multiple_argument_values',
'url_error',
'url_too_long',
'url_schema',
'url_host_required',
]
25 changes: 25 additions & 0 deletions src/errors/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,22 @@ pub enum ErrorType {
MissingPositionalArgument,
#[strum(message = "Got multiple values for argument")]
MultipleArgumentValues,
// ---------------------
// URL errors
#[strum(message = "Invalid URL, {error}")]
UrlError {
error: String,
},
#[strum(message = "URL should have at most {max_length} characters")]
UrlTooLong {
max_length: usize,
},
#[strum(message = "URL schema should be {expected_schemas}")]
UrlSchema {
expected_schemas: String,
},
#[strum(message = "URL host required")]
UrlHostRequired,
}

macro_rules! render {
Expand Down Expand Up @@ -449,6 +465,9 @@ impl ErrorType {
expected_tags: String
),
Self::UnionTagNotFound { .. } => extract_context!(UnionTagNotFound, ctx, discriminator: String),
Self::UrlError { .. } => extract_context!(UrlError, ctx, error: String),
Self::UrlTooLong { .. } => extract_context!(UrlTooLong, ctx, max_length: usize),
Self::UrlSchema { .. } => extract_context!(UrlSchema, ctx, expected_schemas: String),
_ => {
if ctx.is_some() {
py_err!(PyTypeError; "'{}' errors do not require context", value)
Expand Down Expand Up @@ -536,6 +555,9 @@ impl ErrorType {
expected_tags,
} => render!(self, discriminator, tag, expected_tags),
Self::UnionTagNotFound { discriminator } => render!(self, discriminator),
Self::UrlError { error } => render!(self, error),
Self::UrlTooLong { max_length } => to_string_render!(self, max_length),
Self::UrlSchema { expected_schemas } => render!(self, expected_schemas),
_ => Ok(self.message_template().to_string()),
}
}
Expand Down Expand Up @@ -585,6 +607,9 @@ impl ErrorType {
expected_tags,
} => py_dict!(py, discriminator, tag, expected_tags),
Self::UnionTagNotFound { discriminator } => py_dict!(py, discriminator),
Self::UrlError { error } => py_dict!(py, error),
Self::UrlTooLong { max_length } => py_dict!(py, max_length),
Self::UrlSchema { expected_schemas } => py_dict!(py, expected_schemas),
_ => Ok(None),
}
}
Expand Down
6 changes: 2 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@ mod input;
mod lookup_key;
mod questions;
mod recursion_guard;
mod url_type;
mod validators;

// required for benchmarks
pub use build_tools::SchemaError;
pub use errors::{list_all_errors, PydanticCustomError, PydanticKnownError, PydanticOmit, ValidationError};
pub use url_type::Url;
pub use validators::SchemaValidator;
pub use validators::{SchemaValidator, Url};

pub fn get_version() -> String {
let version = env!("CARGO_PKG_VERSION").to_string();
Expand All @@ -43,7 +41,7 @@ fn _pydantic_core(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<PydanticCustomError>()?;
m.add_class::<PydanticKnownError>()?;
m.add_class::<PydanticOmit>()?;
m.add_class::<Url>()?;
m.add_class::<validators::Url>()?;
m.add_function(wrap_pyfunction!(list_all_errors, m)?)?;
Ok(())
}
88 changes: 0 additions & 88 deletions src/url_type.rs

This file was deleted.

10 changes: 5 additions & 5 deletions src/validators/literal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ impl LiteralMultipleStringsValidator {
return None;
}
}
let (expected_repr, name) = expected_repr_name(repr_args);
let (expected_repr, name) = expected_repr_name(repr_args, "literal");
Some(Self {
expected,
expected_repr,
Expand Down Expand Up @@ -211,7 +211,7 @@ impl LiteralMultipleIntsValidator {
return None;
}
}
let (expected_repr, name) = expected_repr_name(repr_args);
let (expected_repr, name) = expected_repr_name(repr_args, "literal");
Some(Self {
expected,
expected_repr,
Expand Down Expand Up @@ -273,7 +273,7 @@ impl LiteralGeneralValidator {
expected_py.append(item)?;
}
}
let (expected_repr, name) = expected_repr_name(repr_args);
let (expected_repr, name) = expected_repr_name(repr_args, "literal");
Ok(Self {
expected_int,
expected_str,
Expand Down Expand Up @@ -328,8 +328,8 @@ impl Validator for LiteralGeneralValidator {
}
}

fn expected_repr_name(mut repr_args: Vec<String>) -> (String, String) {
let name = format!("literal[{}]", repr_args.join(","));
pub fn expected_repr_name(mut repr_args: Vec<String>, base_name: &'static str) -> (String, String) {
let name = format!("{base_name}[{}]", repr_args.join(","));
// unwrap is okay since we check the length in build at the top of this file
let last_repr = repr_args.pop().unwrap();
let repr = if repr_args.is_empty() {
Expand Down
7 changes: 7 additions & 0 deletions src/validators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,11 @@ mod timedelta;
mod tuple;
mod typed_dict;
mod union;
mod url;
mod with_default;

pub use self::url::Url;

#[pyclass(module = "pydantic_core._pydantic_core")]
#[derive(Debug, Clone)]
pub struct SchemaValidator {
Expand Down Expand Up @@ -385,6 +388,8 @@ pub fn build_validator<'a>(
custom_error::CustomErrorValidator,
// json data
json::JsonValidator,
// url type
url::UrlValidator,
)
}

Expand Down Expand Up @@ -504,6 +509,8 @@ pub enum CombinedValidator {
CustomError(custom_error::CustomErrorValidator),
// json data
Json(json::JsonValidator),
// url type
Url(url::UrlValidator),
}

/// This trait must be implemented by all validators, it allows various validators to be accessed consistently,
Expand Down

0 comments on commit 40033d4

Please sign in to comment.