docsig 0.81.2__tar.gz → 0.81.3__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.4
2
2
  Name: docsig
3
- Version: 0.81.2
3
+ Version: 0.81.3
4
4
  Summary: Check signature params for proper documentation
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -184,7 +184,7 @@ ensure your installation has registered `docsig`
184
184
  .. code-block:: console
185
185
 
186
186
  $ flake8 --version
187
- 7.3.0 (docsig: 0.81.2, mccabe: 0.7.0, pycodestyle: 2.14.0, pyflakes: 3.4.0) CPython 3.10.19 on Darwin
187
+ 7.3.0 (docsig: 0.81.3, mccabe: 0.7.0, pycodestyle: 2.14.0, pyflakes: 3.4.0) CPython 3.10.19 on Darwin
188
188
 
189
189
  And now use `flake8` to lint your files
190
190
 
@@ -270,7 +270,7 @@ Standalone
270
270
 
271
271
  repos:
272
272
  - repo: https://github.com/jshwi/docsig
273
- rev: v0.81.2
273
+ rev: v0.81.3
274
274
  hooks:
275
275
  - id: docsig
276
276
  args:
@@ -289,7 +289,7 @@ or integrated with ``flake8``
289
289
  hooks:
290
290
  - id: flake8
291
291
  additional_dependencies:
292
- - docsig==0.81.2
292
+ - docsig==0.81.3
293
293
  args:
294
294
  - "--sig-check-class"
295
295
  - "--sig-check-dunders"
@@ -155,7 +155,7 @@ ensure your installation has registered `docsig`
155
155
  .. code-block:: console
156
156
 
157
157
  $ flake8 --version
158
- 7.3.0 (docsig: 0.81.2, mccabe: 0.7.0, pycodestyle: 2.14.0, pyflakes: 3.4.0) CPython 3.10.19 on Darwin
158
+ 7.3.0 (docsig: 0.81.3, mccabe: 0.7.0, pycodestyle: 2.14.0, pyflakes: 3.4.0) CPython 3.10.19 on Darwin
159
159
 
160
160
  And now use `flake8` to lint your files
161
161
 
@@ -241,7 +241,7 @@ Standalone
241
241
 
242
242
  repos:
243
243
  - repo: https://github.com/jshwi/docsig
244
- rev: v0.81.2
244
+ rev: v0.81.3
245
245
  hooks:
246
246
  - id: docsig
247
247
  args:
@@ -260,7 +260,7 @@ or integrated with ``flake8``
260
260
  hooks:
261
261
  - id: flake8
262
262
  additional_dependencies:
263
- - docsig==0.81.2
263
+ - docsig==0.81.3
264
264
  args:
265
265
  - "--sig-check-class"
266
266
  - "--sig-check-dunders"
@@ -0,0 +1,82 @@
1
+ """
2
+ docsig._check
3
+ =============
4
+ """
5
+
6
+ from __future__ import annotations as _
7
+
8
+ from ._config import Config as _Config
9
+ from ._module import Function as _Function
10
+ from ._module import Parent as _Parent
11
+ from ._report import Failure as _Failure
12
+ from ._report import Failures as _Failures
13
+
14
+
15
+ def _should_check_function(
16
+ child: _Function,
17
+ parent: _Parent,
18
+ config: _Config,
19
+ ) -> bool:
20
+ if child.isoverridden and not config.check.overridden:
21
+ return False
22
+
23
+ if child.isprotected and not config.check.protected:
24
+ return False
25
+
26
+ if child.isdunder and not config.check.dunders:
27
+ return False
28
+
29
+ if child.docstring.bare and config.ignore.no_params:
30
+ return False
31
+
32
+ if child.isinit and (
33
+ not (config.check.class_ or config.check.class_constructor)
34
+ or (parent.isprotected and not config.check.protected)
35
+ ):
36
+ return False
37
+
38
+ return True
39
+
40
+
41
+ def _run_check(
42
+ child: _Parent,
43
+ parent: _Parent,
44
+ config: _Config,
45
+ failures: _Failures,
46
+ ) -> None:
47
+ if isinstance(child, _Function) and _should_check_function(
48
+ child,
49
+ parent,
50
+ config,
51
+ ):
52
+ failure = _Failure(child, config.target, config.check.property_returns)
53
+ if failure:
54
+ failures.append(failure)
55
+
56
+ # recurse for either class methods or, if enabled, nested functions
57
+ if not isinstance(child, _Function) or config.check.nested:
58
+ for func in child.children:
59
+ _run_check(func, child, config, failures)
60
+
61
+
62
+ def run_checks(module: _Parent, config: _Config) -> _Failures:
63
+ """Run checks on the module and return a list of failures.
64
+
65
+ Traverse the module's functions and classes. A callable is checked
66
+ only if it is public or if configuration allows protected or
67
+ overridden members. Failures are collected and returned.
68
+
69
+ :param module: A module object that contains functions or classes.
70
+ :param config: Configuration object.
71
+ :return: A list of function and class failures.
72
+ """
73
+ failures = _Failures()
74
+ for top_level in module.children:
75
+ if (
76
+ not top_level.isprotected
77
+ or config.check.protected
78
+ or config.check.protected_class_methods
79
+ ):
80
+ _run_check(top_level, module, config, failures)
81
+
82
+ return failures
@@ -18,7 +18,7 @@ from pathlib import Path as _Path
18
18
  import tomli as _tomli
