Skip to content

mflova/lazy-type-hint

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

78 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LazyTypeHint

Quickly generate accurate type hints for any Python data structure! LazyTypeHint supports a wide range of data types including dictionaries, callables, Pandas DataFrames, numpy arrays, and more.

pip install lazy-type-hint

Quick start

Here are some quick examples to get you started:

from lazy_type_hint import LazyTypeHint, LazyTypeHintLive

data = ({"name": "Peter", "age": 22},)
LazyTypeHint().from_data(data, class_name="MyClass").to_file("file.py")
LazyTypeHint().from_data(data, class_name="MyClass").to_string()
data_type_hinted = LazyTypeHintLive().from_data(data, class_name="MyClass")

Tools

lazy-type-hint comes with 2 tools (LazyTypeHint and LazyTypeHintLive) to generate type hints for:

  • from_data: Any Python built-in data structure and more. This encompasses large or deeply nested structures, regular or multi-index Pandas DataFrames, numpy arrays, etc.
  • from_yaml_file: The same functionality as from_data. If the YAML file contains comments, they're parsed as docstrings.

Each tool provides a variety of parameters that allow you to refine the type hint generation process or validate your data structures.

LazyTypeHintLive

Use LazyTypeHintLive to generate reusable type hints for any data structure. By doing this, type hints will be immediately applied and preserved after the code is executed. Note that the original data remains unaffected and the effect is limited to the type hint level only.

from lazy_type_hint import LazyTypeHintLive

data = ({"name": "Peter", "age": 22},)
data_type_hinted = LazyTypeHintLive().from_data(data, class_name="Data")
# After executing this snippet, an interface `Data` is locally created and
# can be reused anywhere in any other file as long as `class_name="Data"`
#
# `LazyTypeHintLive.reset()` will erase all type hints created by this class.

Adding that extra line will allow you to:

Perform static analysis to detect extra issues After executing the code
Enjoy full autocompletion support from your IDE After executing the code

LazyTypeHint

LazyTypeHint also generates type hints for any input data. It differs from LazyTypeHintLive in that you have the flexibility to manage the generated hints. With the to_file() method, you can export an importable module that is used to provide type hints and to keep the interface of your data structure updated whenever needed.

from lazy_type_hint import LazyTypeHint

data = {"name": "Peter", "age": 22}
LazyTypeHint().from_data(data, class_name="Data").to_string()
LazyTypeHint().from_data(data, class_name="Data").to_file("my_file.py")

When would this tool be useful?

As mentioned earlier, type hinting aids developers by providing additional IDE information for code development and issue detection. Keeping type hints up to date for certain data structures can often be tedious and error-prone. Whenever they are updated, the developer must remember to revise the type hints as well. LazyTypeHint is designed to enjoy the advantages of type hinted data structures without the burdensome maintenance.

What makes it a different tool?

There are tools like Cattrs and pydantic that aim to perform similar functions. However, they require prior knowledge of the data structure, placing the responsibility of declaration on the developer. These tools are primarily validation-focused. In contrast, LazyTypeHint is geared towards generating improved type hints automatically and ensuring access to structure is correct. Although other tools such as Stubgen automate .pyi file generation, you may still need to adjust the environment to indicate the location of these stub-based files.

How does it work?

Both LazyTypeHint tools parse the data structure to a tree-based data structure where each node represents a different type or container (list, dict...). This tree structure facilitates the detection and simplification of equal nodes from a type hinting perspective, enables similarity-based merges, and offers full control of each type alias generation.

Moreover, LazyTypeHintLive will locally update its own internal API and its pyi files to include or remove new type hints generated. These features allow users to exploit the benefits of their own type hints without managing anything related to the files.

Fine tuning type hint generation: All features

LazyTypeHint offers a range of strategies that can be passed to the initializer to customize the tool's behavior and refine the generation of type hints:

Data structure validation

Use if_type_hint_exists to specify the strategy if type hints were already generated. validate provides a string-based validation with respect to the previously generated type hints for the same class_name

from lazy_type_hint import LazyTypeHintLive

data = [1,2,3]
data_type_hinted = LazyTypeHintLive(if_type_hint_exists="overwrite").from_data(data, class_name="MyClass")
data2 = [1,2,3, "a"]
data2_type_hinted = LazyTypeHintLive(if_type_hint_exists="validate").from_data(data2, class_name="MyClass")

Type hint YAML files

You can type hint the content of YAML files. This method will also parse all found comments.

LazyTypeHint().from_yaml_file(
    loader=my_yaml_loader,
    path="path_yo_yaml",
    class_name="Example",
    comments_are=("above", "side"),
).to_string()

# YAML content
"""
# This comment is above
name: Peter  # This comment is on the side.
"""

# Type hint generated (Note how both comments were parsed and included as docstrings)
'''
from typing import TypedDict

class Example:
  name: str
  """This comment is above. This comment is on the side."""
'''

Documenting type hints

Define a reserved keyword within your dictionaries to hold documentation. This is a common practice for file formats such as JSON where comments must be stored in this way.

from lazy_type_hint import LazyTypeHint, ParsingStrategies

data = {"name": "Peter", "doc": "Dictionary containing person data"}
lazy_type_hint = LazyTypeHint(strategies=ParsingStrategies(key_used_as_doc="doc"))
lazy_type_hint.from_data(data, class_name="Example").to_string()

# This will generate the following structure. Note the docstring under `Example`
'''
from typing import TypedDict


class Example(TypedDict):
    """Dictionary containing person data."""

    name: str
    doc: str
'''

Similarity based merge

