diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index aa9fca22..46d791c1 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,6 +10,7 @@ **Fixed** - fix: ensure upload ready +- fix: add ExtendJSONEncoder to safely dump json data with python object, such as MultipartEncoder ## 3.0.9 (2020-06-07) diff --git a/httprunner/compat.py b/httprunner/compat.py index 35b996f9..20c442f4 100644 --- a/httprunner/compat.py +++ b/httprunner/compat.py @@ -264,7 +264,7 @@ import time import pytest from loguru import logger -from httprunner.utils import get_platform +from httprunner.utils import get_platform, ExtendJSONEncoder @pytest.fixture(scope="session", autouse=True) @@ -316,7 +316,7 @@ def session_fixture(request): os.makedirs(summary_dir, exist_ok=True) with open(summary_path, "w", encoding="utf-8") as f: - json.dump(summary, f, indent=4) + json.dump(summary, f, indent=4, ensure_ascii=False, cls=ExtendJSONEncoder) logger.info(f"generated task summary: {summary_path}") diff --git a/httprunner/utils.py b/httprunner/utils.py index 02d9b127..117dfbbd 100644 --- a/httprunner/utils.py +++ b/httprunner/utils.py @@ -212,3 +212,14 @@ def ensure_file_path_valid(file_path: Text) -> Text: new_file_path = os.path.join(os.getcwd(), f"{os.sep.join(path_names)}{file_suffix}") return new_file_path + + +class ExtendJSONEncoder(json.JSONEncoder): + """ especially used to safely dump json data with python object, such as MultipartEncoder + """ + + def default(self, obj): + try: + return super(ExtendJSONEncoder, self).default(obj) + except (UnicodeDecodeError, TypeError): + return repr(obj) diff --git a/tests/utils_test.py b/tests/utils_test.py index b15a5c7f..9903c1d9 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -1,8 +1,10 @@ +import decimal +import json import os import unittest from httprunner import loader, utils -from httprunner.utils import ensure_file_path_valid +from httprunner.utils import ensure_file_path_valid, ExtendJSONEncoder class TestUtils(unittest.TestCase): @@ -116,3 +118,14 @@ class TestUtils(unittest.TestCase): ensure_file_path_valid("examples/postman_echo/request_methods/"), os.path.join(os.getcwd(), "examples/postman_echo/request_methods"), ) + + def test_safe_dump_json(self): + class A(object): + pass + + data = {"a": A(), "b": decimal.Decimal("1.45")} + + with self.assertRaises(TypeError): + json.dumps(data) + + json.dumps(data, cls=ExtendJSONEncoder)