Skip to content

Commit 78a8888

Browse files
lagruOriolAbril
andauthored
Collect docnames of analyzed source in advance (#2)
* Remove container_of and support "of {key: value}" syntax This syntax is used by Pandas [1]. I'm not entirely sold yet, but let's add it for now. To avoid confusion, literals should probably be made to only be accepted on the top-level. Otherwise something like `dict of {{"a", "b"}: int}` becomes possible. Co-authored-by: Oriol Abril-Pla <oriol.abril.pla@gmail.com> * Only allow literals on top-level which avoids potentially confusing constructs like `dict of {{"a", "b"}: int}` Co-authored-by: Oriol Abril-Pla <oriol.abril.pla@gmail.com> * Make "= | :" optional in default syntax since this is what NumPyDoc recommends as well [1]. [1] https://numpydoc.readthedocs.io/en/latest/format.html#parameters Co-authored-by: Oriol Abril-Pla <oriol.abril.pla@gmail.com> * Add ipython as dev dependency * Move matching to imports to qualname level Doing this on the NAME level meant that stuff like "np.int16" would be turned into "np.np.int16" because both "np" and "int16" where matched. * Split docname into KnownImport and replace Reduce responsibility of the former DocName class. Replacing docstring specific type description should be handled separately. * Test correctness when nesting classes in classes * Rework analysis and other major changes While not perfect or anywhere near to finished the refactored code uses clearer separation of responsiblities and is one step closer to an architecture that feels right. :) --------- Co-authored-by: Oriol Abril-Pla <oriol.abril.pla@gmail.com>
1 parent a1b0e10 commit 78a8888

17 files changed

Lines changed: 1373 additions & 483 deletions

File tree

examples/docstub.toml

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ extend_grammar = """
55
66
"""
77

8-
# A mapping of docnames to import information. Each item maps a docname on the
9-
# left side to a dictionary on the right side, which supports the following
10-
# fields:
11-
# use : A string to replace the docname with, defaults to the docname.
12-
# from : Indicate that the docname can be imported from this path.
13-
# import : Import this object, defaults to the docname.
8+
# Import information for type annotations, declared ahead of time.
9+
#
10+
# Each item maps an annotation name on the left side to a dictionary on the
11+
# right side.
12+
#
13+
# Import information can be declared with the following fields:
14+
# from : Indicate that the DocType can be imported from this path.
15+
# import : Import this object, defaults to the DocType.
1416
# as : Use this alias for the imported object
15-
# is_builtin : Indicate that this docname doesn't need to be imported,
17+
# is_builtin : Indicate that this DocType doesn't need to be imported,
1618
# defaults to "false"
17-
[tool.docstub.docnames]
19+
[tool.docstub.known_imports]
1820
configparser = {import = "configparser"}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import _numpy as np_
2-
from _basic import func_comment, func_contains
2+
from _basic import func_contains
33

44
__all__ = [
5-
"func_comment",
65
"func_contains",
76
"np_",
87
]
8+
9+
class CustomException(Exception):
10+
pass

examples/example_pkg-stubs/_basic.pyi

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,48 @@ import logging
33
from collections.abc import Sequence
44
from typing import Any, Literal, Self, Union
55

6-
logger = logging.getLogger(__name__)
6+
from . import CustomException
7+
8+
logger = ...
79

810
__all__ = [
911
"func_empty",
1012
"ExampleClass",
1113
]
1214

13-
def func_empty(a1: Any, a2: Any, a3: Any) -> None: ...
15+
def func_empty(a1, a2, a3) -> None: ...
1416
def func_contains(
15-
self,
1617
a1: list[float],
1718
a2: dict[str, Union[int, str]],
1819
a3: Sequence[int | float],
1920
a4: frozenset[bytes],
20-
) -> tuple[tuple[int, ...], list[int]]: ...
21+
a5: tuple[int],
22+
a6: list[int, str],
23+
a7: dict[str, int],
24+
) -> None: ...
2125
def func_literals(
2226
a1: Literal[1, 3, "foo"], a2: Literal["uno", 2, "drei", "four"] = ...
2327
) -> None: ...
28+
def func_use_from_elsewhere(
29+
a1: CustomException,
30+
a2: ExampleClass,
31+
a3: CustomException.NestedClass,
32+
a4: ExampleClass.NestedClass,
33+
) -> tuple[CustomException, ExampleClass.NestedClass]: ...
2434

2535
class ExampleClass:
26-
def __init__(self, a1: int, a2: float | None = ...) -> None: ...
36+
class NestedClass:
37+
def method_in_nested_class(self, a1: complex) -> None: ...
38+
39+
def __init__(self, a1: str, a2: float = ...) -> None: ...
2740
def method(self, a1: float, a2: float | None) -> list[float]: ...
2841
@staticmethod
2942
def some_staticmethod(a1: float, a2: float | None = ...) -> dict[str, Any]: ...
3043
@property
3144
def some_property(self) -> str: ...
45+
@some_property.setter
46+
def some_property(self, value: str) -> None: ...
3247
@classmethod
3348
def method_returning_cls(cls, config: configparser.ConfigParser) -> Self: ...
49+
@classmethod
50+
def method_returning_cls2(cls, config: configparser.ConfigParser) -> Self: ...