If two data structures within a container are detected to be the same, only one type alias will be created. Moreover, by default, all dictionaries sharing 80% similarity will be merged. However, this parameter can be adjusted for different but similar mapping-based structures.

from lazy_type_hint import LazyTypeHint, ParsingStrategies

data = ({"name": "Peter", "age": 22}, {"name": "Keving", "age": 21, "married": True})
LazyTypeHint(ParsingStrategies(merge_different_typed_dicts_if_similarity_above=90)).from_data(data, class_name="MyClass").to_string()
"""
from typing import Tuple, TypedDict


class MyClassDict(TypedDict):
    name: str
    age: int


class MyClassDict2(TypedDict):
    name: str
    age: int
    married: bool

MyClass = Tuple[MyClassDict, MyClassDict2]
"""

data = ({"name": "Peter", "age": 22}, {"name": "Keving", "age": 21, "married": True})
LazyTypeHint(ParsingStrategies(merge_different_typed_dicts_if_similarity_above=50)).from_data(data, class_name="MyClass").to_string()
"""
from typing import Tuple, TypedDict
from typing_extensions import NotRequired


class MyClassDict(TypedDict):
    name: str
    age: int
    married: NotRequired[bool]

MyClass = Tuple[MyClassDict, MyClassDict]
"""

Type hinting dictionaries

Choose between Mapping, dict, or TypedDict (default) for type hinting dictionaries. Additionally, TypedDict allows for read-only fields.

from lazy_type_hint import LazyTypeHint, ParsingStrategies

data = {"name": "Peter"}
LazyTypeHint(ParsingStrategies(dict_strategy="dict")).from_data(data, class_name="MyClass").to_string()
"""MyClass: TypeAlias = Dict[str, str]"""
LazyTypeHint(ParsingStrategies(dict_strategy="Mapping")).from_data(data, class_name="MyClass").to_string()
"""MyClass: TypeAlias = Mapping[str, str]"""
LazyTypeHint(ParsingStrategies(dict_strategy="TypedDict")).from_data(data, class_name="MyClass").to_string()
"""
class MyClass(TypedDict):
   name: str
"""
LazyTypeHint(ParsingStrategies(dict_strategy="TypedDict", typed_dict_read_only_values=True)).from_data(data, class_name="MyClass").to_string()
"""
class MyClass(TypedDict):
    name: ReadOnly[str]
"""

Type hinting lists

Choose between list (default) and Sequence for type hinting lists.

from lazy_type_hint import LazyTypeHint, ParsingStrategies

data = [1,2,3]
LazyTypeHint(ParsingStrategies(list_strategy="list")).from_data(data, class_name="MyClass").to_string()
"""MyClass: TypeAlias = List[int]"""
LazyTypeHint(ParsingStrategies(list_strategy="Sequence")).from_data(data, class_name="MyClass").to_string()
"""MyClass: TypeAlias = Sequence[int]"""

Type hinting tuples

Choose between type hinting its size (default) or leaving it as an arbitrary size for tuples.

from lazy_type_hint import LazyTypeHint, ParsingStrategies

data = (1,2,3)
LazyTypeHint(ParsingStrategies(tuple_size_strategy="any size")).from_data(data, class_name="MyClass").to_string()
"""MyClass: TypeAlias = Tuple[int, ...]"""
LazyTypeHint(ParsingStrategies(tuple_size_strategy="fixed")).from_data(data, class_name="MyClass").to_string()
"""MyClass: TypeAlias = Tuple[int, int, int]"""

Type hinting Pandas based objects

Any Pandas dataframe, including those with simple and MultiIndex columns, can be type hinted. When initializing any of the tools, ParsingStrategies(pandas_strategies=[...]) can be set. Available options are:

  • Do not type hint columns
  • Type hint only for autocomplete This one will only create the minimum amount of type hints to be used for IDE autocompletion.
  • Full type hint (default): In addition to the previous one, this one will also raise static analysis errors if a wrong column is accessed.
import pandas as pd

from lazy_type_hint import LazyTypeHint

data = pd.DataFrame({"column1": [1, 2], "column2": [1, 2]})
LazyTypeHint().from_data(data, class_name="MyClass").to_string()
data = pd.DataFrame({("column1", "nested_column1"): [1, 2], ("column2", "nested_column2"): [1, 2]})
LazyTypeHint().from_data(data, class_name="MyClass").to_string()

Depth of the type aliases

To simplify the creation of type aliases, use min_height_to_define_type_alias. Higher numbers will result in fewer type aliases being created.

from lazy_type_hint import LazyTypeHint, ParsingStrategies

data = (1,2,3, [1, 2])
LazyTypeHint(ParsingStrategies(min_height_to_define_type_alias=0)).from_data(data, class_name="MyClass").to_string()
"""MyClass: TypeAlias = Tuple[int, int, int, List[int]]"""
LazyTypeHint(ParsingStrategies(min_height_to_define_type_alias=1)).from_data(data, class_name="MyClass").to_string()
"""
MyClassList: TypeAlias = List[int]
MyClass: TypeAlias = Tuple[int, int, int, MyClassList]
"""

Structures supported

These include any combination of:

  • Sequence-based: list, tuple
  • Set-based: set, frozenset
  • Mapping-based: dict, MappingProxyType
  • Simple built-in types: bool, int, float, range, slice, None, str
  • Callables: lambda, functions, staticmethod, classmethod, built-in functions
  • ModuleType
  • IOBase
  • Iterator
  • Custom objects: instances and classes
  • Pandas based structures: Including DataFrame (with MultiIndex and Index) and Series.
  • numpy arrays

About

Type hint any Python (nested) data structure!

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages