docsig 0.46.0__tar.gz → 0.47.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: docsig
3
- Version: 0.46.0
3
+ Version: 0.47.0
4
4
  Summary: Check signature params for proper documentation
5
5
  Home-page: https://pypi.org/project/docsig/
6
6
  License: MIT
@@ -106,8 +106,8 @@ Commandline
106
106
 
107
107
  .. code-block:: console
108
108
 
109
- usage: docsig [-h] [-V] [-l] [-c | -C] [-D] [-m] [-o] [-p] [-P] [-i] [-a] [-k]
110
- [-n] [-S] [-v] [-s STR] [-d LIST] [-t LIST] [-e EXCLUDE]
109
+ usage: docsig [-h] [-V] [-l] [-c | -C] [-D] [-m] [-N] [-o] [-p] [-P] [-i] [-a]
110
+ [-k] [-n] [-S] [-v] [-s STR] [-d LIST] [-t LIST] [-e EXCLUDE]
111
111
  [path [path ...]]
112
112
 
113
113
  Check signature params for proper documentation
@@ -125,6 +125,7 @@ Commandline
125
125
  -D, --check-dunders check dunder methods
126
126
  -m, --check-protected-class-methods check public methods belonging to protected
127
127
  classes
128
+ -N, --check-nested check nested functions and classes
128
129
  -o, --check-overridden check overridden methods
129
130
  -p, --check-protected check protected functions and classes
130
131
  -P, --check-property-returns check property return values
@@ -232,7 +233,7 @@ It can be added to your .pre-commit-config.yaml as follows:
232
233
 
233
234
  repos:
234
235
  - repo: https://github.com/jshwi/docsig
235
- rev: v0.46.0
236
+ rev: v0.47.0
236
237
  hooks:
237
238
  - id: docsig
238
239
  args:
@@ -77,8 +77,8 @@ Commandline
77
77
 
78
78
  .. code-block:: console
79
79
 
80
- usage: docsig [-h] [-V] [-l] [-c | -C] [-D] [-m] [-o] [-p] [-P] [-i] [-a] [-k]
81
- [-n] [-S] [-v] [-s STR] [-d LIST] [-t LIST] [-e EXCLUDE]
80
+ usage: docsig [-h] [-V] [-l] [-c | -C] [-D] [-m] [-N] [-o] [-p] [-P] [-i] [-a]
81
+ [-k] [-n] [-S] [-v] [-s STR] [-d LIST] [-t LIST] [-e EXCLUDE]
82
82
  [path [path ...]]
83
83
 
84
84
  Check signature params for proper documentation
@@ -96,6 +96,7 @@ Commandline
96
96
  -D, --check-dunders check dunder methods
97
97
  -m, --check-protected-class-methods check public methods belonging to protected
98
98
  classes
99
+ -N, --check-nested check nested functions and classes
99
100
  -o, --check-overridden check overridden methods
100
101
  -p, --check-protected check protected functions and classes
101
102
  -P, --check-property-returns check property return values
@@ -203,7 +204,7 @@ It can be added to your .pre-commit-config.yaml as follows:
203
204
 
204
205
  repos:
205
206
  - repo: https://github.com/jshwi/docsig
206
- rev: v0.46.0
207
+ rev: v0.47.0
207
208
  hooks:
208
209
  - id: docsig
209
210
  args:
@@ -67,6 +67,12 @@ class Parser(_ArgumentParser):
67
67
  action="store_true",
68
68
  help="check public methods belonging to protected classes",
69
69
  )