19
19
 
20
20
  from ._version import __version__
21
- from .messages import Messages
21
+ from .messages import Messages as _Messages
22
22
 
23
23
  PYPROJECT_TOML = "pyproject.toml"
24
24
 
@@ -361,8 +361,8 @@ class Config:
361
361
 
362
362
  check: Check = _field(default_factory=Check)
363
363
  ignore: Ignore = _field(default_factory=Ignore)
364
- target: Messages = _field(default_factory=Messages)
365
- disable: Messages = _field(default_factory=Messages)
364
+ target: _Messages = _field(default_factory=_Messages)
365
+ disable: _Messages = _field(default_factory=_Messages)
366
366
  exclude: list[str] = _field(default_factory=list)
367
367
  excludes: list[str] | None = None
368
368
  list_checks: bool = False
@@ -18,6 +18,7 @@ from warnings import warn as _warn
18
18
  import astroid as _ast
19
19
 
20
20
  from . import _decorators
21
+ from ._check import run_checks as _run_checks
21
22
  from ._config import Check as _Check
22
23
  from ._config import Config as _Config
23
24
  from ._config import Ignore as _Ignore
@@ -25,9 +26,7 @@ from ._directives import Directives as _Directives
25
26
  from ._files import FILE_INFO as _FILE_INFO
26
27
  from ._files import Paths as _Paths
27
28
  from ._module import Error as _Error
28
- from ._module import Function as _Function
29
29
  from ._module import Parent as _Parent
30
- from ._report import Failure as _Failure
31
30
  from ._report import Failures as _Failures
32
31
  from ._utils import print_checks as _print_checks
33
32
  from .messages import NEW as _NEW
@@ -70,57 +69,10 @@ def setup_logger(verbose: bool) -> None:
70
69
  logger.addHandler(stream_handler)
71
70
 
72
71
 
73
- def _should_check_function(
74
- child: _Function,
75
- parent: _Parent,
76
- config: _Config,
77
- ) -> bool:
78
- if child.isoverridden and not config.check.overridden:
79
- return False
80
-
81
- if child.isprotected and not config.check.protected:
82
- return False
83
-
84
- if child.isdunder and not config.check.dunders:
85
- return False
86
-
87
- if child.docstring.bare and config.ignore.no_params:
88
- return False
89
-
90
- if child.isinit and (
91
- not (config.check.class_ or config.check.class_constructor)
92
- or (parent.isprotected and not config.check.protected)
93
- ):
94
- return False
95
-
96
- return True
97
-
98
-
99
- def _run_check(
100
- child: _Parent,
101
- parent: _Parent,
102
- config: _Config,
103
- failures: _Failures,
104
- ) -> None:
105
- if isinstance(child, _Function) and _should_check_function(
106
- child,
107
- parent,
108
- config,
109
- ):
110
- failure = _Failure(child, config.target, config.check.property_returns)
111
- if failure:
112
- failures.append(failure)
113
-
114
- # recurse for either class methods or, if enabled, nested functions
115
- if not isinstance(child, _Function) or config.check.nested:
116
- for func in child.children:
117
- _run_check(func, child, config, failures)
118
-
119
-
120
- def _from_file(path: _Path, config: _Config) -> _Parent:
72
+ def _parse_from_file(path: _Path, config: _Config) -> _Parent:
121
73
  try:
122
74
  code = path.read_text(encoding="utf-8")
