Skip to content

Commit 2028880

Browse files
committed
WIP: fix linting errors and added test_fraction.py
1 parent 2fce2cc commit 2028880

File tree

4 files changed

+63
-24
lines changed

4 files changed

+63
-24
lines changed

src/input/input_python.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ impl<'py> Input<'py> for Bound<'py, PyAny> {
280280
} else if let Ok(decimal) = self.validate_decimal(true, self.py()) {
281281
decimal_as_int(self, &decimal.into_inner())
282282
} else if let Ok(fraction) = self.validate_fraction(true, self.py()) {
283-
fraction_as_int(self)
283+
fraction_as_int(self, &fraction.into_inner())
284284
} else if let Ok(float) = self.extract::<f64>() {
285285
float_as_int(self, float)
286286
} else if let Some(enum_val) = maybe_as_enum(self) {

src/input/shared.rs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -228,22 +228,18 @@ pub fn decimal_as_int<'py>(
228228
Ok(EitherInt::Py(numerator))
229229
}
230230

231-
pub fn fraction_as_int<'py>(input: &Bound<'py, PyAny>) -> ValResult<EitherInt<'py>> {
232-
#[cfg(Py_3_12)]
233-
let is_integer = input.call_method0("is_integer")?.extract::<bool>()?;
234-
#[cfg(not(Py_3_12))]
235-
let is_integer = input.getattr("denominator")?.extract::<i64>().map_or(false, |d| d == 1);
236-
237-
if is_integer {
238-
#[cfg(Py_3_11)]
239-
let as_int = input.call_method0("__int__");
240-
#[cfg(not(Py_3_11))]
241-
let as_int = input.call_method0("__trunc__");
242-
match as_int {
243-
Ok(i) => Ok(EitherInt::Py(i.as_any().to_owned())),
244-
Err(_) => Err(ValError::new(ErrorTypeDefaults::IntType, input)),
245-
}
246-
} else {
247-
Err(ValError::new(ErrorTypeDefaults::IntFromFloat, input))
231+
pub fn fraction_as_int<'py>(
232+
input: &(impl Input<'py> + ?Sized),
233+
fraction: &Bound<'py, PyAny>,
234+
) -> ValResult<EitherInt<'py>> {
235+
let py = fraction.py();
236+
237+
let (numerator, denominator) = fraction
238+
.call_method0(intern!(py, "as_integer_ratio"))?
239+
.extract::<(Bound<'_, PyAny>, Bound<'_, PyAny>)>()?;
240+
if denominator.extract::<i64>().map_or(true, |d| d != 1) {
241+
return Err(ValError::new(ErrorTypeDefaults::IntFromFloat, input));
248242
}
243+
Ok(EitherInt::Py(numerator))
244+
249245
}

src/validators/fraction.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,11 @@ use pyo3::sync::PyOnceLock;
66
use pyo3::types::{PyDict, PyString, PyType};
77
use pyo3::{prelude::*, PyTypeInfo};
88

9-
use crate::build_tools::{is_strict, schema_or_config_same};
10-
use crate::errors::ErrorType;
9+
use crate::build_tools::is_strict;
1110
use crate::errors::ValResult;
12-
use crate::errors::{ErrorTypeDefaults, Number};
11+
use crate::errors::ErrorTypeDefaults;
1312
use crate::errors::{ToErrorValue, ValError};
1413
use crate::input::Input;
15-
use crate::tools::SchemaDict;
1614

1715
use super::{BuildValidator, CombinedValidator, DefinitionsBuilder, ValidationState, Validator};
1816

@@ -64,8 +62,6 @@ impl BuildValidator for FractionValidator {
6462
) -> PyResult<Arc<CombinedValidator>> {
6563
let py = schema.py();
6664

67-
let allow_inf_nan = schema_or_config_same(schema, config, intern!(py, "allow_inf_nan"))?.unwrap_or(false);
68-
6965
Ok(CombinedValidator::Fraction(Self {
7066
strict: is_strict(schema, config)?,
7167
le: validate_as_fraction(py, schema, intern!(py, "le"))?,

tests/serializers/test_fraction.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from fractions import Fraction
2+
3+
import pytest
4+
5+
from pydantic_core import SchemaSerializer, core_schema
6+
7+
8+
def test_fraction():
9+
v = SchemaSerializer(core_schema.fraction_schema())
10+
assert v.to_python(Fraction('3 / 4')) == Fraction(3, 4)
11+
assert v.to_python(Fraction(3, 4)) == Fraction(3, 4)
12+
13+
# check correct casting to int when denominator is 1
14+
assert v.to_python(Fraction(10, 10), mode='json') == '1'
15+
assert v.to_python(Fraction(1, 10), mode='json') == '1/10'
16+
17+
assert v.to_json(Fraction(3, 4)) == b'"3/4"'
18+
19+
20+
def test_fraction_key():
21+
v = SchemaSerializer(core_schema.dict_schema(core_schema.fraction_schema(), core_schema.fraction_schema()))
22+
assert v.to_python({Fraction(3, 4): Fraction(1, 10)}) == {Fraction(3, 4): Fraction(1, 10)}
23+
assert v.to_python({Fraction(3, 4): Fraction(1, 10)}, mode='json') == {'3/4': '1/10'}
24+
assert v.to_json({Fraction(3, 4): Fraction(1, 10)}) == b'{"3/4":"1/10"}'
25+
26+
27+
@pytest.mark.parametrize(
28+
'value,expected',
29+
[
30+
(Fraction(3, 4), '3/4'),
31+
(Fraction(1, 10), '1/10'),
32+
(Fraction(10, 1), '10'),
33+
(Fraction(-5, 2), '-5/2'),
34+
],
35+
)
36+
def test_fraction_json(value, expected):
37+
v = SchemaSerializer(core_schema.fraction_schema())
38+
assert v.to_python(value, mode='json') == expected
39+
assert v.to_json(value).decode() == f'"{expected}"'
40+
41+
42+
def test_any_fraction_key():
43+
v = SchemaSerializer(core_schema.dict_schema())
44+
input_value = {Fraction(3, 4): 1}
45+
46+
assert v.to_python(input_value, mode='json') == {'3/4': 1}
47+
assert v.to_json(input_value) == b'{"3/4":1}'

0 commit comments

Comments
 (0)