Skip to content

Commit 97a6b76

Browse files
committed
add CoreConfig typed struct during schema build
1 parent 37d2ce4 commit 97a6b76

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+357
-159
lines changed

src/build_tools.rs

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::convert::Infallible;
12
use std::error::Error;
23
use std::fmt;
34
use std::ops::Deref;
@@ -9,6 +10,7 @@ use pyo3::prelude::*;
910
use pyo3::types::{PyDict, PyList, PyString};
1011
use pyo3::{intern, FromPyObject, PyErrArguments};
1112

13+
use crate::config::CoreConfig;
1214
use crate::errors::{PyLineError, ValError};
1315
use crate::input::InputType;
1416
use crate::tools::SchemaDict;
@@ -43,9 +45,14 @@ where
4345
schema_or_config(schema, config, key, key)
4446
}
4547

46-
pub fn is_strict(schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>) -> PyResult<bool> {
48+
pub fn is_strict(schema: &Bound<'_, PyDict>, config: &CoreConfig) -> PyResult<bool> {
4749
let py = schema.py();
48-
Ok(schema_or_config_same(schema, config, intern!(py, "strict"))?.unwrap_or(false))
50+
let is_strict = schema
51+
.get_as(intern!(py, "strict"))?
52+
.flatten()
53+
.or(config.strict)
54+
.unwrap_or(false);
55+
Ok(is_strict)
4956
}
5057

5158
enum SchemaErrorEnum {
@@ -189,21 +196,36 @@ impl ExtraBehavior {
189196
pub fn from_schema_or_config(
190197
py: Python,
191198
schema: &Bound<'_, PyDict>,
192-
config: Option<&Bound<'_, PyDict>>,
199+
config: &CoreConfig,
193200
default: Self,
194201
) -> PyResult<Self> {
195-
let extra_behavior = schema_or_config::<Option<Bound<'_, PyString>>>(
196-
schema,
197-
config,
198-
intern!(py, "extra_behavior"),
199-
intern!(py, "extra_fields_behavior"),
200-
)?
201-
.flatten();
202-
let res = match extra_behavior.as_ref().map(|s| s.to_str()).transpose()? {
203-
Some(s) => Self::from_str(s)?,
204-
None => default,
202+
let extra_behavior = schema.get_as(intern!(py, "extra_behavior"))?.flatten();
203+
let extra_behavior = extra_behavior.or(config.extra_fields_behavior);
204+
Ok(extra_behavior.unwrap_or(default))
205+
}
206+
}
207+
208+
impl FromPyObject<'_> for ExtraBehavior {
209+
fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
210+
let s: &str = ob.extract()?;
211+
Self::from_str(s)
212+
}
213+
}
214+
215+
impl<'py> IntoPyObject<'py> for ExtraBehavior {
216+
type Target = PyString;
217+
218+
type Output = Borrowed<'py, 'py, PyString>;
219+
220+
type Error = Infallible;
221+
222+
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
223+
let s = match self {
224+
Self::Allow => intern!(py, "allow"),
225+
Self::Forbid => intern!(py, "forbid"),
226+
Self::Ignore => intern!(py, "ignore"),
205227
};
206-
Ok(res)
228+
Ok(s.as_borrowed())
207229
}
208230
}
209231

src/config.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use std::convert::Infallible;
2+
3+
use pyo3::{
4+
intern,
5+
types::{PyAnyMethods, PyString},
6+
Borrowed, Bound, FromPyObject, IntoPyObject, PyAny, PyResult, Python,
7+
};
8+
9+
use crate::{
10+
build_tools::{py_schema_error_type, ExtraBehavior},
11+
validators::{Revalidate, TemporalUnitMode, ValBytesMode},
12+
};
13+
14+
#[derive(FromPyObject, IntoPyObject, Default, Clone, Debug)]
15+
pub struct CoreConfig {
16+
pub loc_by_alias: Option<bool>,
17+
pub extra_fields_behavior: Option<ExtraBehavior>,
18+
pub validate_by_alias: Option<bool>,
19+
pub validate_by_name: Option<bool>,
20+
pub val_json_bytes: Option<ValBytesMode>,
21+
pub val_temporal_unit: Option<TemporalUnitMode>,
22+
pub revalidate_instances: Option<Revalidate>,
23+
pub microseconds_precision: Option<MicrosecondsPrecisionOverflowBehavior>,
24+
pub strict: Option<bool>,
25+
pub allow_inf_nan: Option<bool>,
26+
}
27+
28+
/// Wrapper around the speedate config value to add Python conversions
29+
#[derive(Debug, Clone, Copy, PartialEq)]
30+
struct MicrosecondsPrecisionOverflowBehavior(pub speedate::MicrosecondsPrecisionOverflowBehavior);
31+
32+
impl FromPyObject<'_> for MicrosecondsPrecisionOverflowBehavior {
33+
fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
34+
let s: &str = ob.extract()?;
35+
s.parse().map(MicrosecondsPrecisionOverflowBehavior).map_err(|_| {
36+
py_schema_error_type!("Invalid `microseconds_precision`, must be one of \"truncate\" or \"error\"")
37+
})
38+
}
39+
}
40+
41+
impl<'py> IntoPyObject<'py> for MicrosecondsPrecisionOverflowBehavior {
42+
type Target = PyString;
43+
44+
type Output = Borrowed<'py, 'py, PyString>;
45+
46+
type Error = Infallible;
47+
48+
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
49+
let s = match self.0 {
50+
speedate::MicrosecondsPrecisionOverflowBehavior::Truncate => intern!(py, "truncate"),
51+
speedate::MicrosecondsPrecisionOverflowBehavior::Error => intern!(py, "error"),
52+
};
53+
Ok(s.as_borrowed())
54+
}
55+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod py_gc;
1717
mod argument_markers;
1818
mod build_tools;
1919
mod common;
20+
mod config;
2021
mod definitions;
2122
mod errors;
2223
mod input;

