Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-canonicaljson for openSUSE:Factory checked in at 2023-05-30 22:02:18 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-canonicaljson (Old) and /work/SRC/openSUSE:Factory/.python-canonicaljson.new.1533 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-canonicaljson" Tue May 30 22:02:18 2023 rev:17 rq:1089612 version:2.0.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-canonicaljson/python-canonicaljson.changes 2023-05-15 16:54:24.592202715 +0200 +++ /work/SRC/openSUSE:Factory/.python-canonicaljson.new.1533/python-canonicaljson.changes 2023-05-30 22:02:32.155043360 +0200 @@ -1,0 +2,13 @@ +Mon May 29 16:34:14 UTC 2023 - Dirk Müller <dmuel...@suse.com> + +- update to 2.0.0: + * Add a generic `register_preserialisation_callback` mechanism, + which allows users to teach canonicaljson how to JSON-encode custom + types. + * Remove support for serialising `frozendict` instances. Use + the new `register_preserialisation_callback` mechanism to replace + this functionality if needed. + * Remove support for `simplejson` and the + `set_json_library`alternative json libraries. + +------------------------------------------------------------------- Old: ---- v1.6.5.tar.gz New: ---- v2.0.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-canonicaljson.spec ++++++ --- /var/tmp/diff_new_pack.CYrO3v/_old 2023-05-30 22:02:32.651046284 +0200 +++ /var/tmp/diff_new_pack.CYrO3v/_new 2023-05-30 22:02:32.663046354 +0200 @@ -27,7 +27,7 @@ %define github_user matrix-org %define short_name canonicaljson Name: python-%{short_name}%{psuffix} -Version: 1.6.5 +Version: 2.0.0 Release: 0 Summary: Canonical JSON for Python License: Apache-2.0 ++++++ v1.6.5.tar.gz -> v2.0.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-canonicaljson-1.6.5/CHANGES.md new/python-canonicaljson-2.0.0/CHANGES.md --- old/python-canonicaljson-1.6.5/CHANGES.md 2023-02-15 23:34:05.000000000 +0100 +++ new/python-canonicaljson-2.0.0/CHANGES.md 2023-03-15 02:32:42.000000000 +0100 @@ -1,3 +1,18 @@ +Version 2.0.0 released 2023-03-15 + +Additions: + +* Add a generic `register_preserialisation_callback` mechanism, which + allows users to teach canonicaljson how to JSON-encode custom types. + +Breaking changes: + +* Remove support for serialising `frozendict` instances. Use the new + `register_preserialisation_callback` mechanism to replace this + functionality if needed. +* Remove support for `simplejson` and the `set_json_library`alternative + json libraries. + Version 1.6.5 released 2023-02-15 * Update type hints to pass under mypy 1.0. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-canonicaljson-1.6.5/README.rst new/python-canonicaljson-2.0.0/README.rst --- old/python-canonicaljson-1.6.5/README.rst 2023-02-15 23:34:05.000000000 +0100 +++ new/python-canonicaljson-2.0.0/README.rst 2023-03-15 02:32:42.000000000 +0100 @@ -15,7 +15,7 @@ U+0056, to keep the output as small as possible. * Uses the shortest escape sequence for each escaped character. * Encodes the JSON as UTF-8. -* Can encode ``frozendict`` immutable dictionaries. +* Can be configured to encode custom types unknown to the stdlib JSON encoder. Supports Python versions 3.7 and newer. @@ -59,3 +59,20 @@ which uses the standard library json module). .. _simplejson: https://simplejson.readthedocs.io/ + +A preserialisation hook allows you to encode objects which aren't encodable by the +standard library ``JSONEncoder``. + +.. code:: python + + import canonicaljson + from typing import Dict + + class CustomType: + pass + + def callback(c: CustomType) -> Dict[str, str]: + return {"Hello": "world!"} + + canonicaljson.register_preserialisation_callback(CustomType, callback) + assert canonicaljson.encode_canonical_json(CustomType()) == b'{"Hello":"world!"}' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-canonicaljson-1.6.5/setup.cfg new/python-canonicaljson-2.0.0/setup.cfg --- old/python-canonicaljson-1.6.5/setup.cfg 2023-02-15 23:34:05.000000000 +0100 +++ new/python-canonicaljson-2.0.0/setup.cfg 2023-03-15 02:32:42.000000000 +0100 @@ -26,20 +26,6 @@ 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.extras_require] -# frozendict support can be enabled using the `canonicaljson[frozendict]` syntax -frozendict = - frozendict>=1.0 - - [options.package_data] canonicaljson = py.typed diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-canonicaljson-1.6.5/src/canonicaljson/__init__.py new/python-canonicaljson-2.0.0/src/canonicaljson/__init__.py --- old/python-canonicaljson-1.6.5/src/canonicaljson/__init__.py 2023-02-15 23:34:05.000000000 +0100 +++ new/python-canonicaljson-2.0.0/src/canonicaljson/__init__.py 2023-03-15 02:32:42.000000000 +0100 @@ -13,81 +13,66 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import functools +import json +from typing import Callable, Generator, Type, TypeVar -import platform -from typing import Any, Generator, Iterator, Optional, Type - -try: - from typing import Protocol -except ImportError: # pragma: no cover - from typing_extensions import Protocol # type: ignore[assignment] - -frozendict_type: Optional[Type[Any]] -try: - from frozendict import frozendict as frozendict_type -except ImportError: - frozendict_type = None # pragma: no cover - -__version__ = "1.6.5" - - -def _default(obj: object) -> object: # pragma: no cover - if type(obj) is frozendict_type: - # If frozendict is available and used, cast `obj` into a dict - return dict(obj) # type: ignore[call-overload] - raise TypeError( - "Object of type %s is not JSON serializable" % obj.__class__.__name__ - ) +__version__ = "2.0.0" -class Encoder(Protocol): # pragma: no cover - def encode(self, data: object) -> str: - pass - def iterencode(self, data: object) -> Iterator[str]: - pass +@functools.singledispatch +def _preprocess_for_serialisation(obj: object) -> object: # pragma: no cover + """Transform an `obj` into something the JSON library knows how to encode. - def __init__(self, *args: Any, **kwargs: Any) -> None: - pass + This is only called for types that the JSON library does not recognise. + """ + raise TypeError( + "Object of type %s is not JSON serializable" % obj.__class__.__name__ + ) -class JsonLibrary(Protocol): # pragma: no cover - @property - def JSONEncoder(self) -> Type[Encoder]: - pass +T = TypeVar("T") -# 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 register_preserialisation_callback( + data_type: Type[T], callback: Callable[[T], object] +) -> None: + """ + Register a `callback` to preprocess `data_type` objects unknown to the JSON encoder. + When canonicaljson encodes an object `x` at runtime that its JSON library does not + know how to encode, it will + - select a `callback`, + - compute `y = callback(x)`, then + - JSON-encode `y` and return the result. -def set_json_library(json_lib: JsonLibrary) -> None: - """ - Set the underlying JSON library that canonicaljson uses to json_lib. + The `callback` should return an object that is JSON-serialisable by the stdlib + json module. - Params: - json_lib: The module to use for JSON encoding. Must have a - `JSONEncoder` property. + If this is called multiple times with the same `data_type`, the most recently + registered callback is used when serialising that `data_type`. """ - global _canonical_encoder - _canonical_encoder = json_lib.JSONEncoder( - ensure_ascii=False, - allow_nan=False, - separators=(",", ":"), - sort_keys=True, - default=_default, - ) - - global _pretty_encoder - _pretty_encoder = json_lib.JSONEncoder( - ensure_ascii=False, - allow_nan=False, - indent=4, - sort_keys=True, - default=_default, - ) + if data_type is object: + raise ValueError("Cannot register callback for the `object` type") + _preprocess_for_serialisation.register(data_type, callback) + + +# 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: @@ -129,20 +114,3 @@ """ 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) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-canonicaljson-1.6.5/tests/test_canonicaljson.py new/python-canonicaljson-2.0.0/tests/test_canonicaljson.py --- old/python-canonicaljson-1.6.5/tests/test_canonicaljson.py 2023-02-15 23:34:05.000000000 +0100 +++ new/python-canonicaljson-2.0.0/tests/test_canonicaljson.py 2023-03-15 02:32:42.000000000 +0100 @@ -13,20 +13,19 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from unittest.mock import Mock from math import inf, nan from canonicaljson import ( encode_canonical_json, encode_pretty_printed_json, - frozendict_type, iterencode_canonical_json, iterencode_pretty_printed_json, - set_json_library, + register_preserialisation_callback, ) import unittest -from unittest import mock class TestCanonicalJson(unittest.TestCase): @@ -107,22 +106,6 @@ b'{\n "la merde amus\xc3\xa9e": "\xF0\x9F\x92\xA9"\n}', ) - @unittest.skipIf( - frozendict_type is None, - "If `frozendict` is not available, skip test", - ) - def test_frozen_dict(self) -> None: - # For mypy's benefit: - assert frozendict_type is not None - self.assertEqual( - encode_canonical_json(frozendict_type({"a": 1})), - b'{"a":1}', - ) - self.assertEqual( - encode_pretty_printed_json(frozendict_type({"a": 1})), - b'{\n "a": 1\n}', - ) - def test_unknown_type(self) -> None: class Unknown(object): pass @@ -155,15 +138,45 @@ 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] + def test_encode_unknown_class_raises(self) -> None: + class C: + pass + + with self.assertRaises(Exception): + encode_canonical_json(C()) + + def test_preserialisation_callback(self) -> None: + class C: + pass + + # Naughty: this alters the global state of the module. However this + # `C` class is limited to this test only, so this shouldn't affect + # other types and other tests. + register_preserialisation_callback(C, lambda c: "I am a C instance") + + result = encode_canonical_json(C()) + self.assertEqual(result, b'"I am a C instance"') + + def test_cannot_register_preserialisation_callback_for_object(self) -> None: + with self.assertRaises(Exception): + register_preserialisation_callback( + object, lambda c: "shouldn't be able to do this" + ) + + def test_most_recent_preserialisation_callback_called(self) -> None: + class C: + pass + + callback1 = Mock(return_value="callback 1 was called") + callback2 = Mock(return_value="callback 2 was called") + + # Naughty: this alters the global state of the module. However this + # `C` class is limited to this test only, so this shouldn't affect + # other types and other tests. + register_preserialisation_callback(C, callback1) + register_preserialisation_callback(C, callback2) + + encode_canonical_json(C()) - set_json_library(json) + callback1.assert_not_called() + callback2.assert_called_once() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-canonicaljson-1.6.5/tox.ini new/python-canonicaljson-2.0.0/tox.ini --- old/python-canonicaljson-1.6.5/tox.ini 2023-02-15 23:34:05.000000000 +0100 +++ new/python-canonicaljson-2.0.0/tox.ini 2023-03-15 02:32:42.000000000 +0100 @@ -33,8 +33,5 @@ [testenv:mypy] deps = mypy==1.0 - types-frozendict==2.0.8 - types-simplejson==3.17.5 types-setuptools==57.4.14 commands = mypy src tests -