def test_convert_with_multiple_levels_nested_dict() -> None: source = {"level1": {"level2": {"level3": {"level4": "foo"}}}} # fmt: off expected = "\n".join([ "from typing_extensions import TypedDict", "", "", "class Level3(TypedDict):", " level4: str", "", "", "class Level2(TypedDict):", " level3: Level3", "", "", "class Level1(TypedDict):", " level2: Level2", "", "", "class Root(TypedDict):", " level1: Level1", ]) # fmt: on assert expected == get_type_definitions(source)
def test_convert_optional_combined_dicts() -> None: source = { "owner": {"name": "foo", "age": 44}, "coOwner": {"name": "bar", "age": None}, } # fmt: off expected = "\n".join([ "from typing import Optional", "", "from typing_extensions import TypedDict", "", "", "class Owner(TypedDict):", " name: str", " age: Optional[int]", "", "", "class Root(TypedDict):", " owner: Owner", " coOwner: Owner", ]) # fmt: on assert expected == get_type_definitions(source)
def test_convert_with_repeated_nested_dict() -> None: source = { "nest": { "foo": "bar" }, "other_nest": { "foo": "qux" }, "unique_nest": { "baz": "qux" }, } # fmt: off expected = "\n".join([ "from typing_extensions import TypedDict", "", "", "class Nest(TypedDict):", " foo: str", "", "", "class UniqueNest(TypedDict):", " baz: str", "", "", "class Root(TypedDict):", " nest: Nest", " other_nest: Nest", " unique_nest: UniqueNest", ]) # fmt: on assert expected == get_type_definitions(source)
def test_snapshots(snapshot: Any, fixture: str) -> None: with open(f"tests/snapshot/fixtures/{fixture}.json", "r") as f: source = json.load(f) output = get_type_definitions(source) snapshot.assert_match(output)
def test_convert_with_invalid_key_names_nested() -> None: source = {"invalid-key": {"id": 123}} # fmt: off expected = "\n".join([ "from typing_extensions import TypedDict", "", "", "class InvalidKey(TypedDict):", " id: int", "", "", 'Root = TypedDict("Root", {', ' "invalid-key": InvalidKey,', '})' ]) # fmt: on assert expected == get_type_definitions(source)
def test_convert_with_invalid_key_names() -> None: source = {"invalid-key": 123, "from": "far away"} # fmt: off expected = "\n".join([ "from typing_extensions import TypedDict", "", "", 'Root = TypedDict("Root", {', ' "invalid-key": int,', ' "from": str,', '})' ]) # fmt: on assert expected == get_type_definitions(source)
def test_convert_imports_with_no_typed_dict() -> None: source = [1, 2, 3] # fmt: off expected = "\n".join([ "from typing import List", "", "", "Root = List[int]", ]) # fmt: on assert get_type_definitions(source, show_imports=True) == expected
def test_convert_not_optional_if_multiple_types_with_none() -> None: source = [1, 2, None, 3.0, 4.0, None, 5, 6] # fmt: off expected = "\n".join([ "from typing import List, Union", "", "", "Root = List[Union[None, float, int]]", ]) # fmt: on assert expected == get_type_definitions(source)
def test_convert_empty_root_list() -> None: source: List = [] # fmt: off expected = "\n".join([ "from typing import List", "", "", "Root = List", ]) # fmt: on assert expected == get_type_definitions(source)
def test_convert_single_optional_in_list() -> None: source = [1, 2, None, 3, 4, None, 5, 6] # fmt: off expected = "\n".join([ "from typing import List, Optional", "", "", "Root = List[Optional[int]]", ]) # fmt: on assert expected == get_type_definitions(source)
def test_script_runs_with_nonzero() -> None: """ A python e2e test 1. Takes a source and converts it to a definition 2. Adds a simple script to use the Type 3. Saves the definition output into a Python file 4. Runs the file to verify it runs """ # 1. source_type_name = "Test" type_postfix = "Type" output = get_type_definitions( TEST_SOURCE, root_type_name=source_type_name, type_postfix=type_postfix, show_imports=True, ) # 2. input_string = f"test_source: {source_type_name}{type_postfix} = {TEST_SOURCE}" # fmt: off output += "\n".join([ "", "", f"{input_string}", "print(test_source)", ]) # fmt: on # 3. with tempfile.NamedTemporaryFile(suffix=".py") as f: f.write(output.encode("utf-8")) f.flush() # 4. with subprocess.Popen(["python", f.name], stdout=subprocess.PIPE) as proc: stdout, stderr = proc.communicate() assert not proc.returncode, "\n".join([ "Non zero return code from script.", "stderr:", stderr and stderr.decode("utf-8") or "", "Full script:", "-" * 60, _line_numbered(output), "-" * 60, ]) assert stdout.decode("utf-8") == f"{TEST_SOURCE}\n"
def test_convert_none() -> None: source = {"value": None} # fmt: off expected = "\n".join([ "from typing_extensions import TypedDict", "", "", "class Root(TypedDict):", " value: None", ]) # fmt: on assert expected == get_type_definitions(source)
def test_convert_base_types() -> None: source = { "bool_type": True, "bytearray_type": bytearray([102, 111, 111]), "bytes_types": b"foo", "complex_Type": 1 + 1j, "enumerate_type": enumerate(["a", "b", "c"]), "float_type": 1.2, "int_type": 1, "memoryview_type": memoryview(b"foo"), "range_type": range(30), "str_type": "foo", "filter_type": filter(lambda x: x < 10, range(20)), "map_type": map(lambda x: x * 2, range(20)), "zip_type": zip([1, 2], ["a", "b"]), "list_type": [1, 2, 3], "tuple_type": (1, 2, 3), "set_type": {1, 2, 3}, "frozenset_type": frozenset([1, 2, 3]), } # fmt: off expected = "\n".join([ "from typing import FrozenSet, List, Set, Tuple", "", "from typing_extensions import TypedDict", "", "", "class Root(TypedDict):", " bool_type: bool", " bytearray_type: bytearray", " bytes_types: bytes", " complex_Type: complex", " enumerate_type: enumerate", " float_type: float", " int_type: int", " memoryview_type: memoryview", " range_type: range", " str_type: str", " filter_type: filter", " map_type: map", " zip_type: zip", " list_type: List[int]", " tuple_type: Tuple[int]", " set_type: Set[int]", " frozenset_type: FrozenSet[int]", ]) # fmt: on assert expected == get_type_definitions(source)
def test_convert_empty_root_dict() -> None: source: Dict = {} # fmt: off expected = "\n".join([ "from typing_extensions import TypedDict", "", "", "class Root(TypedDict):", " pass", ]) # fmt: on assert expected == get_type_definitions(source)
def test_mypy_has_no_issues() -> None: """ A mypy e2e test 1. Takes a source and converts it to a definition 2. Adds a simple script to use the Type 3. Saves the definition output into a Python file 4. Runs mypy on the script to verify there are no typing issues """ # 1. source_type_name = "Test" type_postfix = "Type" output = get_type_definitions( TEST_SOURCE, root_type_name=source_type_name, type_postfix=type_postfix, show_imports=True, ) # 2. input_string = f"test_source: {source_type_name}{type_postfix} = {TEST_SOURCE}" # fmt: off output += "\n".join([ "", "", f"{input_string}", "print(test_source)", ]) # fmt: on # 3. with tempfile.NamedTemporaryFile(suffix=".py") as f: f.write(output.encode("utf-8")) f.flush() # 4. # 4. with subprocess.Popen(["mypy", f.name], stdout=subprocess.PIPE) as proc: stdout, stderr = proc.communicate() assert not proc.returncode, "\n".join([ "Non zero return code from mypy.", "stderr:", stderr.decode("utf-8") ]) assert (stdout.decode("utf-8") == "Success: no issues found in 1 source file\n")
def test_convert_imports_with_no_typing_imports() -> None: source = {"id": 10, "value": "value"} # fmt: off expected = "\n".join([ "from typing_extensions import TypedDict", "", "", "class Root(TypedDict):", " id: int", " value: str", ]) # fmt: on assert get_type_definitions(source, show_imports=True) == expected
def test_convert_with_simple_tuple() -> None: source = {"items": (1, 2, 3)} # fmt: off expected = "\n".join([ "from typing import Tuple", "", "from typing_extensions import TypedDict", "", "", "class Root(TypedDict):", " items: Tuple[int]", ]) # fmt: on assert expected == get_type_definitions(source)
def test_convert_with_mixed_list() -> None: source = {"items": [1, "2", 3.5]} # fmt: off expected = "\n".join([ "from typing import List, Union", "", "from typing_extensions import TypedDict", "", "", "class Root(TypedDict):", " items: List[Union[float, int, str]]", ]) # fmt: on assert expected == get_type_definitions(source)
def test_convert_simple_json() -> None: source = {"id": 123, "item": "value", "progress": 0.71} # fmt: off expected = "\n".join([ "from typing_extensions import TypedDict", "", "", "class Root(TypedDict):", " id: int", " item: str", " progress: float", ]) # fmt: on assert expected == get_type_definitions(source)
def test_convert_with_empty_list() -> None: source = {"items": []} # type: ignore # fmt: off expected = "\n".join([ "from typing import List", "", "from typing_extensions import TypedDict", "", "", "class Root(TypedDict):", " items: List", ]) # fmt: on assert expected == get_type_definitions(source)
def test_convert_hides_import_optionally() -> None: source = { "itemsList": [1, 2.0, "3"], "itemsTuple": (4, 5, 6), "itemsSet": {7, 8, 9.0}, } # fmt: off expected = "\n".join([ "class Root(TypedDict):", " itemsList: List[Union[float, int, str]]", " itemsTuple: Tuple[int]", " itemsSet: Set[Union[float, int]]", ]) # fmt: on assert get_type_definitions(source, show_imports=False) == expected
def test_convert_simple_json_forced_alternative() -> None: source = {"id": 123, "item": "value", "progress": 0.71} # fmt: off expected = "\n".join([ "from typing_extensions import TypedDict", "", "", 'Root = TypedDict("Root", {', ' "id": int,', ' "item": str,', ' "progress": float,', '})', ]) # fmt: on assert expected == get_type_definitions(source, force_alternative=True)
def test_convert_with_nested_dict() -> None: source = {"nest": {"foo": "bar"}} # fmt: off expected = "\n".join([ "from typing_extensions import TypedDict", "", "", "class Nest(TypedDict):", " foo: str", "", "", "class Root(TypedDict):", " nest: Nest", ]) # fmt: on assert expected == get_type_definitions(source)
def test_convert_list_of_repeated_dicts() -> None: source = {"dictList": [{"id": 123}, {"id": 456}, {"id": 789}]} # fmt: off expected = "\n".join([ "from typing import List", "", "from typing_extensions import TypedDict", "", "", "class DictListItem0(TypedDict):", " id: int", "", "", "class Root(TypedDict):", " dictList: List[DictListItem0]", ]) # fmt: on assert expected == get_type_definitions(source)
def test_convert_list_of_repeated_dicts_different_types_combined() -> None: source = {"dictList": [{"id": 123}, {"id": 456.0}, {"id": "789"}]} # fmt: off expected = "\n".join([ "from typing import List, Union", "", "from typing_extensions import TypedDict", "", "", "class DictListItem0(TypedDict):", " id: Union[float, int, str]", "", "", "class Root(TypedDict):", " dictList: List[DictListItem0]", ]) # fmt: on assert expected == get_type_definitions(source)
def test_convert_optionally_adds_imports_with_nested_defs() -> None: source = {"itemsList": [1, 2, 3], "nest": {"itemsTuple": ({4, 5.0}, 5, 6)}} # fmt: off expected = "\n".join([ "from typing import List, Set, Tuple, Union", "", "from typing_extensions import TypedDict", "", "", "class Nest(TypedDict):", " itemsTuple: Tuple[Union[Set[Union[float, int]], int]]", "", "", "class Root(TypedDict):", " itemsList: List[int]", " nest: Nest", ]) # fmt: on assert get_type_definitions(source, show_imports=True) == expected
def test_convert_with_a_name_map() -> None: source = { "id": 123, "item": "value", "things": [{ "foo": "bar" }, { "baz": "quz" }] } name_map = { "Root": "Source", "ThingsItem0": "Foo", "ThingsItem1": "Baz", "NotUser": "******", } expected = "\n".join([ "from typing import List, Union", "", "from typing_extensions import TypedDict", "", "", "class Foo(TypedDict):", " foo: str", "", "", "class Baz(TypedDict):", " baz: str", "", "", "class Source(TypedDict):", " id: int", " item: str", " things: List[Union[Baz, Foo]]", ]) assert expected == get_type_definitions(source, name_map=name_map)
def test_convert_list_of_mixed_dicts() -> None: source = { "dictList": [{ "foo": 123 }, { "bar": 456 }, { "baz": 789 }, { "baz": 000 }] } # fmt: off expected = "\n".join([ "from typing import List, Union", "", "from typing_extensions import TypedDict", "", "", "class DictListItem0(TypedDict):", " foo: int", "", "", "class DictListItem1(TypedDict):", " bar: int", "", "", "class DictListItem2(TypedDict):", " baz: int", "", "", "class Root(TypedDict):", " dictList: List[Union[DictListItem0, DictListItem1, DictListItem2]]", ]) # fmt: on assert expected == get_type_definitions(source)
def test_convert_nested_overlapping_dict() -> None: source = [ { "x": { "foo": "bar" } }, { "x": { "baz": "qux" } }, ] # fmt: off expected = "\n".join([ "from typing import List, Union", "", "from typing_extensions import TypedDict", "", "", "class X(TypedDict):", " foo: str", "", "", "class X1(TypedDict):", " baz: str", "", "", "class RootItem0(TypedDict):", " x: Union[X, X1]", "", "", "Root = List[RootItem0]", ]) # fmt: on assert expected == get_type_definitions(source)
def test_convert_base_root_values(source: Source, expected: str) -> None: assert get_type_definitions(source) == f"Root = {expected}"