Skip to content

Commit

Permalink
tests
Browse files Browse the repository at this point in the history
Signed-off-by: Nathaniel Starkman (@nstarman) <nstarkman@protonmail.com>
  • Loading branch information
nstarman committed Jan 31, 2022
1 parent e4807fc commit ae70171
Show file tree
Hide file tree
Showing 7 changed files with 399 additions and 0 deletions.
70 changes: 70 additions & 0 deletions astropy/coordinates/tests/test_io.py
@@ -0,0 +1,70 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
# -*- coding: utf-8 -*-

import ast
import json
import re

import numpy as np
import pytest

import astropy.units as u
import astropy.coordinates as coord
from astropy.io.misc.json import JSONExtendedEncoder, JSONExtendedDecoder
from astropy.io.misc.json.tests.test_core import JSONExtendedTestBase


class TestJSONExtendedUnits(JSONExtendedTestBase):
"""Tests for serializing builtins with extended JSON encoders and decoders."""

def test_longitude(self):
"""Test round-tripping `astropy.coordinates.Longitude`."""
obj = coord.Longitude([3, 4], dtype=float, unit=u.deg, wrap_angle=180*u.deg)

# Raises errors without extended encoder
with pytest.raises(TypeError, match=re.escape("Object of type Longitude is not JSON serializable")):
json.dumps(obj)

# Works with the extended encoder
serialized = json.dumps(obj, cls=JSONExtendedEncoder)
assert isinstance(serialized, str)
d = ast.literal_eval(serialized)
assert d["__class__"] == "astropy.coordinates.angles.Longitude"
assert d["value"] == [3.0, 4.0]
assert d["unit"] == "deg"
assert d["wrap_angle"]["value"] == 180.0
assert d["wrap_angle"]["unit"] == "deg"

# Comes back partially processed without extended decoder
out = json.loads(serialized)
assert isinstance(out, dict)

# Roundtrips
out = json.loads(serialized, cls=JSONExtendedDecoder)
assert isinstance(out, coord.Longitude)
assert np.array_equal(out, obj)

def test_latitude(self):
"""Test round-tripping `astropy.coordinates.Latitude`."""
obj = coord.Latitude([3, 4], dtype=float, unit=u.deg)

# Raises errors without extended encoder
with pytest.raises(TypeError, match=re.escape("Object of type Latitude is not JSON serializable")):
json.dumps(obj)

# Works with the extended encoder
serialized = json.dumps(obj, cls=JSONExtendedEncoder)
assert isinstance(serialized, str)
d = ast.literal_eval(serialized)
assert d["__class__"] == "astropy.coordinates.angles.Latitude"
assert d["value"] == [3.0, 4.0]
assert d["unit"] == "deg"

# Comes back partially processed without extended decoder
out = json.loads(serialized)
assert isinstance(out, dict)

# Roundtrips
out = json.loads(serialized, cls=JSONExtendedDecoder)
assert isinstance(out, coord.Latitude)
assert np.array_equal(out, obj)
2 changes: 2 additions & 0 deletions astropy/io/misc/json/tests/__init__.py
@@ -0,0 +1,2 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
# -*- coding: utf-8 -*-
135 changes: 135 additions & 0 deletions astropy/io/misc/json/tests/test_builtins.py
@@ -0,0 +1,135 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
# -*- coding: utf-8 -*-

import ast
import json

import pytest

from astropy.io.misc.json import JSONExtendedEncoder, JSONExtendedDecoder

from .test_core import JSONExtendedTestBase


class TestJSONExtendedBuiltins(JSONExtendedTestBase):
"""Tests for serializing builtins with extended JSON encoders and decoders."""

def test_bytes(self):
"""Test round-tripping `bytes`."""
obj = b"1234"

# Raises errors without extended encoder
with pytest.raises(TypeError, match="Object of type bytes is not JSON serializable"):
json.dumps(obj)

# Works with the extended encoder
serialized = json.dumps(obj, cls=JSONExtendedEncoder)
assert isinstance(serialized, str)
d = ast.literal_eval(serialized)
assert d["__class__"] == "builtins.bytes"
assert d["value"] == "1234"

