my.core.serialize: simplejson support, more types (#176)
* my.core.serialize: simplejson support, more types I added a couple extra checks to the default function, serializing datetime, dates and dataclasses (incase orjson isn't installed) (copied from below) if orjson couldn't be imported, try simplejson This is included for compatibility reasons because orjson is rust-based and compiling on rarer architectures may not work out of the box as an example, I've been having issues getting it to install on my phone (termux/android) unlike the builtin JSON modue which serializes NamedTuples as lists (even if you provide a default function), simplejson correctly serializes namedtuples to dictionaries this just gives another option to people, simplejson is pure python so no one should have issues with that. orjson is still way faster, so still preferable if its easy and theres a precompiled build for your architecture (which there typically is) If you're ever running this with simplejson installed and not orjson, its pretty easy to tell as the JSON styling is different; orjson has no spaces between tokens, simplejson puts spaces between tokens. e.g. simplejson: {"a": 5, "b": 10} orjson: {"a":5,"b":10}
This commit is contained in:
parent
821bc08a23
commit
46198a6447
3 changed files with 67 additions and 9 deletions
|
@ -1,4 +1,5 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
import dataclasses
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional, Callable, NamedTuple
|
from typing import Any, Optional, Callable, NamedTuple
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
@ -26,9 +27,13 @@ def _default_encode(obj: Any) -> Any:
|
||||||
return obj._asdict()
|
return obj._asdict()
|
||||||
if isinstance(obj, datetime.timedelta):
|
if isinstance(obj, datetime.timedelta):
|
||||||
return obj.total_seconds()
|
return obj.total_seconds()
|
||||||
|
if isinstance(obj, datetime.datetime) or isinstance(obj, datetime.date):
|
||||||
|
return str(obj)
|
||||||
# convert paths to their string representation
|
# convert paths to their string representation
|
||||||
if isinstance(obj, Path):
|
if isinstance(obj, Path):
|
||||||
return str(obj)
|
return str(obj)
|
||||||
|
if dataclasses.is_dataclass(obj):
|
||||||
|
return dataclasses.asdict(obj)
|
||||||
if isinstance(obj, Exception):
|
if isinstance(obj, Exception):
|
||||||
return error_to_json(obj)
|
return error_to_json(obj)
|
||||||
# note: _serialize would only be called for items which aren't already
|
# note: _serialize would only be called for items which aren't already
|
||||||
|
@ -76,15 +81,36 @@ def _dumps_factory(**kwargs) -> Callable[[Any], str]:
|
||||||
|
|
||||||
return _orjson_dumps
|
return _orjson_dumps
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
import json
|
pass
|
||||||
from .warnings import high
|
|
||||||
|
|
||||||
high("You might want to install 'orjson' to support serialization for lots more types!")
|
try:
|
||||||
|
from simplejson import dumps as simplejson_dumps
|
||||||
|
# if orjson couldn't be imported, try simplejson
|
||||||
|
# This is included for compatibility reasons because orjson
|
||||||
|
# is rust-based and compiling on rarer architectures may not work
|
||||||
|
# out of the box
|
||||||
|
#
|
||||||
|
# unlike the builtin JSON modue which serializes NamedTuples as lists
|
||||||
|
# (even if you provide a default function), simplejson correctly
|
||||||
|
# serializes namedtuples to dictionaries
|
||||||
|
|
||||||
def _stdlib_dumps(obj: Any) -> str:
|
def _simplejson_dumps(obj: Any) -> str:
|
||||||
return json.dumps(obj, **kwargs)
|
return simplejson_dumps(obj, namedtuple_as_object=True, **kwargs)
|
||||||
|
|
||||||
return _stdlib_dumps
|
return _simplejson_dumps
|
||||||
|
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
import json
|
||||||
|
from .warnings import high
|
||||||
|
|
||||||
|
high("You might want to install 'orjson' to support serialization for lots more types! If that does not work for you, you can install 'simplejson' instead")
|
||||||
|
|
||||||
|
def _stdlib_dumps(obj: Any) -> str:
|
||||||
|
return json.dumps(obj, **kwargs)
|
||||||
|
|
||||||
|
return _stdlib_dumps
|
||||||
|
|
||||||
|
|
||||||
def dumps(
|
def dumps(
|
||||||
|
@ -93,8 +119,8 @@ def dumps(
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Any additional arguments are forwarded -- either to orjson.dumps
|
Any additional arguments are forwarded -- either to orjson.dumps,
|
||||||
or json.dumps if orjson is not installed
|
simplejson.dumps or json.dumps if orjson is not installed
|
||||||
|
|
||||||
You can pass the 'option' kwarg to orjson, see here for possible options:
|
You can pass the 'option' kwarg to orjson, see here for possible options:
|
||||||
https://github.com/ijl/orjson#option
|
https://github.com/ijl/orjson#option
|
||||||
|
|
23
tests/serialize_simplejson.py
Normal file
23
tests/serialize_simplejson.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
'''
|
||||||
|
This file should only run when simplejson is installed,
|
||||||
|
but orjson is not installed to check compatability
|
||||||
|
'''
|
||||||
|
|
||||||
|
# none of these should fail
|
||||||
|
|
||||||
|
import json
|
||||||
|
import simplejson
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from my.core.serialize import dumps, _A
|
||||||
|
|
||||||
|
def test_simplejson_fallback() -> None:
|
||||||
|
|
||||||
|
# should fail to import
|
||||||
|
with pytest.raises(ModuleNotFoundError):
|
||||||
|
import orjson
|
||||||
|
|
||||||
|
# simplejson should serialize namedtuple properly
|
||||||
|
res: str = dumps(_A(x=1, y=2.0))
|
||||||
|
assert json.loads(res) == {"x": 1, "y": 2.0}
|
||||||
|
|
11
tox.ini
11
tox.ini
|
@ -23,6 +23,13 @@ commands =
|
||||||
setenv = MY_CONFIG = nonexistent
|
setenv = MY_CONFIG = nonexistent
|
||||||
commands =
|
commands =
|
||||||
pip install -e .[testing]
|
pip install -e .[testing]
|
||||||
|
|
||||||
|
# installed to test my.core.serialize while using simplejson and not orjson
|
||||||
|
pip install simplejson
|
||||||
|
python3 -m pytest \
|
||||||
|
tests/serialize_simplejson.py \
|
||||||
|
{posargs}
|
||||||
|
|
||||||
pip install cachew
|
pip install cachew
|
||||||
pip install orjson
|
pip install orjson
|
||||||
|
|
||||||
|
@ -43,7 +50,9 @@ commands =
|
||||||
python3 -m pytest tests \
|
python3 -m pytest tests \
|
||||||
# ignore some tests which might take a while to run on ci..
|
# ignore some tests which might take a while to run on ci..
|
||||||
--ignore tests/takeout.py \
|
--ignore tests/takeout.py \
|
||||||
--ignore tests/extra/polar.py
|
--ignore tests/extra/polar.py \
|
||||||
|
# dont run simplejson compatability test since orjson is now installed
|
||||||
|
--ignore tests/serialize_simplejson.py
|
||||||
{posargs}
|
{posargs}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue