Skip to content

Commit

Permalink
refactor: Rename FunctionView to CallableView.
Browse files Browse the repository at this point in the history
  • Loading branch information
DanCardin committed Aug 15, 2024
1 parent a065e1b commit 947a2c6
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 23 deletions.
22 changes: 11 additions & 11 deletions tests/test_function_view.py → tests/test_callable_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import pytest
from typing_extensions import Annotated

from type_lens import FunctionView, ParameterView, TypeView
from type_lens import CallableView, ParameterView, TypeView


def test_invalid() -> None:
Expand All @@ -13,18 +13,18 @@ class Foo: ...
instance = Foo()

with pytest.raises(ValueError) as e:
FunctionView.from_function(instance)
CallableView.from_callable(instance) # type: ignore

error = str(e.value)
assert error.startswith("<tests.test_function_view.test_invalid.<locals>.Foo object")
assert error.startswith("<tests.test_callable_view.test_invalid.<locals>.Foo object")
assert error.endswith("is not a valid callable.")


def test_no_return_annotation() -> None:
def fn(): # type: ignore[no-untyped-def]
return

function_view = FunctionView.from_function(fn)
function_view = CallableView.from_callable(fn)
assert function_view.parameters == ()
assert function_view.return_type == TypeView(None)

Expand All @@ -33,7 +33,7 @@ def test_return_annotation() -> None:
def fn() -> int:
return 4

function_view = FunctionView.from_function(fn)
function_view = CallableView.from_callable(fn)
assert function_view.parameters == ()
assert function_view.return_type == TypeView(int)

Expand All @@ -42,15 +42,15 @@ def test_untyped_param() -> None:
def fn(foo): # type: ignore[no-untyped-def]
return foo

function_view = FunctionView.from_function(fn)
function_view = CallableView.from_callable(fn)
assert function_view.parameters == (ParameterView("foo", TypeView(Any)),)


def test_typed_param() -> None:
def fn(foo: int) -> int:
return foo

function_view = FunctionView.from_function(fn)
function_view = CallableView.from_callable(fn)
assert function_view.parameters == (ParameterView("foo", TypeView(int)),)


Expand All @@ -61,10 +61,10 @@ def fn1(foo: Annotated[Optional[int], "d"] = None) -> Optional[int]:
def fn2(foo: Annotated[Optional[int], "d"] = None) -> Optional[int]:
return foo

fn_view1 = FunctionView.from_function(fn1, include_extras=True)
fn_view1 = CallableView.from_callable(fn1, include_extras=True)
assert fn_view1.parameters == (ParameterView("foo", TypeView(Annotated[Union[int, None], "d"]), default=None),)

fn_view2 = FunctionView.from_function(fn2, include_extras=True)
fn_view2 = CallableView.from_callable(fn2, include_extras=True)
assert fn_view2.parameters == (ParameterView("foo", TypeView(Annotated[Union[int, None], "d"]), default=None),)


Expand All @@ -74,7 +74,7 @@ class Foo:
a: int
b: str = "asdf"

fn_view1 = FunctionView.from_function(Foo, include_extras=True)
fn_view1 = CallableView.from_callable(Foo, include_extras=True)
assert fn_view1.parameters == (
ParameterView("a", TypeView(int)),
ParameterView("b", TypeView(str), default="asdf"),
Expand All @@ -92,5 +92,5 @@ def __call__(self, c: bool) -> bool:

foo = Foo(1, "qwer")

fn_view1 = FunctionView.from_function(foo, include_extras=True)
fn_view1 = CallableView.from_callable(foo, include_extras=True)
assert fn_view1.parameters == (ParameterView("c", TypeView(bool)),)
5 changes: 2 additions & 3 deletions type_lens/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from __future__ import annotations

from .function_view import FunctionView
from .callable_view import CallableView
from .parameter_view import ParameterView
from .type_view import TypeView

__all__ = (
"FunctionView",
"FunctionView",
"CallableView",
"ParameterView",
"TypeView",
)
33 changes: 24 additions & 9 deletions type_lens/function_view.py → type_lens/callable_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,64 @@

import inspect
import sys
import types
from typing import TYPE_CHECKING, Any, Callable

from typing_extensions import get_type_hints

from type_lens.parameter_view import ParameterView
from type_lens.type_view import TypeView

__all__ = ("FunctionView",)
__all__ = ("CallableView",)


if TYPE_CHECKING:
from typing_extensions import Self


class FunctionView:
class CallableView:
def __init__(self, fn: Callable, type_hints: dict[str, type]):
self.function = fn
self.callable = fn
self.signature = getattr(fn, "__signature__", None) or inspect.signature(fn)

self.return_type = TypeView(type_hints.pop("return", None))
return_annotation = type_hints.pop("return", None)
self.return_type = TypeView(return_annotation)

self.parameters = tuple(
ParameterView.from_parameter(param, type_hints) for param in self.signature.parameters.values()
)

def __eq__(self, other: object) -> bool:
if not isinstance(other, FunctionView):
if not isinstance(other, CallableView):
return False

return bool(
self.function == other.function
self.callable == other.callable
and self.parameters == other.parameters
and self.return_type == other.return_type
)

def __repr__(self) -> str:
cls_name = self.__class__.__name__

return f"{cls_name}({self.function.__name__})"
return f"{cls_name}({self.callable.__name__})"

@classmethod
def from_type_hints(cls: type[Self], fn: Callable, include_extras: bool = False) -> Self:
result = get_type_hints(fn, include_extras=include_extras)
def from_callable(
cls: type[Self],
fn: Callable,
*,
get_type_hints: Callable = get_type_hints,
include_extras: bool = False,
) -> Self:
hint_fn = fn
if not isinstance(fn, (type, types.FunctionType)):
callable_ = getattr(fn, "__call__", None) # noqa: B004
if not callable_:
raise ValueError(f"{fn} is not a valid callable.")

hint_fn = callable_
result = get_type_hints(hint_fn, include_extras=include_extras)
if sys.version_info < (3, 11): # pragma: no cover
result = _fix_annotated_optional_type_hints(result)

Expand Down

0 comments on commit 947a2c6

Please sign in to comment.