Package: dict-typer
Dict-TyperA simple tool to take a json payload and convert it into Python TypedDict class
definitionsA web version is available onhttps://pytyper.dev/Why is this useful?When dealing with API responses, you're very likely to be using JSON responses,
and you might have deeply nested dictionaries, lists of items and it can be
slightly hard to wrap your head around the structure of the responess you are
working with. The first thing I usually do it try to create some data structure
around it so that I can benefit from linting and typing information in my code.Now this tends to be time consuming and error prone, so I thought it might be a
good idea to automate this process with a tool for the future. Just as an
example, if we take the output generated from the Example section below and
imagine it's a response we get from an api. We can plug it in like this:fromproject.typesimportRootdefget_from_api()->Root:passdefrun()->None:response=get_from_api()test1=response["nested_dict"]["number"]+1test2=response["nested_dict"]["string"]+1test3=response["nested_dict"]["non_existant"]+1foriteminresponse["optional_items"]:print(item+1)and if we run mypy on this->%poetryrunmypytest.py
test.py:43:error:Unsupportedoperandtypesfor+("str"and"int")test.py:44:error:TypedDict"NestedDict"hasnokey'non_existant'test.py:46:error:Unsupportedoperandtypesfor+("None"and"int")test.py:46:error:Unsupportedoperandtypesfor+("str"and"int")test.py:46:note:Leftoperandisoftype"Union[None, int, str]"Found4errorsin1file(checked1sourcefile)it will immediately detect four issues!I also want to use this project to learn more about analysing code, making sure
the project is well tested so that it's easy to experiment and try different
approaches.UsageEither supply a path to a file or pipe json output todict-typer-> % dict-typer --help

Usage: dict-typer [OPTIONS] [FILE]...

Options:
  --imports / --no-imports  Show imports at the top, default: True
  -r, --rich                Show rich output.
  -l, --line-numbers        Show line numbers if rich.
  --version                 Show the version and exit.
  --help                    Show this message and exit.

-> % dict-typer ./.example.json
...

-> % curl example.com/test.json | dict-typer
...TypeDict definitionsThere are two ways to define a TypedDict, the primary one that uses the class
based structure, as seen in the examples here. It's easier to read, but it has
a limitation that the each key has to be avalid identifier and not a reserved
keyword. Normally that's not an issue, but if you have for example, the
following data{"numeric-id":123,"from":"far away",}which is valid json, but has the invalid identifiernumeric-idand reserved
keywordfrom, meaning the definitionclassRoot(TypedDict):numeric-id:intfrom:stris invalid. In detected cases, dict-typer will use analternative
wayto define
those types, looking like thisRoot=TypedDict('Root',{'numeric-id':int,'from':str'})which is not as readable, but valid.dict-typer by default only uses the alternative definition for the types with
invalid keys.ListsIf the root of the payload is a list, it will be treated just like a list
within a dictionary, where each item is parsed and definitions created for sub
items. In these cases, a type alias is added as well to the output to capture
the type of the list. For example, the list[1, "2", 3.0, { "id": 123 }, { "id": 456 }]will generate the following definitions:fromtyping_extensionsimportTypedDictclassRootItem(TypedDict):id:intRoot=List[Union[RootItem,float,int,str]]ExamplesCalling from shell->%cat.example.json{"number_int":123,"number_float":3.0,"string":"string","list_single_type":["a","b","c"],"list_mixed_type":["1",2,3.0],"nested_dict":{"number":1,"string":"value"},"same_nested_dict":{"number":2,"string":"different value"},"multipe_levels":{"level2":{"level3":{"number":3,"string":"more values"}}},"nested_invalid":{"numeric-id":123,"from":"far away"},"optional_items":[1,2,"3","4",null,5,6,null]}->%cat.example.json|dict-typer
fromtypingimportList,Union

fromtyping_extensionsimportTypedDict


classNestedDict(TypedDict):number:intstring:str


classLevel2(TypedDict):level3:NestedDict


classMultipeLevels(TypedDict):level2:Level2NestedInvalid=TypedDict("NestedInvalid",{"numeric-id":int,"from":str,})classRoot(TypedDict):number_int:intnumber_float:floatstring:strlist_single_type:List[str]list_mixed_type:List[Union[float,int,str]]nested_dict:NestedDictsame_nested_dict:NestedDictmultipe_levels:MultipeLevelsnested_invalid:NestedInvalidoptional_items:List[Union[None,int,str]]Calling from PythonIn[1]:source={...:"number_int":123,...:"number_float":3.0,...:"string":"string",...:"list_single_type":["a","b","c"],...:"list_mixed_type":["1",2,3.0],...:"nested_dict":{...:"number":1,...:"string":"value"...:},...:"same_nested_dict":{...:"number":2,...:"string":"different value"...:},...:"multipe_levels":{...:"level2":{...:"level3":{...:"number":3,...:"string":"more values"...:}...:}...:},...:"nested_invalid":{"numeric-id":123,"from":"far away"},...:"optional_items":[1,2,"3","4",None,5,6,None]...:}...:In[2]:fromdict_typerimportget_type_definitionsIn[3]:print(get_type_definitions(source,show_imports=True))fromtypingimportList,Unionfromtyping_extensionsimportTypedDictclassNestedDict(TypedDict):number:intstring:strclassLevel2(TypedDict):level3:NestedDictclassMultipeLevels(TypedDict):level2:Level2NestedInvalid=TypedDict("NestedInvalid",{"numeric-id":int,"from":str,})classRoot(TypedDict):number_int:intnumber_float:floatstring:strlist_single_type:List[str]list_mixed_type:List[Union[float,int,str]]nested_dict:NestedDictsame_nested_dict:NestedDictmultipe_levels:MultipeLevelsnested_invalid:NestedInvalidoptional_items:List[Union[None,int,str]]