# Comes back partially processed without extended decoder
out = json.loads(serialized)
assert isinstance(out, dict)

# Roundtrips
out = json.loads(serialized, cls=JSONExtendedDecoder)
assert isinstance(out, bytes)
assert out == obj

def test_complex(self):
"""Test round-tripping `complex`."""
obj = 1 + 2j

# Raises errors without extended encoder
with pytest.raises(TypeError, match="Object of type complex is not JSON serializable"):
json.dumps(obj)

# Works with the extended encoder
serialized = json.dumps(obj, cls=JSONExtendedEncoder)
assert isinstance(serialized, str)
d = ast.literal_eval(serialized)
assert d["__class__"] == "builtins.complex"
assert d["value"] == [1, 2]

# Comes back partially processed without extended decoder
out = json.loads(serialized)
assert isinstance(out, dict)

# Roundtrips
out = json.loads(serialized, cls=JSONExtendedDecoder)
assert isinstance(out, complex)
assert out == obj

def test_set(self):
"""Test round-tripping `set`."""
obj = {1, 2, 3}

# Raises errors without extended encoder
with pytest.raises(TypeError, match="Object of type set is not JSON serializable"):
json.dumps(obj)

# Works with the extended encoder
serialized = json.dumps(obj, cls=JSONExtendedEncoder)
assert isinstance(serialized, str)
d = ast.literal_eval(serialized)
assert d["__class__"] == "builtins.set"
assert d["value"] == [1, 2, 3]

# Comes back partially processed without extended decoder
out = json.loads(serialized)
assert isinstance(out, dict)

# Roundtrips
out = json.loads(serialized, cls=JSONExtendedDecoder)
assert isinstance(out, set)
assert out == obj

def test_NotImplemented(self):
"""Test round-tripping `NotImplemented`."""
obj = NotImplemented

# Raises errors without extended encoder
with pytest.raises(TypeError, match="Object of type NotImplementedType is not JSON serializable"):
json.dumps(obj)

# Works with the extended encoder
serialized = json.dumps(obj, cls=JSONExtendedEncoder)
assert isinstance(serialized, str)
d = ast.literal_eval(serialized)
assert d["__class__"] == "builtins.NotImplemented"
assert d["value"] == "NotImplemented"

# Comes back partially processed without extended decoder
out = json.loads(serialized)
assert isinstance(out, dict)

# Roundtrips
out = json.loads(serialized, cls=JSONExtendedDecoder)
assert isinstance(out, type(NotImplemented))
assert out == obj

def test_Ellipsis(self):
"""Test round-tripping `Ellipsis`."""
obj = Ellipsis

# Raises errors without extended encoder
with pytest.raises(TypeError, match="Object of type ellipsis is not JSON serializable"):
json.dumps(obj)

# Works with the extended encoder
serialized = json.dumps(obj, cls=JSONExtendedEncoder)
assert isinstance(serialized, str)
d = ast.literal_eval(serialized)
assert d["__class__"] == "builtins.Ellipsis"
assert d["value"] == "Ellipsis"

# Comes back partially processed without extended decoder
out = json.loads(serialized)
assert isinstance(out, dict)

# Roundtrips
out = json.loads(serialized, cls=JSONExtendedDecoder)
assert isinstance(out, type(Ellipsis))
assert out == obj
6 changes: 6 additions & 0 deletions astropy/io/misc/json/tests/test_core.py
@@ -0,0 +1,6 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
# -*- coding: utf-8 -*-


class JSONExtendedTestBase:
"""Base for testing JSON extended encoders and decoders"""
101 changes: 101 additions & 0 deletions astropy/io/misc/json/tests/test_numpy.py
@@ -0,0 +1,101 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
# -*- coding: utf-8 -*-

import ast
import json
import re

import numpy as np
import pytest

from astropy.io.misc.json import JSONExtendedEncoder, JSONExtendedDecoder

from .test_core import JSONExtendedTestBase


class TestJSONExtendedNumPy(JSONExtendedTestBase):
"""Tests for serializing builtins with extended JSON encoders and decoders."""

