Skip to content

Commit 2cfe2a4

Browse files
authored
Add support of callable expressions (#123)
* Add support of callable expressions * Remove tests for legal subscription expressions * Use more precise callable doctypes where possible * Sync stubs for docstub * Fix typo on docs/typing_syntax.md
1 parent aa075d9 commit 2cfe2a4

File tree

9 files changed

+85
-8
lines changed

9 files changed

+85
-8
lines changed

docs/typing_syntax.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ In addition, docstub extends this syntax with several "natural language" express
77

88
Docstrings should follow a form that is inspired by the [NumPyDoc style](https://numpydoc.readthedocs.io/en/latest/format.html):
99
```none
10-
Section namew
10+
Section name
1111
------------
1212
name : doctype, optional_info
1313
Description.

src/docstub-stubs/_concurrency.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ class LoggingProcessExecutor:
2525

2626
max_workers: int | None = ...
2727
logging_handlers: tuple[logging.Handler, ...] = ...
28-
initializer: Callable | None = ...
28+
initializer: Callable[..., None] | None = ...
2929
initargs: tuple | None = ...
3030

3131
@staticmethod
3232
def _initialize_worker(
3333
queue: multiprocessing.Queue,
3434
worker_log_level: int,
35-
initializer: Callable,
35+
initializer: Callable[..., None],
3636
initargs: tuple[Any],
3737
) -> None: ...
3838
def __enter__(self) -> ProcessPoolExecutor | MockPoolExecutor: ...

src/docstub-stubs/_docstrings.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ class DoctypeTransformer(lark.visitors.Transformer):
7373
def rst_role(self, tree: lark.Tree) -> lark.Token: ...
7474
def union(self, tree: lark.Tree) -> str: ...
7575
def subscription(self, tree: lark.Tree) -> str: ...
76+
def param_spec(self, tree: lark.Tree) -> str: ...
77+
def callable(self, tree: lark.Tree) -> str: ...
7678
def natlang_literal(self, tree: lark.Tree) -> str: ...
7779
def natlang_container(self, tree: lark.Tree) -> str: ...
7880
def natlang_array(self, tree: lark.Tree) -> str: ...

src/docstub-stubs/_utils.pyi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ from zlib import crc32
99

1010
def accumulate_qualname(qualname: str, *, start_right: bool = ...) -> None: ...
1111
def escape_qualname(name: str) -> str: ...
12-
def _resolve_path_before_caching(func: Callable) -> Callable: ...
12+
def _resolve_path_before_caching(
13+
func: Callable[[Path], str],
14+
) -> Callable[[Path], str]: ...
1315
def module_name_from_path(path: Path) -> str: ...
1416
def pyfile_checksum(path: Path) -> str: ...
1517
def update_with_add_values(

src/docstub/_concurrency.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class LoggingProcessExecutor:
5656

5757
max_workers: int | None = None
5858
logging_handlers: tuple[logging.Handler, ...] = ()
59-
initializer: Callable | None = None
59+
initializer: Callable[..., None] | None = None
6060
initargs: tuple | None = ()
6161

6262
@staticmethod
@@ -67,7 +67,7 @@ def _initialize_worker(queue, worker_log_level, initializer, initargs):
6767
----------
6868
queue : multiprocessing.Queue
6969
worker_log_level : int
70-
initializer : Callable
70+
initializer : Callable[..., None]
7171
initargs : tuple of Any
7272
"""
7373
queue_handler = logging.handlers.QueueHandler(queue)

src/docstub/_docstrings.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,35 @@ def subscription(self, tree):
384384
out = f"{_container}[{_content}]"
385385
return out
386386

387+
def param_spec(self, tree):
388+
"""
389+
Parameters
390+
----------
391+
tree : lark.Tree
392+
393+
Returns
394+
-------
395+
out : str
396+
"""
397+
_content = ", ".join(tree.children)
398+
out = f"[{_content}]"
399+
return out
400+
401+
def callable(self, tree):
402+
"""
403+
Parameters
404+
----------
405+
tree : lark.Tree
406+
407+
Returns
408+
-------
409+
out : str
410+
"""
411+
_callable, *_content = tree.children
412+
_content = ", ".join(_content)
413+
out = f"{_callable}[{_content}]"
414+
return out
415+
387416
def natlang_literal(self, tree):
388417
"""
389418
Parameters

src/docstub/_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@ def _resolve_path_before_caching(func):
7171
7272
Parameters
7373
----------
74-
func : Callable
74+
func : Callable[[Path], str]
7575
7676
Returns
7777
-------
78-
wrapped : Callable
78+
wrapped : Callable[[Path], str]
7979
"""
8080

8181
@wraps(func)

src/docstub/doctype.lark

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
?type: qualname
2121
| union
2222
| subscription
23+
| callable
2324
| natlang_literal
2425
| natlang_container
2526
| natlang_array
@@ -47,6 +48,17 @@ _OR: "or" | "|"
4748
subscription: qualname "[" type ("," type)* ("," ELLIPSES)? "]"
4849

4950

51+
// An expression describing a callable like "Callable[[int], str]"
52+
// [1] https://typing.python.org/en/latest/spec/callables.html#callable
53+
//
54+
callable: qualname "[" ELLIPSES ("," type)? "]"
55+
| qualname "[" param_spec "," type "]"
56+
57+
58+
// The parameter specification inside a callable expression.
59+
param_spec: "[" type? ("," type)* ("," ELLIPSES)? "]"
60+
61+
5062
// Allow Python's ellipses object
5163
ELLIPSES: "..."
5264

tests/test_docstrings.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,38 @@ def test_subscription_error(self, doctype):
154154
with pytest.raises(lark.exceptions.UnexpectedInput):
155155
transformer.doctype_to_annotation(doctype)
156156

157+
@pytest.mark.parametrize(
158+
"doctype",
159+
[
160+
"Callable[[int], str]",
161+
"some_func[[int], str]",
162+
"Callable[[int, float, byte], list[str]]",
163+
"Callable[..., str]",
164+
"Callable[[], str]",
165+
"Callback[...]",
166+
"Callable[Concatenate[int, float], str]",
167+
"Callable[Concatenate[int, ...], str]",
168+
"Callable[P, str]",
169+
],
170+
)
171+
def test_callable(self, doctype):
172+
transformer = DoctypeTransformer()
173+
annotation, _ = transformer.doctype_to_annotation(doctype)
174+
assert annotation.value == doctype
175+
176+
@pytest.mark.parametrize(
177+
"doctype",
178+
[
179+
"Callable[[...], int]",
180+
"Callable[[..., str], int]",
181+
"Callable[[float, str], int, byte]",
182+
],
183+
)
184+
def test_callable_error(self, doctype):
185+
transformer = DoctypeTransformer()
186+
with pytest.raises(lark.exceptions.UnexpectedInput):
187+
transformer.doctype_to_annotation(doctype)
188+
157189
@pytest.mark.parametrize(
158190
("doctype", "expected"),
159191
[

0 commit comments

Comments
 (0)