Skip to content

Commit 89c310e

Browse files
committed
use BigInt for int inequality
1 parent 0dab63d commit 89c310e

File tree

4 files changed

+62
-50
lines changed

4 files changed

+62
-50
lines changed

src/errors/types.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::borrow::Cow;
22
use std::fmt;
33

44
use ahash::AHashMap;
5+
use num_bigint::BigInt;
56
use pyo3::exceptions::{PyKeyError, PyTypeError, PyValueError};
67
use pyo3::once_cell::GILOnceCell;
78
use pyo3::prelude::*;
@@ -699,6 +700,7 @@ impl ErrorType {
699700
#[derive(Clone, Debug)]
700701
pub enum Number {
701702
Int(i64),
703+
BigInt(BigInt),
702704
Float(f64),
703705
String(String),
704706
}
@@ -715,6 +717,12 @@ impl From<i64> for Number {
715717
}
716718
}
717719

720+
impl From<BigInt> for Number {
721+
fn from(i: BigInt) -> Self {
722+
Self::BigInt(i)
723+
}
724+
}
725+
718726
impl From<f64> for Number {
719727
fn from(f: f64) -> Self {
720728
Self::Float(f)
@@ -746,6 +754,7 @@ impl fmt::Display for Number {
746754
match self {
747755
Self::Float(s) => write!(f, "{s}"),
748756
Self::Int(i) => write!(f, "{i}"),
757+
Self::BigInt(i) => write!(f, "{i}"),
749758
Self::String(s) => write!(f, "{s}"),
750759
}
751760
}
@@ -754,6 +763,7 @@ impl ToPyObject for Number {
754763
fn to_object(&self, py: Python<'_>) -> PyObject {
755764
match self {
756765
Self::Int(i) => i.into_py(py),
766+
Self::BigInt(i) => i.clone().into_py(py),
757767
Self::Float(f) => f.into_py(py),
758768
Self::String(s) => s.into_py(py),
759769
}

src/input/return_enums.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,15 @@ impl<'a> EitherInt<'a> {
871871
},
872872
}
873873
}
874+
875+
pub fn as_bigint(&self) -> PyResult<BigInt> {
876+
match self {
877+
EitherInt::I64(i) => Ok(BigInt::from(*i)),
878+
EitherInt::U64(u) => Ok(BigInt::from(*u)),
879+
EitherInt::BigInt(i) => Ok(i.clone()),
880+
EitherInt::Py(i) => i.extract(),
881+
}
882+
}
874883
}
875884

876885
impl<'a> IntoPy<PyObject> for EitherInt<'a> {

src/validators/int.rs

Lines changed: 19 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use num_bigint::BigInt;
12
use pyo3::intern;
23
use pyo3::prelude::*;
3-
use pyo3::types::{PyDict, PyInt};
4+
use pyo3::types::PyDict;
45

56
use crate::build_tools::is_strict;
67
use crate::errors::{ErrorType, ValError, ValResult};
@@ -72,11 +73,11 @@ impl Validator for IntValidator {
7273
#[derive(Debug, Clone)]
7374
pub struct ConstrainedIntValidator {
7475
strict: bool,
75-
multiple_of: Option<Py<PyInt>>,
76-
le: Option<Py<PyInt>>,
77-
lt: Option<Py<PyInt>>,
78-
ge: Option<Py<PyInt>>,
79-
gt: Option<Py<PyInt>>,
76+
multiple_of: Option<BigInt>,
77+
le: Option<BigInt>,
78+
lt: Option<BigInt>,
79+
ge: Option<BigInt>,
80+
gt: Option<BigInt>,
8081
}
8182

8283
impl Validator for ConstrainedIntValidator {
@@ -89,62 +90,42 @@ impl Validator for ConstrainedIntValidator {
8990
_recursion_guard: &'s mut RecursionGuard,
9091
) -> ValResult<'data, PyObject> {
9192
let either_int = input.validate_int(extra.strict.unwrap_or(self.strict))?;
92-
let int_obj = either_int.into_py(py);
93-
let int = int_obj.as_ref(py);
93+
let int = either_int.as_bigint()?;
9494

9595
if let Some(ref multiple_of) = self.multiple_of {
96-
let rem: i64 = int.call_method1(intern!(py, "__mod__"), (multiple_of,))?.extract()?;
97-
if rem != 0 {
96+
if &int % multiple_of != BigInt::from(0) {
9897
return Err(ValError::new(
9998
ErrorType::MultipleOf {
100-
multiple_of: multiple_of.extract::<i64>(py)?.into(),
99+
multiple_of: multiple_of.clone().into(),
101100
},
102101
input,
103102
));
104103
}
105104
}
106-
107105
if let Some(ref le) = self.le {
108-
if !int.le(le)? {
109-
return Err(ValError::new(
110-
ErrorType::LessThanEqual {
111-
le: le.extract::<i64>(py)?.into(),
112-
},
113-
input,
114-
));
106+
if &int > le {
107+
return Err(ValError::new(ErrorType::LessThanEqual { le: le.clone().into() }, input));
115108
}
116109
}
117110
if let Some(ref lt) = self.lt {
118-
if !int.lt(lt)? {
119-
return Err(ValError::new(
120-
ErrorType::LessThan {
121-
lt: lt.extract::<i64>(py)?.into(),
122-
},
123-
input,
124-
));
111+
if &int >= lt {
112+
return Err(ValError::new(ErrorType::LessThan { lt: lt.clone().into() }, input));
125113
}
126114
}
127115
if let Some(ref ge) = self.ge {
128-
if !int.ge(ge)? {
116+
if &int < ge {
129117
return Err(ValError::new(
130-
ErrorType::GreaterThanEqual {
131-
ge: ge.extract::<i64>(py)?.into(),
132-
},
118+
ErrorType::GreaterThanEqual { ge: ge.clone().into() },
133119
input,
134120
));
135121
}
136122
}
137123
if let Some(ref gt) = self.gt {
138-
if !int.gt(gt)? {
139-
return Err(ValError::new(
140-
ErrorType::GreaterThan {
141-
gt: gt.extract::<i64>(py)?.into(),
142-
},
143-
input,
144-
));
124+
if &int <= gt {
125+
return Err(ValError::new(ErrorType::GreaterThan { gt: gt.clone().into() }, input));
145126
}
146127
}
147-
Ok(int_obj)
128+
Ok(either_int.into_py(py))
148129
}
149130

150131
fn different_strict_behavior(

tests/validators/test_int.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import pytest
77
from dirty_equals import IsStr
88

9-
from pydantic_core import SchemaValidator, ValidationError
9+
from pydantic_core import SchemaValidator, ValidationError, __version__
1010

1111
from ..conftest import Err, PyAndJson, plain_repr
1212

@@ -351,20 +351,20 @@ def test_too_long():
351351
'input': '1' * 4301,
352352
}
353353
]
354-
# # insert_assert(repr(exc_info.value))
355-
# assert repr(exc_info.value) == (
356-
# "1 validation error for int\n"
357-
# " Input should be a valid integer, unable to parse string as an integer "
358-
# "[type=int_parsing, input_value='111111111111111111111111...11111111111111111111111', input_type=str]\n"
359-
# f" For further information visit https://errors.pydantic.dev/{__version__}/v/int_parsing"
360-
# )
354+
# insert_assert(repr(exc_info.value))
355+
assert repr(exc_info.value) == (
356+
"1 validation error for int\n"
357+
" Input should be a valid integer, unable to parse string as an integer "
358+
"[type=int_parsing, input_value='111111111111111111111111...11111111111111111111111', input_type=str]\n"
359+
f" For further information visit https://errors.pydantic.dev/{__version__}/v/int_parsing"
360+
)
361361

362362

363-
def test_long_int_python():
363+
def test_long_python():
364364
v = SchemaValidator({'type': 'int'})
365365

366-
s = v.validate_python('1' * 4300)
367-
assert s == int('1' * 4300)
366+
s = v.validate_python('1' * 4_300)
367+
assert s == int('1' * 4_300)
368368

369369
s = v.validate_python('-' + '1' * 400)
370370
assert s == -int('1' * 400)
@@ -373,7 +373,19 @@ def test_long_int_python():
373373
v.validate_python('nan')
374374

375375

376-
def test_long_int_json():
376+
def test_long_python_inequality():
377+
v = SchemaValidator({'type': 'int', 'gt': 0, 'lt': int('1' * 4_300) - 5})
378+
379+
s = str(int('1' * 4_300) - 6)
380+
s = v.validate_python(s)
381+
assert s == int('1' * 4_300) - 6
382+
383+
s = str(int('1' * 4_300) - 5)
384+
with pytest.raises(ValidationError, match='Input should be less than 1'):
385+
v.validate_python(s)
386+
387+
388+
def test_long_json():
377389
v = SchemaValidator({'type': 'int'})
378390

379391
with pytest.raises(ValidationError, match=r'number out of range at line 1 column 401 \[type=json_invalid,'):

0 commit comments

Comments
 (0)