70
+ self.add_argument(
71
+ "-N",
72
+ "--check-nested",
73
+ action="store_true",
74
+ help="check nested functions and classes",
75
+ )
70
76
  self.add_argument(
71
77
  "-o",
72
78
  "--check-overridden",
@@ -13,6 +13,7 @@ from ._display import Failure as _Failure
13
13
  from ._display import Failures as _Failures
14
14
  from ._display import FuncStr as _FuncStr
15
15
  from ._message import Message as _Message
16
+ from ._module import Function as _Function
16
17
  from ._module import Modules as _Modules
17
18
  from ._module import Parent as _Parent
18
19
  from ._report import generate_report as _generate_report
@@ -45,40 +46,75 @@ def _print_checks() -> None:
45
46
 
46
47
 
47
48
  def _run_check( # pylint: disable=too-many-arguments
49
+ child: _Parent,
48
50
  parent: _Parent,
49
51
  check_class: bool,
50
52
  check_class_constructor: bool,
51
53
  check_dunders: bool,
54
+ check_nested: bool,
52
55
  check_overridden: bool,
53
56
  check_protected: bool,
54
57
  check_property_returns: bool,
55
58
  ignore_no_params: bool,
56
59
  no_ansi: bool,
57
60
  targets: list[_Message],
58
- ) -> _Failures:
59
- failures = _Failures()
60
- for func in parent:
61
- if not (func.isoverridden and not check_overridden) and (
62
- not (func.isprotected and not check_protected)
61
+ failures: _Failures,
62
+ ) -> None:
63
+ if isinstance(child, _Function):
64
+ if not (child.isoverridden and not check_overridden) and (
65
+ not (child.isprotected and not check_protected)
63
66
  and not (
64
- func.isinit
67
+ child.isinit
65
68
  and not (
66
69
  (check_class or check_class_constructor)
67
70
  and not (parent.isprotected and not check_protected)
68
71
  )
69
72
  )
70
- and not (func.isdunder and not check_dunders)
71
- and not (func.docstring.bare and ignore_no_params)
73
+ and not (child.isdunder and not check_dunders)
74
+ and not (child.docstring.bare and ignore_no_params)
72
75
  ):
73
76
  report = _generate_report(
74
- func, targets, func.disabled, check_property_returns
77
+ child, targets, child.disabled, check_property_returns
75
78
  )
76
79
  if report:
77
80
  failures.append(
78
- _Failure(func, _FuncStr(func, no_ansi), report)
81
+ _Failure(child, _FuncStr(child, no_ansi), report)
79
82
  )
80
83
 
81
- return failures
84
+ if check_nested:
85
+ for func in child:
86
+ _run_check(
87
+ func,
88
+ child,
89
+ check_class,
90
+ check_class_constructor,
91
+ check_dunders,
92
+ check_nested,
93
+ check_overridden,
94
+ check_protected,
95
+ check_property_returns,
96
+ ignore_no_params,
97
+ no_ansi,
98
+ targets,
99
+ failures,
100
+ )
101
+ else:
102
+ for func in child:
103
+ _run_check(
104
+ func,
105
+ child,
106
+ check_class,
107
+ check_class_constructor,
108
+ check_dunders,
109
+ check_nested,
110
+ check_overridden,
111
+ check_protected,
112
+ check_property_returns,
113
+ ignore_no_params,
114
+ no_ansi,
115
+ targets,
116
+ failures,
117
+ )
82
118
 
83
119
 
84
120
  @_decorators.parse_msgs
@@ -91,6 +127,7 @@ def docsig( # pylint: disable=too-many-locals,too-many-arguments
91
127
  check_class_constructor: bool = False,
92
128
  check_dunders: bool = False,
93
129
  check_protected_class_methods: bool = False,
130
+ check_nested: bool = False,
94
131
  check_overridden: bool = False,
95
132
  check_protected: bool = False,
96
133
  check_property_returns: bool = False,
@@ -122,6 +159,7 @@ def docsig( # pylint: disable=too-many-locals,too-many-arguments
122
159
  :param check_dunders: Check dunder methods
123
160
  :param check_protected_class_methods: Check public methods belonging
124
161
  to protected classes.
162
+ :param check_nested: Check nested functions and classes.
125
163
  :param check_overridden: Check overridden methods
126
164
  :param check_protected: Check protected functions and classes.
127
165
  :param check_property_returns: Run return checks on properties.
@@ -163,17 +201,21 @@ def docsig( # pylint: disable=too-many-locals,too-many-arguments
163
201
  or check_protected
164
202
  or check_protected_class_methods
165
203
  ):
166
- failures = _run_check(
204
+ failures = _Failures()
205
+ _run_check(
167
206
  top_level,
207
+ module,
168
208
  check_class,
169
209
  check_class_constructor,
170
210
  check_dunders,
211
+ check_nested,
171
212
  check_overridden,
172
213
  check_protected,
173
214
  check_property_returns,
174
215
  ignore_no_params,
175
216
  no_ansi,
176
217
  targets or [],
218
+ failures,
177
219
  )
178
220
  if failures:
179
221
  display[top_level.path].append(failures)
@@ -17,11 +17,11 @@ from pygments.formatters.terminal256 import (
17
17
  # noinspection PyUnresolvedReferences
18
18
  from pygments.lexers.python import PythonLexer as _PythonLexer
19
19
 
20
- from ._function import ARG as _ARG
21
- from ._function import KEY as _KEY
22
- from ._function import Function as _Function
23
- from ._function import Param as _Param
20
+ from ._module import Function as _Function
24
21
  from ._report import Report as _Report
22
+ from ._stub import ARG as _ARG
23
+ from ._stub import KEY as _KEY
24
+ from ._stub import Param as _Param
25
25
 
26
26
  color = _Color()
27
27
 
@@ -31,6 +31,7 @@ def main() -> str | int:
31
31
  check_protected_class_methods=(
32
32
  parser.args.check_protected_class_methods
33
33
  ),
34
+ check_nested=parser.args.check_nested,
34
35
  check_overridden=parser.args.check_overridden,
35
36
  check_protected=parser.args.check_protected,
36
37
  check_property_returns=parser.args.check_property_returns,
@@ -0,0 +1,428 @@
1
+ """
2
+ docsig._module
3
+ ==============
4
+ """
5
+
6
+ from __future__ import annotations as _
7
+
8
+ import re as _re
9
+ import typing as _t
10
+ from pathlib import Path as _Path
11
+
12
+ import astroid as _ast
13
+ from astroid import AstroidSyntaxError as _AstroidSyntaxError
14
+
15
+ from ._directives import Directive as _Directive
16
+ from ._directives import Directives as _Directives
17
+ from ._message import Message as _Message
18
+ from ._stub import Docstring as _Docstring
19
+ from ._stub import Signature as _Signature
20
+ from ._utils import vprint as _vprint
21
+
22
+ _FILE_INFO = "{path}: {msg}"
23
+
24
+
25
+ class Parent(_t.List["Parent"]):
26
+ """Represents an object that contains functions or methods.
27
+
28
+ :param node: Parent's abstract syntax tree.
29
+ :param directives: Data for directives and, subsequently, total of
30
+ errors which are excluded from function checks.
31
+ :param path: Path to base path representation on.
32
+ :param ignore_args: Ignore args prefixed with an asterisk.
33
+ :param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
34
+ :param check_class_constructor: Check the class constructor's
35
+ docstring. Otherwise, expect the constructor's documentation to
36
+ be on the class level docstring.
37
+ """
38
+
39
+ def __init__( # pylint: disable=too-many-arguments
40
+ self,
41
+ node: _ast.Module | _ast.ClassDef | _ast.FunctionDef,
42
+ directives: _Directives,
43
+ path: _Path | None = None,
44
+ ignore_args: bool = False,
45
+ ignore_kwargs: bool = False,
46
+ check_class_constructor: bool = False,
47
+ ) -> None:
48
+ super().__init__()
49
+ self._name = node.name
50
+ self._path = f"{path}:" if path is not None else ""
51
+ overloads: list[str] = []
52
+ returns = None
53
+ self._parse_ast(
54
+ node,
55
+ directives,
56
+ path,
57
+ ignore_args,
58
+ ignore_kwargs,
59
+ check_class_constructor,
60
+ overloads,
61
+ returns,
62
+ )
63
+
64
+ def _parse_ast( # pylint: disable=protected-access,too-many-arguments
65
+ self,
66
+ node,
67
+ directives,
68
+ path,
69
+ ignore_args,
70
+ ignore_kwargs,
71
+ check_class_constructor,
72
+ overloads,
73
+ returns,
74
+ ) -> None:
75
+ parent_comments, parent_disabled = directives.get(
76
+ node.lineno, ([], [])
77
+ )
78
+ if hasattr(node, "body"):
79
+ for subnode in node.body:
80
+ comments, disabled = directives.get(subnode.lineno, ([], []))
81
+ comments.extend(parent_comments)
82
+ disabled.extend(parent_disabled)
83
+ if isinstance(subnode, _ast.FunctionDef):
84
+ func = Function(
85
+ subnode,
86
+ comments,
87
+ directives,
88
+ disabled,
89
+ path,
90
+ ignore_args,
91
+ ignore_kwargs,
92
+ check_class_constructor,
93
+ )
94
+ if func.isoverloaded:
95
+ overloads.append(func.name)
96
+ returns = func.signature.rettype
97
+ else:
98
+ if func.name in overloads:
99
+ subnode.returns = returns
100
+ # noinspection PyProtectedMember
101
+ func._signature._rettype = (
102
+ returns
103
+ if isinstance(returns, str)
104
+ else func._signature._get_rettype(returns)
105
+ )
106
+ # noinspection PyProtectedMember
107
+ func._signature._returns = (
108
+ str(func._signature._rettype) != "None"
109
+ )
110
+
111
+ self.append(func)
112
+ elif isinstance(subnode, _ast.ClassDef):
113
+ self.append(
114
+ Parent(
115
+ subnode,
116
+ directives,
117
+ path,
118
+ ignore_args,
119
+ ignore_kwargs,
120
+ check_class_constructor,
121
+ )
122
+ )
123
+ else:
124
+ self._parse_ast(
125
+ subnode,
126
+ directives,
127
+ path,
128
+ ignore_args,
129
+ ignore_kwargs,
130
+ check_class_constructor,
131
+ overloads,
132
+ returns,
133
+ )
134
+
135
+ @property
136
+ def path(self) -> str:
137
+ """Representation of path to parent."""
138
+ return self._path
139
+
140
+ @property
141
+ def isprotected(self) -> bool:
142
+ """Boolean value for whether class is protected."""
143
+ return self._name.startswith("_")
144
+
145
+
146
+ class Function(Parent):
147
+ """Represents a function with signature and docstring parameters.
148
+
149
+ :param node: Function's abstract syntax tree.
150
+ :param comments: Comments in list form containing directives.
151
+ :param directives: Directive, if any, belonging to this function.
152
+ :param disabled: List of disabled checks specific to this function.
153
+ :param path: Path to base path representation on.
154
+ :param ignore_args: Ignore args prefixed with an asterisk.
155
+ :param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
156
+ :param check_class_constructor: If the function is the class
157
+ constructor, use its own docstring. Otherwise, use the class
158
+ level docstring for the constructor function.
159
+ """
160
+
161
+ def __init__( # pylint: disable=too-many-arguments
162
+ self,
163
+ node: _ast.FunctionDef,
164
+ comments: _t.List[_Directive],
165
+ directives: _Directives,
166
+ disabled: list[_Message],
167
+ path: _Path | None = None,
168
+ ignore_args: bool = False,
169
+ ignore_kwargs: bool = False,
170
+ check_class_constructor: bool = False,
171
+ ) -> None:
172
+ super().__init__(
173
+ node,
174
+ directives,
175
+ path,
176
+ ignore_args,
177
+ ignore_kwargs,
178
+ check_class_constructor,
179
+ )
180
+ self._node = node
181
+ self._directives = comments
182
+ self._disabled = disabled
183
+ self._parent = node.parent.frame()
184
+ self._signature = _Signature(
185
+ node.args,
186
+ node.returns,
187
+ self.ismethod,
188
+ self.isstaticmethod,
189
+ ignore_args,
190
+ ignore_kwargs,
191
+ )
192
+ if self.isinit and not check_class_constructor:
193
+ # docstring for __init__ is expected on the class docstring
194
+ relevant_doc_node = self._parent.doc_node
195
+ else:
196
+ relevant_doc_node = node.doc_node
197
+ self._docstring = _Docstring(relevant_doc_node, ignore_kwargs)
198
+
199
+ def __len__(self) -> int:
200
+ """Length of the longest sequence of args."""
201
+ return max([len(self.signature.args), len(self.docstring.args)])
202
+
203
+ def _decorated_with(self, name: str) -> bool:
204
+ if self._node.decorators is not None:
205
+ for dec in self._node.decorators.nodes:
206
+ return (isinstance(dec, _ast.Name) and dec.name == name) or (
207
+ isinstance(dec, _ast.Attribute) and dec.attrname == name
208
+ )
209
+
210
+ return False
211
+
212
+ @property
213
+ def ismethod(self) -> bool:
214
+ """Boolean value for whether function is a method."""
215
+ return isinstance(self._parent, _ast.ClassDef)
216
+
217
+ @property
218
+ def isproperty(self) -> bool:
219
+ """Boolean value for whether function is a property."""
220
+ valid_properties = [
221
+ "property",
222
+ "cached_property",
223
+ ]
224
+ return self.ismethod and any(
225
+ self._decorated_with(i) for i in valid_properties
226
+ )
227
+
228
+ @property
229
+ def isoverloaded(self) -> bool:
230
+ """Boolean value for whether function is a property."""
231
+ return self._decorated_with("overload")
232
+
233
+ @property
234
+ def isinit(self) -> bool:
235
+ """Boolean value for whether function is a class constructor."""
236
+ return self.ismethod and self.name == "__init__"
237
+
238
+ @property
239
+ def isoverridden(self) -> bool:
240
+ """Boolean value for whether function is overridden."""
241
+ if self.ismethod and not self.isinit:
242
+ for ancestor in self._parent.ancestors():
243
+ if self.name in ancestor and isinstance(
244
+ ancestor[self.name], _ast.FunctionDef
245
+ ):
246
+ return True
247
+
248
+ return False
249
+
250
+ @property
251
+ def isprotected(self) -> bool:
252
+ """Boolean value for whether function is protected."""
253
+ return super().isprotected and not self.isinit and not self.isdunder
254
+
255
+ @property
256
+ def isstaticmethod(self) -> bool:
257
+ """Boolean value for whether function is a static method."""
258
+ return self.ismethod and self._decorated_with("staticmethod")
259
+
260
+ @property
261
+ def isdunder(self) -> bool:
262
+ """Boolean value for whether function is a dunder method."""
263
+ return (
264
+ self.ismethod
265
+ and not self.isinit
266
+ and bool(_re.match(r"__(.*)__", self.name))
267
+ )
268
+
269
+ @property
270
+ def name(self) -> str:
271
+ """The name of the function."""
272
+ return self._node.name
273
+
274
+ @property
275
+ def parent(
276
+ self,
277
+ ) -> _ast.FunctionDef | _ast.Module | _ast.ClassDef | _ast.Lambda:
278
+ """Function's parent node."""
279
+ return self._parent
280
+
281
+ @property
282
+ def lineno(self) -> int:
283
+ """Line number of function declaration."""
284
+ return self._node.lineno or 0
285
+
286
+ @property
287
+ def signature(self) -> _Signature:
288
+ """The function's signature parameters."""
289
+ return self._signature
290
+
291
+ @property
292
+ def docstring(self) -> _Docstring:
293
+ """The function's docstring."""
294
+ return self._docstring
295
+
296
+ @property
297
+ def disabled(self) -> list[_Message]:
298
+ """List of disabled checks specific to this function."""
299
+ return self._disabled
300
+
301
+ @property
302
+ def directives(self) -> _t.List[_Directive]:
303
+ """Directive, if any, belonging to this function."""
304
+ return self._directives
305
+
306
+
307
+ class Modules(_t.List[Parent]):
308
+ """Sequence of ``Module`` objects parsed from Python modules or str.
309
+
310
+ Recursively collect Python files from within all dirs that exist
311
+ under paths provided.
312
+
313
+ If string is provided, ignore paths.
314
+
315
+ :param paths: Path(s) to parse ``Module``(s) from.
316
+ :param disable: List of checks to disable.
317
+ :param excludes: List pf regular expression of files and dirs to
318
+ exclude from checks.
319
+ :param string: String to parse if provided.
320
+ :param ignore_args: Ignore args prefixed with an asterisk.
321
+ :param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
322
+ :param check_class_constructor: Check the class constructor's
323
+ docstring. Otherwise, expect the constructor's documentation to
324
+ be on the class level docstring.
325
+ :param verbose: increase output verbosity.
326
+ """
327
+
328
+ # handle errors here before appending a module
329
+ def _add_module( # pylint: disable=too-many-arguments
330
+ self,
331
+ disable: list[_Message],
332
+ string: str | None = None,
333
+ root: _Path | None = None,
334
+ ignore_args: bool = False,
335
+ ignore_kwargs: bool = False,
336
+ check_class_constructor: bool = False,
337
+ ) -> None:
338
+ try:
339
+ if root is not None:
340
+ string = root.read_text(encoding="utf-8")
341
+
342
+ # empty string won't happen but keeps the
343
+ # typechecker happy
344
+ string = string or ""
345
+ self.append(
346
+ Parent(
347
+ _ast.parse(string),
348
+ _Directives(string, disable),
349
+ root,
350
+ ignore_args,
351
+ ignore_kwargs,
352
+ check_class_constructor,
353
+ )
354
+ )
355
+ except (_AstroidSyntaxError, UnicodeDecodeError) as err:
356
+ if root is not None and root.name.endswith(".py"):
357
+ # keep raising errors for .py files as was done prior to
358
+ # this change
359
+ # pass by silently for files that do not end with .py,
360
+ # which were not checked at all prior (these may result
361
+ # in a 123 syntax error exit status in the future)
362
+ # with this there should be no breaking change, and
363
+ # files that are supposed to be python, files evident by
364
+ # their suffix, will continue to fail
365
+ raise err
366
+
367
+ _vprint(
368
+ _FILE_INFO.format(
369
+ path=root or "stdin", msg=str(err).replace("\n", " ")
370
+ ),
371
+ self._verbose,
372
+ )
373
+
374
+ def __init__( # pylint: disable=too-many-arguments
375
+ self,
376
+ *paths: _Path,
377
+ disable: list[_Message],
378
+ excludes: list[str],
379
+ string: str | None = None,
380
+ ignore_args: bool = False,
381
+ ignore_kwargs: bool = False,
382
+ check_class_constructor: bool = False,
383
+ verbose: bool = False,
384
+ ) -> None:
385
+ super().__init__()
386
+ self._disable = disable
387
+ self._excludes = excludes
388
+ self._ignore_args = ignore_args
389
+ self._ignore_kwargs = ignore_kwargs
390
+ self.check_class_constructor = check_class_constructor
391
+ self._verbose = verbose
392
+ if string is not None:
393
+ self._add_module(
394
+ disable,
395
+ string=string,
396
+ ignore_args=ignore_args,
397
+ ignore_kwargs=ignore_kwargs,
398
+ check_class_constructor=check_class_constructor,
399
+ )
400
+ else:
401
+ for path in paths:
402
+ self._populate(path)
403
+
404
+ def _populate(self, root: _Path) -> None:
405
+ if not root.exists():
406
+ raise FileNotFoundError(root)
407
+
408
+ if str(root) != "." and any(
409
+ _re.match(i, root.name) for i in self._excludes
410
+ ):
411
+ _vprint(
412
+ _FILE_INFO.format(path=root, msg="in exclude list, skipping"),
413
+ self._verbose,
414
+ )
415
+ return
416
+
417
+ if root.is_file():
418
+ self._add_module(
419
+ self._disable,
420
+ root=root,
421
+ ignore_args=self._ignore_args,
422
+ ignore_kwargs=self._ignore_kwargs,
423
+ check_class_constructor=self.check_class_constructor,
424
+ )
425
+
426
+ if root.is_dir():
427
+ for path in root.iterdir():
428
+ self._populate(path)
@@ -7,10 +7,10 @@ from __future__ import annotations as _
7
7
 
8
8
  import typing as _t
9
9
 
10
- from ._function import RETURN as _RETURN
11
- from ._function import Function as _Function
12
- from ._function import Param as _Param
13
10
  from ._message import Message as _Message
11
+ from ._module import Function as _Function
12
+ from ._stub import RETURN as _RETURN
13
+ from ._stub import Param as _Param
14
14
  from ._utils import almost_equal as _almost_equal
15
15
  from .messages import TEMPLATE as _TEMPLATE
16
16
  from .messages import E as _E
@@ -13,10 +13,6 @@ from collections import Counter as _Counter
13
13
  import astroid as _ast
14
14
  import sphinx.ext.napoleon as _s
15
15
 
16
- from ._directives import Directive as _Directive
17
- from ._message import Message as _Message
18
- from ._utils import isprotected as _isprotected
19
-
20
16
  PARAM = "param"
21
17
  KEYWORD = "keyword"
22
18
  KEY = "key"
@@ -151,7 +147,7 @@ class _Params(_t.List[Param]):
151
147
  return any(k for k, v in _Counter(self).items() if v > 1)
152
148
 
153
149
 
154
- class _DocSig:
150
+ class _Stub:
155
151
  def __init__(
156
152
  self, ignore_args: bool = False, ignore_kwargs: bool = False
157
153
  ) -> None:
@@ -169,7 +165,18 @@ class _DocSig:
169
165
  return self._returns
170
166
 
171
167
 
172
- class _Signature(_DocSig):
168
+ class Signature(_Stub):
169
+ """Represents a function signature.
170
+
171
+ :param arguments: Arguments provided to signature.
172
+ :param returns: Returns declared in signature.
173
+ :param ismethod: Whether this signature belongs to a method.
174
+ :param isstaticmethod: Whether this signature belongs to a static
175
+ method.
176
+ :param ignore_args: Ignore args prefixed with an asterisk.
177
+ :param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
178
+ """
179
+
173
180
  def __init__( # pylint: disable=too-many-arguments
174
181
  self,
175
182
  arguments: _ast.Arguments,
@@ -239,7 +246,14 @@ class _Signature(_DocSig):
239
246
  return self._rettype
240
247
 
241
248
 
242
- class _Docstring(_DocSig):
249
+ class Docstring(_Stub):
250
+ """Represents a function docstring.
251
+
252
+ :param node: Docstring ast node.
253
+ :param ignore_args: Ignore args prefixed with an asterisk.
254
+ :param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
255
+ """
256
+
243
257
  def __init__(
244
258
  self,
245
259
  node: _ast.Const | None = None,
@@ -269,154 +283,3 @@ class _Docstring(_DocSig):
269
283
  Docstring has to exist for docstring to be considered bare.
270
284
  """
271
285
  return self._string is not None and not self._args and not self.returns
272
-
273
-
274
- class Function: # pylint: disable=too-many-arguments
275
- """Represents a function with signature and docstring parameters.
276
-
277
- :param node: Function's abstract syntax tree.
278
- :param directives: Directive, if any, belonging to this function.
279
- :param disabled: List of disabled checks specific to this function.
280
- :param ignore_args: Ignore args prefixed with an asterisk.
281
- :param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
282
- :param check_class_constructor: If the function is the class
283
- constructor, use its own docstring. Otherwise, use the class
284
- level docstring for the constructor function.
285
- """
286
-
287
- def __init__( # pylint: disable=too-many-arguments
288
- self,
289
- node: _ast.FunctionDef,
290
- directives: _t.List[_Directive],
291
- disabled: list[_Message],
292
- ignore_args: bool = False,
293
- ignore_kwargs: bool = False,
294
- check_class_constructor: bool = False,
295
- ) -> None:
296
- self._node = node
297
- self._directives = directives
298
- self._disabled = disabled
299
- self._parent = node.parent.frame()
300
- self._signature = _Signature(
301
- node.args,
302
- node.returns,
303
- self.ismethod,
304
- self.isstaticmethod,
305
- ignore_args,
306
- ignore_kwargs,
307
- )
308
- if self.isinit and not check_class_constructor:
309
- # docstring for __init__ is expected on the class docstring
310
- relevant_doc_node = self._parent.doc_node
311
- else:
312
- relevant_doc_node = node.doc_node
313
- self._docstring = _Docstring(relevant_doc_node, ignore_kwargs)
314
-
315
- def __len__(self) -> int:
316
- """Length of the longest sequence of args."""
317
- return max([len(self.signature.args), len(self.docstring.args)])
318
-
319
- def _decorated_with(self, name: str) -> bool:
320
- if self._node.decorators is not None:
321
- for dec in self._node.decorators.nodes:
322
- return (isinstance(dec, _ast.Name) and dec.name == name) or (
323
- isinstance(dec, _ast.Attribute) and dec.attrname == name
324
- )
325
-
326
- return False
327
-
328
- @property
329
- def ismethod(self) -> bool:
330
- """Boolean value for whether function is a method."""
331
- return isinstance(self._parent, _ast.ClassDef)
332
-
333
- @property
334
- def isproperty(self) -> bool:
335
- """Boolean value for whether function is a property."""
336
- valid_properties = [
337
- "property",
338
- "cached_property",
339
- ]
340
- return self.ismethod and any(
341
- self._decorated_with(i) for i in valid_properties
342
- )
343
-
344
- @property
345
- def isoverloaded(self) -> bool:
346
- """Boolean value for whether function is a property."""
347
- return self._decorated_with("overload")
348
-
349
- @property
350
- def isinit(self) -> bool:
351
- """Boolean value for whether function is a class constructor."""
352
- return self.ismethod and self.name == "__init__"
353
-
354
- @property
355
- def isoverridden(self) -> bool:
356
- """Boolean value for whether function is overridden."""
357
- if self.ismethod and not self.isinit:
358
- for ancestor in self._parent.ancestors():
359
- if self.name in ancestor and isinstance(
360
- ancestor[self.name], _ast.FunctionDef
361
- ):
362
- return True
363
-
364
- return False
365
-
366
- @property
367
- def isprotected(self) -> bool:
368
- """Boolean value for whether function is protected."""
369
- return (
370
- _isprotected(self.name) and not self.isinit and not self.isdunder
371
- )
372
-
373
- @property
374
- def isstaticmethod(self) -> bool:
375
- """Boolean value for whether function is a static method."""
376
- return self.ismethod and self._decorated_with("staticmethod")
377
-
378
- @property
379
- def isdunder(self) -> bool:
380
- """Boolean value for whether function is a dunder method."""
381
- return (
382
- self.ismethod
383
- and not self.isinit
384
- and bool(_re.match(r"__(.*)__", self.name))
385
- )
386
-
387
- @property
388
- def name(self) -> str:
389
- """The name of the function."""
390
- return self._node.name
391
-
392
- @property
393
- def parent(
394
- self,
395
- ) -> _ast.FunctionDef | _ast.Module | _ast.ClassDef | _ast.Lambda:
396
- """Function's parent node."""
397
- return self._parent
398
-
399
- @property
400
- def lineno(self) -> int:
401
- """Line number of function declaration."""
402
- return self._node.lineno or 0
403
-
404
- @property
405
- def signature(self) -> _Signature:
406
- """The function's signature parameters."""
407
- return self._signature
408
-
409
- @property
410
- def docstring(self) -> _Docstring:
411
- """The function's docstring."""
412
- return self._docstring
413
-
414
- @property
415
- def disabled(self) -> list[_Message]:
416
- """List of disabled checks specific to this function."""
417
- return self._disabled
418
-
419
- @property
420
- def directives(self) -> _t.List[_Directive]:
421
- """Directive, if any, belonging to this function."""
422
- return self._directives
@@ -8,15 +8,6 @@ from __future__ import annotations as _
8
8
  from difflib import SequenceMatcher as _SequenceMatcher
9
9
 
10
10
 
11
- def isprotected(name: str | None) -> bool:
12
- """Confirm whether attribute is protected or not.
13
-
14
- :param name: Name to check.
15
- :return: Boolean value for whether attribute is protected or not.
16
- """
17
- return str(name).startswith("_")
18
-
19
-
20
11
  def almost_equal(str1: str, str2: str, mini: float, maxi: float) -> bool:
21
12
  """Show result for more than the minimum but less than the maximum.
22
13
 
@@ -8,4 +8,4 @@ Allows for access to the version internally without cyclic imports
8
8
  caused by accessing it through __init__.
9
9
  """
10
10
 
11
- __version__ = "0.46.0"
11
+ __version__ = "0.47.0"
@@ -72,7 +72,7 @@ maintainers = [
72
72
  name = "docsig"
73
73
  readme = "README.rst"
74
74
  repository = "https://github.com/jshwi/docsig"
75
- version = "0.46.0"
75
+ version = "0.47.0"
76
76
 
77
77
  [tool.poetry.dependencies]
78
78
  Pygments = "^2.13.0"
@@ -1,246 +0,0 @@
1
- """
2
- docsig._module
3
- ==============
4
- """
5
-
6
- from __future__ import annotations as _
7
-
8
- import re as _re
9
- import typing as _t
10
- from pathlib import Path as _Path
11
-
12
- import astroid as _ast
13
- from astroid import AstroidSyntaxError as _AstroidSyntaxError
14
-
15
- from ._directives import Directives as _Directives
16
- from ._function import Function as _Function
17
- from ._message import Message as _Message
18
- from ._utils import isprotected as _isprotected
19
- from ._utils import vprint as _vprint
20
-
21
- _FILE_INFO = "{path}: {msg}"
22
-
23
-
24
- class Parent(_t.List[_Function]):
25
- """Represents an object that contains functions or methods.
26
-
27
- :param node: Parent's abstract syntax tree.
28
- :param directives: Data for directives and, subsequently, total of
29
- errors which are excluded from function checks.
30
- :param path: Path to base path representation on.
31
- :param ignore_args: Ignore args prefixed with an asterisk.
32
- :param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
33
- :param check_class_constructor: Check the class constructor's
34
- docstring. Otherwise, expect the constructor's documentation to
35
- be on the class level docstring.
36
- """
37
-
38
- def __init__( # pylint: disable=too-many-arguments
39
- self,
40
- node: _ast.Module | _ast.ClassDef,
41
- directives: _Directives,
42
- path: _Path | None = None,
43
- ignore_args: bool = False,
44
- ignore_kwargs: bool = False,
45
- check_class_constructor: bool = False,
46
- ) -> None:
47
- super().__init__()
48
- self._name = node.name
49
- self._path = f"{path}:" if path is not None else ""
50
- overloads = []
51
- returns = None
52
- parent_comments, parent_disabled = directives.get(
53
- node.lineno, ([], [])
54
- )
55
- for subnode in node.body:
56
- comments, disabled = directives.get(subnode.lineno, ([], []))
57
- comments.extend(parent_comments)
58
- disabled.extend(parent_disabled)
59
- if isinstance(subnode, _ast.FunctionDef):
60
- func = _Function(
61
- subnode,
62
- comments,
63
- disabled,
64
- ignore_args,
65
- ignore_kwargs,
66
- check_class_constructor,
67
- )
68
- if func.isoverloaded:
69
- overloads.append(func.name)
70
- returns = func.signature.rettype
71
- else:
72
- if func.name in overloads:
73
- subnode.returns = returns
74
- # noinspection PyProtectedMember
75
- func._signature._rettype = (
76
- returns
77
- if isinstance(returns, str)
78
- else func._signature._get_rettype(returns)
79
- )
80
- # noinspection PyProtectedMember
81
- func._signature._returns = (
82
- str(func._signature._rettype) != "None"
83
- )
84
-
85
- self.append(func)
86
-
87
- @property
88
- def path(self) -> str:
89
- """Representation of path to parent."""
90
- return self._path
91
-
92
- @property
93
- def isprotected(self) -> bool:
94
- """Boolean value for whether class is protected."""
95
- return _isprotected(self._name)
96
-
97
-
98
- class _Module(_t.List[Parent]):
99
- def __init__( # pylint: disable=too-many-arguments
100
- self,
101
- string: str,
102
- disable: list[_Message],
103
- path: _Path | None = None,
104
- ignore_args: bool = False,
105
- ignore_kwargs: bool = False,
106
- check_class_constructor: bool = False,
107
- ) -> None:
108
- super().__init__()
109
- ast = _ast.parse(string)
110
- directives = _Directives(string, disable)
111
- self.append(Parent(ast, directives, path, ignore_args, ignore_kwargs))
112
- for subnode in ast.body:
113
- if isinstance(subnode, _ast.ClassDef):
114
- self.append(
115
- Parent(
116
- subnode,
117
- directives,
118
- path,
119
- ignore_args,
120
- ignore_kwargs,
121
- check_class_constructor,
122
- )
123
- )
124
-
125
-
126
- class Modules(_t.List[_Module]):
127
- """Sequence of ``Module`` objects parsed from Python modules or str.
128
-
129
- Recursively collect Python files from within all dirs that exist
130
- under paths provided.
131
-
132
- If string is provided, ignore paths.
133
-
134
- :param paths: Path(s) to parse ``Module``(s) from.
135
- :param disable: List of checks to disable.
136
- :param excludes: List pf regular expression of files and dirs to
137
- exclude from checks.
138
- :param string: String to parse if provided.
139
- :param ignore_args: Ignore args prefixed with an asterisk.
140
- :param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
141
- :param check_class_constructor: Check the class constructor's
142
- docstring. Otherwise, expect the constructor's documentation to
143
- be on the class level docstring.
144
- :param verbose: increase output verbosity.
145
- """
146
-
147
- # handle errors here before appending a module
148
- def _add_module( # pylint: disable=too-many-arguments
149
- self,
150
- disable: list[_Message],
151
- string: str | None = None,
152
- root: _Path | None = None,
153
- ignore_args: bool = False,
154
- ignore_kwargs: bool = False,
155
- check_class_constructor: bool = False,
156
- ) -> None:
157
- try:
158
- if root is not None:
159
- string = root.read_text(encoding="utf-8")
160
-
161
- self.append(
162
- _Module(
163
- # empty string won't happen but keeps the
164
- # typechecker happy
165
- string or "",
166
- disable,
167
- root,
168
- ignore_args,
169
- ignore_kwargs,
170
- check_class_constructor,
171
- )
172
- )
173
- except (_AstroidSyntaxError, UnicodeDecodeError) as err:
174
- if root is not None and root.name.endswith(".py"):
175
- # keep raising errors for .py files as was done prior to
176
- # this change
177
- # pass by silently for files that do not end with .py,
178
- # which were not checked at all prior (these may result
179
- # in a 123 syntax error exit status in the future)
180
- # with this there should be no breaking change, and
181
- # files that are supposed to be python, files evident by
182
- # their suffix, will continue to fail
183
- raise err
184
-
185
- _vprint(
186
- _FILE_INFO.format(
187
- path=root or "stdin", msg=str(err).replace("\n", " ")
188
- ),
189
- self._verbose,
190
- )
191
-
192
- def __init__( # pylint: disable=too-many-arguments
193
- self,
194
- *paths: _Path,
195
- disable: list[_Message],
196
- excludes: list[str],
197
- string: str | None = None,
198
- ignore_args: bool = False,
199
- ignore_kwargs: bool = False,
200
- check_class_constructor: bool = False,
201
- verbose: bool = False,
202
- ) -> None:
203
- super().__init__()
204
- self._disable = disable
205
- self._excludes = excludes
206
- self._ignore_args = ignore_args
207
- self._ignore_kwargs = ignore_kwargs
208
- self.check_class_constructor = check_class_constructor
209
- self._verbose = verbose
210
- if string is not None:
211
- self._add_module(
212
- disable,
213
- string=string,
214
- ignore_args=ignore_args,
215
- ignore_kwargs=ignore_kwargs,
216
- check_class_constructor=check_class_constructor,
217
- )
218
- else:
219
- for path in paths:
220
- self._populate(path)
221
-
222
- def _populate(self, root: _Path) -> None:
223
- if not root.exists():
224
- raise FileNotFoundError(root)
225
-
226
- if str(root) != "." and any(
227
- _re.match(i, root.name) for i in self._excludes
228
- ):
229
- _vprint(
230
- _FILE_INFO.format(path=root, msg="in exclude list, skipping"),
231
- self._verbose,
232
- )
233
- return
234
-
235
- if root.is_file():
236
- self._add_module(
237
- self._disable,
238
- root=root,
239
- ignore_args=self._ignore_args,
240
- ignore_kwargs=self._ignore_kwargs,
241
- check_class_constructor=self.check_class_constructor,
242
- )
243
-
244
- if root.is_dir():
245
- for path in root.iterdir():
246
- self._populate(path)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes