diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 945d235..c2e9752 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,12 +26,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9, "pypy-3.6", "pypy-3.7"] fail-fast: false steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ea271e..55ac5ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +- PyPy support (#77) + #### 0.19.0 - 2021-01-06 - Generate empty lists when `maxItems > 0` but no elements are allowed (#75) - Correct handling of regex patterns which are invalid in Python (#75) diff --git a/src/hypothesis_jsonschema/_encode.py b/src/hypothesis_jsonschema/_encode.py index b0c8dbc..8f1a423 100644 --- a/src/hypothesis_jsonschema/_encode.py +++ b/src/hypothesis_jsonschema/_encode.py @@ -1,38 +1,53 @@ """Canonical encoding for the JSONSchema semantics, where 1 == 1.0.""" import json import math -from json.encoder import _make_iterencode, encode_basestring_ascii # type: ignore +import platform from typing import Any, Dict, Tuple, Union # Mypy does not (yet!) support recursive type definitions. # (and writing a few steps by hand is a DoS attack on the AST walker in Pytest) +PYTHON_IMPLEMENTATION = platform.python_implementation() JSONType = Union[None, bool, float, str, list, Dict[str, Any]] +if PYTHON_IMPLEMENTATION != "PyPy": + from json.encoder import _make_iterencode, encode_basestring_ascii # type: ignore +else: + _make_iterencode = None + encode_basestring_ascii = None + + +def _floatstr(o: float) -> str: + # This is the bit we're overriding - integer-valued floats are + # encoded as integers, to support JSONschemas's uniqueness. + assert math.isfinite(o) + if o == int(o): + return repr(int(o)) + return repr(o) + class CanonicalisingJsonEncoder(json.JSONEncoder): - def iterencode(self, o: Any, _one_shot: bool = False) -> Any: - """Replace a stdlib method, so we encode integer-valued floats as ints.""" - - def floatstr(o: float) -> str: - # This is the bit we're overriding - integer-valued floats are - # encoded as integers, to support JSONschemas's uniqueness. - assert math.isfinite(o) - if o == int(o): - return repr(int(o)) - return repr(o) - - return _make_iterencode( - {}, - self.default, - encode_basestring_ascii, - self.indent, - floatstr, - self.key_separator, - self.item_separator, - self.sort_keys, - self.skipkeys, - _one_shot, - )(o, 0) + + if PYTHON_IMPLEMENTATION == "PyPy": + + def _JSONEncoder__floatstr(self, o: float) -> str: # noqa: N802 + return _floatstr(o) + + else: + + def iterencode(self, o: Any, _one_shot: bool = False) -> Any: + """Replace a stdlib method, so we encode integer-valued floats as ints.""" + return _make_iterencode( + {}, + self.default, + encode_basestring_ascii, + self.indent, + _floatstr, + self.key_separator, + self.item_separator, + self.sort_keys, + self.skipkeys, + _one_shot, + )(o, 0) def encode_canonical_json(value: JSONType) -> str: