Skip to content
Draft
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
50 changes: 36 additions & 14 deletions src/build_tools.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::convert::Infallible;
use std::error::Error;
use std::fmt;
use std::ops::Deref;
Expand All @@ -9,6 +10,7 @@ use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList, PyString};
use pyo3::{intern, FromPyObject, PyErrArguments};

use crate::config::CoreConfig;
use crate::errors::{PyLineError, ValError};
use crate::input::InputType;
use crate::tools::SchemaDict;
Expand Down Expand Up @@ -43,9 +45,14 @@ where
schema_or_config(schema, config, key, key)
}

pub fn is_strict(schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>) -> PyResult<bool> {
pub fn is_strict(schema: &Bound<'_, PyDict>, config: &CoreConfig) -> PyResult<bool> {
let py = schema.py();
Ok(schema_or_config_same(schema, config, intern!(py, "strict"))?.unwrap_or(false))
let is_strict = schema
.get_as(intern!(py, "strict"))?
.flatten()
.or(config.strict)
.unwrap_or(false);
Ok(is_strict)
}

enum SchemaErrorEnum {
Expand Down Expand Up @@ -189,21 +196,36 @@ impl ExtraBehavior {
pub fn from_schema_or_config(
py: Python,
schema: &Bound<'_, PyDict>,
config: Option<&Bound<'_, PyDict>>,
config: &CoreConfig,
default: Self,
) -> PyResult<Self> {
let extra_behavior = schema_or_config::<Option<Bound<'_, PyString>>>(
schema,
config,
intern!(py, "extra_behavior"),
intern!(py, "extra_fields_behavior"),
)?
.flatten();
let res = match extra_behavior.as_ref().map(|s| s.to_str()).transpose()? {
Some(s) => Self::from_str(s)?,
None => default,
let extra_behavior = schema.get_as(intern!(py, "extra_behavior"))?.flatten();
let extra_behavior = extra_behavior.or(config.extra_fields_behavior);
Ok(extra_behavior.unwrap_or(default))
}
}

impl FromPyObject<'_> for ExtraBehavior {
fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
let s: &str = ob.extract()?;
Self::from_str(s)
}
}

impl<'py> IntoPyObject<'py> for ExtraBehavior {
type Target = PyString;

type Output = Borrowed<'py, 'py, PyString>;

type Error = Infallible;

fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let s = match self {
Self::Allow => intern!(py, "allow"),
Self::Forbid => intern!(py, "forbid"),
Self::Ignore => intern!(py, "ignore"),
};
Ok(res)
Ok(s.as_borrowed())
}
}

Expand Down
55 changes: 55 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use std::convert::Infallible;

use pyo3::{
intern,
types::{PyAnyMethods, PyString},
Borrowed, Bound, FromPyObject, IntoPyObject, PyAny, PyResult, Python,
};

use crate::{
build_tools::{py_schema_error_type, ExtraBehavior},
validators::{Revalidate, TemporalUnitMode, ValBytesMode},
};

#[derive(FromPyObject, IntoPyObject, Default, Clone, Debug)]
pub struct CoreConfig {
pub loc_by_alias: Option<bool>,
pub extra_fields_behavior: Option<ExtraBehavior>,
pub validate_by_alias: Option<bool>,
pub validate_by_name: Option<bool>,
pub val_json_bytes: Option<ValBytesMode>,
pub val_temporal_unit: Option<TemporalUnitMode>,
pub revalidate_instances: Option<Revalidate>,
pub microseconds_precision: Option<MicrosecondsPrecisionOverflowBehavior>,
pub strict: Option<bool>,
pub allow_inf_nan: Option<bool>,
}

/// Wrapper around the speedate config value to add Python conversions
#[derive(Debug, Clone, Copy, PartialEq)]
struct MicrosecondsPrecisionOverflowBehavior(pub speedate::MicrosecondsPrecisionOverflowBehavior);

impl FromPyObject<'_> for MicrosecondsPrecisionOverflowBehavior {
fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
let s: &str = ob.extract()?;
s.parse().map(MicrosecondsPrecisionOverflowBehavior).map_err(|_| {
py_schema_error_type!("Invalid `microseconds_precision`, must be one of \"truncate\" or \"error\"")
})
}
}

