1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
|
import math
import platform
import re
import sys
import pytest
from pydantic_core import SchemaValidator, ValidationError
from pydantic_core import core_schema as cs
from ..conftest import Err
EXPECTED_PARSE_ERROR_MESSAGE = 'Input should be a valid complex string following the rules at https://docs.python.org/3/library/functions.html#complex'
EXPECTED_TYPE_ERROR_MESSAGE = 'Input should be a valid python complex object, a number, or a valid complex string following the rules at https://docs.python.org/3/library/functions.html#complex'
EXPECTED_TYPE_ERROR_PY_STRICT_MESSAGE = 'Input should be an instance of complex'
@pytest.mark.parametrize(
'input_value,expected',
[
(complex(2, 4), complex(2, 4)),
('2', complex(2, 0)),
('2j', complex(0, 2)),
('+1.23e-4-5.67e+8J', complex(1.23e-4, -5.67e8)),
('1.5-j', complex(1.5, -1)),
('-j', complex(0, -1)),
('j', complex(0, 1)),
(3, complex(3, 0)),
(2.0, complex(2, 0)),
('1e-700j', complex(0, 0)),
('', Err(EXPECTED_TYPE_ERROR_MESSAGE)),
('\t( -1.23+4.5J \n', Err(EXPECTED_TYPE_ERROR_MESSAGE)),
({'real': 2, 'imag': 4}, Err(EXPECTED_TYPE_ERROR_MESSAGE)),
({'real': 'test', 'imag': 1}, Err(EXPECTED_TYPE_ERROR_MESSAGE)),
({'real': True, 'imag': 1}, Err(EXPECTED_TYPE_ERROR_MESSAGE)),
('foobar', Err(EXPECTED_TYPE_ERROR_MESSAGE)),
([], Err(EXPECTED_TYPE_ERROR_MESSAGE)),
([('x', 'y')], Err(EXPECTED_TYPE_ERROR_MESSAGE)),
((), Err(EXPECTED_TYPE_ERROR_MESSAGE)),
((('x', 'y'),), Err(EXPECTED_TYPE_ERROR_MESSAGE)),
(
(type('Foobar', (), {'x': 1})()),
Err(EXPECTED_TYPE_ERROR_MESSAGE),
),
],
ids=repr,
)
def test_complex_cases(input_value, expected):
v = SchemaValidator(cs.complex_schema())
if isinstance(expected, Err):
with pytest.raises(ValidationError, match=re.escape(expected.message)):
v.validate_python(input_value)
else:
assert v.validate_python(input_value) == expected
@pytest.mark.parametrize(
'input_value,expected',
[
(complex(2, 4), complex(2, 4)),
('2', Err(EXPECTED_TYPE_ERROR_PY_STRICT_MESSAGE)),
('2j', Err(EXPECTED_TYPE_ERROR_PY_STRICT_MESSAGE)),
('+1.23e-4-5.67e+8J', Err(EXPECTED_TYPE_ERROR_PY_STRICT_MESSAGE)),
('1.5-j', Err(EXPECTED_TYPE_ERROR_PY_STRICT_MESSAGE)),
('-j', Err(EXPECTED_TYPE_ERROR_PY_STRICT_MESSAGE)),
('j', Err(EXPECTED_TYPE_ERROR_PY_STRICT_MESSAGE)),
(3, Err(EXPECTED_TYPE_ERROR_PY_STRICT_MESSAGE)),
(2.0, Err(EXPECTED_TYPE_ERROR_PY_STRICT_MESSAGE)),
('1e-700j', Err(EXPECTED_TYPE_ERROR_PY_STRICT_MESSAGE)),
('', Err(EXPECTED_TYPE_ERROR_PY_STRICT_MESSAGE)),
('\t( -1.23+4.5J \n', Err(EXPECTED_TYPE_ERROR_PY_STRICT_MESSAGE)),
({'real': 2, 'imag': 4}, Err(EXPECTED_TYPE_ERROR_PY_STRICT_MESSAGE)),
({'real': 'test', 'imag': 1}, Err(EXPECTED_TYPE_ERROR_PY_STRICT_MESSAGE)),
({'real': True, 'imag': 1}, Err(EXPECTED_TYPE_ERROR_PY_STRICT_MESSAGE)),
('foobar', Err(EXPECTED_TYPE_ERROR_PY_STRICT_MESSAGE)),
],
ids=repr,
)
def test_complex_strict(input_value, expected):
v = SchemaValidator(cs.complex_schema(strict=True))
if isinstance(expected, Err):
with pytest.raises(ValidationError, match=re.escape(expected.message)):
v.validate_python(input_value)
else:
assert v.validate_python(input_value) == expected
@pytest.mark.xfail(
platform.python_implementation() == 'PyPy' and sys.pypy_version_info < (7, 3, 17),
reason='PyPy cannot process this string due to a bug, even if this string is considered valid in python',
)
def test_valid_complex_string_with_space():
v = SchemaValidator(cs.complex_schema())
assert v.validate_python('\t( -1.23+4.5J )\n') == complex(-1.23, 4.5)
def test_nan_inf_complex():
v = SchemaValidator(cs.complex_schema())
c = v.validate_python('NaN+Infinityj')
# c != complex(float('nan'), float('inf')) as nan != nan,
# so we need to examine the values individually
assert math.isnan(c.real)
assert math.isinf(c.imag)
def test_overflow_complex():
# Python simply converts too large float values to inf, so these strings
# are still valid, even if the numbers are out of range
v = SchemaValidator(cs.complex_schema())
c = v.validate_python('5e600j')
assert math.isinf(c.imag)
c = v.validate_python('-5e600j')
assert math.isinf(c.imag)
def test_json_complex():
v = SchemaValidator(cs.complex_schema())
assert v.validate_json('"-1.23e+4+5.67e-8J"') == complex(-1.23e4, 5.67e-8)
assert v.validate_json('1') == complex(1, 0)
assert v.validate_json('1.0') == complex(1, 0)
# "1" is a valid complex string
assert v.validate_json('"1"') == complex(1, 0)
with pytest.raises(ValidationError) as exc_info:
v.validate_json('{"real": 2, "imag": 4}')
assert exc_info.value.errors(include_url=False) == [
{
'type': 'complex_type',
'loc': (),
'msg': EXPECTED_TYPE_ERROR_MESSAGE,
'input': {'real': 2, 'imag': 4},
}
]
def test_json_complex_strict():
v = SchemaValidator(cs.complex_schema(strict=True))
assert v.validate_json('"-1.23e+4+5.67e-8J"') == complex(-1.23e4, 5.67e-8)
# "1" is a valid complex string
assert v.validate_json('"1"') == complex(1, 0)
with pytest.raises(ValidationError, match=re.escape(EXPECTED_PARSE_ERROR_MESSAGE)):
v.validate_json('1')
with pytest.raises(ValidationError, match=re.escape(EXPECTED_PARSE_ERROR_MESSAGE)):
v.validate_json('1.0')
with pytest.raises(ValidationError, match=re.escape(EXPECTED_TYPE_ERROR_MESSAGE)):
v.validate_json('{"real": 2, "imag": 4}')
def test_string_complex():
v = SchemaValidator(cs.complex_schema())
assert v.validate_strings('+1.23e-4-5.67e+8J') == complex(1.23e-4, -5.67e8)
with pytest.raises(ValidationError, match=re.escape(EXPECTED_PARSE_ERROR_MESSAGE)):
v.validate_strings("{'real': 1, 'imag': 0}")
|