diff --git a/docs/typing_syntax.md b/docs/typing_syntax.md index 3d0e780..6e787a6 100644 --- a/docs/typing_syntax.md +++ b/docs/typing_syntax.md @@ -7,7 +7,7 @@ In addition, docstub extends this syntax with several "natural language" express Docstrings should follow a form that is inspired by the [NumPyDoc style](https://numpydoc.readthedocs.io/en/latest/format.html): ```none -Section namew +Section name ------------ name : doctype, optional_info Description. diff --git a/src/docstub-stubs/_concurrency.pyi b/src/docstub-stubs/_concurrency.pyi index 572b649..d660106 100644 --- a/src/docstub-stubs/_concurrency.pyi +++ b/src/docstub-stubs/_concurrency.pyi @@ -25,14 +25,14 @@ class LoggingProcessExecutor: max_workers: int | None = ... logging_handlers: tuple[logging.Handler, ...] = ... - initializer: Callable | None = ... + initializer: Callable[..., None] | None = ... initargs: tuple | None = ... @staticmethod def _initialize_worker( queue: multiprocessing.Queue, worker_log_level: int, - initializer: Callable, + initializer: Callable[..., None], initargs: tuple[Any], ) -> None: ... def __enter__(self) -> ProcessPoolExecutor | MockPoolExecutor: ... diff --git a/src/docstub-stubs/_docstrings.pyi b/src/docstub-stubs/_docstrings.pyi index ea5e0c7..cfcedf5 100644 --- a/src/docstub-stubs/_docstrings.pyi +++ b/src/docstub-stubs/_docstrings.pyi @@ -73,6 +73,8 @@ class DoctypeTransformer(lark.visitors.Transformer): def rst_role(self, tree: lark.Tree) -> lark.Token: ... def union(self, tree: lark.Tree) -> str: ... def subscription(self, tree: lark.Tree) -> str: ... + def param_spec(self, tree: lark.Tree) -> str: ... + def callable(self, tree: lark.Tree) -> str: ... def natlang_literal(self, tree: lark.Tree) -> str: ... def natlang_container(self, tree: lark.Tree) -> str: ... def natlang_array(self, tree: lark.Tree) -> str: ... diff --git a/src/docstub-stubs/_utils.pyi b/src/docstub-stubs/_utils.pyi index c29d6a8..47dc35c 100644 --- a/src/docstub-stubs/_utils.pyi +++ b/src/docstub-stubs/_utils.pyi @@ -9,7 +9,9 @@ from zlib import crc32 def accumulate_qualname(qualname: str, *, start_right: bool = ...) -> None: ... def escape_qualname(name: str) -> str: ... -def _resolve_path_before_caching(func: Callable) -> Callable: ... +def _resolve_path_before_caching( + func: Callable[[Path], str], +) -> Callable[[Path], str]: ... def module_name_from_path(path: Path) -> str: ... def pyfile_checksum(path: Path) -> str: ... def update_with_add_values( diff --git a/src/docstub/_concurrency.py b/src/docstub/_concurrency.py index 5d20ca3..a2d219b 100644 --- a/src/docstub/_concurrency.py +++ b/src/docstub/_concurrency.py @@ -56,7 +56,7 @@ class LoggingProcessExecutor: max_workers: int | None = None logging_handlers: tuple[logging.Handler, ...] = () - initializer: Callable | None = None + initializer: Callable[..., None] | None = None initargs: tuple | None = () @staticmethod @@ -67,7 +67,7 @@ def _initialize_worker(queue, worker_log_level, initializer, initargs): ---------- queue : multiprocessing.Queue worker_log_level : int - initializer : Callable + initializer : Callable[..., None] initargs : tuple of Any """ queue_handler = logging.handlers.QueueHandler(queue) diff --git a/src/docstub/_docstrings.py b/src/docstub/_docstrings.py index 48541ae..e8a5ad7 100644 --- a/src/docstub/_docstrings.py +++ b/src/docstub/_docstrings.py @@ -384,6 +384,35 @@ def subscription(self, tree): out = f"{_container}[{_content}]" return out + def param_spec(self, tree): + """ + Parameters + ---------- + tree : lark.Tree + + Returns + ------- + out : str + """ + _content = ", ".join(tree.children) + out = f"[{_content}]" + return out + + def callable(self, tree): + """ + Parameters + ---------- + tree : lark.Tree + + Returns + ------- + out : str + """ + _callable, *_content = tree.children + _content = ", ".join(_content) + out = f"{_callable}[{_content}]" + return out + def natlang_literal(self, tree): """ Parameters diff --git a/src/docstub/_utils.py b/src/docstub/_utils.py index 297d881..3bd13fa 100644 --- a/src/docstub/_utils.py +++ b/src/docstub/_utils.py @@ -71,11 +71,11 @@ def _resolve_path_before_caching(func): Parameters ---------- - func : Callable + func : Callable[[Path], str] Returns ------- - wrapped : Callable + wrapped : Callable[[Path], str] """ @wraps(func) diff --git a/src/docstub/doctype.lark b/src/docstub/doctype.lark index d114272..904f735 100644 --- a/src/docstub/doctype.lark +++ b/src/docstub/doctype.lark @@ -20,6 +20,7 @@ ?type: qualname | union | subscription + | callable | natlang_literal | natlang_container | natlang_array @@ -47,6 +48,17 @@ _OR: "or" | "|" subscription: qualname "[" type ("," type)* ("," ELLIPSES)? "]" +// An expression describing a callable like "Callable[[int], str]" +// [1] https://typing.python.org/en/latest/spec/callables.html#callable +// +callable: qualname "[" ELLIPSES ("," type)? "]" + | qualname "[" param_spec "," type "]" + + +// The parameter specification inside a callable expression. +param_spec: "[" type? ("," type)* ("," ELLIPSES)? "]" + + // Allow Python's ellipses object ELLIPSES: "..." diff --git a/tests/test_docstrings.py b/tests/test_docstrings.py index b996949..205415f 100644 --- a/tests/test_docstrings.py +++ b/tests/test_docstrings.py @@ -154,6 +154,38 @@ def test_subscription_error(self, doctype): with pytest.raises(lark.exceptions.UnexpectedInput): transformer.doctype_to_annotation(doctype) + @pytest.mark.parametrize( + "doctype", + [ + "Callable[[int], str]", + "some_func[[int], str]", + "Callable[[int, float, byte], list[str]]", + "Callable[..., str]", + "Callable[[], str]", + "Callback[...]", + "Callable[Concatenate[int, float], str]", + "Callable[Concatenate[int, ...], str]", + "Callable[P, str]", + ], + ) + def test_callable(self, doctype): + transformer = DoctypeTransformer() + annotation, _ = transformer.doctype_to_annotation(doctype) + assert annotation.value == doctype + + @pytest.mark.parametrize( + "doctype", + [ + "Callable[[...], int]", + "Callable[[..., str], int]", + "Callable[[float, str], int, byte]", + ], + ) + def test_callable_error(self, doctype): + transformer = DoctypeTransformer() + with pytest.raises(lark.exceptions.UnexpectedInput): + transformer.doctype_to_annotation(doctype) + @pytest.mark.parametrize( ("doctype", "expected"), [