impl<'py> IntoPyObject<'py> for MicrosecondsPrecisionOverflowBehavior {
type Target = PyString;

type Output = Borrowed<'py, 'py, PyString>;

type Error = Infallible;

fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let s = match self.0 {
speedate::MicrosecondsPrecisionOverflowBehavior::Truncate => intern!(py, "truncate"),
speedate::MicrosecondsPrecisionOverflowBehavior::Error => intern!(py, "error"),
};
Ok(s.as_borrowed())
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod py_gc;
mod argument_markers;
mod build_tools;
mod common;
mod config;
mod definitions;
mod errors;
mod input;
Expand Down
9 changes: 8 additions & 1 deletion src/serializers/type_serializers/dataclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use ahash::AHashMap;
use serde::ser::SerializeMap;

use crate::build_tools::{py_schema_error_type, ExtraBehavior};
use crate::config::CoreConfig;
use crate::definitions::DefinitionsBuilder;
use crate::tools::SchemaDict;

Expand All @@ -29,10 +30,16 @@ impl BuildSerializer for DataclassArgsBuilder {
) -> PyResult<Arc<CombinedSerializer>> {
let py = schema.py();

let typed_config: CoreConfig = match config {
Some(config) => config.extract()?,
None => CoreConfig::default(),
};

let fields_list: Bound<'_, PyList> = schema.get_as_req(intern!(py, "fields"))?;
let mut fields: AHashMap<String, SerField> = AHashMap::with_capacity(fields_list.len());

let fields_mode = match ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)? {
let fields_mode = match ExtraBehavior::from_schema_or_config(py, schema, &typed_config, ExtraBehavior::Ignore)?
{
ExtraBehavior::Allow => FieldsMode::TypedDictAllow,
_ => FieldsMode::SimpleDict,
};
Expand Down
7 changes: 6 additions & 1 deletion src/serializers/type_serializers/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use super::{
};
use crate::build_tools::py_schema_err;
use crate::build_tools::{py_schema_error_type, ExtraBehavior};
use crate::config::CoreConfig;
use crate::definitions::DefinitionsBuilder;
use crate::serializers::errors::PydanticSerializationUnexpectedValue;
use crate::tools::SchemaDict;
Expand Down Expand Up @@ -133,7 +134,11 @@ impl BuildSerializer for ModelSerializer {

fn has_extra(schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>) -> PyResult<bool> {
let py = schema.py();
let extra_behaviour = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?;
let typed_config: CoreConfig = match config {
Some(config) => config.extract()?,
None => CoreConfig::default(),
};
let extra_behaviour = ExtraBehavior::from_schema_or_config(py, schema, &typed_config, ExtraBehavior::Ignore)?;
Ok(matches!(extra_behaviour, ExtraBehavior::Allow))
}

Expand Down
9 changes: 8 additions & 1 deletion src/serializers/type_serializers/typed_dict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use ahash::AHashMap;

use crate::build_tools::py_schema_err;
use crate::build_tools::{py_schema_error_type, schema_or_config, ExtraBehavior};
use crate::config::CoreConfig;
use crate::definitions::DefinitionsBuilder;
use crate::tools::SchemaDict;

Expand All @@ -29,7 +30,13 @@ impl BuildSerializer for TypedDictBuilder {
let total =
schema_or_config(schema, config, intern!(py, "total"), intern!(py, "typed_dict_total"))?.unwrap_or(true);

let fields_mode = match ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)? {
let typed_config: CoreConfig = match config {
Some(config) => config.extract()?,
None => CoreConfig::default(),
};

let fields_mode = match ExtraBehavior::from_schema_or_config(py, schema, &typed_config, ExtraBehavior::Ignore)?
{
ExtraBehavior::Allow => FieldsMode::TypedDictAllow,
_ => FieldsMode::SimpleDict,
};
Expand Down
3 changes: 2 additions & 1 deletion src/validators/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::sync::Arc;
use pyo3::prelude::*;
use pyo3::types::PyDict;

use crate::config::CoreConfig;
use crate::input::Input;
use crate::{build_tools::LazyLock, errors::ValResult};

Expand All @@ -21,7 +22,7 @@ impl BuildValidator for AnyValidator {

fn build(
_schema: &Bound<'_, PyDict>,
_config: Option<&Bound<'_, PyDict>>,
_config: &CoreConfig,
_definitions: &mut DefinitionsBuilder<Arc<CombinedValidator>>,
) -> PyResult<Arc<CombinedValidator>> {
Ok(ANY_VALIDATOR.clone())
Expand Down
15 changes: 10 additions & 5 deletions src/validators/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use ahash::AHashSet;
use pyo3::IntoPyObjectExt;

use crate::build_tools::py_schema_err;
use crate::build_tools::{schema_or_config_same, ExtraBehavior};
use crate::build_tools::ExtraBehavior;
use crate::config::CoreConfig;
use crate::errors::{ErrorTypeDefaults, ValError, ValLineError, ValResult};
use crate::input::{Arguments, BorrowInput, Input, KeywordArgs, PositionalArgs, ValidationMatch};
use crate::lookup_key::LookupKeyCollection;
Expand Down Expand Up @@ -67,7 +68,7 @@ impl BuildValidator for ArgumentsValidator {

fn build(
schema: &Bound<'_, PyDict>,
config: Option<&Bound<'_, PyDict>>,
config: &CoreConfig,
definitions: &mut DefinitionsBuilder<Arc<CombinedValidator>>,
) -> PyResult<Arc<CombinedValidator>> {
let py = schema.py();
Expand Down Expand Up @@ -166,10 +167,14 @@ impl BuildValidator for ArgumentsValidator {
},
var_kwargs_mode,
var_kwargs_validator,
loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true),
loc_by_alias: config.loc_by_alias.unwrap_or(true),
extra: ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Forbid)?,
validate_by_alias: schema_or_config_same(schema, config, intern!(py, "validate_by_alias"))?,
validate_by_name: schema_or_config_same(schema, config, intern!(py, "validate_by_name"))?,
validate_by_alias: schema
.get_as(intern!(py, "validate_by_alias"))?
.or(config.validate_by_alias),
validate_by_name: schema
.get_as(intern!(py, "validate_by_name"))?
.or(config.validate_by_name),
})
.into())
}
Expand Down
15 changes: 10 additions & 5 deletions src/validators/arguments_v3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use ahash::AHashSet;
use pyo3::IntoPyObjectExt;

use crate::build_tools::py_schema_err;
use crate::build_tools::{schema_or_config_same, ExtraBehavior};
use crate::build_tools::ExtraBehavior;
use crate::config::CoreConfig;
use crate::errors::LocItem;
use crate::errors::{ErrorTypeDefaults, ValError, ValLineError, ValResult};
use crate::input::ConsumeIterator;
Expand Down Expand Up @@ -80,7 +81,7 @@ impl BuildValidator for ArgumentsV3Validator {

fn build(
schema: &Bound<'_, PyDict>,
config: Option<&Bound<'_, PyDict>>,
config: &CoreConfig,
definitions: &mut DefinitionsBuilder<Arc<CombinedValidator>>,
) -> PyResult<Arc<CombinedValidator>> {
let py = schema.py();
Expand Down Expand Up @@ -208,10 +209,14 @@ impl BuildValidator for ArgumentsV3Validator {
Ok(CombinedValidator::ArgumentsV3(Self {
parameters,
positional_params_count,
loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true),
loc_by_alias: config.loc_by_alias.unwrap_or(true),
extra: ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Forbid)?,
validate_by_alias: schema_or_config_same(schema, config, intern!(py, "validate_by_alias"))?,
validate_by_name: schema_or_config_same(schema, config, intern!(py, "validate_by_name"))?,
validate_by_alias: schema
.get_as(intern!(py, "validate_by_alias"))?
.or(config.validate_by_alias),
validate_by_name: schema
.get_as(intern!(py, "validate_by_name"))?
.or(config.validate_by_name),
})
.into())
}
Expand Down
3 changes: 2 additions & 1 deletion src/validators/bool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use pyo3::types::PyDict;
use pyo3::{prelude::*, IntoPyObjectExt};

use crate::build_tools::{is_strict, LazyLock};
use crate::config::CoreConfig;
use crate::errors::ValResult;
use crate::input::Input;

Expand All @@ -25,7 +26,7 @@ impl BuildValidator for BoolValidator {

fn build(
schema: &Bound<'_, PyDict>,
config: Option<&Bound<'_, PyDict>>,
config: &CoreConfig,
_definitions: &mut DefinitionsBuilder<Arc<CombinedValidator>>,
) -> PyResult<Arc<CombinedValidator>> {
if is_strict(schema, config)? {
Expand Down
9 changes: 5 additions & 4 deletions src/validators/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use pyo3::types::PyDict;
use pyo3::IntoPyObjectExt;

use crate::build_tools::is_strict;
use crate::config::CoreConfig;
use crate::errors::{ErrorType, ValError, ValResult};
use crate::input::Input;

Expand All @@ -25,7 +26,7 @@ impl BuildValidator for BytesValidator {

fn build(
schema: &Bound<'_, PyDict>,
config: Option<&Bound<'_, PyDict>>,
config: &CoreConfig,
_definitions: &mut DefinitionsBuilder<Arc<CombinedValidator>>,
) -> PyResult<Arc<CombinedValidator>> {
let py = schema.py();
Expand All @@ -36,7 +37,7 @@ impl BuildValidator for BytesValidator {
} else {
Ok(CombinedValidator::Bytes(Self {
strict: is_strict(schema, config)?,
bytes_mode: ValBytesMode::from_config(config)?,
bytes_mode: ValBytesMode::from_config(config),
})
.into())
}
Expand Down Expand Up @@ -115,11 +116,11 @@ impl Validator for BytesConstrainedValidator {
}

impl BytesConstrainedValidator {
fn build(schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>) -> PyResult<Arc<CombinedValidator>> {
fn build(schema: &Bound<'_, PyDict>, config: &CoreConfig) -> PyResult<Arc<CombinedValidator>> {
let py = schema.py();
Ok(CombinedValidator::ConstrainedBytes(Self {
strict: is_strict(schema, config)?,
bytes_mode: ValBytesMode::from_config(config)?,
bytes_mode: ValBytesMode::from_config(config),
min_length: schema.get_as(intern!(py, "min_length"))?,
max_length: schema.get_as(intern!(py, "max_length"))?,
})
Expand Down
3 changes: 2 additions & 1 deletion src/validators/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use pyo3::prelude::*;
use pyo3::types::PyString;
use pyo3::types::{PyDict, PyTuple};

use crate::config::CoreConfig;
use crate::errors::ValResult;
use crate::input::Input;

Expand All @@ -27,7 +28,7 @@ impl BuildValidator for CallValidator {

fn build(
schema: &Bound<'_, PyDict>,
config: Option<&Bound<'_, PyDict>>,
config: &CoreConfig,
definitions: &mut DefinitionsBuilder<Arc<CombinedValidator>>,
) -> PyResult<Arc<CombinedValidator>> {
let py = schema.py();
Expand Down
3 changes: 2 additions & 1 deletion src/validators/callable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use pyo3::prelude::*;
use pyo3::types::PyDict;

use crate::build_tools::LazyLock;
use crate::config::CoreConfig;
use crate::errors::{ErrorTypeDefaults, ValError, ValResult};
use crate::input::Input;

Expand All @@ -20,7 +21,7 @@ impl BuildValidator for CallableValidator {

fn build(
_schema: &Bound<'_, PyDict>,
_config: Option<&Bound<'_, PyDict>>,
_config: &CoreConfig,
_definitions: &mut DefinitionsBuilder<Arc<CombinedValidator>>,
) -> PyResult<Arc<CombinedValidator>> {
Ok(CALLABLE_VALIDATOR.clone())
Expand Down
Loading
Loading