Skip to content

Commit

Permalink
Convert length constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
adriangb committed Jul 26, 2023
1 parent f5ef7af commit e2cd996
Show file tree
Hide file tree
Showing 28 changed files with 321 additions and 441 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ features = ["pyo3/extension-module"]
[tool.ruff]
line-length = 120
extend-select = ['Q', 'RUF100', 'C90', 'I']
ignore = ['E501']
flake8-quotes = {inline-quotes = 'single', multiline-quotes = 'double'}
mccabe = { max-complexity = 13 }
isort = { known-first-party = ['pydantic_core', 'tests'] }
Expand Down
105 changes: 51 additions & 54 deletions src/errors/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,10 @@ pub enum ErrorType {
// ---------------------
// generic length errors - used for everything with a length except strings and bytes which need custom messages
TooShort {
field_type: String,
min_length: usize,
actual_length: usize,
},
TooLong {
field_type: String,
max_length: usize,
actual_length: usize,
},
Expand Down Expand Up @@ -414,14 +412,12 @@ impl ErrorType {
Self::TooShort { .. } => extract_context!(
TooShort,
ctx,
field_type: String,
min_length: usize,
actual_length: usize
),
Self::TooLong { .. } => extract_context!(
TooLong,
ctx,
field_type: String,
max_length: usize,
actual_length: usize
),
Expand Down Expand Up @@ -479,40 +475,44 @@ impl ErrorType {

pub fn message_template_python(&self) -> &'static str {
match self {
Self::NoSuchAttribute {..} => "Object has no attribute '{attribute}'",
Self::JsonInvalid {..} => "Invalid JSON: {error}",
Self::NoSuchAttribute { .. } => "Object has no attribute '{attribute}'",
Self::JsonInvalid { .. } => "Invalid JSON: {error}",
Self::JsonType => "JSON input should be string, bytes or bytearray",
Self::RecursionLoop => "Recursion error - cyclic reference detected",
Self::Missing => "Field required",
Self::FrozenField => "Field is frozen",
Self::FrozenInstance => "Instance is frozen",
Self::ExtraForbidden => "Extra inputs are not permitted",
Self::InvalidKey => "Keys should be strings",
Self::GetAttributeError {..} => "Error extracting attribute: {error}",
Self::ModelType {..} => "Input should be a valid dictionary or instance of {class_name}",
Self::GetAttributeError { .. } => "Error extracting attribute: {error}",
Self::ModelType { .. } => "Input should be a valid dictionary or instance of {class_name}",
Self::ModelAttributesType => "Input should be a valid dictionary or object to extract fields from",
Self::DataclassType {..} => "Input should be a dictionary or an instance of {class_name}",
Self::DataclassExactType {..} => "Input should be an instance of {class_name}",
Self::DataclassType { .. } => "Input should be a dictionary or an instance of {class_name}",
Self::DataclassExactType { .. } => "Input should be an instance of {class_name}",
Self::NoneRequired => "Input should be None",
Self::GreaterThan {..} => "Input should be greater than {gt}",
Self::GreaterThanEqual {..} => "Input should be greater than or equal to {ge}",
Self::LessThan {..} => "Input should be less than {lt}",
Self::LessThanEqual {..} => "Input should be less than or equal to {le}",
Self::MultipleOf {..} => "Input should be a multiple of {multiple_of}",
Self::GreaterThan { .. } => "Input should be greater than {gt}",
Self::GreaterThanEqual { .. } => "Input should be greater than or equal to {ge}",
Self::LessThan { .. } => "Input should be less than {lt}",
Self::LessThanEqual { .. } => "Input should be less than or equal to {le}",
Self::MultipleOf { .. } => "Input should be a multiple of {multiple_of}",
Self::FiniteNumber => "Input should be a finite number",
Self::TooShort {..} => "{field_type} should have at least {min_length} item{expected_plural} after validation, not {actual_length}",
Self::TooLong {..} => "{field_type} should have at most {max_length} item{expected_plural} after validation, not {actual_length}",
Self::TooShort { .. } => {
"Data should have at least {min_length} item{expected_plural} after validation, not {actual_length}"
}
Self::TooLong { .. } => {
"Data should have at most {max_length} item{expected_plural} after validation, not {actual_length}"
}
Self::IterableType => "Input should be iterable",
Self::IterationError {..} => "Error iterating over object, error: {error}",
Self::IterationError { .. } => "Error iterating over object, error: {error}",
Self::StringType => "Input should be a valid string",
Self::StringSubType => "Input should be a string, not an instance of a subclass of str",
Self::StringUnicode => "Input should be a valid string, unable to parse raw data as a unicode string",
Self::StringTooShort {..} => "String should have at least {min_length} characters",
Self::StringTooLong {..} => "String should have at most {max_length} characters",
Self::StringPatternMismatch {..} => "String should match pattern '{pattern}'",
Self::Enum {..} => "Input should be {expected}",
Self::StringTooShort { .. } => "String should have at least {min_length} characters",
Self::StringTooLong { .. } => "String should have at most {max_length} characters",
Self::StringPatternMismatch { .. } => "String should match pattern '{pattern}'",
Self::Enum { .. } => "Input should be {expected}",
Self::DictType => "Input should be a valid dictionary",
Self::MappingType {..} => "Input should be a valid mapping, error: {error}",
Self::MappingType { .. } => "Input should be a valid mapping, error: {error}",
Self::ListType => "Input should be a valid list",
Self::TupleType => "Input should be a valid tuple",
Self::SetType => "Input should be a valid set",
Expand All @@ -525,36 +525,38 @@ impl ErrorType {
Self::FloatType => "Input should be a valid number",
Self::FloatParsing => "Input should be a valid number, unable to parse string as a number",
Self::BytesType => "Input should be a valid bytes",
Self::BytesTooShort {..} => "Data should have at least {min_length} bytes",
Self::BytesTooLong {..} => "Data should have at most {max_length} bytes",
Self::ValueError {..} => "Value error, {error}",
Self::AssertionError {..} => "Assertion failed, {error}",
Self::CustomError {..} => "", // custom errors are handled separately
Self::LiteralError {..} => "Input should be {expected}",
Self::BytesTooShort { .. } => "Data should have at least {min_length} bytes",
Self::BytesTooLong { .. } => "Data should have at most {max_length} bytes",
Self::ValueError { .. } => "Value error, {error}",
Self::AssertionError { .. } => "Assertion failed, {error}",
Self::CustomError { .. } => "", // custom errors are handled separately
Self::LiteralError { .. } => "Input should be {expected}",
Self::DateType => "Input should be a valid date",
Self::DateParsing {..} => "Input should be a valid date in the format YYYY-MM-DD, {error}",
Self::DateFromDatetimeParsing {..} => "Input should be a valid date or datetime, {error}",
Self::DateParsing { .. } => "Input should be a valid date in the format YYYY-MM-DD, {error}",
Self::DateFromDatetimeParsing { .. } => "Input should be a valid date or datetime, {error}",
Self::DateFromDatetimeInexact => "Datetimes provided to dates should have zero time - e.g. be exact dates",
Self::DatePast => "Date should be in the past",
Self::DateFuture => "Date should be in the future",
Self::TimeType => "Input should be a valid time",
Self::TimeParsing {..} => "Input should be in a valid time format, {error}",
Self::TimeParsing { .. } => "Input should be in a valid time format, {error}",
Self::DatetimeType => "Input should be a valid datetime",
Self::DatetimeParsing {..} => "Input should be a valid datetime, {error}",
Self::DatetimeObjectInvalid {..} => "Invalid datetime object, got {error}",
Self::DatetimeParsing { .. } => "Input should be a valid datetime, {error}",
Self::DatetimeObjectInvalid { .. } => "Invalid datetime object, got {error}",
Self::DatetimePast => "Input should be in the past",
Self::DatetimeFuture => "Input should be in the future",
Self::TimezoneNaive => "Input should not have timezone info",
Self::TimezoneAware => "Input should have timezone info",
Self::TimezoneOffset {..} => "Timezone offset of {tz_expected} required, got {tz_actual}",
Self::TimezoneOffset { .. } => "Timezone offset of {tz_expected} required, got {tz_actual}",
Self::TimeDeltaType => "Input should be a valid timedelta",
Self::TimeDeltaParsing {..} => "Input should be a valid timedelta, {error}",
Self::TimeDeltaParsing { .. } => "Input should be a valid timedelta, {error}",
Self::FrozenSetType => "Input should be a valid frozenset",
Self::IsInstanceOf {..} => "Input should be an instance of {class}",
Self::IsSubclassOf {..} => "Input should be a subclass of {class}",
Self::IsInstanceOf { .. } => "Input should be an instance of {class}",
Self::IsSubclassOf { .. } => "Input should be a subclass of {class}",
Self::CallableType => "Input should be callable",
Self::UnionTagInvalid {..} => "Input tag '{tag}' found using {discriminator} does not match any of the expected tags: {expected_tags}",
Self::UnionTagNotFound {..} => "Unable to extract tag using discriminator {discriminator}",
Self::UnionTagInvalid { .. } => {
"Input tag '{tag}' found using {discriminator} does not match any of the expected tags: {expected_tags}"
}
Self::UnionTagNotFound { .. } => "Unable to extract tag using discriminator {discriminator}",
Self::ArgumentsType => "Arguments must be a tuple, list or a dictionary",
Self::MissingArgument => "Missing required argument",
Self::UnexpectedKeywordArgument => "Unexpected keyword argument",
Expand All @@ -563,14 +565,13 @@ impl ErrorType {
Self::MissingPositionalOnlyArgument => "Missing required positional only argument",
Self::MultipleArgumentValues => "Got multiple values for argument",
Self::UrlType => "URL input should be a string or URL",
Self::UrlParsing {..} => "Input should be a valid URL, {error}",
Self::UrlSyntaxViolation {..} => "Input violated strict URL syntax rules, {error}",
Self::UrlTooLong {..} => "URL should have at most {max_length} characters",
Self::UrlScheme {..} => "URL scheme should be {expected_schemes}",
Self::UrlParsing { .. } => "Input should be a valid URL, {error}",
Self::UrlSyntaxViolation { .. } => "Input violated strict URL syntax rules, {error}",
Self::UrlTooLong { .. } => "URL should have at most {max_length} characters",
Self::UrlScheme { .. } => "URL scheme should be {expected_schemes}",
Self::UuidType => "UUID input should be a string, bytes or UUID object",
Self::UuidParsing { .. } => "Input should be a valid UUID, {error}",
Self::UuidVersion { .. } => "UUID version {expected_version} expected"

Self::UuidVersion { .. } => "UUID version {expected_version} expected",
}
}

Expand Down Expand Up @@ -632,20 +633,18 @@ impl ErrorType {
Self::LessThanEqual { le } => to_string_render!(tmpl, le),
Self::MultipleOf { multiple_of } => to_string_render!(tmpl, multiple_of),
Self::TooShort {
field_type,
min_length,
actual_length,
} => {
let expected_plural = plural_s(*min_length);
to_string_render!(tmpl, field_type, min_length, actual_length, expected_plural)
to_string_render!(tmpl, min_length, actual_length, expected_plural)
}
Self::TooLong {
field_type,
max_length,
actual_length,
} => {
let expected_plural = plural_s(*max_length);
to_string_render!(tmpl, field_type, max_length, actual_length, expected_plural)
to_string_render!(tmpl, max_length, actual_length, expected_plural)
}
Self::IterationError { error } => render!(tmpl, error),
Self::StringTooShort { min_length } => to_string_render!(tmpl, min_length),
Expand Down Expand Up @@ -710,15 +709,13 @@ impl ErrorType {
Self::LessThanEqual { le } => py_dict!(py, le),
Self::MultipleOf { multiple_of } => py_dict!(py, multiple_of),
Self::TooShort {
field_type,
min_length,
actual_length,
} => py_dict!(py, field_type, min_length, actual_length),
} => py_dict!(py, min_length, actual_length),
Self::TooLong {
field_type,
max_length,
actual_length,
} => py_dict!(py, field_type, max_length, actual_length),
} => py_dict!(py, max_length, actual_length),
Self::IterationError { error } => py_dict!(py, error),
Self::StringTooShort { min_length } => py_dict!(py, min_length),
Self::StringTooLong { max_length } => py_dict!(py, max_length),
Expand Down
1 change: 1 addition & 0 deletions src/input/input_abstract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,4 +306,5 @@ pub trait Input<'a>: fmt::Debug + ToPyObject {
) -> ValResult<EitherTimedelta> {
self.strict_timedelta(microseconds_overflow_behavior)
}
fn len(&self, py: Python<'_>) -> PyResult<usize>;
}
11 changes: 11 additions & 0 deletions src/input/input_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,13 @@ impl<'a> Input<'a> for JsonInput {
_ => Err(ValError::new(ErrorType::TimeDeltaType, self)),
}
}

fn len(&self, py: Python<'_>) -> PyResult<usize> {
match self.len() {
Some(len) => Ok(len),
None => self.to_object(py).as_ref(py).len(),
}
}
}

/// Required for Dict keys so the string can behave like an Input
Expand Down Expand Up @@ -522,6 +529,10 @@ impl<'a> Input<'a> for String {
) -> ValResult<EitherTimedelta> {
self.validate_timedelta(false, microseconds_overflow_behavior)
}

fn len(&self, _py: Python<'_>) -> PyResult<usize> {
Ok(self.len())
}
}

fn string_to_vec(s: &str) -> JsonArray {
Expand Down
4 changes: 4 additions & 0 deletions src/input/input_python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,10 @@ impl<'a> Input<'a> for PyAny {
Err(ValError::new(ErrorType::TimeDeltaType, self))
}
}

fn len(&self, _py: Python) -> PyResult<usize> {
self.len()
}
}

/// Best effort check of whether it's likely to make sense to inspect obj for attributes and iterate over it
Expand Down
11 changes: 11 additions & 0 deletions src/input/parse_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,14 @@ impl<'de> Visitor<'de> for KeyDeserializer {
unreachable!()
}
}

impl JsonInput {
pub fn len(&self) -> Option<usize> {
match self {
Self::Array(v) => Some(v.len()),
Self::Object(o) => Some(o.len()),
Self::String(s) => Some(s.len()),
_ => None,
}
}
}

0 comments on commit e2cd996

Please sign in to comment.