Skip to content

adding tools.rs, add extract_i64 #635

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/argument_markers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use pyo3::basic::CompareOp;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyTuple};

use crate::build_tools::safe_repr;
use crate::tools::safe_repr;

#[pyclass(module = "pydantic_core._pydantic_core", get_all, frozen, freelist = 100)]
#[derive(Debug, Clone)]
Expand Down
110 changes: 11 additions & 99 deletions src/build_tools.rs
Original file line number Diff line number Diff line change
@@ -1,70 +1,15 @@
use std::borrow::Cow;
use std::error::Error;
use std::fmt;

use pyo3::exceptions::{PyException, PyKeyError};
use pyo3::exceptions::PyException;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList, PyString};
use pyo3::{intern, FromPyObject, PyErrArguments};

use crate::errors::{ErrorMode, ValError};
use crate::tools::SchemaDict;
use crate::ValidationError;

pub trait SchemaDict<'py> {
fn get_as<T>(&'py self, key: &PyString) -> PyResult<Option<T>>
where
T: FromPyObject<'py>;

fn get_as_req<T>(&'py self, key: &PyString) -> PyResult<T>
where
T: FromPyObject<'py>;
}

impl<'py> SchemaDict<'py> for PyDict {
fn get_as<T>(&'py self, key: &PyString) -> PyResult<Option<T>>
where
T: FromPyObject<'py>,
{
match self.get_item(key) {
Some(t) => Ok(Some(<T>::extract(t)?)),
None => Ok(None),
}
}

fn get_as_req<T>(&'py self, key: &PyString) -> PyResult<T>
where
T: FromPyObject<'py>,
{
match self.get_item(key) {
Some(t) => <T>::extract(t),
None => py_err!(PyKeyError; "{}", key),
}
}
}

impl<'py> SchemaDict<'py> for Option<&PyDict> {
fn get_as<T>(&'py self, key: &PyString) -> PyResult<Option<T>>
where
T: FromPyObject<'py>,
{
match self {
Some(d) => d.get_as(key),
None => Ok(None),
}
}

#[cfg_attr(has_no_coverage, no_coverage)]
fn get_as_req<T>(&'py self, key: &PyString) -> PyResult<T>
where
T: FromPyObject<'py>,
{
match self {
Some(d) => d.get_as_req(key),
None => py_err!(PyKeyError; "{}", key),
}
}
}

pub fn schema_or_config<'py, T>(
schema: &'py PyDict,
config: Option<&'py PyDict>,
Expand Down Expand Up @@ -196,58 +141,25 @@ impl SchemaError {
}
}

macro_rules! py_error_type {
macro_rules! py_schema_error_type {
($msg:expr) => {
crate::build_tools::py_error_type!(crate::build_tools::SchemaError; $msg)
crate::tools::py_error_type!(crate::build_tools::SchemaError; $msg)
};
($msg:expr, $( $msg_args:expr ),+ ) => {
crate::build_tools::py_error_type!(crate::build_tools::SchemaError; $msg, $( $msg_args ),+)
};

($error_type:ty; $msg:expr) => {
<$error_type>::new_err($msg)
};

($error_type:ty; $msg:expr, $( $msg_args:expr ),+ ) => {
<$error_type>::new_err(format!($msg, $( $msg_args ),+))
crate::tools::py_error_type!(crate::build_tools::SchemaError; $msg, $( $msg_args ),+)
};
}
pub(crate) use py_error_type;
pub(crate) use py_schema_error_type;

macro_rules! py_err {
macro_rules! py_schema_err {
($msg:expr) => {
Err(crate::build_tools::py_error_type!($msg))
Err(crate::build_tools::py_schema_error_type!($msg))
};
($msg:expr, $( $msg_args:expr ),+ ) => {
Err(crate::build_tools::py_error_type!($msg, $( $msg_args ),+))
};

($error_type:ty; $msg:expr) => {
Err(crate::build_tools::py_error_type!($error_type; $msg))
Err(crate::build_tools::py_schema_error_type!($msg, $( $msg_args ),+))
};

($error_type:ty; $msg:expr, $( $msg_args:expr ),+ ) => {
Err(crate::build_tools::py_error_type!($error_type; $msg, $( $msg_args ),+))
};
}
pub(crate) use py_err;

pub fn function_name(f: &PyAny) -> PyResult<String> {
match f.getattr(intern!(f.py(), "__name__")) {
Ok(name) => name.extract(),
_ => f.repr()?.extract(),
}
}

pub fn safe_repr(v: &PyAny) -> Cow<str> {
if let Ok(s) = v.repr() {
s.to_string_lossy()
} else if let Ok(name) = v.get_type().name() {
format!("<unprintable {name} object>").into()
} else {
"<unprintable object>".into()
}
}
pub(crate) use py_schema_err;

#[derive(Debug, Clone)]
pub(crate) enum ExtraBehavior {
Expand All @@ -274,7 +186,7 @@ impl ExtraBehavior {
Some("allow") => Self::Allow,
Some("ignore") => Self::Ignore,
Some("forbid") => Self::Forbid,
Some(v) => return py_err!("Invalid extra_behavior: `{}`", v),
Some(v) => return py_schema_err!("Invalid extra_behavior: `{}`", v),
None => default,
};
Ok(res)
Expand Down
10 changes: 5 additions & 5 deletions src/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use pyo3::prelude::*;

use ahash::AHashMap;

use crate::build_tools::py_err;
use crate::build_tools::py_schema_err;

// An integer id for the reference
pub type ReferenceId = usize;
Expand Down Expand Up @@ -68,7 +68,7 @@ impl<T: Clone + std::fmt::Debug> DefinitionsBuilder<T> {
let next_id = self.definitions.len();
match self.definitions.entry(reference.clone()) {
Entry::Occupied(mut entry) => match entry.get_mut().value.replace(value) {
Some(_) => py_err!("Duplicate ref: `{}`", reference),
Some(_) => py_schema_err!("Duplicate ref: `{}`", reference),
None => Ok(entry.get().id),
},
Entry::Vacant(entry) => {
Expand All @@ -86,11 +86,11 @@ impl<T: Clone + std::fmt::Debug> DefinitionsBuilder<T> {
pub fn get_definition(&self, reference_id: ReferenceId) -> PyResult<&T> {
let (reference, def) = match self.definitions.iter().find(|(_, def)| def.id == reference_id) {
Some(v) => v,
None => return py_err!("Definitions error: no definition for ReferenceId `{}`", reference_id),
None => return py_schema_err!("Definitions error: no definition for ReferenceId `{}`", reference_id),
};
match def.value.as_ref() {
Some(v) => Ok(v),
None => py_err!(
None => py_schema_err!(
"Definitions error: attempted to use `{}` before it was filled",
reference
),
Expand All @@ -103,7 +103,7 @@ impl<T: Clone + std::fmt::Debug> DefinitionsBuilder<T> {
let mut defs: Vec<(usize, T)> = Vec::new();
for (reference, def) in self.definitions.into_iter() {
match def.value {
None => return py_err!("Definitions error: definition {} was never filled", reference),
None => return py_schema_err!("Definitions error: definition {} was never filled", reference),
Some(v) => defs.push((def.id, v)),
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/errors/location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use serde::ser::SerializeSeq;
use serde::{Serialize, Serializer};

use crate::lookup_key::{LookupPath, PathItem};
use crate::tools::extract_i64;

/// Used to store individual items of the error location, e.g. a string for key/field names
/// or a number for array indices.
Expand Down Expand Up @@ -88,7 +89,7 @@ impl TryFrom<&PyAny> for LocItem {
if let Ok(py_str) = loc_item.downcast::<PyString>() {
let str = py_str.to_str()?.to_string();
Ok(Self::S(str))
} else if let Ok(int) = loc_item.extract::<i64>() {
} else if let Ok(int) = extract_i64(loc_item) {
Ok(Self::I(int))
} else {
Err(PyTypeError::new_err("Item in a location must be a string or int"))
Expand Down
4 changes: 2 additions & 2 deletions src/errors/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use pyo3::once_cell::GILOnceCell;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList};

use crate::build_tools::{py_err, py_error_type};
use crate::tools::{extract_i64, py_err, py_error_type};
use strum::{Display, EnumMessage, IntoEnumIterator};
use strum_macros::EnumIter;

Expand Down Expand Up @@ -729,7 +729,7 @@ impl From<String> for Number {

impl FromPyObject<'_> for Number {
fn extract(obj: &PyAny) -> PyResult<Self> {
if let Ok(int) = obj.extract::<i64>() {
if let Ok(int) = extract_i64(obj) {
Ok(Number::Int(int))
} else if let Ok(float) = obj.extract::<f64>() {
Ok(Number::Float(float))
Expand Down
5 changes: 3 additions & 2 deletions src/errors/validation_exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ use serde::{Serialize, Serializer};

use serde_json::ser::PrettyFormatter;

use crate::build_tools::{py_error_type, safe_repr, SchemaDict};
use crate::build_tools::py_schema_error_type;
use crate::errors::LocItem;
use crate::get_version;
use crate::serializers::{SerMode, SerializationState};
use crate::tools::{safe_repr, SchemaDict};

use super::line_error::ValLineError;
use super::location::Location;
Expand Down Expand Up @@ -83,7 +84,7 @@ impl ValidationError {
}

pub fn omit_error() -> PyErr {
py_error_type!("Uncaught Omit error, please check your usage of `default` validators.")
py_schema_error_type!("Uncaught Omit error, please check your usage of `default` validators.")
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/errors/value_exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use pyo3::prelude::*;
use pyo3::types::{PyDict, PyString};

use crate::input::Input;
use crate::tools::extract_i64;

use super::{ErrorType, ValError};

Expand Down Expand Up @@ -74,7 +75,7 @@ impl PydanticCustomError {
let key: &PyString = key.downcast()?;
if let Ok(py_str) = value.downcast::<PyString>() {
message = message.replace(&format!("{{{}}}", key.to_str()?), py_str.to_str()?);
} else if let Ok(value_int) = value.extract::<i64>() {
} else if let Ok(value_int) = extract_i64(value) {
message = message.replace(&format!("{{{}}}", key.to_str()?), &value_int.to_string());
} else {
// fallback for anything else just in case
Expand Down
20 changes: 10 additions & 10 deletions src/input/input_python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use pyo3::types::{
use pyo3::types::{PyDictItems, PyDictKeys, PyDictValues};
use pyo3::{ffi, intern, AsPyPointer, PyTypeInfo};

use crate::build_tools::safe_repr;
use crate::errors::{ErrorType, InputValue, LocItem, ValError, ValResult};
use crate::tools::{extract_i64, safe_repr};
use crate::{ArgsKwargs, PyMultiHostUrl, PyUrl};

use super::datetime::{
Expand Down Expand Up @@ -89,7 +89,7 @@ impl<'a> Input<'a> for PyAny {
fn as_loc_item(&self) -> LocItem {
if let Ok(py_str) = self.downcast::<PyString>() {
py_str.to_string_lossy().as_ref().into()
} else if let Ok(key_int) = self.extract::<i64>() {
} else if let Ok(key_int) = extract_i64(self) {
key_int.into()
} else {
safe_repr(self).to_string().into()
Expand Down Expand Up @@ -244,19 +244,19 @@ impl<'a> Input<'a> for PyAny {
}

fn strict_bool(&self) -> ValResult<bool> {
if let Ok(bool) = self.extract::<bool>() {
Ok(bool)
if let Ok(bool) = self.downcast::<PyBool>() {
Ok(bool.is_true())
} else {
Err(ValError::new(ErrorType::BoolType, self))
}
}

fn lax_bool(&self) -> ValResult<bool> {
if let Ok(bool) = self.extract::<bool>() {
Ok(bool)
if let Ok(bool) = self.downcast::<PyBool>() {
Ok(bool.is_true())
} else if let Some(cow_str) = maybe_as_string(self, ErrorType::BoolParsing)? {
str_as_bool(self, &cow_str)
} else if let Ok(int) = self.extract::<i64>() {
} else if let Ok(int) = extract_i64(self) {
int_as_bool(self, int)
} else if let Ok(float) = self.extract::<f64>() {
match float_as_int(self, float) {
Expand Down Expand Up @@ -547,7 +547,7 @@ impl<'a> Input<'a> for PyAny {
bytes_as_time(self, py_bytes.as_bytes())
} else if PyBool::is_exact_type_of(self) {
Err(ValError::new(ErrorType::TimeType, self))
} else if let Ok(int) = self.extract::<i64>() {
} else if let Ok(int) = extract_i64(self) {
int_as_time(self, int, 0)
} else if let Ok(float) = self.extract::<f64>() {
float_as_time(self, float)
Expand All @@ -574,7 +574,7 @@ impl<'a> Input<'a> for PyAny {
bytes_as_datetime(self, py_bytes.as_bytes())
} else if PyBool::is_exact_type_of(self) {
Err(ValError::new(ErrorType::DatetimeType, self))
} else if let Ok(int) = self.extract::<i64>() {
} else if let Ok(int) = extract_i64(self) {
int_as_datetime(self, int, 0)
} else if let Ok(float) = self.extract::<f64>() {
float_as_datetime(self, float)
Expand All @@ -601,7 +601,7 @@ impl<'a> Input<'a> for PyAny {
bytes_as_timedelta(self, str.as_bytes())
} else if let Ok(py_bytes) = self.downcast::<PyBytes>() {
bytes_as_timedelta(self, py_bytes.as_bytes())
} else if let Ok(int) = self.extract::<i64>() {
} else if let Ok(int) = extract_i64(self) {
Ok(int_as_duration(self, int)?.into())
} else if let Ok(float) = self.extract::<f64>() {
Ok(float_as_duration(self, float)?.into())
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod lazy_index_map;
mod lookup_key;
mod recursion_guard;
mod serializers;
mod tools;
mod url;
mod validators;

Expand Down
9 changes: 5 additions & 4 deletions src/lookup_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ use pyo3::exceptions::{PyAttributeError, PyTypeError};
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList, PyMapping, PyString};

use crate::build_tools::py_err;
use crate::build_tools::py_schema_err;
use crate::errors::{ErrorType, ValLineError};
use crate::input::{Input, JsonInput, JsonObject};
use crate::tools::{extract_i64, py_err};

/// Used got getting items from python dicts, python objects, or JSON objects, in different ways
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -73,7 +74,7 @@ impl LookupKey {
let list: &PyList = value.downcast()?;
let first = match list.get_item(0) {
Ok(v) => v,
Err(_) => return py_err!("Lookup paths should have at least one element"),
Err(_) => return py_schema_err!("Lookup paths should have at least one element"),
};
let mut locs: Vec<LookupPath> = if first.downcast::<PyString>().is_ok() {
// list of strings rather than list of lists
Expand Down Expand Up @@ -331,7 +332,7 @@ impl LookupPath {
.collect::<PyResult<Vec<PathItem>>>()?;

if v.is_empty() {
py_err!("Each alias path should have at least one element")
py_schema_err!("Each alias path should have at least one element")
} else {
Ok(Self(v))
}
Expand Down Expand Up @@ -408,7 +409,7 @@ impl PathItem {
} else {
Ok(Self::Pos(usize_key))
}
} else if let Ok(int_key) = obj.extract::<i64>() {
} else if let Ok(int_key) = extract_i64(obj) {
if index == 0 {
py_err!(PyTypeError; "The first item in an alias path should be a string")
} else {
Expand Down
Loading