Skip to content

Commit

Permalink
allow custom inheritances, fix typos, version bump, fix deps (1.1.0)
Browse files Browse the repository at this point in the history
  • Loading branch information
seven7ty committed Jul 20, 2024
1 parent 0bad47d commit bb23a35
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 20 deletions.
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@
'License :: OSI Approved :: MIT License'
],
python_requires='>=3.11',
install_requires=[
'frozendict'
]
)
2 changes: 1 addition & 1 deletion typeshi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# coding: utf-8

"""
typeshi - TypedDict generation utilities
typeshi - Dict to TypedDict generation and conversion utilities
~~~~~~~~~~~~~~~~~~
:copyright: (c) 2024 Paul Przybyszewski
:license: MIT, see LICENSE for more details.
Expand Down
11 changes: 6 additions & 5 deletions typeshi/cls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

import typing
from frozendict import frozendict
from typeshi.util import to_pascal_case, dict_full_path
from typeshi.util import to_pascal_case, dict_full_path, T

__all__: tuple = ('typeddict_from_dict', 'BUILTIN_TYPE_HOOKS')
T = typing.TypeVar('T')


def __generic_sequence_or_set_type_hook(t: type[T], v: T) -> type[T]:
Expand All @@ -28,13 +27,13 @@ def _tuple_type_hook(t: type[tuple], v: tuple) -> type[tuple]:
{list: _list_type_hook, tuple: _tuple_type_hook, set: _set_type_hook, frozenset: _set_type_hook})


def _nested_td_name_base_hook(p: tuple[str, ...]) -> str:
def _nested_td_name_base_hook(p: tuple[str, ...], _) -> str:
return to_pascal_case('_'.join(p))


def typeddict_from_dict(typeddict_name: str, original_dict: dict[str, ...], *, total: bool = True,
nested_typeddict_cls_name_hook: typing.Callable[
[tuple[str, ...]], str] = _nested_td_name_base_hook,
[tuple[str, ...], typing.Any], str] = _nested_td_name_base_hook,
type_hooks: dict[type, typing.Callable[[type[T], T], type[T]]] = BUILTIN_TYPE_HOOKS,
include_builtin_type_hooks: bool = True) -> typing._TypedDictMeta:
"""
Expand All @@ -46,6 +45,7 @@ def typeddict_from_dict(typeddict_name: str, original_dict: dict[str, ...], *, t
:param total: whether total=total should be passed down to every generated TypedDict
:param nested_typeddict_cls_name_hook: an optional function to generate the desired classnames for nested TypedDicts
that accepts a tuple-based representation of the dictionary path of the given nested TypedDict
and the TypedDict itself for which the name is to be generated
:param type_hooks: a dict of hooks (callbacks) that are called when a type can be enriched (sequence values, etc.)
where a simple T can be transformed into T[A, B, C, ...]
:param include_builtin_type_hooks: whether to include builtin type hooks (tuple, list, set, frozenset).
Expand All @@ -63,7 +63,8 @@ def _nested_typeddict_from_dict(td_cls_name: str, stage_dict: dict[str, ...]) ->
td_kv_pairs: dict[str, typing.Any] = {}
for k, v in stage_dict.items():
if isinstance(v, dict):
td_kv_pairs[k] = _nested_typeddict_from_dict(nested_typeddict_cls_name_hook(dict_full_path(original_dict, k, v)),v)
td_kv_pairs[k] = _nested_typeddict_from_dict(
nested_typeddict_cls_name_hook(dict_full_path(original_dict, k, v), v), v)
else:
v_type: typing.Any = type(v)
td_kv_pairs[k] = type_hooks[v_type](v_type, v) if v_type in type_hooks else v_type
Expand Down
26 changes: 13 additions & 13 deletions typeshi/str_repr.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
import typing
from types import NoneType
from typeshi.version import __version__
from typeshi.util import is_builtin, remove_all_but_first, resolve_main_module
from typeshi.cls import T
from typeshi.util import is_builtin, remove_all_but_first, resolve_main_module, T

__all__: tuple = ('declaration_module_from_typeddict',)
__PEP8_INDENT__: str = ' '
Expand All @@ -19,30 +18,33 @@ def _prep_type(t: T) -> str:
f'[{", ".join(map(lambda _t: _t.__name__, t.__args__))}]' if hasattr(t, '__args__') and t.__args__ else '')