src/serializers/type_serializers/dataclass.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use ahash::AHashMap;
88
use serde::ser::SerializeMap;
99

1010
use crate::build_tools::{py_schema_error_type, ExtraBehavior};
11+
use crate::config::CoreConfig;
1112
use crate::definitions::DefinitionsBuilder;
1213
use crate::tools::SchemaDict;
1314

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

33+
let typed_config: CoreConfig = match config {
34+
Some(config) => config.extract()?,
35+
None => CoreConfig::default(),
36+
};
37+
3238
let fields_list: Bound<'_, PyList> = schema.get_as_req(intern!(py, "fields"))?;
3339
let mut fields: AHashMap<String, SerField> = AHashMap::with_capacity(fields_list.len());
3440

35-
let fields_mode = match ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)? {
41+
let fields_mode = match ExtraBehavior::from_schema_or_config(py, schema, &typed_config, ExtraBehavior::Ignore)?
42+
{
3643
ExtraBehavior::Allow => FieldsMode::TypedDictAllow,
3744
_ => FieldsMode::SimpleDict,
3845
};

src/serializers/type_serializers/model.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use super::{
1515
};
1616
use crate::build_tools::py_schema_err;
1717
use crate::build_tools::{py_schema_error_type, ExtraBehavior};
18+
use crate::config::CoreConfig;
1819
use crate::definitions::DefinitionsBuilder;
1920
use crate::serializers::errors::PydanticSerializationUnexpectedValue;
2021
use crate::tools::SchemaDict;
@@ -133,7 +134,11 @@ impl BuildSerializer for ModelSerializer {
133134

134135
fn has_extra(schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>) -> PyResult<bool> {
135136
let py = schema.py();
136-
let extra_behaviour = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?;
137+
let typed_config: CoreConfig = match config {
138+
Some(config) => config.extract()?,
139+
None => CoreConfig::default(),
140+
};
141+
let extra_behaviour = ExtraBehavior::from_schema_or_config(py, schema, &typed_config, ExtraBehavior::Ignore)?;
137142
Ok(matches!(extra_behaviour, ExtraBehavior::Allow))
138143
}
139144

src/serializers/type_serializers/typed_dict.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use ahash::AHashMap;
88

99
use crate::build_tools::py_schema_err;
1010
use crate::build_tools::{py_schema_error_type, schema_or_config, ExtraBehavior};
11+
use crate::config::CoreConfig;
1112
use crate::definitions::DefinitionsBuilder;
1213
use crate::tools::SchemaDict;
1314

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

32-
let fields_mode = match ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)? {
33+
let typed_config: CoreConfig = match config {
34+
Some(config) => config.extract()?,
35+
None => CoreConfig::default(),
36+
};
37+
38+
let fields_mode = match ExtraBehavior::from_schema_or_config(py, schema, &typed_config, ExtraBehavior::Ignore)?
39+
{
3340
ExtraBehavior::Allow => FieldsMode::TypedDictAllow,
3441
_ => FieldsMode::SimpleDict,
3542
};

src/validators/any.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::sync::Arc;
33
use pyo3::prelude::*;
44
use pyo3::types::PyDict;
55

6+
use crate::config::CoreConfig;
67
use crate::input::Input;
78
use crate::{build_tools::LazyLock, errors::ValResult};
89

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

2223
fn build(
2324
_schema: &Bound<'_, PyDict>,
24-
_config: Option<&Bound<'_, PyDict>>,
25+
_config: &CoreConfig,
2526
_definitions: &mut DefinitionsBuilder<Arc<CombinedValidator>>,
2627
) -> PyResult<Arc<CombinedValidator>> {
2728
Ok(ANY_VALIDATOR.clone())

src/validators/arguments.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ use ahash::AHashSet;
99
use pyo3::IntoPyObjectExt;
1010

1111
use crate::build_tools::py_schema_err;
12-
use crate::build_tools::{schema_or_config_same, ExtraBehavior};
12+
use crate::build_tools::ExtraBehavior;
13+
use crate::config::CoreConfig;
1314
use crate::errors::{ErrorTypeDefaults, ValError, ValLineError, ValResult};
1415
use crate::input::{Arguments, BorrowInput, Input, KeywordArgs, PositionalArgs, ValidationMatch};
1516
use crate::lookup_key::LookupKeyCollection;
@@ -67,7 +68,7 @@ impl BuildValidator for ArgumentsValidator {
6768

6869
fn build(
6970
schema: &Bound<'_, PyDict>,
70-
config: Option<&Bound<'_, PyDict>>,
71+
config: &CoreConfig,
7172
definitions: &mut DefinitionsBuilder<Arc<CombinedValidator>>,
7273
) -> PyResult<Arc<CombinedValidator>> {
7374
let py = schema.py();
@@ -166,10 +167,14 @@ impl BuildValidator for ArgumentsValidator {
166167
},
167168
var_kwargs_mode,
168169
var_kwargs_validator,
169-
loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true),
170+
loc_by_alias: config.loc_by_alias.unwrap_or(true),
170171
extra: ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Forbid)?,
171-
validate_by_alias: schema_or_config_same(schema, config, intern!(py, "validate_by_alias"))?,
172-
validate_by_name: schema_or_config_same(schema, config, intern!(py, "validate_by_name"))?,
172+
validate_by_alias: schema
173+
.get_as(intern!(py, "validate_by_alias"))?
174+
.or(config.validate_by_alias),
175+
validate_by_name: schema
176+
.get_as(intern!(py, "validate_by_name"))?
177+
.or(config.validate_by_name),
173178
})
174179
.into())
175180
}

