diff --git a/aurora/aurora.py b/aurora/aurora.py index 463de76..300d127 100644 --- a/aurora/aurora.py +++ b/aurora/aurora.py @@ -19,7 +19,8 @@ from redbot.core import app_commands, commands, data_manager from redbot.core.app_commands import Choice from redbot.core.bot import Red from redbot.core.commands.converter import parse_relativedelta, parse_timedelta -from redbot.core.utils.chat_formatting import box, error, humanize_list, humanize_timedelta, warning +from redbot.core.utils.chat_formatting import (box, error, humanize_list, + humanize_timedelta, warning) from aurora.importers.aurora import ImportAuroraView from aurora.importers.galacticbot import ImportGalacticBotView @@ -28,10 +29,20 @@ from aurora.menus.guild import Guild from aurora.menus.immune import Immune from aurora.menus.overrides import Overrides from aurora.utilities.config import config, register_config -from aurora.utilities.database import connect, create_guild_table, fetch_case, mysql_log -from aurora.utilities.factory import addrole_embed, case_factory, changes_factory, evidenceformat_factory, guild_embed, immune_embed, message_factory, overrides_embed +from aurora.utilities.database import (connect, create_guild_table, fetch_case, + mysql_log) +from aurora.utilities.factory import (addrole_embed, case_factory, + changes_factory, evidenceformat_factory, + guild_embed, immune_embed, + message_factory, overrides_embed) +from aurora.utilities.json import dump, dumps from aurora.utilities.logger import logger -from aurora.utilities.utils import check_moddable, check_permissions, convert_timedelta_to_str, dump, dumps, fetch_channel_dict, fetch_user_dict, generate_dict, get_footer_image, log, send_evidenceformat, timedelta_from_relativedelta +from aurora.utilities.utils import (check_moddable, check_permissions, + convert_timedelta_to_str, + fetch_channel_dict, fetch_user_dict, + generate_dict, get_footer_image, log, + send_evidenceformat, + timedelta_from_relativedelta) class Aurora(commands.Cog): @@ -40,7 +51,7 @@ class Aurora(commands.Cog): This cog stores all of its data in an SQLite database.""" __author__ = ["SeaswimmerTheFsh"] - __version__ = "2.1.2" + __version__ = "2.2.0" __documentation__ = "https://seacogs.coastalcommits.com/aurora/" async def red_delete_data_for_user(self, *, requester, user_id: int): diff --git a/aurora/models.py b/aurora/models.py index 4c0fff6..5ee33c5 100644 --- a/aurora/models.py +++ b/aurora/models.py @@ -70,5 +70,5 @@ class Moderation(BaseModel): return None def to_json(self, indent: int = None, file: bool = False): - from aurora.utilities.utils import dump, dumps + from aurora.utilities.json import dump, dumps return dump(self.model_dump(), indent=indent) if file else dumps(self.model_dump(), indent=indent) diff --git a/aurora/utilities/encoder.py b/aurora/utilities/encoder.py deleted file mode 100644 index ba69ef2..0000000 --- a/aurora/utilities/encoder.py +++ /dev/null @@ -1,15 +0,0 @@ -import json -from datetime import datetime, timedelta - -from aurora.models import Moderation - - -class JSONEncoder(json.JSONEncoder): - def default(self, o): - if isinstance(o, datetime): - return int(o.timestamp()) - if isinstance(o, timedelta): - return str(o) - if isinstance(o, Moderation): - return o.model_dump() - return super().default(o) diff --git a/aurora/utilities/json.py b/aurora/utilities/json.py new file mode 100644 index 0000000..90b335f --- /dev/null +++ b/aurora/utilities/json.py @@ -0,0 +1,124 @@ +import json +from datetime import datetime, timedelta + +from aurora.models import Moderation + + +class JSONEncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, datetime): + return int(o.timestamp()) + if isinstance(o, timedelta): + return str(o) + if isinstance(o, Moderation): + return o.model_dump() + return super().default(o) + + +# This is a wrapper around the json module's dumps function that uses our custom JSONEncoder class +def dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, indent=None, separators=None, + default=None, sort_keys=False, **kw) -> str: + """Serialize ``obj`` to a JSON formatted ``str``. + + If ``skipkeys`` is true then ``dict`` keys that are not basic types + (``str``, ``int``, ``float``, ``bool``, ``None``) will be skipped + instead of raising a ``TypeError``. + + If ``ensure_ascii`` is false, then the return value can contain non-ASCII + characters if they appear in strings contained in ``obj``. Otherwise, all + such characters are escaped in JSON strings. + + If ``check_circular`` is false, then the circular reference check + for container types will be skipped and a circular reference will + result in an ``RecursionError`` (or worse). + + If ``allow_nan`` is false, then it will be a ``ValueError`` to + serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in + strict compliance of the JSON specification, instead of using the + JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + + If ``indent`` is a non-negative integer, then JSON array elements and + object members will be pretty-printed with that indent level. An indent + level of 0 will only insert newlines. ``None`` is the most compact + representation. + + If specified, ``separators`` should be an ``(item_separator, key_separator)`` + tuple. The default is ``(', ', ': ')`` if *indent* is ``None`` and + ``(',', ': ')`` otherwise. To get the most compact JSON representation, + you should specify ``(',', ':')`` to eliminate whitespace. + + ``default(obj)`` is a function that should return a serializable version + of obj or raise TypeError. The default simply raises TypeError. + + If *sort_keys* is true (default: ``False``), then the output of + dictionaries will be sorted by key. + """ + return json.dumps( + obj, + cls=JSONEncoder, + shipkeys=skipkeys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + indent=indent, + separators=separators, + default=default, + sort_keys=sort_keys, + **kw + ) + +# This is a wrapper around the json module's dump function that uses our custom JSONEncoder class +def dump(obj, fp, *, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, indent=None, separators=None, + default=None, sort_keys=False, **kw) -> str: + """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a + ``.write()``-supporting file-like object). + + If ``skipkeys`` is true then ``dict`` keys that are not basic types + (``str``, ``int``, ``float``, ``bool``, ``None``) will be skipped + instead of raising a ``TypeError``. + + If ``ensure_ascii`` is false, then the strings written to ``fp`` can + contain non-ASCII characters if they appear in strings contained in + ``obj``. Otherwise, all such characters are escaped in JSON strings. + + If ``check_circular`` is false, then the circular reference check + for container types will be skipped and a circular reference will + result in an ``RecursionError`` (or worse). + + If ``allow_nan`` is false, then it will be a ``ValueError`` to + serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) + in strict compliance of the JSON specification, instead of using the + JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + + If ``indent`` is a non-negative integer, then JSON array elements and + object members will be pretty-printed with that indent level. An indent + level of 0 will only insert newlines. ``None`` is the most compact + representation. + + If specified, ``separators`` should be an ``(item_separator, key_separator)`` + tuple. The default is ``(', ', ': ')`` if *indent* is ``None`` and + ``(',', ': ')`` otherwise. To get the most compact JSON representation, + you should specify ``(',', ':')`` to eliminate whitespace. + + ``default(obj)`` is a function that should return a serializable version + of obj or raise TypeError. The default simply raises TypeError. + + If *sort_keys* is true (default: ``False``), then the output of + dictionaries will be sorted by key. + """ + return json.dump( + obj, + fp, + cls=JSONEncoder, + skipkeys=skipkeys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + indent=indent, + separators=separators, + default=default, + sort_keys=sort_keys, + **kw + ) diff --git a/aurora/utilities/utils.py b/aurora/utilities/utils.py index 8a31063..1ff21fa 100644 --- a/aurora/utilities/utils.py +++ b/aurora/utilities/utils.py @@ -296,13 +296,3 @@ def get_footer_image(coginstance: commands.Cog) -> File: """Returns the footer image for the embeds.""" image_path = data_manager.bundled_data_path(coginstance) / "arrow.png" return File(image_path, filename="arrow.png", description="arrow") - -def dumps(obj: any, indent: int = None) -> str: - """Returns a JSON string from an object.""" - from aurora.utilities.encoder import JSONEncoder - return json.dumps(obj, indent=indent, cls=JSONEncoder) - -def dump(obj: any, indent: int = None) -> str: - """Returns a JSON string from an object.""" - from aurora.utilities.encoder import JSONEncoder - return json.dump(obj, indent=indent, cls=JSONEncoder)