diff --git a/CHANGELOG.md b/CHANGELOG.md index 56e58e9..874a59f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ +# Changelog +## 0.2.1 +- Add `codecs.jsonencode` ## 0.2.0 diff --git a/src/suou/__init__.py b/src/suou/__init__.py index 8b663a4..1848eef 100644 --- a/src/suou/__init__.py +++ b/src/suou/__init__.py @@ -17,7 +17,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. """ from .iding import Siq, SiqCache, SiqType, SiqGen -from .codecs import StringCase, cb32encode, cb32decode +from .codecs import StringCase, cb32encode, cb32decode, jsonencode from .bits import count_ones, mask_shift from .configparse import MissingConfigError, MissingConfigWarning, ConfigOptions, ConfigParserConfigSource, ConfigSource, DictConfigSource, ConfigValue, EnvConfigSource from .functools import deprecated, not_implemented @@ -25,11 +25,11 @@ from .classtools import Wanted, Incomplete from .itertools import makelist, kwargs_prefix from .i18n import I18n, JsonI18n, TomlI18n -__version__ = "0.2.0" +__version__ = "0.2.1" __all__ = ( 'Siq', 'SiqCache', 'SiqType', 'SiqGen', 'StringCase', 'MissingConfigError', 'MissingConfigWarning', 'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue', 'EnvConfigSource', 'DictConfigSource', - 'deprecated', 'not_implemented', 'Wanted', 'Incomplete', + 'deprecated', 'not_implemented', 'Wanted', 'Incomplete', 'jsonencode' 'makelist', 'kwargs_prefix', 'I18n', 'JsonI18n', 'TomlI18n', 'cb32encode', 'cb32decode', 'count_ones', 'mask_shift' ) diff --git a/src/suou/codecs.py b/src/suou/codecs.py index 5412875..537780b 100644 --- a/src/suou/codecs.py +++ b/src/suou/codecs.py @@ -15,8 +15,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. """ import base64 +import datetime import enum +import json import re +from typing import Any, Callable from suou.functools import not_implemented @@ -74,6 +77,22 @@ def b64decode(val: bytes | str) -> bytes: val = val.encode('ascii') return base64.urlsafe_b64decode(val.replace(b'/', b'_').replace(b'+', b'-') + b'=' * ((4 - len(val) % 4) % 4)) +def _json_default(func = None) -> Callable[Any, str | list | dict]: + def default_converter(obj: Any) -> str | list | dict: + if isinstance(obj, (datetime.datetime, datetime.date)): + return obj.isoformat() + elif callable(func): + return func(obj) + else: + raise TypeError + return default_converter + +def jsonencode(obj: dict, *, skipkeys: bool = True, separators: tuple[str, str] = (',', ':'), default: Callable | None = None, **kwargs) -> str: + ''' + JSON encoder with stricter and smarter defaults. + ''' + return json.dumps(obj, skipkeys=skipkeys, separators=separators, default=_json_default(default), **kwargs) + class StringCase(enum.Enum): """ Enum values used by regex validators and storage converters. @@ -108,6 +127,6 @@ class StringCase(enum.Enum): __all__ = ( - 'cb32encode', 'cb32decode', 'b32lencode', 'b32ldecode', 'b64encode', 'b64decode', + 'cb32encode', 'cb32decode', 'b32lencode', 'b32ldecode', 'b64encode', 'b64decode', 'jsonencode' 'StringCase' ) \ No newline at end of file