Skip to content

Commit

Permalink
Merge pull request #23 from last-partizan/add-typing
Browse files Browse the repository at this point in the history
feat: Add typing for function_decorator
  • Loading branch information
smarie authored Feb 21, 2022
2 parents 4715f9b + ad3e2f3 commit e7f5e7e
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 0 deletions.
72 changes: 72 additions & 0 deletions src/decopatch/main.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from typing import Any, Callable, Optional, Protocol, TypeVar, overload

from decopatch.utils_disambiguation import FirstArgDisambiguation
from decopatch.utils_modes import SignatureInfo

try:
from typing import ParamSpec
except ImportError:
from typing_extensions import ParamSpec

P = ParamSpec("P")
F = TypeVar("F", bound=Callable[..., Any])

class _Decorator(Protocol[P]):
"""
This is callable Protocol, to distinguish between cases where
created decorator is called as `@decorator` or `@decorator()`
"""

# decorator is called without parenthesis: @decorator
@overload
def __call__(self, func: F) -> F: ...
# decorator is called with options or parenthesis: @decorator(some_option=...)
@overload
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Callable[[F], F]: ...

# @function_decorator is called without options or parenthesis
@overload
def function_decorator(
enable_stack_introspection: Callable[P, Any],
custom_disambiguator: Callable[[Any], FirstArgDisambiguation] = ...,
flat_mode_decorated_name: Optional[str] = ...,
) -> _Decorator[P]: ...

# @function_decorator() is called with options or parenthesis.
@overload
def function_decorator(
enable_stack_introspection: bool = ...,
custom_disambiguator: Callable[[Any], FirstArgDisambiguation] = ...,
flat_mode_decorated_name: Optional[str] = ...,
) -> Callable[[Callable[P, Any]], _Decorator[P]]: ...
def class_decorator(
enable_stack_introspection: bool = ...,
custom_disambiguator: Callable[[Any], FirstArgDisambiguation] = ...,
flat_mode_decorated_name: Optional[str] = ...,
): ...
def decorator(
is_function_decorator: bool = ...,
is_class_decorator: bool = ...,
enable_stack_introspection: bool = ...,
custom_disambiguator: Callable[[Any], FirstArgDisambiguation] = ...,
use_signature_trick: bool = ...,
flat_mode_decorated_name: str = ...,
): ...
def create_decorator(
impl_function,
is_function_decorator: bool = ...,
is_class_decorator: bool = ...,
enable_stack_introspection: bool = ...,
custom_disambiguator: Callable[[Any], FirstArgDisambiguation] = ...,
use_signature_trick: bool = ...,
flat_mode_decorated_name: Optional[str] = ...,
): ...
def create_no_args_decorator(
decorator_function, function_for_metadata: Any | None = ...
): ...
def create_kwonly_decorator(
sig_info: SignatureInfo, decorator_function, disambiguator, function_for_metadata
): ...
def create_general_case_decorator(
sig_info: SignatureInfo, impl_function, disambiguator, function_for_metadata
): ...
57 changes: 57 additions & 0 deletions tests/test_typing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# This is test file for typing,
# No automatic testing is used at the moment. Just use your type checker and see if it works.
# Pytest here is used to make sure that runtime behavir matches with type checker expecter errors.
from typing import Any, Callable

import pytest

from decopatch import DECORATED, function_decorator


def test_invalid_parameter():
with pytest.raises(TypeError):
# Error, invalid argument
@function_decorator(invalid_param=True)
def decorator_wint_invalid_param(fn=DECORATED):
return fn


def test_normal_decorator():
@function_decorator
def decorator(scope="test", fn=DECORATED): # type: (str, Any) -> Callable[..., Any]
assert isinstance(scope, str)
return fn

# Ok
@decorator
def decorated_flat():
pass

assert decorated_flat

with pytest.raises(AssertionError):
# Error, Literal[2] is incompatible with str
@decorator(scope=2)
def decorated_with_invalid_options():
pass

# Ok, should reveal correct type for `scope`
@decorator(scope="success")
def decorated_with_valid_options():
pass

assert decorated_with_valid_options


def test_function_decorator_with_params():
# Ok, should reveal correct type for `enable_stack_introspection`
@function_decorator(enable_stack_introspection=True)
def decorator_with_params(scope = "test", fn=DECORATED): # type: (str, Any) -> Callable[..., Any]
return fn

# Ok, should reveal correct type for `scope`
@decorator_with_params(scope="success")
def decorated_with_valid_options():
pass

assert decorated_with_valid_options

0 comments on commit e7f5e7e

Please sign in to comment.