Skip to content

Commit ccf2266

Browse files
authored
Support allow_nan in message JSON output (#183)
* allow nan values to replicate * update setup and changelog * make pylint happy * add test cases for allow nan
1 parent 9145ecb commit ccf2266

File tree

4 files changed

+72
-6
lines changed

4 files changed

+72
-6
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## 6.3.0
4+
* Support allow_nan in message JSON output [#183](https://github.com/singer-io/singer-python/pull/183)
5+
36
## 6.2.3
47
* Default type for non-standard data types is string [#182](https://github.com/singer-io/singer-python/pull/182)
58

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import subprocess
55

66
setup(name="singer-python",
7-
version='6.2.3',
7+
version='6.3.0',
88
description="Singer.io utility library",
99
author="Stitch",
1010
classifiers=['Programming Language :: Python :: 3 :: Only'],

singer/messages.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -218,12 +218,17 @@ def parse_message(msg):
218218
return None
219219

220220

221-
def format_message(message, ensure_ascii=True):
222-
return json.dumps(message.asdict(), use_decimal=True, ensure_ascii=ensure_ascii)
221+
def format_message(message, ensure_ascii=True, allow_nan=False):
222+
return json.dumps(
223+
message.asdict(),
224+
use_decimal=True,
225+
ensure_ascii=ensure_ascii,
226+
allow_nan=allow_nan
227+
)
223228

224229

225-
def write_message(message, ensure_ascii=True):
226-
sys.stdout.write(format_message(message, ensure_ascii=ensure_ascii) + '\n')
230+
def write_message(message, ensure_ascii=True, allow_nan=False):
231+
sys.stdout.write(format_message(message, ensure_ascii=ensure_ascii, allow_nan=allow_nan) + '\n')
227232
sys.stdout.flush()
228233

229234

tests/test_transform.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import io
2+
import sys
13
import unittest
24
import decimal
5+
import simplejson as json
6+
import singer.messages as messages
37
from singer import transform
48
from singer.transform import *
59

6-
710
class TestTransform(unittest.TestCase):
811
def test_integer_transform(self):
912
schema = {'type': 'integer'}
@@ -486,3 +489,58 @@ def test_pattern_properties_match_multiple(self):
486489
dict_value = {"name": "chicken", "unit_cost": 1.45, "SKU": '123456'}
487490
expected = dict(dict_value)
488491
self.assertEqual(expected, transform(dict_value, schema))
492+
493+
class DummyMessage:
494+
"""A dummy message object with an asdict() method."""
495+
def __init__(self, value):
496+
self.value = value
497+
498+
def asdict(self):
499+
return {"value": self.value}
500+
501+
502+
class TestAllowNan(unittest.TestCase):
503+
"""Unit tests for allow_nan support in singer.messages."""
504+
505+
def test_format_message_allow_nan_true(self):
506+
"""Should serialize NaN successfully when allow_nan=True."""
507+
msg = DummyMessage(float("nan"))
508+
result = messages.format_message(msg, allow_nan=True)
509+
510+
# The output JSON should contain NaN literal (not quoted)
511+
self.assertIn("NaN", result)
512+
513+
# Replace NaN with null to make it valid JSON for parsing check
514+
json.loads(result.replace("NaN", "null"))
515+
516+
def test_format_message_allow_nan_false(self):
517+
"""Should raise ValueError when allow_nan=False and value is NaN."""
518+
msg = DummyMessage(float("nan"))
519+
with self.assertRaises(ValueError):
520+
messages.format_message(msg, allow_nan=False)
521+
522+
def test_write_message_allow_nan_true(self):
523+
"""Should write to stdout successfully when allow_nan=True."""
524+
msg = DummyMessage(float("nan"))
525+
fake_stdout = io.StringIO()
526+
original_stdout = sys.stdout
527+
sys.stdout = fake_stdout
528+
try:
529+
messages.write_message(msg, allow_nan=True)
530+
output = fake_stdout.getvalue()
531+
self.assertIn("NaN", output)
532+
self.assertTrue(output.endswith("\n"))
533+
finally:
534+
sys.stdout = original_stdout
535+
536+
def test_write_message_allow_nan_false(self):
537+
"""Should raise ValueError when allow_nan=False and message has NaN."""
538+
msg = DummyMessage(float("nan"))
539+
fake_stdout = io.StringIO()
540+
original_stdout = sys.stdout
541+
sys.stdout = fake_stdout
542+
try:
543+
with self.assertRaises(ValueError):
544+
messages.write_message(msg, allow_nan=False)
545+
finally:
546+
sys.stdout = original_stdout

0 commit comments

Comments
 (0)