src/validators/arguments_v3.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ use ahash::AHashSet;
99
use pyo3::IntoPyObjectExt;
1010

1111
use crate::build_tools::py_schema_err;
12-
use crate::build_tools::{schema_or_config_same, ExtraBehavior};
12+
use crate::build_tools::ExtraBehavior;
13+
use crate::config::CoreConfig;
1314
use crate::errors::LocItem;
1415
use crate::errors::{ErrorTypeDefaults, ValError, ValLineError, ValResult};
1516
use crate::input::ConsumeIterator;
@@ -80,7 +81,7 @@ impl BuildValidator for ArgumentsV3Validator {
8081

8182
fn build(
8283
schema: &Bound<'_, PyDict>,
83-
config: Option<&Bound<'_, PyDict>>,
84+
config: &CoreConfig,
8485
definitions: &mut DefinitionsBuilder<Arc<CombinedValidator>>,
8586
) -> PyResult<Arc<CombinedValidator>> {
8687
let py = schema.py();
@@ -208,10 +209,14 @@ impl BuildValidator for ArgumentsV3Validator {
208209
Ok(CombinedValidator::ArgumentsV3(Self {
209210
parameters,
210211
positional_params_count,
211-
loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true),
212+
loc_by_alias: config.loc_by_alias.unwrap_or(true),
212213
extra: ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Forbid)?,
213-
validate_by_alias: schema_or_config_same(schema, config, intern!(py, "validate_by_alias"))?,
214-
validate_by_name: schema_or_config_same(schema, config, intern!(py, "validate_by_name"))?,
214+
validate_by_alias: schema
215+
.get_as(intern!(py, "validate_by_alias"))?
216+
.or(config.validate_by_alias),
217+
validate_by_name: schema
218+
.get_as(intern!(py, "validate_by_name"))?
219+
.or(config.validate_by_name),
215220
})
216221
.into())
217222
}

src/validators/bool.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use pyo3::types::PyDict;
44
use pyo3::{prelude::*, IntoPyObjectExt};
55

66
use crate::build_tools::{is_strict, LazyLock};
7+
use crate::config::CoreConfig;
78
use crate::errors::ValResult;
89
use crate::input::Input;
910

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

2627
fn build(
2728
schema: &Bound<'_, PyDict>,
28-
config: Option<&Bound<'_, PyDict>>,
29+
config: &CoreConfig,
2930
_definitions: &mut DefinitionsBuilder<Arc<CombinedValidator>>,
3031
) -> PyResult<Arc<CombinedValidator>> {
3132
if is_strict(schema, config)? {

0 commit comments

Comments
 (0)