Skip to content

Commit 2aee6eb

Browse files
committed
Use Decimal for multipleOf when the value is float
1 parent d7d6f81 commit 2aee6eb

File tree

5 files changed

+43
-25
lines changed

5 files changed

+43
-25
lines changed

CHANGELOG.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
=== unreleased
22

33
* Fix of `additionalProperties=true` for JSON schema 4
4+
* Use decimal for multipleOf implementation and add respective tests
45

56

67
=== 2.14.1 (2019-10-09)

fastjsonschema/draft04.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import decimal
12
import re
23

34
from .exceptions import JsonSchemaDefinitionException
@@ -301,8 +302,14 @@ def generate_multiple_of(self):
301302
with self.l('if isinstance({variable}, (int, float)):'):
302303
if not isinstance(self._definition['multipleOf'], (int, float)):
303304
raise JsonSchemaDefinitionException('multipleOf must be a number')
304-
self.l('from decimal import Decimal')
305-
self.l('quotient = Decimal(repr({variable})) / Decimal(repr({multipleOf}))')
305+
# For proper multiplication check of floats we need to use decimals,
306+
# because for example 19.01 / 0.01 = 1901.0000000000002.
307+
if isinstance(self._definition['multipleOf'], float):
308+
self._extra_imports_lines.append('from decimal import Decimal')
309+
self._extra_imports_objects['Decimal'] = decimal.Decimal
310+
self.l('quotient = Decimal(repr({variable})) / Decimal(repr({multipleOf}))')
311+
else:
312+
self.l('quotient = {variable} / {multipleOf}')
306313
with self.l('if int(quotient) != quotient:'):
307314
self.exc('{name} must be multiple of {multipleOf}', rule='multipleOf')
308315

fastjsonschema/generator.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ def __init__(self, definition, resolver=None):
3232
self._code = []
3333
self._compile_regexps = {}
3434

35+
# Any extra library should be here to be imported only once.
36+
# Lines are imports to be printed in the file and objects
37+
# key-value pair to pass to compile function directly.
38+
self._extra_imports_lines = []
39+
self._extra_imports_objects = {}
40+
3541
self._variables = set()
3642
self._indent = 0
3743
self._indent_last_line = None
@@ -74,6 +80,7 @@ def global_state(self):
7480
self._generate_func_code()
7581

7682
return dict(
83+
**self._extra_imports_objects,
7784
REGEX_PATTERNS=self._compile_regexps,
7885
re=re,
7986
JsonSchemaException=JsonSchemaException,
@@ -88,26 +95,22 @@ def global_state_code(self):
8895
self._generate_func_code()
8996

9097
if not self._compile_regexps:
91-
return '\n'.join(
92-
[
93-
'from fastjsonschema import JsonSchemaException',
94-
'',
95-
'',
96-
]
97-
)
98-
regexs = ['"{}": re.compile(r"{}")'.format(key, value.pattern) for key, value in self._compile_regexps.items()]
99-
return '\n'.join(
100-
[
101-
'import re',
98+
return '\n'.join(self._extra_imports_lines + [
10299
'from fastjsonschema import JsonSchemaException',
103100
'',
104101
'',
105-
'REGEX_PATTERNS = {',
106-
' ' + ',\n '.join(regexs),
107-
'}',
108-
'',
109-
]
110-
)
102+
])
103+
regexs = ['"{}": re.compile(r"{}")'.format(key, value.pattern) for key, value in self._compile_regexps.items()]
104+
return '\n'.join(self._extra_imports_lines + [
105+
'import re',
106+
'from fastjsonschema import JsonSchemaException',
107+
'',
108+
'',
109+
'REGEX_PATTERNS = {',
110+
' ' + ',\n '.join(regexs),
111+
'}',
112+
'',
113+
])
111114

112115

113116
def _generate_func_code(self):

tests/json_schema/utils.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ def template_test(schema_version, schema, data, is_valid):
6767
"""
6868
# For debug purposes. When test fails, it will print stdout.
6969
resolver = RefResolver.from_schema(schema, handlers={'http': remotes_handler})
70-
print(_get_code_generator_class(schema_version)(schema, resolver=resolver).func_code)
70+
71+
debug_generator = _get_code_generator_class(schema_version)(schema, resolver=resolver)
72+
print(debug_generator.global_state_code)
73+
print(debug_generator.func_code)
7174

7275
# JSON schema test suits do not contain schema version.
7376
# Our library needs to know that or it would use always the latest implementation.

tests/test_number.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ def test_multiple_of(asserter, number_type, value, expected):
111111
}, value, expected)
112112

113113

114-
115114
exc = JsonSchemaException('data must be multiple of 0.0001', value='{data}', name='data', definition='{definition}', rule='multipleOf')
116115
@pytest.mark.parametrize('value, expected', [
117116
(0.00751, exc),
@@ -123,18 +122,23 @@ def test_multiple_of_float(asserter, value, expected):
123122
'multipleOf': 0.0001,
124123
}, value, expected)
125124

126-
exc = JsonSchemaException('data must be multiple of 1.5', value='{data}', name='data', definition='{definition}', rule='multipleOf')
125+
126+
exc = JsonSchemaException('data must be multiple of 0.01', value='{data}', name='data', definition='{definition}', rule='multipleOf')
127127
@pytest.mark.parametrize('value, expected', [
128128
(0, 0),
129-
(4.5, 4.5),
130-
(35, exc),
129+
(0.01, 0.01),
130+
(0.1, 0.1),
131+
(19.01, 19.01),
132+
(0.001, exc),
133+
(19.001, exc),
131134
])
132135
def test_multiple_of_float_1_5(asserter, value, expected):
133136
asserter({
134137
'type': 'number',
135-
'multipleOf': 1.5,
138+
'multipleOf': 0.01,
136139
}, value, expected)
137140

141+
138142
@pytest.mark.parametrize('value', (
139143
1.0,
140144
0.1,

0 commit comments

Comments
 (0)