Skip to content

Commit

Permalink
Stop using simplejson. (#60)
Browse files Browse the repository at this point in the history
  • Loading branch information
clokep authored Mar 14, 2023
1 parent f17afae commit 95b6ec7
Show file tree
Hide file tree
Showing 4 changed files with 17 additions and 97 deletions.
8 changes: 0 additions & 8 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,6 @@ package_dir = =src
packages =
canonicaljson

install_requires =
# simplejson versions before 3.14.0 had a bug with some characters
# (e.g. \u2028) if ensure_ascii was set to false.
simplejson>=3.14.0
# typing.Protocol was only added to the stdlib in Python 3.8
typing_extensions>=4.0.0; python_version < '3.8'


[options.package_data]
canonicaljson = py.typed

Expand Down
89 changes: 17 additions & 72 deletions src/canonicaljson/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import functools
import platform
from typing import Any, Callable, Generator, Iterator, Type, TypeVar

try:
from typing import Protocol
except ImportError: # pragma: no cover
from typing_extensions import Protocol # type: ignore[assignment]
import json
from typing import Callable, Generator, Type, TypeVar


__version__ = "1.6.5"
Expand Down Expand Up @@ -63,54 +58,21 @@ def register_preserialisation_callback(
_preprocess_for_serialisation.register(data_type, callback)


class Encoder(Protocol): # pragma: no cover
def encode(self, data: object) -> str:
pass

def iterencode(self, data: object) -> Iterator[str]:
pass

def __init__(self, *args: Any, **kwargs: Any) -> None:
pass


class JsonLibrary(Protocol): # pragma: no cover
@property
def JSONEncoder(self) -> Type[Encoder]:
pass


# Declare these in the module scope, but they get configured in
# set_json_library.
_canonical_encoder: Encoder = None # type: ignore[assignment]
_pretty_encoder: Encoder = None # type: ignore[assignment]


def set_json_library(json_lib: JsonLibrary) -> None:
"""
Set the underlying JSON library that canonicaljson uses to json_lib.
Params:
json_lib: The module to use for JSON encoding. Must have a
`JSONEncoder` property.
"""
global _canonical_encoder
_canonical_encoder = json_lib.JSONEncoder(
ensure_ascii=False,
allow_nan=False,
separators=(",", ":"),
sort_keys=True,
default=_preprocess_for_serialisation,
)

global _pretty_encoder
_pretty_encoder = json_lib.JSONEncoder(
ensure_ascii=False,
allow_nan=False,
indent=4,
sort_keys=True,
default=_preprocess_for_serialisation,
)
# Declare these once for re-use.
_canonical_encoder = json.JSONEncoder(
ensure_ascii=False,
allow_nan=False,
separators=(",", ":"),
sort_keys=True,
default=_preprocess_for_serialisation,
)
_pretty_encoder = json.JSONEncoder(
ensure_ascii=False,
allow_nan=False,
indent=4,
sort_keys=True,
default=_preprocess_for_serialisation,
)


def encode_canonical_json(data: object) -> bytes:
Expand Down Expand Up @@ -152,20 +114,3 @@ def iterencode_pretty_printed_json(data: object) -> Generator[bytes, None, None]
"""
for chunk in _pretty_encoder.iterencode(data):
yield chunk.encode("utf-8")


if platform.python_implementation() == "PyPy": # pragma: no cover
# pypy ships with an optimised JSON encoder/decoder that is faster than
# simplejson's C extension.
import json
else: # pragma: no cover
# using simplejson rather than regular json on CPython for backwards
# compatibility (simplejson on Python 3.5 handles parsing of bytes while
# the standard library json does not).
#
# Note that it seems performance is on par or better using json from the
# standard library as of Python 3.7.
import simplejson as json # type: ignore[no-redef]

# Set the JSON library to the backwards compatible version.
set_json_library(json)
15 changes: 0 additions & 15 deletions tests/test_canonicaljson.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,10 @@
encode_pretty_printed_json,
iterencode_canonical_json,
iterencode_pretty_printed_json,
set_json_library,
register_preserialisation_callback,
)

import unittest
from unittest import mock


class TestCanonicalJson(unittest.TestCase):
Expand Down Expand Up @@ -140,19 +138,6 @@ def test_invalid_float_values(self) -> None:
with self.assertRaises(ValueError):
encode_pretty_printed_json(nan)

def test_set_json(self) -> None:
"""Ensure that changing the underlying JSON implementation works."""
mock_json = mock.Mock(spec=["JSONEncoder"])
mock_json.JSONEncoder.return_value.encode.return_value = "sentinel"
try:
set_json_library(mock_json)
self.assertEqual(encode_canonical_json({}), b"sentinel")
finally:
# Reset the JSON library to whatever was originally set.
from canonicaljson import json # type: ignore[attr-defined]

set_json_library(json)

def test_encode_unknown_class_raises(self) -> None:
class C:
pass
Expand Down
2 changes: 0 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,5 @@ commands = python -m black --check --diff src tests
[testenv:mypy]
deps =
mypy==1.0
types-simplejson==3.17.5
types-setuptools==57.4.14
commands = mypy src tests

0 comments on commit 95b6ec7

Please sign in to comment.