examples/example_pkg-stubs/_numpy.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import numpy as np
22
from numpy.typing import ArrayLike, NDArray
33

44
def func_object_with_numpy_objects(
5-
a1: np.np.int8, a2: np.np.int16, a3: np.typing.DTypeLike, a4: np.typing.DTypeLike
5+
a1: np.int8, a2: np.int16, a3: np.typing.DTypeLike, a4: np.typing.DTypeLike
66
) -> None: ...
77
def func_ndarray(
88
a1: NDArray, a2: np.NDArray, a3: NDArray[float], a4: NDArray[np.uint8] = ...

examples/example_pkg/__init__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
"""Example of an init file."""
22

33
import _numpy as np_
4-
from _basic import func_comment, func_contains
4+
from _basic import func_contains
55

66
__all__ = [
7-
"func_comment",
87
"func_contains",
98
"np_",
109
]
10+
11+
12+
class CustomException(Exception):
13+
pass

examples/example_pkg/_basic.py

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def func_empty(a1, a2, a3):
2626
"""
2727

2828

29-
def func_contains(self, a1, a2, a3, a4):
29+
def func_contains(a1, a2, a3, a4, a5, a6, a7):
3030
"""Dummy.
3131
3232
Parameters
@@ -35,11 +35,9 @@ def func_contains(self, a1, a2, a3, a4):
3535
a2 : dict[str, Union[int, str]]
3636
a3 : Sequence[int | float]
3737
a4 : frozenset[bytes]
38-
39-
Returns
40-
-------
41-
r1 : tuple of int
42-
r2 : list of int
38+
a5 : tuple of int
39+
a6 : list of (int, str)
40+
a7 : dict of {str: int}
4341
"""
4442

4543

@@ -53,16 +51,44 @@ def func_literals(a1, a2="uno"):
5351
"""
5452

5553

54+
def func_use_from_elsewhere(a1, a2, a3, a4):
55+
"""Check if types with full import names are matched.
56+
57+
Parameters
58+
----------
59+
a1 : example_pkg.CustomException
60+
a2 : ExampleClass
61+
a3 : example_pkg.CustomException.NestedClass
62+
a4 : ExampleClass.NestedClass
63+
64+
Returns
65+
-------
66+
r1 : ~.CustomException
67+
r2 : ~.NestedClass
68+
"""
69+
70+
5671
class ExampleClass:
57-
# TODO also take into account class level docstring
72+
"""Dummy.
5873
59-
def __init__(self, a1, a2=None):
60-
"""
61-
Parameters
62-
----------
63-
a1 : int
64-
a2 : float, optional
65-
"""
74+
Parameters
75+
----------
76+
a1 : str
77+
a2 : float, default 0
78+
"""
79+
80+
class NestedClass:
81+
82+
def method_in_nested_class(self, a1):
83+
"""
84+
85+
Parameters
86+
----------
87+
a1 : complex
88+
"""
89+
90+
def __init__(self, a1, a2=0):
91+
pass
6692

6793
def method(self, a1, a2):
6894
"""Dummy.
@@ -101,6 +127,15 @@ def some_property(self):
101127
"""
102128
return str(self)
103129

130+
@some_property.setter
131+
def some_property(self, value):
132+
"""Dummy
133+
134+
Parameters
135+
----------
136+
value : str
137+
"""
138+
104139
@classmethod
105140
def method_returning_cls(cls, config):
106141
"""Using `Self` in context of classmethods is supported.
@@ -115,3 +150,18 @@ def method_returning_cls(cls, config):
115150
out : Self
116151
New class.
117152
"""
153+
154+
@classmethod
155+
def method_returning_cls2(cls, config):
156+
"""Using `Self` in context of classmethods is supported.
157+
158+
Parameters
159+
----------
160+
config : configparser.ConfigParser
161+
Configuation.
162+
163+
Returns
164+
-------
165+
out : Self
166+
New class.
167+
"""

examples/example_pkg/_numpy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def func_ndarray(a1, a2, a3, a4=None):
1919
Parameters
2020
----------
2121
a1 : ndarray
22-
a2 : np.ndarray
22+
a2 : np.NDArray
2323
a3 : (N, 3) ndarray of float
2424
a4 : ndarray of shape (1,) and dtype uint8
2525

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ optional = [
4040
]
4141
dev = [
4242
"pre-commit >=3.7",
43+
"ipython",
4344
]
4445
test = [
4546
"pytest >=5.0.0",
@@ -87,3 +88,8 @@ ignore = [
8788
"RET504", # Assignment before `return` statement facilitates debugging
8889
"PTH123", # Using builtin open() instead of Path.open() is fine
8990
]
91+
92+
93+
[tool.docstub.docnames]
94+
cst = {import = "libcst", as="cst"}
95+
lark = {import = "lark"}

0 commit comments

Comments
 (0)