1
- from typing import Callable
1
+ from textwrap import dedent
2
+ from typing import Any , Callable , TypeVar
3
+
4
+ try :
5
+ import docstring_parser
6
+ except ImportError :
7
+ docstring_parser = None
2
8
3
9
WHITELISTED_TAG = "--Public API--"
4
10
11
+ F = TypeVar ("F" , bound = Callable [..., Any ])
12
+
5
13
6
14
def public_api (func ) -> Callable :
7
15
"""Add the public API tag for processing by the auto documentation generator.
8
16
17
+ Used as a decorator:
18
+
19
+ @public_api
20
+ def my_method(some_argument):
21
+ ...
22
+
9
23
This tag is added at import time.
10
24
"""
11
25
@@ -14,3 +28,234 @@ def public_api(func) -> Callable:
14
28
func .__doc__ = WHITELISTED_TAG + existing_docstring
15
29
16
30
return func
31
+
32
+
33
+ def deprecated_method (
34
+ version : str ,
35
+ message : str = "" ,
36
+ ):
37
+ """Add a deprecation warning to the docstring of the decorated method.
38
+
39
+ Used as a decorator:
40
+
41
+ @deprecated_method(version="1.2.3", message="Optional message")
42
+ def my_method(some_argument):
43
+ ...
44
+
45
+ Args:
46
+ version: Version number when the method was deprecated.
47
+ message: Optional deprecation message.
48
+ """
49
+
50
+ text = f".. deprecated:: { version } " "\n " f" { message } "
51
+
52
+ def wrapper (func : F ) -> F :
53
+ """Wrapper method that accepts func, so we can modify the docstring."""
54
+ return _add_text_to_function_docstring_after_summary (
55
+ func = func ,
56
+ text = text ,
57
+ )
58
+
59
+ return wrapper
60
+
61
+
62
+ def new_method (
63
+ version : str ,
64
+ message : str = "" ,
65
+ ):
66
+ """Add a version added note to the docstring of the decorated method.
67
+
68
+ Used as a decorator:
69
+
70
+ @new_method(version="1.2.3", message="Optional message")
71
+ def my_method(some_argument):
72
+ ...
73
+
74
+ Args:
75
+ version: Version number when the method was added.
76
+ message: Optional message.
77
+ """
78
+
79
+ text = f".. versionadded:: { version } " "\n " f" { message } "
80
+
81
+ def wrapper (func : F ) -> F :
82
+ """Wrapper method that accepts func, so we can modify the docstring."""
83
+ return _add_text_to_function_docstring_after_summary (
84
+ func = func ,
85
+ text = text ,
86
+ )
87
+
88
+ return wrapper
89
+
90
+
91
+ def deprecated_argument (
92
+ argument_name : str ,
93
+ version : str ,
94
+ message : str = "" ,
95
+ ):
96
+ """Add an arg-specific deprecation warning to the docstring of the decorated method.
97
+
98
+ Used as a decorator:
99
+
100
+ @deprecated_argument(argument_name="some_argument", version="1.2.3", message="Optional message")
101
+ def my_method(some_argument):
102
+ ...
103
+
104
+ If docstring_parser is not installed, this will not modify the docstring.
105
+
106
+ Args:
107
+ argument_name: Name of the argument to associate with the deprecation note.
108
+ version: Version number when the method was deprecated.
109
+ message: Optional deprecation message.
110
+ """
111
+
112
+ text = f".. deprecated:: { version } " "\n " f" { message } "
113
+
114
+ def wrapper (func : F ) -> F :
115
+ """Wrapper method that accepts func, so we can modify the docstring."""
116
+ if not docstring_parser :
117
+ return func
118
+
119
+ return _add_text_below_function_docstring_argument (
120
+ func = func ,
121
+ argument_name = argument_name ,
122
+ text = text ,
123
+ )
124
+
125
+ return wrapper
126
+
127
+
128
+ def new_argument (
129
+ argument_name : str ,
130
+ version : str ,
131
+ message : str = "" ,
132
+ ):
133
+ """Add note for new arguments about which version the argument was added.
134
+
135
+ Used as a decorator:
136
+
137
+ @new_argument(argument_name="some_argument", version="1.2.3", message="Optional message")
138
+ def my_method(some_argument):
139
+ ...
140
+
141
+ If docstring_parser is not installed, this will not modify the docstring.
142
+
143
+ Args:
144
+ argument_name: Name of the argument to associate with the note.
145
+ version: The version number to associate with the note.
146
+ message: Optional message.
147
+ """
148
+
149
+ text = f".. versionadded:: { version } " "\n " f" { message } "
150
+
151
+ def wrapper (func : F ) -> F :
152
+ """Wrapper method that accepts func, so we can modify the docstring."""
153
+ if not docstring_parser :
154
+ return func
155
+
156
+ return _add_text_below_function_docstring_argument (
157
+ func = func ,
158
+ argument_name = argument_name ,
159
+ text = text ,
160
+ )
161
+
162
+ return wrapper
163
+
164
+
165
+ def _add_text_to_function_docstring_after_summary (func : F , text : str ) -> F :
166
+ """Insert text into docstring, e.g. rst directive.
167
+
168
+ Args:
169
+ func: Add text to provided func docstring.
170
+ text: String to add to the docstring, can be a rst directive e.g.:
171
+ text = (
172
+ ".. versionadded:: 1.2.3\n "
173
+ " Added in version 1.2.3\n "
174
+ )
175
+
176
+ Returns:
177
+ func with modified docstring.
178
+ """
179
+ existing_docstring = func .__doc__ if func .__doc__ else ""
180
+ split_docstring = existing_docstring .split ("\n " , 1 )
181
+
182
+ docstring = ""
183
+ if len (split_docstring ) == 2 :
184
+ short_description , docstring = split_docstring
185
+ docstring = (
186
+ f"{ short_description .strip ()} \n "
187
+ "\n "
188
+ f"{ text } \n "
189
+ "\n "
190
+ f"{ dedent (docstring )} "
191
+ )
192
+ elif len (split_docstring ) == 1 :
193
+ short_description = split_docstring [0 ]
194
+ docstring = f"{ short_description .strip ()} \n " "\n " f"{ text } \n "
195
+ elif len (split_docstring ) == 0 :
196
+ docstring = f"{ text } \n "
197
+
198
+ func .__doc__ = docstring
199
+
200
+ return func
201
+
202
+
203
+ def _add_text_below_function_docstring_argument (
204
+ func : F ,
205
+ argument_name : str ,
206
+ text : str ,
207
+ ) -> F :
208
+ """Add text below specified docstring argument.
209
+
210
+ Args:
211
+ func: Function whose docstring will be modified.
212
+ argument_name: Name of the argument to add text to its description.
213
+ text: Text to add to the argument description.
214
+
215
+ Returns:
216
+ func with modified docstring.
217
+ """
218
+ existing_docstring = func .__doc__ if func .__doc__ else ""
219
+
220
+ func .__doc__ = _add_text_below_string_docstring_argument (
221
+ docstring = existing_docstring , argument_name = argument_name , text = text
222
+ )
223
+
224
+ return func
225
+
226
+
227
+ def _add_text_below_string_docstring_argument (
228
+ docstring : str , argument_name : str , text : str
229
+ ) -> str :
230
+ """Add text below an argument in a docstring.
231
+
232
+ Note: Can be used for rst directives.
233
+
234
+ Args:
235
+ docstring: Docstring to modify.
236
+ argument_name: Argument to place text below.
237
+ text: Text to place below argument. Can be an rst directive.
238
+
239
+ Returns:
240
+ Modified docstring.
241
+ """
242
+ parsed_docstring = docstring_parser .parse (docstring )
243
+
244
+ if argument_name not in (param .arg_name for param in parsed_docstring .params ):
245
+ raise ValueError (
246
+ f"Please specify an existing argument, you specified { argument_name } ."
247
+ )
248
+
249
+ for param in parsed_docstring .params :
250
+ if param .arg_name == argument_name :
251
+ if param .description is None :
252
+ param .description = text
253
+ else :
254
+ param .description += "\n \n " + text + "\n \n "
255
+
256
+ # RenderingStyle.EXPANDED used to make sure any line breaks before and
257
+ # after the added text are included (for Sphinx html rendering).
258
+ return docstring_parser .compose (
259
+ docstring = parsed_docstring ,
260
+ rendering_style = docstring_parser .RenderingStyle .EXPANDED ,
261
+ )
0 commit comments