123
- parent = _from_str(
75
+ parent = _parse_from_string(
124
76
  code,
125
77
  config,
126
78
  str(path)[:-3].replace(_os.sep, ".").replace("-", "_"),
@@ -137,7 +89,7 @@ def _from_file(path: _Path, config: _Config) -> _Parent:
137
89
  return parent
138
90
 
139
91
 
140
- def _from_str(
92
+ def _parse_from_string(
141
93
  code: str,
142
94
  config: _Config,
143
95
  module_name: str = "",
@@ -169,23 +121,13 @@ def _from_str(
169
121
  except _ast.AstroidSyntaxError as err:
170
122
  logger.debug(_FILE_INFO, source_name, str(err).replace("\n", " "))
171
123
  parent = _Parent(error=_Error.SYNTAX)
124
+ except RecursionError as err:
125
+ logger.debug(_FILE_INFO, source_name, str(err).replace("\n", " "))
126
+ parent = _Parent(error=_Error.RECURSION)
172
127
 
173
128
  return parent
174
129
 
175
130
 
176
- def _get_failures(module: _Parent, config: _Config) -> _Failures:
177
- failures = _Failures()
178
- for top_level in module.children:
179
- if (
180
- not top_level.isprotected
181
- or config.check.protected
182
- or config.check.protected_class_methods
183
- ):
184
- _run_check(top_level, module, config, failures)
185
-
186
- return failures
187
-
188
-
189
131
  def _report(
190
132
  failures: _Failures,
191
133
  config: _Config,
@@ -252,8 +194,8 @@ def runner(path: _Path, config: _Config) -> _Failures:
252
194
  :param config: Configuration object.
253
195
  :return: Collected failures for the file.
254
196
  """
255
- module = _from_file(path, config)
256
- return _get_failures(module, config)
197
+ module = _parse_from_file(path, config)
198
+ return _run_checks(module, config)
257
199
 
258
200
 
259
201
  @_decorators.parse_msgs
@@ -367,20 +309,20 @@ def docsig( # pylint: disable=too-many-locals,too-many-arguments
367
309
  if config.list_checks:
368
310
  return int(bool(_print_checks())) # type: ignore
369
311
 
370
- if string is None:
371
- retcodes = [0]
372
- paths = _Paths(
373
- *path,
374
- patterns=config.exclude,
375
- excludes=config.excludes,
376
- include_ignored=config.include_ignored,
377
- )
378
- for path_ in paths:
379
- failures = runner(path_, config)
380
- retcodes.append(_report(failures, config, str(path_)))
312
+ if string:
313
+ module = _parse_from_string(string, config)
314
+ failures = _run_checks(module, config)
315
+ return _report(failures, config)
381
316
 
382
- return max(retcodes)
317
+ retcodes = [0]
318
+ paths = _Paths(
319
+ *path,
320
+ patterns=config.exclude,
321
+ excludes=config.excludes,
322
+ include_ignored=config.include_ignored,
323
+ )
324
+ for path_ in paths:
325
+ failures = runner(path_, config)
326
+ retcodes.append(_report(failures, config, str(path_)))
383
327
 
384
- module = _from_str(string, config)
385
- failures = _get_failures(module, config)
386
- return _report(failures, config)
328
+ return max(retcodes)
@@ -15,6 +15,10 @@ from .messages import E as _E
15
15
  from .messages import Messages as _Messages
16
16
 
17
17
 
18
+ class Comments(_t.List["Comment"]):
19
+ """List of comments."""
20
+
21
+
18
22
  class Comment(_Messages):
19
23
  """A single comment directive.
20
24
 
@@ -73,10 +77,6 @@ class Comment(_Messages):
73
77
  return None
74
78
 
75
79
 
76
- class Comments(_t.List[Comment]):
77
- """List of comments."""
78
-
79
-
80
80
  class Directives(_t.Dict[int, _t.Tuple[Comments, _Messages]]):
81
81
  """Map line number to comments and disabled messages for that line.
82
82
 
@@ -22,15 +22,24 @@ from ._stub import Signature as _Signature
22
22
  from .messages import Messages as _Messages
23
23
 
24
24
 
25
+ class _Imports(_t.Dict[str, str]):
26
+ """Represents python imports."""
27
+
28
+
29
+ class _Overloads(_t.Dict[str, "Function"]):
30
+ """Represents overloaded methods."""
31
+
32
+
33
+ class _Children(_t.List[_t.Union["Parent", "Function"]]):
34
+ """Represents children of an object."""
35
+
36
+
25
37
  class Error(_Enum):
26
38
  """Represents an unrecoverable error."""
27
39
 
28
40
  SYNTAX = 1
29
41
  UNICODE = 2
30
-
31
-
32
- class _Imports(_t.Dict[str, str]):
33
- """Represents python imports."""
42
+ RECURSION = 3
34
43
 
35
44
 
36
45
  class Parent: # pylint: disable=too-many-instance-attributes
@@ -67,8 +76,8 @@ class Parent: # pylint: disable=too-many-instance-attributes
67
76
  error: Error | None = None,
68
77
  ) -> None:
69
78
  super().__init__()
70
- self._name = "module"
71
79
  self._error = error
80
+ self._directives = directives or _Directives()
72
81
  self._ignore_args = ignore_args
73
82
  self._ignore_kwargs = ignore_kwargs
74
83
  self._check_class_constructor = check_class_constructor
@@ -76,11 +85,12 @@ class Parent: # pylint: disable=too-many-instance-attributes
76
85
  self._imports = imports or _Imports()
77
86
  self._overloads = _Overloads()
78
87
  if node is None:
88
+ self._name = "module"
79
89
  if not isinstance(self, Function) and error is not None:
80
90
  self._children.append(Function(path, error=error))
81
91
  else:
82
92
  self._name = node.name
83
- self._parse_ast(node, directives or _Directives(), path)
93
+ self._parse_ast(node, path)
84
94
 
85
95
  def _parse_ast(
86
96
  self,
@@ -90,19 +100,18 @@ class Parent: # pylint: disable=too-many-instance-attributes
90
100
  | _ast.nodes.FunctionDef
91
101
  | _ast.nodes.NodeNG
92
102
  ),
93
- directives: _Directives,
94
103
  path: _Path | None = None,
95
104
  ) -> None:
96
105
  # need to keep track of `comments` as, even though they are
97
106
  # resolved in the directive object, they are needed to notify
98
107
  # the user in the case that they are invalid
99
- parent_comments, parent_disabled = directives.get(
108
+ parent_comments, parent_disabled = self._directives.get(
100
109
  node.lineno,
101
110
  (_Comments(), _Messages()),
102
111
  )
103
112
  if hasattr(node, "body"):
104
113
  for subnode in node.body:
105
- comments, disabled = directives.get(
114
+ comments, disabled = self._directives.get(
106
115
  subnode.lineno,
107
116
  (_Comments(), _Messages()),
108
117
  )
@@ -119,7 +128,7 @@ class Parent: # pylint: disable=too-many-instance-attributes
119
128
  func = Function(
120
129
  subnode,
121
130
  comments,
122
- directives,
131
+ self._directives,
123
132
  disabled,
124
133
  path,
125
134
  self._ignore_args,
@@ -145,7 +154,7 @@ class Parent: # pylint: disable=too-many-instance-attributes
145
154
  self._children.append(
146
155
  Parent(
147
156
  subnode,
148
- directives,
157
+ self._directives,
149
158
  path,
150
159
  self._ignore_args,
151
160
  self._ignore_kwargs,
@@ -154,7 +163,7 @@ class Parent: # pylint: disable=too-many-instance-attributes
154
163
  ),
155
164
  )
156
165
  else:
157
- self._parse_ast(subnode, directives, path)
166
+ self._parse_ast(subnode, path)
158
167
 
159
168
  @property
160
169
  def isprotected(self) -> bool:
@@ -364,11 +373,3 @@ class Function(Parent): # pylint: disable=too-many-instance-attributes
364
373
  :param rettype: Return type of the overloaded variant.
365
374
  """
366
375
  self._signature.overload(rettype)
367
-
368
-
369
- class _Overloads(_t.Dict[str, Function]):
370
- """Represents overloaded methods."""
371
-
372
-
373
- class _Children(_t.List[_t.Union[Parent, Function]]):
374
- """Represents children of an object."""
@@ -10,7 +10,7 @@ return the highest exit code.
10
10
 
11
11
  from __future__ import annotations as _
12
12
 
13
- import contextlib
13
+ import contextlib as _contextlib
14
14
  import typing as _t
15
15
 
16
16
  from astroid.nodes.scoped_nodes import scoped_nodes as _scoped_nodes
@@ -32,6 +32,10 @@ _MIN_MATCH = 0.8
32
32
  _MAX_MATCH = 1.0
33
33
 
34
34
 
35
+ class Failures(_t.List["Failure"]):
36
+ """Sequence of Failure instances (one per function checked)."""
37
+
38
+
35
39
  class Failed(_t.NamedTuple):
36
40
  """Single reported issue."""
37
41
 
@@ -139,7 +143,7 @@ class Failure(_t.List[Failed]):
139
143
  # then we know that they aren't almost equal, param1 is
140
144
  # missing and does need to be inserted in the docstring
141
145
  if is_equal:
142
- with contextlib.suppress(IndexError):
146
+ with _contextlib.suppress(IndexError):
143
147
  is_equal = to[count].name != from_[count + 1].name
144
148
 
145
149
  if not is_equal:
@@ -316,6 +320,8 @@ class Failure(_t.List[Failed]):
316
320
  self._retcode = 123
317
321
  if self._func.error == _Error.UNICODE:
318
322
  self._add(_E[902])
323
+ if self._func.error == _Error.RECURSION:
324
+ self._add(_E[903])
319
325
 
320
326
  @property
321
327
  def name(self) -> str:
@@ -331,7 +337,3 @@ class Failure(_t.List[Failed]):
331
337
  def retcode(self) -> int:
332
338
  """Exit code (non-zero if any check failed)."""
333
339
  return self._retcode
334
-
335
-
336
- class Failures(_t.List[Failure]):
337
- """Sequence of Failure instances (one per function checked)."""
@@ -100,11 +100,11 @@ class Param:
100
100
  indent: int = 0,
101
101
  closing_token: str = ":",
102
102
  ) -> None:
103
- self.kind = kind
104
- self.name = name
105
- self.description = description
106
- self.indent = indent
107
- self.closing_token = closing_token
103
+ self._kind = kind
104
+ self._name = name
105
+ self._description = description
106
+ self._indent = indent
107
+ self._closing_token = closing_token
108
108
 
109
109
  def __eq__(self, other: object) -> bool:
110
110
  iseq = False
@@ -125,6 +125,31 @@ class Param:
125
125
  """True if the parameter name starts with an underscore."""
126
126
  return str(self.name).startswith("_")
127
127
 
128
+ @property
129
+ def kind(self) -> DocType:
130
+ """Type of the param."""
131
+ return self._kind
132
+
133
+ @property
134
+ def name(self) -> str | None:
135
+ """Name of the param."""
136
+ return self._name
137
+
138
+ @property
139
+ def description(self) -> str | None:
140
+ """Description of param."""
141
+ return self._description
142
+
143
+ @property
144
+ def indent(self) -> int:
145
+ """Number of spaces in the indent."""
146
+ return self._indent
147
+
148
+ @property
149
+ def closing_token(self) -> str:
150
+ """Token used to terminate the param name definition."""
151
+ return self._closing_token
152
+
128
153
 
129
154
  class Params(_t.List[Param]):
130
155
  """A list-like collection of params.
@@ -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.81.2"
11
+ __version__ = "0.81.3"
@@ -6,7 +6,7 @@ Error message definitions (Message, MessageMap, E) and format templates
6
6
  for docstring-check output.
7
7
  """
8
8
 
9
- from __future__ import annotations
9
+ from __future__ import annotations as _
10
10
 
11
11
  import typing as _t
12
12
 
@@ -24,6 +24,10 @@ NEW = """\
24
24
  """
25
25
 
26
26
 
27
+ class Messages(_t.List["Message"]):
28
+ """Sequence of Message instances, typically for one failure."""
29
+
30
+
27
31
  class Message(_t.NamedTuple):
28
32
  """One docstring-check error (ref, description, symbolic, hint)."""
29
33
 
@@ -63,10 +67,6 @@ class Message(_t.NamedTuple):
63
67
  )
64
68
 
65
69
 
66
- class Messages(_t.List[Message]):
67
- """Sequence of Message instances, typically for one failure."""
68
-
69
-
70
70
  class MessageMap(_t.Dict[int, Message]):
71
71
  """Mapping from integer key to Message (used for the E registry)."""
72
72
 
@@ -252,5 +252,10 @@ E = MessageMap(
252
252
  "failed to read file",
253
253
  "unicode-decode-error",
254
254
  ),
255
+ 903: Message(
256
+ "SIG903",
257
+ "maximum recursion depth exceeded",
258
+ "recursion-error",
259
+ ),
255
260
  },
256
261
  )
@@ -11,7 +11,7 @@ line-length = 79
11
11
  allow_dirty = true
12
12
  commit = true
13
13
  commit_args = "-sS"
14
- current_version = "0.81.2"
14
+ current_version = "0.81.3"
15
15
  message = "bump: version {current_version} → {new_version}"
16
16
  sign_tags = true
17
17
  tag = true
@@ -122,7 +122,7 @@ maintainers = [
122
122
  name = "docsig"
123
123
  readme = "README.rst"
124
124
  repository = "https://github.com/jshwi/docsig"
125
- version = "0.81.2"
125
+ version = "0.81.3"
126
126
 
127
127
  [tool.poetry.dependencies]
128
128
  Sphinx = ">=7,<9"
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
File without changes