diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e98746f..b9465c6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,10 +8,9 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9"] + python-version: ["3.5", "3.6", "3.7", "3.8", "3.9"] os: [ubuntu-18.04, ubuntu-16.04, macos-latest, windows-2019] exclude: - - { python-version: 2.7, os: windows-2019 } - { python-version: 3.9, os: ubuntu-16.04 } - { python-version: 3.9, os: macos-latest } - { python-version: 3.9, os: windows-2019 } @@ -72,7 +71,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install --upgrade pytest six + python -m pip install --upgrade pytest python -m pip install . - name: Tests diff --git a/.gitignore b/.gitignore index 292de16..32ca83c 100644 --- a/.gitignore +++ b/.gitignore @@ -38,19 +38,20 @@ pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml *.cover *.py,cover +.cache +.coverage +.coverage.* .hypothesis/ +.nox/ .pytest_cache/ +.testmondata +.tox/ cover/ +coverage.xml +htmlcov/ +nosetests.xml # Translations *.mo diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b3df78b..c8a225f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,12 +3,13 @@ repos: rev: v2.1.0 hooks: - id: pyupgrade + args: ["--py3-plus"] - repo: https://github.com/psf/black rev: 19.10b0 hooks: - id: black - args: ["--target-version", "py27"] + args: ["--target-version", "py35"] - repo: https://gitlab.com/pycqa/flake8 rev: 3.7.9 diff --git a/.travis.yml b/.travis.yml index d854782..1922197 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,6 @@ arch: - s390x python: - - "2.7" - "3.5" - "3.8" - "3.9-dev" @@ -17,7 +16,7 @@ jobs: install: - pip install -U pip - - pip install -U pytest six + - pip install -U pytest - pip install . script: pytest diff --git a/README.rst b/README.rst index 8db5031..2afa048 100644 --- a/README.rst +++ b/README.rst @@ -29,7 +29,7 @@ UltraJSON :alt: Code style: Black :target: https://github.com/psf/black -UltraJSON is an ultra fast JSON encoder and decoder written in pure C with bindings for Python 2.7 and 3.5+. +UltraJSON is an ultra fast JSON encoder and decoder written in pure C with bindings for Python 3.5+. To install it just run pip as usual: diff --git a/pyproject.toml b/pyproject.toml index b60ba30..3f77411 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,4 +2,4 @@ requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4"] [tool.black] -target_version = ["py27"] +target_version = ["py35"] diff --git a/python/JSONtoObj.c b/python/JSONtoObj.c index b02aa02..cf8a3bc 100644 --- a/python/JSONtoObj.c +++ b/python/JSONtoObj.c @@ -36,7 +36,7 @@ http://www.opensource.apple.com/source/tcl/tcl-14/tcl/license.terms * Copyright (c) 1994 Sun Microsystems, Inc. */ -#include "py_defines.h" +#include #include @@ -90,7 +90,7 @@ static JSOBJ Object_newArray(void *prv) static JSOBJ Object_newInteger(void *prv, JSINT32 value) { - return PyInt_FromLong( (long) value); + return PyLong_FromLong( (long) value); } static JSOBJ Object_newLong(void *prv, JSINT64 value) @@ -147,7 +147,7 @@ PyObject* JSONToObj(PyObject* self, PyObject *args, PyObject *kwargs) return NULL; } - if (PyString_Check(arg)) + if (PyBytes_Check(arg)) { sarg = arg; } @@ -172,7 +172,7 @@ PyObject* JSONToObj(PyObject* self, PyObject *args, PyObject *kwargs) dconv_s2d_init(DCONV_S2D_ALLOW_TRAILING_JUNK, 0.0, 0.0, "Infinity", "NaN"); - ret = JSON_DecodeObject(&decoder, PyString_AS_STRING(sarg), PyString_GET_SIZE(sarg)); + ret = JSON_DecodeObject(&decoder, PyBytes_AS_STRING(sarg), PyBytes_GET_SIZE(sarg)); dconv_s2d_free(); diff --git a/python/objToJSON.c b/python/objToJSON.c index 4083c79..381ddf0 100644 --- a/python/objToJSON.c +++ b/python/objToJSON.c @@ -36,7 +36,7 @@ http://www.opensource.apple.com/source/tcl/tcl-14/tcl/license.terms * Copyright (c) 1994 Sun Microsystems, Inc. */ -#include "py_defines.h" +#include #include #include @@ -98,14 +98,14 @@ void initObjToJSON(void) static void *PyIntToINT64(JSOBJ _obj, JSONTypeContext *tc, void *outValue, size_t *_outLen) { PyObject *obj = (PyObject *) _obj; - *((JSINT64 *) outValue) = PyInt_AS_LONG (obj); + *((JSINT64 *) outValue) = PyLong_AsLong (obj); return NULL; } #else static void *PyIntToINT32(JSOBJ _obj, JSONTypeContext *tc, void *outValue, size_t *_outLen) { PyObject *obj = (PyObject *) _obj; - *((JSINT32 *) outValue) = PyInt_AS_LONG (obj); + *((JSINT32 *) outValue) = PyLong_AsLong (obj); return NULL; } #endif @@ -132,15 +132,14 @@ static void *PyFloatToDOUBLE(JSOBJ _obj, JSONTypeContext *tc, void *outValue, si static void *PyStringToUTF8(JSOBJ _obj, JSONTypeContext *tc, void *outValue, size_t *_outLen) { PyObject *obj = (PyObject *) _obj; - *_outLen = PyString_GET_SIZE(obj); - return PyString_AS_STRING(obj); + *_outLen = PyBytes_GET_SIZE(obj); + return PyBytes_AS_STRING(obj); } static void *PyUnicodeToUTF8(JSOBJ _obj, JSONTypeContext *tc, void *outValue, size_t *_outLen) { PyObject *obj = (PyObject *) _obj; PyObject *newObj; -#if (PY_VERSION_HEX >= 0x03030000) if (PyUnicode_IS_COMPACT_ASCII(obj)) { Py_ssize_t len; @@ -148,7 +147,6 @@ static void *PyUnicodeToUTF8(JSOBJ _obj, JSONTypeContext *tc, void *outValue, si *_outLen = len; return data; } -#endif newObj = PyUnicode_AsUTF8String(obj); if(!newObj) { @@ -157,8 +155,8 @@ static void *PyUnicodeToUTF8(JSOBJ _obj, JSONTypeContext *tc, void *outValue, si GET_TC(tc)->newObj = newObj; - *_outLen = PyString_GET_SIZE(newObj); - return PyString_AS_STRING(newObj); + *_outLen = PyBytes_GET_SIZE(newObj); + return PyBytes_AS_STRING(newObj); } static void *PyRawJSONToUTF8(JSOBJ _obj, JSONTypeContext *tc, void *outValue, size_t *_outLen) @@ -266,20 +264,18 @@ static int Dict_iterNext(JSOBJ obj, JSONTypeContext *tc) Py_DECREF(itemNameTmp); } else - if (!PyString_Check(GET_TC(tc)->itemName)) + if (!PyBytes_Check(GET_TC(tc)->itemName)) { if (UNLIKELY(GET_TC(tc)->itemName == Py_None)) { - GET_TC(tc)->itemName = PyString_FromString("null"); + GET_TC(tc)->itemName = PyUnicode_FromString("null"); return 1; } GET_TC(tc)->itemName = PyObject_Str(GET_TC(tc)->itemName); -#if PY_MAJOR_VERSION >= 3 itemNameTmp = GET_TC(tc)->itemName; GET_TC(tc)->itemName = PyUnicode_AsUTF8String (GET_TC(tc)->itemName); Py_DECREF(itemNameTmp); -#endif } else { @@ -308,17 +304,15 @@ static JSOBJ Dict_iterGetValue(JSOBJ obj, JSONTypeContext *tc) static char *Dict_iterGetName(JSOBJ obj, JSONTypeContext *tc, size_t *outLen) { - *outLen = PyString_GET_SIZE(GET_TC(tc)->itemName); - return PyString_AS_STRING(GET_TC(tc)->itemName); + *outLen = PyBytes_GET_SIZE(GET_TC(tc)->itemName); + return PyBytes_AS_STRING(GET_TC(tc)->itemName); } static int SortedDict_iterNext(JSOBJ obj, JSONTypeContext *tc) { PyObject *items = NULL, *item = NULL, *key = NULL, *value = NULL; Py_ssize_t i, nitems; -#if PY_MAJOR_VERSION >= 3 PyObject* keyTmp; -#endif // Upon first call, obtain a list of the keys and sort them. This follows the same logic as the // stanard library's _json.c sort_keys handler. @@ -355,14 +349,12 @@ static int SortedDict_iterNext(JSOBJ obj, JSONTypeContext *tc) { key = PyUnicode_AsUTF8String(key); } - else if (!PyString_Check(key)) + else if (!PyBytes_Check(key)) { key = PyObject_Str(key); -#if PY_MAJOR_VERSION >= 3 keyTmp = key; key = PyUnicode_AsUTF8String(key); Py_DECREF(keyTmp); -#endif } else { @@ -421,8 +413,8 @@ static JSOBJ SortedDict_iterGetValue(JSOBJ obj, JSONTypeContext *tc) static char *SortedDict_iterGetName(JSOBJ obj, JSONTypeContext *tc, size_t *outLen) { - *outLen = PyString_GET_SIZE(GET_TC(tc)->itemName); - return PyString_AS_STRING(GET_TC(tc)->itemName); + *outLen = PyBytes_GET_SIZE(GET_TC(tc)->itemName); + return PyBytes_AS_STRING(GET_TC(tc)->itemName); } static void SetupDictIter(PyObject *dictObj, TypeContext *pc, JSONObjectEncoder *enc) @@ -522,7 +514,7 @@ static void Object_beginTypeContext (JSOBJ _obj, JSONTypeContext *tc, JSONObject return; } else - if (PyInt_Check(obj)) + if (PyLong_Check(obj)) { PRINTMARK(); #ifdef _LP64 @@ -533,7 +525,7 @@ static void Object_beginTypeContext (JSOBJ _obj, JSONTypeContext *tc, JSONObject return; } else - if (PyString_Check(obj)) + if (PyBytes_Check(obj)) { PRINTMARK(); pc->PyTypeToJSON = PyStringToUTF8; tc->type = JT_UTF8; @@ -644,7 +636,7 @@ ISITERABLE: goto INVALID; } - if (!PyString_Check(toJSONResult) && !PyUnicode_Check(toJSONResult)) + if (!PyBytes_Check(toJSONResult) && !PyUnicode_Check(toJSONResult)) { Py_DECREF(toJSONResult); PyErr_Format (PyExc_TypeError, "expected string"); @@ -662,13 +654,9 @@ ISITERABLE: PyErr_Clear(); objRepr = PyObject_Repr(obj); -#if PY_MAJOR_VERSION >= 3 PyObject* str = PyUnicode_AsEncodedString(objRepr, "utf-8", "~E~"); - PyErr_Format (PyExc_TypeError, "%s is not JSON serializable", PyString_AS_STRING(str)); + PyErr_Format (PyExc_TypeError, "%s is not JSON serializable", PyBytes_AS_STRING(str)); Py_XDECREF(str); -#else - PyErr_Format (PyExc_TypeError, "%s is not JSON serializable", PyString_AS_STRING(objRepr)); -#endif Py_DECREF(objRepr); INVALID: @@ -841,7 +829,7 @@ PyObject* objToJSON(PyObject* self, PyObject *args, PyObject *kwargs) return NULL; } - newobj = PyString_FromString (ret); + newobj = PyUnicode_FromString (ret); if (ret != buffer) { diff --git a/python/py_defines.h b/python/py_defines.h deleted file mode 100644 index 5b89ccd..0000000 --- a/python/py_defines.h +++ /dev/null @@ -1,53 +0,0 @@ -/* -Developed by ESN, an Electronic Arts Inc. studio. -Copyright (c) 2014, Electronic Arts Inc. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: -* Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. -* Neither the name of ESN, Electronic Arts Inc. nor the -names of its contributors may be used to endorse or promote products -derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS INC. BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -Portions of code from MODP_ASCII - Ascii transformations (upper/lower, etc) -https://github.com/client9/stringencoders -Copyright (c) 2007 Nick Galbreath -- nickg [at] modp [dot] com. All rights reserved. - -Numeric decoder derived from from TCL library -https://opensource.apple.com/source/tcl/tcl-14/tcl/license.terms - * Copyright (c) 1988-1993 The Regents of the University of California. - * Copyright (c) 1994 Sun Microsystems, Inc. -*/ - -#include - -#if PY_MAJOR_VERSION >= 3 - -#define PyInt_Check PyLong_Check -#define PyInt_AS_LONG PyLong_AsLong -#define PyInt_FromLong PyLong_FromLong - -#define PyString_Check PyBytes_Check -#define PyString_GET_SIZE PyBytes_GET_SIZE -#define PyString_AS_STRING PyBytes_AS_STRING - -#define PyString_FromString PyUnicode_FromString - -#endif diff --git a/python/ujson.c b/python/ujson.c index 715ac8c..26bfecb 100644 --- a/python/ujson.c +++ b/python/ujson.c @@ -36,7 +36,7 @@ http://www.opensource.apple.com/source/tcl/tcl-14/tcl/license.terms * Copyright (c) 1994 Sun Microsystems, Inc. */ -#include "py_defines.h" +#include #include "version.h" /* objToJSON */ @@ -65,8 +65,6 @@ static PyMethodDef ujsonMethods[] = { {NULL, NULL, 0, NULL} /* Sentinel */ }; -#if PY_MAJOR_VERSION >= 3 - static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "ujson", @@ -79,35 +77,21 @@ static struct PyModuleDef moduledef = { NULL /* m_free */ }; -#define PYMODINITFUNC PyObject *PyInit_ujson(void) -#define PYMODULE_CREATE() PyModule_Create(&moduledef) -#define MODINITERROR return NULL - -#else - -#define PYMODINITFUNC PyMODINIT_FUNC initujson(void) -#define PYMODULE_CREATE() Py_InitModule("ujson", ujsonMethods) -#define MODINITERROR return - -#endif - -PYMODINITFUNC +PyObject *PyInit_ujson(void) { PyObject *module; PyObject *version_string; initObjToJSON(); - module = PYMODULE_CREATE(); + module = PyModule_Create(&moduledef); if (module == NULL) { - MODINITERROR; + return NULL; } - version_string = PyString_FromString (UJSON_VERSION); + version_string = PyUnicode_FromString (UJSON_VERSION); PyModule_AddObject (module, "__version__", version_string); -#if PY_MAJOR_VERSION >= 3 return module; -#endif } diff --git a/setup.cfg b/setup.cfg index 94069c9..ef7aac6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ max_line_length = 88 [tool:isort] -known_third_party = pytest,setuptools,six,ujson +known_third_party = pytest,setuptools,ujson force_grid_wrap = 0 include_trailing_comma = True line_length = 88 diff --git a/setup.py b/setup.py index 06eb031..87b96fa 100644 --- a/setup.py +++ b/setup.py @@ -9,13 +9,12 @@ Development Status :: 5 - Production/Stable Intended Audience :: Developers License :: OSI Approved :: BSD License Programming Language :: C -Programming Language :: Python :: 2 -Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 +Programming Language :: Python :: 3 :: Only """ dconv_source_files = glob("./deps/double-conversion/double-conversion/*.cc") @@ -84,6 +83,6 @@ setup( "write_to_template": version_template, }, setup_requires=["setuptools_scm"], - python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", + python_requires=">=3.5", classifiers=[x for x in CLASSIFIERS.split("\n") if x], ) diff --git a/tests/benchmark.py b/tests/benchmark.py index 9e25b0d..13d5588 100644 --- a/tests/benchmark.py +++ b/tests/benchmark.py @@ -1,5 +1,4 @@ # coding=UTF-8 -from __future__ import division, print_function, unicode_literals import json import os @@ -376,7 +375,7 @@ def benchmark_complex_object(): results_new_benchmark("Complex object") COUNT = 100 - with open(os.path.join(os.path.dirname(__file__), "sample.json"), "r") as f: + with open(os.path.join(os.path.dirname(__file__), "sample.json")) as f: test_object = json.load(f) run_encode(COUNT) diff --git a/tests/test_ujson.py b/tests/test_ujson.py index f1aa5e5..52adec7 100644 --- a/tests/test_ujson.py +++ b/tests/test_ujson.py @@ -1,8 +1,7 @@ # coding=UTF-8 -from __future__ import print_function, unicode_literals import decimal -import functools +import io import json import math import re @@ -10,13 +9,7 @@ import sys from collections import OrderedDict import pytest -import six import ujson -from six.moves import range, zip - -json_unicode = ( - functools.partial(json.dumps, encoding="utf-8") if six.PY2 else json.dumps -) def assert_almost_equal(a, b): @@ -144,7 +137,7 @@ def test_encode_control_escaping(): enc = ujson.encode(test_input) dec = ujson.decode(enc) assert test_input == dec - assert enc == json_unicode(test_input) + assert enc == json.dumps(test_input) # Characters outside of Basic Multilingual Plane(larger than @@ -164,11 +157,8 @@ def test_encode_unicode_bmp(): decoded = ujson.loads(encoded) assert s == decoded - # ujson outputs an UTF-8 encoded str object - if six.PY2: - encoded = ujson.dumps(s, ensure_ascii=False).decode("utf-8") - else: - encoded = ujson.dumps(s, ensure_ascii=False) + # ujson outputs a UTF-8 encoded str object + encoded = ujson.dumps(s, ensure_ascii=False) # json outputs an unicode object encoded_json = json.dumps(s, ensure_ascii=False) @@ -187,11 +177,8 @@ def test_encode_symbols(): decoded = ujson.loads(encoded) assert s == decoded - # ujson outputs an UTF-8 encoded str object - if six.PY2: - encoded = ujson.dumps(s, ensure_ascii=False).decode("utf-8") - else: - encoded = ujson.dumps(s, ensure_ascii=False) + # ujson outputs a UTF-8 encoded str object + encoded = ujson.dumps(s, ensure_ascii=False) # json outputs an unicode object encoded_json = json.dumps(s, ensure_ascii=False) @@ -251,9 +238,7 @@ def test_encode_dict_key_ref_counting(): def test_encode_to_utf8(): - test_input = b"\xe6\x97\xa5\xd1\x88" - if not six.PY2: - test_input = test_input.decode("utf-8") + test_input = b"\xe6\x97\xa5\xd1\x88".decode("utf-8") enc = ujson.encode(test_input, ensure_ascii=False) dec = ujson.decode(enc) assert enc == json.dumps(test_input, ensure_ascii=False) @@ -325,7 +310,7 @@ def test_decode_null_character(): def test_dump_to_file(): - f = six.StringIO() + f = io.StringIO() ujson.dump([1, 2, 3], f) assert "[1,2,3]" == f.getvalue() @@ -349,7 +334,7 @@ def test_dump_file_args_error(): def test_load_file(): - f = six.StringIO("[1,2,3,4]") + f = io.StringIO("[1,2,3,4]") assert [1, 2, 3, 4] == ujson.load(f) @@ -393,22 +378,15 @@ def test_decode_number_with32bit_sign_bit(): def test_encode_big_escape(): for x in range(10): - if six.PY2: - base = "\xc3\xa5" - else: - base = "\u00e5".encode("utf-8") + base = "\u00e5".encode() test_input = base * 1024 * 1024 * 2 ujson.encode(test_input) def test_decode_big_escape(): for x in range(10): - if six.PY2: - base = "\xc3\xa5" - quote = '"' - else: - base = "\u00e5".encode("utf-8") - quote = b'"' + base = "\u00e5".encode() + quote = b'"' test_input = quote + (base * 1024 * 1024 * 2) + quote ujson.decode(test_input) @@ -489,7 +467,6 @@ def test_decode_array_empty(): assert [] == obj -@pytest.mark.skipif(six.PY2, reason="Only raises on Python 3") def test_encoding_invalid_unicode_character(): s = "\udc7f" with pytest.raises(UnicodeEncodeError): @@ -622,19 +599,11 @@ class SomeObject: return "Some Object" -if sys.version_info.major == 2: - EMPTY_SET_ERROR = "set([]) is not JSON serializable" - FILLED_SET_ERROR = "set([1, 2, 3]) is not JSON serializable" -else: - EMPTY_SET_ERROR = "set() is not JSON serializable" - FILLED_SET_ERROR = "{1, 2, 3} is not JSON serializable" - - @pytest.mark.parametrize( "test_input, expected_exception, expected_message", [ - (set(), TypeError, EMPTY_SET_ERROR), - ({1, 2, 3}, TypeError, FILLED_SET_ERROR), + (set(), TypeError, "set() is not JSON serializable"), + ({1, 2, 3}, TypeError, "{1, 2, 3} is not JSON serializable"), (SomeObject(), TypeError, "Some Object is not JSON serializable"), ], ) @@ -749,7 +718,7 @@ def test_encode_unicode(test_input): enc = ujson.encode(test_input) dec = ujson.decode(enc) - assert enc == json_unicode(test_input) + assert enc == json.dumps(test_input) assert dec == json.loads(enc)