Skip to content

Commit dc1020d

Browse files
Merge pull request #179 from davicorreiajr/fix/validation-error-decimal
Fix parse to properly support multipleOf (to avoid validation error)
2 parents 4f07509 + db6a87b commit dc1020d

File tree

5 files changed

+114
-3
lines changed

5 files changed

+114
-3
lines changed

target_postgres/json_schema.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from copy import deepcopy
2+
import decimal
23
import json
34
import re
45

@@ -20,7 +21,8 @@
2021
float: NUMBER,
2122
bool: BOOLEAN,
2223
str: STRING,
23-
type(None): NULL
24+
type(None): NULL,
25+
decimal.Decimal: NUMBER
2426
}
2527

2628

target_postgres/target_tools.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pkg_resources
55
import sys
66
import threading
7+
import decimal
78

89
import singer
910
from singer import utils, metadata, metrics
@@ -90,7 +91,7 @@ def _report_invalid_records(streams):
9091
def _line_handler(state_tracker, target, invalid_records_detect, invalid_records_threshold, max_batch_rows,
9192
max_batch_size, line):
9293
try:
93-
line_data = json.loads(line)
94+
line_data = json.loads(line, parse_float=decimal.Decimal)
9495
except json.decoder.JSONDecodeError:
9596
LOGGER.error("Unable to parse JSON: {}".format(line))
9697
raise

tests/unit/test_BufferedSingerStream.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
from decimal import Decimal
12
from copy import deepcopy
23

34
import pytest
45

56
from target_postgres import singer
6-
from target_postgres.singer_stream import BufferedSingerStream, SingerStreamError
7+
from target_postgres.singer_stream import BufferedSingerStream, SingerStreamError, RAW_LINE_SIZE
78

89
from utils.fixtures import CatStream, InvalidCatStream, CATS_SCHEMA
910

@@ -75,6 +76,71 @@ def test_add_record_message__invalid_record():
7576
assert [] == missing_sdc_properties(singer_stream)
7677

7778

79+
SIMPLE_MULTIPLE_OF_VALID_SCHEMA = {
80+
'properties': {
81+
'multipleOfKey': {
82+
'type': 'number',
83+
'multipleOf': Decimal('1e-15')
84+
}
85+
}
86+
}
87+
88+
SIMPLE_MULTIPLE_OF_INVALID_SCHEMA = {
89+
'properties': {
90+
'multipleOfKey': {
91+
'type': 'number',
92+
'multipleOf': 1e-15
93+
}
94+
}
95+
}
96+
97+
def test_add_record_message__multipleOf():
98+
stream_name = 'test'
99+
singer_stream = BufferedSingerStream(stream_name,
100+
deepcopy(SIMPLE_MULTIPLE_OF_VALID_SCHEMA),
101+
[])
102+
103+
multiple_of_values = ['1', '2', '3', '4', '5', '1.1', '2.3', '1.23456789', '20', '100.1']
104+
105+
for value in multiple_of_values:
106+
singer_stream.add_record_message(
107+
{
108+
'type': 'RECORD',
109+
'stream': stream_name,
110+
'record': {'multipleOfKey': Decimal(value)},
111+
'sequence': 0,
112+
RAW_LINE_SIZE: 100
113+
}
114+
)
115+
116+
assert not singer_stream.peek_invalid_records()
117+
assert singer_stream.count == len(multiple_of_values)
118+
119+
120+
def test_add_record_message__multipleOf_invalid_record():
121+
stream_name = 'test'
122+
singer_stream = BufferedSingerStream(stream_name,
123+
deepcopy(SIMPLE_MULTIPLE_OF_INVALID_SCHEMA),
124+
[])
125+
126+
multiple_of_values = [1, 2]
127+
128+
for value in multiple_of_values:
129+
with pytest.raises(SingerStreamError):
130+
singer_stream.add_record_message(
131+
{
132+
'type': 'RECORD',
133+
'stream': stream_name,
134+
'record': {'multipleOfKey': value},
135+
'sequence': 0,
136+
RAW_LINE_SIZE: 100
137+
}
138+
)
139+
140+
assert singer_stream.peek_invalid_records()
141+
assert singer_stream.count == 0
142+
143+
78144
SIMPLE_ALLOF_SCHEMA = {
79145
'type': 'object',
80146
'properties': {

tests/unit/test_json_schema.py

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

34
import pytest
45

@@ -31,6 +32,8 @@ def test_python_type():
3132
== json_schema.STRING
3233
assert json_schema.python_type('world') \
3334
== json_schema.STRING
35+
assert json_schema.python_type(decimal.Decimal(1)) \
36+
== json_schema.NUMBER
3437

3538

3639
def test_is_object():

tests/unit/test_target_tools.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,45 @@ class TestStream(ListStream):
115115
assert rows_persisted == expected_rows
116116

117117

118+
def test_record_with_multiple_of():
119+
values = [1, 1.0, 2, 2.0, 3, 7, 10.1]
120+
records = []
121+
for value in values:
122+
records.append({
123+
"type": "RECORD",
124+
"stream": "test",
125+
"record": {"multipleOfKey": value},
126+
})
127+
128+
class TestStream(ListStream):
129+
stream = [
130+
{
131+
"type": "SCHEMA",
132+
"stream": "test",
133+
"schema": {
134+
"properties": {
135+
"multipleOfKey": {
136+
"type": "number",
137+
"multipleOf": 1e-15
138+
}
139+
}
140+
},
141+
"key_properties": []
142+
}
143+
] + records
144+
145+
target = Target()
146+
147+
target_tools.stream_to_target(TestStream(), target, config=CONFIG.copy())
148+
149+
expected_rows = len(records)
150+
rows_persisted = 0
151+
for call in target.calls['write_batch']:
152+
rows_persisted += call['records_count']
153+
154+
assert rows_persisted == expected_rows
155+
156+
118157
def test_state__capture(capsys):
119158
stream = [
120159
json.dumps({'type': 'STATE', 'value': {'test': 'state-1'}}),

0 commit comments

Comments
 (0)