Skip to content

Instantly share code, notes, and snippets.

@edvardm
Last active October 29, 2020 22:05
Show Gist options
  • Save edvardm/c6f6689aee493710f8389c28bc0ae3d6 to your computer and use it in GitHub Desktop.
Save edvardm/c6f6689aee493710f8389c28bc0ae3d6 to your computer and use it in GitHub Desktop.
Show power of PyContracts through mutually recursive typedef
from contracts import contract, new_contract
import pytest
from contracts.interface import ContractNotRespected
import json
# There are probably bugs, but this should check that given
# argument is a valid object which can be encoded as json.
#
# It assumes the following Python structure, if type hints
# could be recursive:
#
# JsonObj = Union[JsonDict, JsonList]
# JsonDict = Union[EmptyDict, Dict[str, JsonValue]]
# JsonList = Union[EmptyList, List[JsonValue], Tuple[JsonValue, ...]]
# JsonValue = Union[str, int, float, bool, None, JsonObj]
def scalar(obj):
return obj == None or isinstance(obj, (int, float, str, bool))
# note: mutually recursive with valid_json_root()
def json_value(obj):
return scalar(obj) or json_obj(obj)
def json_obj(obj):
if not isinstance(obj, (list, dict, tuple)):
return False
if isinstance(obj, (list, tuple)):
return all(json_value(x) for x in obj)
return all(isinstance(key, str) and json_value(val) for key, val in obj.items())
new_contract("json_obj", lambda x: json_obj(x))
# This is how it can be then used: single, clean decorator should now
# accept only objects which can be encoded as json
@contract(obj="json_obj")
def encode(obj):
json.dumps(obj)
def describe_valid_cases():
def describe_simple():
def empty_object():
encode(dict())
def empty_list():
encode([])
def simple_list():
encode([1])
def simple_dict():
encode({"a": 1})
def describe_nested():
probes = (
[1, {"a": 2}],
[1, {"a": [2]}],
[1, None, [{"a": 1}]],
[1, (1, 2)], # encodes equal to [1, [1, 2]]
)
@pytest.mark.parametrize("probe", probes)
def lists(probe):
encode(probe)
probes = ({"a": [1]}, {"a": {"b": 1}}, {"a": {"b": [1, {"c": []}]}})
@pytest.mark.parametrize("probe", probes)
def dicts(probe):
encode(probe)
def describe_invalid():
probes = (1, None, True, {1: "a"})
@pytest.mark.parametrize("probe", probes)
def invalid(probe):
with pytest.raises(ContractNotRespected):
encode(probe)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment