diff --git a/moulinette/interfaces/__init__.py b/moulinette/interfaces/__init__.py index dc33e276..a5d32cac 100644 --- a/moulinette/interfaces/__init__.py +++ b/moulinette/interfaces/__init__.py @@ -4,7 +4,9 @@ import re import logging import argparse import copy +import datetime from collections import deque, OrderedDict +from json.encoder import JSONEncoder from moulinette import m18n from moulinette.core import MoulinetteError @@ -560,3 +562,40 @@ class PositionalsFirstHelpFormatter(argparse.HelpFormatter): # prefix with 'usage:' return "%s%s\n\n" % (prefix, usage) + + +class JSONExtendedEncoder(JSONEncoder): + + """Extended JSON encoder + + Extend default JSON encoder to recognize more types and classes. It will + never raise an exception if the object can't be encoded and return its repr + instead. + + The following objects and types are supported: + - set: converted into list + + """ + + def default(self, o): + + import pytz # Lazy loading, this takes like 3+ sec on a RPi2 ?! + + """Return a serializable object""" + # Convert compatible containers into list + if isinstance(o, set) or (hasattr(o, "__iter__") and hasattr(o, "next")): + return list(o) + + # Display the date in its iso format ISO-8601 Internet Profile (RFC 3339) + if isinstance(o, datetime.date): + if o.tzinfo is None: + o = o.replace(tzinfo=pytz.utc) + return o.isoformat() + + # Return the repr for object that json can't encode + logger.warning( + "cannot properly encode in JSON the object %s, " "returned repr is: %r", + type(o), + o, + ) + return repr(o) diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index 3e05e57f..c7b929c1 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -21,9 +21,9 @@ from moulinette.core import MoulinetteError, MoulinetteValidationError from moulinette.interfaces import ( BaseActionsMapParser, ExtendedArgumentParser, + JSONExtendedEncoder, ) from moulinette.utils import log -from moulinette.utils.serialize import JSONExtendedEncoder from moulinette.utils.text import random_ascii logger = log.getLogger("moulinette.interface.api") diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index 733f6301..7d64a1e6 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -5,7 +5,7 @@ import sys import getpass import locale import logging -from argparse import SUPPRESS +import argparse from collections import OrderedDict from datetime import date, datetime @@ -17,6 +17,7 @@ from moulinette.core import MoulinetteError, MoulinetteValidationError from moulinette.interfaces import ( BaseActionsMapParser, ExtendedArgumentParser, + JSONExtendedEncoder, ) from moulinette.utils import log @@ -32,17 +33,14 @@ from moulinette.utils import log # But it display instead: # Error: unable to parse arguments 'firewall' because: sequence item 0: expected str instance, NoneType found -import argparse - - def monkey_get_action_name(argument): if argument is None: return None elif argument.option_strings: return "/".join(argument.option_strings) - elif argument.metavar not in (None, SUPPRESS): + elif argument.metavar not in (None, argparse.SUPPRESS): return argument.metavar - elif argument.dest not in (None, SUPPRESS): + elif argument.dest not in (None, argparse.SUPPRESS): return argument.dest elif argument.choices: return "{" + ",".join(argument.choices) + "}" @@ -307,7 +305,7 @@ class ActionsMapParser(BaseActionsMapParser): # Append each top parser action to the global group for action in top_parser._actions: - action.dest = SUPPRESS + action.dest = argparse.SUPPRESS self.global_parser._add_action(action) # Implement virtual properties @@ -509,8 +507,6 @@ class Interface: if output_as: if output_as == "json": import json - from moulinette.utils.serialize import JSONExtendedEncoder - print(json.dumps(ret, cls=JSONExtendedEncoder)) else: plain_print_dict(ret) diff --git a/moulinette/utils/serialize.py b/moulinette/utils/serialize.py deleted file mode 100644 index 345cb4d4..00000000 --- a/moulinette/utils/serialize.py +++ /dev/null @@ -1,45 +0,0 @@ -import logging -from json.encoder import JSONEncoder -import datetime - -logger = logging.getLogger("moulinette.utils.serialize") - - -# JSON utilities ------------------------------------------------------- - - -class JSONExtendedEncoder(JSONEncoder): - - """Extended JSON encoder - - Extend default JSON encoder to recognize more types and classes. It will - never raise an exception if the object can't be encoded and return its repr - instead. - - The following objects and types are supported: - - set: converted into list - - """ - - def default(self, o): - - import pytz # Lazy loading, this takes like 3+ sec on a RPi2 ?! - - """Return a serializable object""" - # Convert compatible containers into list - if isinstance(o, set) or (hasattr(o, "__iter__") and hasattr(o, "next")): - return list(o) - - # Display the date in its iso format ISO-8601 Internet Profile (RFC 3339) - if isinstance(o, datetime.date): - if o.tzinfo is None: - o = o.replace(tzinfo=pytz.utc) - return o.isoformat() - - # Return the repr for object that json can't encode - logger.warning( - "cannot properly encode in JSON the object %s, " "returned repr is: %r", - type(o), - o, - ) - return repr(o) diff --git a/test/test_serialize.py b/test/test_serialize.py index a87bfa9b..bf4b2342 100644 --- a/test/test_serialize.py +++ b/test/test_serialize.py @@ -1,5 +1,5 @@ from datetime import datetime as dt -from moulinette.utils.serialize import JSONExtendedEncoder +from moulinette.interface import JSONExtendedEncoder def test_json_extended_encoder(caplog):