def get_type_module(t: T) -> str:
return t.__module__ if not t.__module__ == '__main__' else __MAIN_MODULE__


def declaration_module_from_typeddict(typeddict: typing._TypedDictMeta, *,
newline: str = '\n', end_with_newline: bool = True,
typeshi_header: bool = True, _inherit_typeddict: bool = True) -> str:
typeshi_header: bool = True, inherit_cls: type | None = typing.TypedDict) -> str:
"""
Get the class definitions of a TypedDict in the form of the content of a self-contained, ready-to-import module.
This function resolves imports and handles class ordering, returning a ready-to-go, complete TypedDict declaration.
:param typeddict: the TypedDict for which to generate the definitions
:param newline: what character to use for the newline
:param end_with_newline: whether to end the "file" with a newline
:param typeshi_header: whether to inclue the typeshi version header in the declaration file contents
:param _inherit_typeddict: whether the classes generated by this function should inherit TypedDict.
This is meant for cases where plain class/attribute-based type hints are needed.
:param typeshi_header: whether to include the typeshi version header in the declaration file contents
:param inherit_cls: the class all generated defs should use, defaults to TypedDict
:return: the generated definition file contents
"""
cls_definitions: list[str] = []
imports: list[str] = ['typing'] if _inherit_typeddict else []
from_imports: dict[str, list[str]] = {}
from_imports: dict[str, list[str]] = {get_type_module(inherit_cls): [inherit_cls.__name__]} if inherit_cls else {}
inherit_cls_suffix: str = f'({inherit_cls.__name__})' if inherit_cls else ''
header: str = __TYPESHI_HEADER__ + '\n\n' if typeshi_header else ''

def _nested_class_declarations_from_typeddict(td: typing._TypedDictMeta):
nonlocal cls_definitions

defn: str = f'class {td.__name__}{"(typing.TypedDict)" if _inherit_typeddict else ""}:'
defn: str = f'class {td.__name__}{inherit_cls_suffix}:'
for k, v in td.__annotations__['fields'].items():
defn += f'\n{__PEP8_INDENT__}{k if not k.isnumeric() else f"_{k}"}: {_prep_type(v)}'
if not is_builtin(v) and v.__module__ != 'typeshi.cls':
Expand All @@ -60,10 +62,8 @@ def _nested_class_declarations_from_typeddict(td: typing._TypedDictMeta):
for cls_d in cls_definitions:
remove_all_but_first(cls_definitions, cls_d)

for i in imports:
header += f'import {i}\n'

for fik, fiv in from_imports.items():
header += f'from {fik} import ' + ', '.join(fiv) + '\n'

return header + (newline * 2 if imports or from_imports else '') + f'{newline * 3}'.join(cls_definitions) + (newline if end_with_newline else '')
return (header + (newline * 2 if from_imports else '')
+ f'{newline * 3}'.join(cls_definitions) + (newline if end_with_newline else ''))
14 changes: 14 additions & 0 deletions typeshi/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@
import sys
import typing

__all__: tuple = (
'to_pascal_case',
'get_all_dict_paths',
'get_nested_key',
'dict_full_path',
'is_builtin',
'remove_all_but_first',
'resolve_main_module',
'T'
)

T = typing.TypeVar('T')


def to_pascal_case(str_: str) -> str:
"""
Expand Down Expand Up @@ -72,6 +85,7 @@ def dict_full_path(dict_: dict,
:param value: The optional value for determining if a key is the right one
:return: None if key not in dict_ or dict_[key] != value if value is not None else the full path to the key
"""

def _recursive(__prev: tuple = ()) -> tuple[str, ...] | None:
reduced: dict = get_nested_key(dict_, __prev)
for k, v in reduced.items():
Expand Down
2 changes: 1 addition & 1 deletion typeshi/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
from collections import namedtuple

VersionInfo = namedtuple('VersionInfo', 'major minor micro releaselevel serial')
version_info = VersionInfo(major=1, minor=0, micro=1, releaselevel='final', serial=0)
version_info = VersionInfo(major=1, minor=1, micro=0, releaselevel='final', serial=0)
__version__ = f'{version_info.major}.{version_info.minor}.{version_info.micro}'

0 comments on commit bb23a35

Please sign in to comment.