def test_number(self):
"""Test round-tripping `numpy.number`."""
obj = np.int64(10)

# Raises errors without extended encoder
with pytest.raises(TypeError, match="Object of type int64 is not JSON serializable"):
json.dumps(obj)

# Works with the extended encoder
serialized = json.dumps(obj, cls=JSONExtendedEncoder)
assert isinstance(serialized, str)
d = ast.literal_eval(serialized)
assert d["__class__"] == "numpy.int64"
assert d["value"] == "10"

# Comes back partially processed without extended decoder
out = json.loads(serialized)
assert isinstance(out, dict)

# Roundtrips
out = json.loads(serialized, cls=JSONExtendedDecoder)
assert isinstance(out, np.int64)
assert out == obj

def test_dtype_simple(self):
"""Test round-tripping `numpy.dtype`."""
obj = np.dtype("int64")

# Raises errors without extended encoder
# TODO! "Object of type dtype[int64] is not JSON serializable" when py3.9+
with pytest.raises(TypeError, match=re.escape("Object of type")):
json.dumps(obj)

# Works with the extended encoder
serialized = json.dumps(obj, cls=JSONExtendedEncoder)
assert isinstance(serialized, str)
d = ast.literal_eval(serialized)
assert d["__class__"] == "numpy.dtype"
assert d["value"] == "int64"

# Comes back partially processed without extended decoder
out = json.loads(serialized)
assert isinstance(out, dict)

# Roundtrips
out = json.loads(serialized, cls=JSONExtendedDecoder)
assert isinstance(out, np.dtype)
assert out == obj

@pytest.mark.skip("TODO!")
def test_dtype_structured(self):
"""Test round-tripping structured `numpy.dtype`."""
obj = np.dtype([("f1", np.int64), ("f2", np.float16)])

def test_ndarray_simple(self):
"""Test round-tripping `numpy.ndarray`."""
obj = np.array([3, 4], dtype=float)

# Raises errors without extended encoder
with pytest.raises(TypeError, match=re.escape("Object of type ndarray is not JSON serializable")):
json.dumps(obj)

# Works with the extended encoder
serialized = json.dumps(obj, cls=JSONExtendedEncoder)
assert isinstance(serialized, str)
d = ast.literal_eval(serialized)
assert d["__class__"] == "numpy.ndarray"
assert d["value"] == [3.0, 4.0]

# Comes back partially processed without extended decoder
out = json.loads(serialized)
assert isinstance(out, dict)

# Roundtrips
out = json.loads(serialized, cls=JSONExtendedDecoder)
assert isinstance(out, np.ndarray)
assert np.array_equal(out, obj)

@pytest.mark.skip("TODO!")
def test_ndarray_structured(self):
"""Test round-tripping structured `numpy.ndarray`."""
dt = np.dtype([("f1", np.int64), ("f2", np.float16)])
obj = np.array((1, 3.0), dtype=dt)
8 changes: 8 additions & 0 deletions astropy/units/io.py
Expand Up @@ -19,6 +19,11 @@ def json_encode_unit(obj): # FIXME so works with units defined outside units su
return code


def json_decode_unit(constructor, value, code):
"""Return a |Unit| from an ``json_encode_unit`` dictionary."""
return constructor(value)


def json_encode_quantity(obj):
"""Return a |Quantity| as a JSON-able dictionary."""
from astropy.io.misc.json.core import _json_base_encode
Expand All @@ -41,7 +46,10 @@ def register_json_extended():

# Unit
JSONExtendedEncoder.register_encoding(u.UnitBase)(json_encode_unit)
JSONExtendedDecoder.register_decoding(u.UnitBase)(json_decode_unit)

JSONExtendedEncoder.register_encoding(u.FunctionUnitBase)(json_encode_unit)
JSONExtendedDecoder.register_decoding(u.FunctionUnitBase)(json_decode_unit)

# Quantity
JSONExtendedEncoder.register_encoding(u.Quantity)(json_encode_quantity)
Expand Down

0 comments on commit ae70171

Please sign in to comment.