from __future__ import annotations
import builtins
import inspect
import pickle
from collections.abc import Callable
from itertools import zip_longest
from platform import python_implementation
from typing import Annotated, Any, Protocol, TypeVar
from typing_extensions import Self, get_args, get_origin
def do_signatures_have_same_api(
left: inspect.Signature, right: inspect.Signature
) -> bool:
left_parameters, right_parameters = left.parameters, right.parameters
return (
all(
(
to_typeless_parameter(left_parameters[parameter_name])
== to_typeless_parameter(right_parameters[parameter_name])
)
for parameter_name in (
left_parameters.keys() & right_parameters.keys()
)
)
and all(
is_private_optional_parameter(left_parameters[parameter_name])
for parameter_name in (
left_parameters.keys() - right_parameters.keys()
)
)
and all(
is_private_optional_parameter(right_parameters[parameter_name])
for parameter_name in (
right_parameters.keys() - left_parameters.keys()
)
)
)
def is_private_optional_parameter(parameter: inspect.Parameter) -> bool:
return is_private_object_name(parameter.name) and (
parameter.kind is inspect.Parameter.KEYWORD_ONLY
)
def escape_if_built_in_name(name: str, /) -> str:
return f'{name}_' if name in vars(builtins) else name
def is_class_final(cls: type[Any], /) -> bool:
return getattr(cls, '__final__', False)
if python_implementation() == 'CPython':
def is_descriptor(value: Any, /) -> bool:
return inspect.isgetsetdescriptor(value)
else:
def is_descriptor(value: Any, /) -> bool:
return isinstance(value, property)
def is_private_object_name(value: str, /) -> bool:
return value.startswith('_')
def natural_sorting_key(value: str, /) -> list[int | str]:
result: list[int | str] = []
part_index = 0
while part_index < len(value):
part_start_index = part_index
is_integer_part = value[part_index].isdigit()
while (part_index := part_index + 1) < len(value) and value[
part_index
].isdigit() is is_integer_part:
pass
part = value[part_start_index:part_index]
result.append(int(part) if is_integer_part else part)
return result
def pickling_round_trip(value: Any, /) -> Any:
return pickle.loads(pickle.dumps(value))
def to_base_annotation(annotation: Any, /) -> Any | None:
result = get_origin(annotation)
if result is Annotated:
result = get_origin(get_args(annotation)[0])
return result
def to_class_signature(cls: type[Any], /) -> inspect.Signature | None:
try:
return inspect.signature(cls)
except (TypeError, ValueError):
return None
def to_lists_different_elements(
left: list[_T], right: list[_T], /
) -> list[tuple[_T | None, _T | None]]:
return [
(left_element, right_element)
for left_element, right_element in zip_longest(left, right)
if left_element != right_element
]
_T = TypeVar('_T')
def to_self_referential_class_fields(cls: type[_T], /) -> dict[str, _T]:
return {
name: value
for name, value in vars(cls).items()
if isinstance(value, cls)
}
class _Sortable(Protocol):
def __lt__(self, other: Self, /) -> bool: ...
_SortableT = TypeVar('_SortableT', bound=_Sortable)
def to_sorted_lists_diff(
left: list[_SortableT], right: list[_SortableT], /
) -> list[tuple[_SortableT | None, _SortableT | None]]:
result: list[tuple[_SortableT | None, _SortableT | None]] = []
left_elements, right_elements = iter(left), iter(right)
try:
left_element = next(left_elements)
except StopIteration:
result.extend(
(None, right_element) for right_element in right_elements
)
else:
while True:
try:
while (right_element := next(right_elements)) < left_element:
result.append((None, right_element))
except StopIteration: result.append((left_element, None))
result.extend(
(left_element, None) for left_element in left_elements
)
break
else:
if right_element == left_element:
try:
left_element = next(left_elements)
except StopIteration:
result.extend(
(None, right_element)
for right_element in right_elements
)
break
else:
continue
result.append((left_element, None))
assert left_element < right_element, (
left_element,
right_element,
)
try:
while (
left_element := next(left_elements)
) < right_element:
result.append((left_element, None))
except StopIteration:
result.append((None, right_element))
result.extend(
(None, right_element)
for right_element in right_elements
)
break
else:
if left_element == right_element:
try:
left_element = next(left_elements)
except StopIteration:
result.extend(
(None, right_element)
for right_element in right_elements
)
break
else:
continue
result.append((None, right_element))
assert right_element < left_element, (
left_element,
right_element,
)
return result
def to_typeless_parameter(parameter: inspect.Parameter) -> inspect.Parameter:
result = parameter.replace(annotation=inspect.Parameter.empty)
return (
result
if result.default is inspect.Parameter.empty
else result.replace(default=None)
)
def to_unsorted_list_elements(
value: list[_T], /, *, key: Callable[[_T], _SortableT]
) -> list[tuple[_T, _T]]:
return [
(element, sorted_element)
for element, sorted_element in zip(value, sorted(value, key=key))
if element != sorted_element
]