Skip to content

Commit 3576b50

Browse files
committed
alternate approach for lookup key logic
1 parent 11a1317 commit 3576b50

File tree

9 files changed

+49
-125
lines changed

9 files changed

+49
-125
lines changed

src/lookup_key.rs

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -578,30 +578,6 @@ fn py_get_attrs<'py>(obj: &Bound<'py, PyAny>, attr_name: &Py<PyString>) -> PyRes
578578
}
579579
}
580580

581-
pub fn get_lookup_key(
582-
py: Python,
583-
validation_alias: Option<&Py<PyAny>>,
584-
validate_by_name: bool,
585-
validate_by_alias: bool,
586-
field_name: &str,
587-
) -> PyResult<LookupKey> {
588-
match validation_alias {
589-
Some(va) => {
590-
let va_bound = va.into_bound_py_any(py)?;
591-
let lookup_key = match (validate_by_name, validate_by_alias) {
592-
(true, true) => LookupKey::from_py(py, &va_bound, Some(field_name))?,
593-
(true, false) => LookupKey::from_string(py, field_name),
594-
(false, true) => LookupKey::from_py(py, &va_bound, None)?,
595-
(false, false) => {
596-
return py_schema_err!("`validate_by_name` and `validate_by_alias` cannot both be set to `False`.")
597-
}
598-
};
599-
Ok(lookup_key)
600-
}
601-
None => Ok(LookupKey::from_string(py, field_name)),
602-
}
603-
}
604-
605581
#[derive(Debug)]
606582
#[allow(clippy::struct_field_names)]
607583
pub struct LookupKeyCollection {

src/validators/arguments.rs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::build_tools::py_schema_err;
1111
use crate::build_tools::{schema_or_config_same, ExtraBehavior};
1212
use crate::errors::{ErrorTypeDefaults, ValError, ValLineError, ValResult};
1313
use crate::input::{Arguments, BorrowInput, Input, KeywordArgs, PositionalArgs, ValidationMatch};
14-
use crate::lookup_key::get_lookup_key;
14+
use crate::lookup_key::LookupKeyCollection;
1515
use crate::tools::SchemaDict;
1616

1717
use super::validation_state::ValidationState;
@@ -44,7 +44,7 @@ struct Parameter {
4444
name: String,
4545
kwarg_key: Option<Py<PyString>>,
4646
validator: CombinedValidator,
47-
alias: Option<Py<PyAny>>,
47+
lookup_key_collection: LookupKeyCollection,
4848
mode: String,
4949
}
5050

@@ -126,12 +126,16 @@ impl BuildValidator for ArgumentsValidator {
126126
} else if has_default {
127127
had_default_arg = true;
128128
}
129+
130+
let validation_alias = arg.get_item(intern!(py, "alias"))?;
131+
let lookup_key_collection = LookupKeyCollection::new(py, validation_alias, name.as_str())?;
132+
129133
parameters.push(Parameter {
130134
positional,
131135
name,
132136
kwarg_key,
133137
validator,
134-
alias: arg.get_item(intern!(py, "alias"))?.map(std::convert::Into::into),
138+
lookup_key_collection,
135139
mode: mode.to_string(),
136140
});
137141
}
@@ -193,7 +197,7 @@ impl Validator for ArgumentsValidator {
193197
let mut output_args: Vec<PyObject> = Vec::with_capacity(self.positional_params_count);
194198
let output_kwargs = PyDict::new(py);
195199
let mut errors: Vec<ValLineError> = Vec::new();
196-
let mut used_kwargs: AHashSet<String> = AHashSet::with_capacity(self.parameters.len());
200+
let mut used_kwargs: AHashSet<&str> = AHashSet::with_capacity(self.parameters.len());
197201

198202
let validate_by_alias = state.validate_by_alias_or(self.validate_by_alias);
199203
let validate_by_name = state.validate_by_name_or(self.validate_by_name);
@@ -209,19 +213,17 @@ impl Validator for ArgumentsValidator {
209213
let mut kw_value = None;
210214
let mut kw_lookup_key = None;
211215
if parameter.mode == "keyword_only" || parameter.mode == "positional_or_keyword" {
212-
kw_lookup_key = Some(get_lookup_key(
213-
py,
214-
parameter.alias.as_ref(),
215-
validate_by_name,
216-
validate_by_alias,
217-
&parameter.name,
218-
)?);
216+
kw_lookup_key = Some(
217+
parameter
218+
.lookup_key_collection
219+
.select(validate_by_alias, validate_by_name)?,
220+
);
219221
}
220222

221223
if let Some(kwargs) = args.kwargs() {
222-
if let Some(ref lookup_key) = kw_lookup_key {
224+
if let Some(lookup_key) = kw_lookup_key {
223225
if let Some((lookup_path, value)) = kwargs.get_item(lookup_key)? {
224-
used_kwargs.insert(lookup_path.first_key().to_string());
226+
used_kwargs.insert(lookup_path.first_key());
225227
kw_value = Some((lookup_path, value));
226228
}
227229
}

src/validators/dataclass.rs

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::errors::{ErrorType, ErrorTypeDefaults, ValError, ValLineError, ValRes
1212
use crate::input::{
1313
input_as_python_instance, Arguments, BorrowInput, Input, InputType, KeywordArgs, PositionalArgs, ValidationMatch,
1414
};
15-
use crate::lookup_key::get_lookup_key;
15+
use crate::lookup_key::LookupKeyCollection;
1616
use crate::tools::SchemaDict;
1717
use crate::validators::function::convert_err;
1818

@@ -27,7 +27,7 @@ struct Field {
2727
py_name: Py<PyString>,
2828
init: bool,
2929
init_only: bool,
30-
alias: Option<Py<PyAny>>,
30+
lookup_key_collection: LookupKeyCollection,
3131
validator: CombinedValidator,
3232
frozen: bool,
3333
}
@@ -93,13 +93,14 @@ impl BuildValidator for DataclassArgsValidator {
9393
positional_count += 1;
9494
}
9595

96+
let validation_alias = field.get_item(intern!(py, "validation_alias"))?;
97+
let lookup_key_collection = LookupKeyCollection::new(py, validation_alias, name.as_str())?;
98+
9699
fields.push(Field {
97100
kw_only,
98101
name,
99102
py_name: py_name.into(),
100-
alias: field
101-
.get_item(intern!(py, "validation_alias"))?
102-
.map(std::convert::Into::into),
103+
lookup_key_collection,
103104
validator,
104105
init: field.get_as(intern!(py, "init"))?.unwrap_or(true),
105106
init_only: field.get_as(intern!(py, "init_only"))?.unwrap_or(false),
@@ -151,7 +152,7 @@ impl Validator for DataclassArgsValidator {
151152
let mut init_only_args = self.init_only_count.map(Vec::with_capacity);
152153

153154
let mut errors: Vec<ValLineError> = Vec::new();
154-
let mut used_keys: AHashSet<String> = AHashSet::with_capacity(self.fields.len());
155+
let mut used_keys: AHashSet<&str> = AHashSet::with_capacity(self.fields.len());
155156

156157
let state = &mut state.rebind_extra(|extra| extra.data = Some(output_dict.clone()));
157158

@@ -200,17 +201,14 @@ impl Validator for DataclassArgsValidator {
200201
}
201202
}
202203

203-
let lookup_key = get_lookup_key(
204-
py,
205-
field.alias.as_ref(),
206-
validate_by_name,
207-
validate_by_alias,
208-
&field.name,
209-
)?;
204+
let lookup_key = field
205+
.lookup_key_collection
206+
.select(validate_by_alias, validate_by_name)?;
207+
210208
let mut kw_value = None;
211209
if let Some(kwargs) = args.kwargs() {
212-
if let Some((lookup_path, value)) = kwargs.get_item(&lookup_key)? {
213-
used_keys.insert(lookup_path.first_key().to_string());
210+
if let Some((lookup_path, value)) = kwargs.get_item(lookup_key)? {
211+
used_keys.insert(lookup_path.first_key());
214212
kw_value = Some((lookup_path, value));
215213
}
216214
}

src/validators/model_fields.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ impl BuildValidator for ModelFieldsValidator {
9393
});
9494
}
9595

96+
let validate_by_alias = config.get_as(intern!(py, "validate_by_alias"))?.unwrap_or(true);
97+
let validate_by_name = config.get_as(intern!(py, "validate_by_name"))?.unwrap_or(false);
98+
99+
if !validate_by_alias && !validate_by_name {
100+
return py_schema_err!("`validate_by_name` and `validate_by_alias` cannot both be set to `False`.");
101+
}
102+
96103
Ok(Self {
97104
fields,
98105
model_name,

src/validators/typed_dict.rs

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::input::BorrowInput;
1010
use crate::input::ConsumeIterator;
1111
use crate::input::ValidationMatch;
1212
use crate::input::{Input, ValidatedDict};
13-
use crate::lookup_key::get_lookup_key;
13+
use crate::lookup_key::LookupKeyCollection;
1414
use crate::tools::SchemaDict;
1515
use ahash::AHashSet;
1616
use jiter::PartialMode;
@@ -20,7 +20,7 @@ use super::{build_validator, BuildValidator, CombinedValidator, DefinitionsBuild
2020
#[derive(Debug)]
2121
struct TypedDictField {
2222
name: String,
23-
alias: Option<Py<PyAny>>,
23+
lookup_key_collection: LookupKeyCollection,
2424
name_py: Py<PyString>,
2525
required: bool,
2626
validator: CombinedValidator,
@@ -109,11 +109,12 @@ impl BuildValidator for TypedDictValidator {
109109
}
110110
}
111111

112+
let validation_alias = field_info.get_item(intern!(py, "validation_alias"))?;
113+
let lookup_key_collection = LookupKeyCollection::new(py, validation_alias, field_name)?;
114+
112115
fields.push(TypedDictField {
113116
name: field_name.to_string(),
114-
alias: field_info
115-
.get_item(intern!(py, "validation_alias"))?
116-
.map(std::convert::Into::into),
117+
lookup_key_collection,
117118
name_py: field_name_py.into(),
118119
validator,
119120
required,
@@ -162,7 +163,7 @@ impl Validator for TypedDictValidator {
162163

163164
// we only care about which keys have been used if we're iterating over the object for extra after
164165
// the first pass
165-
let mut used_keys: Option<AHashSet<String>> =
166+
let mut used_keys: Option<AHashSet<&str>> =
166167
if self.extra_behavior == ExtraBehavior::Ignore || dict.is_py_get_attr() {
167168
None
168169
} else {
@@ -175,14 +176,10 @@ impl Validator for TypedDictValidator {
175176
let mut fields_set_count: usize = 0;
176177

177178
for field in &self.fields {
178-
let lookup_key = get_lookup_key(
179-
py,
180-
field.alias.as_ref(),
181-
validate_by_name,
182-
validate_by_alias,
183-
&field.name,
184-
)?;
185-
let op_key_value = match dict.get_item(&lookup_key) {
179+
let lookup_key = field
180+
.lookup_key_collection
181+
.select(validate_by_alias, validate_by_name)?;
182+
let op_key_value = match dict.get_item(lookup_key) {
186183
Ok(v) => v,
187184
Err(ValError::LineErrors(line_errors)) => {
188185
let field_loc: LocItem = field.name.clone().into();
@@ -199,7 +196,7 @@ impl Validator for TypedDictValidator {
199196
if let Some(ref mut used_keys) = used_keys {
200197
// key is "used" whether or not validation passes, since we want to skip this key in
201198
// extra logic either way
202-
used_keys.insert(lookup_path.first_key().to_string());
199+
used_keys.insert(lookup_path.first_key());
203200
}
204201
let is_last_partial = if let Some(ref last_key) = partial_last_key {
205202
let first_key_loc: LocItem = lookup_path.first_key().into();
@@ -265,7 +262,7 @@ impl Validator for TypedDictValidator {
265262
if let Some(used_keys) = used_keys {
266263
struct ValidateExtras<'a, 's, 'py> {
267264
py: Python<'py>,
268-
used_keys: AHashSet<String>,
265+
used_keys: AHashSet<&'a str>,
269266
errors: &'a mut Vec<ValLineError>,
270267
extras_validator: Option<&'a CombinedValidator>,
271268
output_dict: &'a Bound<'py, PyDict>,

tests/validators/test_arguments.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -928,19 +928,6 @@ def test_only_allow_alias(py_and_json) -> None:
928928
assert v.validate_test(ArgsKwargs((), {'a': 'hello'}))
929929

930930

931-
def test_invalid_config_raises() -> None:
932-
with pytest.raises(SchemaError, match='`validate_by_name` and `validate_by_alias` cannot both be set to `False`.'):
933-
SchemaValidator(
934-
core_schema.arguments_schema(
935-
[
936-
core_schema.arguments_parameter(name='a', schema=core_schema.str_schema(), alias='FieldA'),
937-
],
938-
validate_by_name=False,
939-
validate_by_alias=False,
940-
)
941-
)
942-
943-
944931
def validate(config=None):
945932
def decorator(function):
946933
parameters = signature(function).parameters

tests/validators/test_dataclasses.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import pytest
99
from dirty_equals import IsListOrTuple, IsStr
1010

11-
from pydantic_core import ArgsKwargs, SchemaError, SchemaValidator, ValidationError, core_schema
11+
from pydantic_core import ArgsKwargs, SchemaValidator, ValidationError, core_schema
1212

1313
from ..conftest import Err, PyAndJson, assert_gc
1414

@@ -1774,22 +1774,3 @@ def test_only_allow_alias(py_and_json) -> None:
17741774
assert v.validate_test({'FieldA': 'hello'}) == BasicDataclass(a='hello')
17751775
with pytest.raises(ValidationError, match=r'FieldA\n +Field required \[type=missing,'):
17761776
assert v.validate_test({'a': 'hello'})
1777-
1778-
1779-
def test_invalid_config_raises() -> None:
1780-
with pytest.raises(SchemaError, match='`validate_by_name` and `validate_by_alias` cannot both be set to `False`.'):
1781-
SchemaValidator(
1782-
core_schema.dataclass_schema(
1783-
BasicDataclass,
1784-
core_schema.dataclass_args_schema(
1785-
'BasicDataclass',
1786-
[
1787-
core_schema.dataclass_field(
1788-
name='a', schema=core_schema.str_schema(), validation_alias='FieldA'
1789-
),
1790-
],
1791-
),
1792-
['a'],
1793-
config=core_schema.CoreConfig(validate_by_name=False, validate_by_alias=False),
1794-
)
1795-
)

tests/validators/test_model_fields.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -543,17 +543,6 @@ def test_only_allow_alias(py_and_json) -> None:
543543
assert v.validate_test({'field_a': '123'})
544544

545545

546-
def test_invalid_config_raises() -> None:
547-
with pytest.raises(SchemaError, match='`validate_by_name` and `validate_by_alias` cannot both be set to `False`.'):
548-
SchemaValidator(
549-
{
550-
'type': 'model-fields',
551-
'fields': {'field_a': {'validation_alias': 'FieldA', 'type': 'model-field', 'schema': {'type': 'int'}}},
552-
},
553-
config=CoreConfig(validate_by_name=False, validate_by_alias=False),
554-
)
555-
556-
557546
@pytest.mark.parametrize(
558547
'input_value,expected',
559548
[

tests/validators/test_typed_dict.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -436,19 +436,6 @@ def test_only_allow_alias(py_and_json) -> None:
436436
assert v.validate_test({'field_a': '123'})
437437

438438

439-
def test_invalid_config_raises() -> None:
440-
with pytest.raises(SchemaError, match='`validate_by_name` and `validate_by_alias` cannot both be set to `False`.'):
441-
SchemaValidator(
442-
{
443-
'type': 'typed-dict',
444-
'fields': {
445-
'field_a': {'validation_alias': 'FieldA', 'type': 'typed-dict-field', 'schema': {'type': 'int'}}
446-
},
447-
'config': {'validate_by_name': False, 'validate_by_alias': False},
448-
}
449-
)
450-
451-
452439
@pytest.mark.parametrize(
453440
'input_value,expected',
454441
[

0 commit comments

Comments
 (0)