docsig 0.82.1__tar.gz → 0.82.2__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.82.1
3
+ Version: 0.82.2
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.82.1, mccabe: 0.7.0, pycodestyle: 2.14.0, pyflakes: 3.4.0) CPython 3.10.19 on Darwin
187
+ 7.3.0 (docsig: 0.82.2, 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.82.1
273
+ rev: v0.82.2
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.82.1
292
+ - docsig==0.82.2
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.82.1, mccabe: 0.7.0, pycodestyle: 2.14.0, pyflakes: 3.4.0) CPython 3.10.19 on Darwin
158
+ 7.3.0 (docsig: 0.82.2, 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.82.1
244
+ rev: v0.82.2
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.82.1
263
+ - docsig==0.82.2
264
264
  args:
265
265
  - "--sig-check-class"
266
266
  - "--sig-check-dunders"
@@ -9,7 +9,6 @@ from __future__ import annotations as _
9
9
 
10
10
  import re as _re
11
11
  import typing as _t
12
- from enum import Enum as _Enum
13
12
  from pathlib import Path as _Path
14
13
 
15
14
  import astroid as _ast
@@ -32,12 +31,12 @@ class _Overloads(dict[str, "Function"]): ...
32
31
  class _Children(list[_t.Union["Parent", "Function"]]): ...
33
32
 
34
33
 
35
- class Error(_Enum):
36
- """Represents an unrecoverable error."""
37
-
38
- SYNTAX = 1
39
- UNICODE = 2
40
- RECURSION = 3
34
+ ERRORS = (
35
+ _ast.AstroidSyntaxError,
36
+ UnicodeDecodeError,
37
+ RecursionError,
38
+ _ast.DuplicateBasesError,
39
+ )
41
40
 
42
41
 
43
42
  class Parent: # pylint: disable=too-many-instance-attributes
@@ -65,7 +64,7 @@ class Parent: # pylint: disable=too-many-instance-attributes
65
64
  file: _Path | None = None,
66
65
  config: _Config | None = None,
67
66
  imports: _Imports | None = None,
68
- error: Error | None = None,
67
+ error: type[BaseException] | None = None,
69
68
  ) -> None:
70
69
  super().__init__()
71
70
  self._error = error
@@ -127,14 +126,18 @@ class Parent: # pylint: disable=too-many-instance-attributes
127
126
  if func.isoverloaded:
128
127
  if (
129
128
  func.name not in self._overloads
130
- or self._overloads[func.name].signature.rettype
129
+ or self._overloads[
130
+ func.name
131
+ ].signature.returns.type
131
132
  == _RetType.NONE
132
133
  ):
133
134
  self._overloads[func.name] = func
134
135
  else:
135
136
  if func.name in self._overloads:
136
137
  func.overload(
137
- self._overloads[func.name].signature.rettype,
138
+ self._overloads[
139
+ func.name
140
+ ].signature.returns.type,
138
141
  )
139
142
 
140
143
  self._children.append(func)
@@ -157,7 +160,7 @@ class Parent: # pylint: disable=too-many-instance-attributes
157
160
  return self._name.startswith("_")
158
161
 
159
162
  @property
160
- def error(self) -> Error | None:
163
+ def error(self) -> type[BaseException] | None:
161
164
  """Unrecoverable error for this scope, if any."""
162
165
  return self._error
163
166
 
@@ -190,7 +193,7 @@ class Function(Parent): # pylint: disable=too-many-instance-attributes
190
193
  file: _Path | None = None,
191
194
  config: _Config | None = None,
192
195
  imports: _Imports | None = None,
193
- error: Error | None = None,
196
+ error: type[BaseException] | None = None,
194
197
  ) -> None:
195
198
  super().__init__(
196
199
  node,
@@ -15,7 +15,7 @@ import astroid as _ast
15
15
  from ._config import Config as _Config
16
16
  from ._directives import Directives as _Directives
17
17
  from ._files import FILE_INFO as _FILE_INFO
18
- from ._module import Error as _Error
18
+ from ._module import ERRORS as _ERRORS
19
19
  from ._module import Parent as _Parent
20
20
 
21
21
 
@@ -53,12 +53,9 @@ def parse_from_string(
53
53
 
54
54
  parent = _Parent(node, directives, file, config)
55
55
  logger.debug(_FILE_INFO, source_name, "Parsing Python code successful")
56
- except _ast.AstroidSyntaxError as err:
56
+ except _ERRORS as err:
57
57
  logger.debug(_FILE_INFO, source_name, str(err).replace("\n", " "))
58
- parent = _Parent(error=_Error.SYNTAX)
59
- except RecursionError as err:
60
- logger.debug(_FILE_INFO, source_name, str(err).replace("\n", " "))
61
- parent = _Parent(error=_Error.RECURSION)
58
+ parent = _Parent(error=type(err))
62
59
 
63
60
  return parent
64
61
 
@@ -81,7 +78,7 @@ def parse_from_file(file: _Path, config: _Config) -> _Parent:
81
78
  except UnicodeDecodeError as err:
82
79
  logger = _logging.getLogger(__package__)
83
80
  logger.debug(_FILE_INFO, file, str(err).replace("\n", " "))
84
- parent = _Parent(error=_Error.UNICODE)
81
+ parent = _Parent(error=type(err))
85
82
 
86
83
  if parent.error is not None and not file.name.endswith(".py"):
87
84
  parent = _Parent()
@@ -15,10 +15,10 @@ import sys as _sys
15
15
  import typing as _t
16
16
  from warnings import warn as _warn
17
17
 
18
+ import astroid as _ast
18
19
  from astroid.nodes.scoped_nodes import scoped_nodes as _scoped_nodes
19
20
 
20
21
  from ._config import Config as _Config
21
- from ._module import Error as _Error
22
22
  from ._module import Function as _Function
23
23
  from ._stub import UNNAMED as _UNNAMED
24
24
  from ._stub import VALID_DESCRIPTION as _VALID_DESCRIPTION
@@ -288,18 +288,18 @@ class Failure(list[Failed]):
288
288
  self._func.isproperty and not check_property_returns
289
289
  ):
290
290
  # no types, cannot know either way
291
- if self._func.signature.rettype == _RetType.UNTYPED:
291
+ if self._func.signature.returns.type == _RetType.UNTYPED:
292
292
  # confirm-return-needed
293
293
  self._add(_E[501], hint=True)
294
294
  # return-type is none, so no return should be documented
295
- elif self._func.docstring.returns:
296
- if self._func.signature.rettype == _RetType.NONE:
295
+ elif self._func.docstring.returns.returns:
296
+ if self._func.signature.returns.type == _RetType.NONE:
297
297
  # return-documented-for-none
298
298
  self._add(_E[502])
299
- if self._func.docstring.ret_description_missing:
299
+ if self._func.docstring.returns.description_missing:
300
300
  self._add(_E[506])
301
301
  # return-type is some, so return should be documented
302
- elif self._func.signature.returns:
302
+ elif self._func.signature.returns.returns:
303
303
  # return-missing
304
304
  lines = str(self._func.docstring.string).splitlines()
305
305
  self._add(
@@ -310,7 +310,7 @@ class Failure(list[Failed]):
310
310
  and ":param" not in lines[-1]
311
311
  ),
312
312
  )
313
- elif self._func.docstring.returns:
313
+ elif self._func.docstring.returns.returns:
314
314
  # this method is init, so no return should be documented
315
315
  if self._func.isinit:
316
316
  # class-return-documented
@@ -322,13 +322,15 @@ class Failure(list[Failed]):
322
322
 
323
323
  def _sig9xx_error(self) -> None:
324
324
  # invalid-syntax
325
- if self._func.error == _Error.SYNTAX:
325
+ if self._func.error is _ast.AstroidSyntaxError:
326
326
  self._add(_E[901])
327
327
  self._retcode = 123
328
- if self._func.error == _Error.UNICODE:
328
+ if self._func.error is UnicodeDecodeError:
329
329
  self._add(_E[902])
330
- if self._func.error == _Error.RECURSION:
330
+ if self._func.error is RecursionError:
331
331
  self._add(_E[903])
332
+ if self._func.error is _ast.DuplicateBasesError:
333
+ self._add(_E[904])
332
334
 
333
335
  @property
334
336
  def name(self) -> str:
@@ -9,6 +9,7 @@ from __future__ import annotations as _
9
9
 
10
10
  import re as _re
11
11
  import textwrap as _textwrap
12
+ import typing as _t
12
13
  from collections import Counter as _Counter
13
14
  from enum import Enum as _Enum
14
15
 
@@ -80,8 +81,6 @@ class DocType(_Enum):
80
81
  return cls.UNKNOWN
81
82
 
82
83
 
83
- # todo: consider a parent object that can be used for returns that do
84
- # todo: not include the name attribute
85
84
  class Param:
86
85
  """Single parameter from a docstring or function signature.
87
86
 
@@ -152,6 +151,13 @@ class Param:
152
151
  return self._closing_token
153
152
 
154
153
 
154
+ # single return from a docstring or function signature
155
+ class _Return(_t.NamedTuple):
156
+ returns: bool = False
157
+ type: RetType = RetType.UNTYPED
158
+ description_missing: bool = False
159
+
160
+
155
161
  class Params(list[Param]):
156
162
  """A list-like collection of params.
157
163
 
@@ -218,9 +224,13 @@ class Params(list[Param]):
218
224
 
219
225
 
220
226
  class _Stub:
221
- def __init__(self, ignore: _Ignore | None = None) -> None:
227
+ def __init__(
228
+ self,
229
+ returns: _Return | None = None,
230
+ ignore: _Ignore | None = None,
231
+ ) -> None:
222
232
  self._args = Params(ignore or _Ignore())
223
- self._returns = False
233
+ self._returns = returns or _Return()
224
234
 
225
235
  @property
226
236
  def args(self) -> Params:
@@ -228,7 +238,7 @@ class _Stub:
228
238
  return self._args
229
239
 
230
240
  @property
231
- def returns(self) -> bool:
241
+ def returns(self) -> _Return:
232
242
  """True if a return (or yield) is declared or documented."""
233
243
  return self._returns
234
244
 
@@ -236,20 +246,16 @@ class _Stub:
236
246
  class Signature(_Stub):
237
247
  """Parsed function signature (args and return type).
238
248
 
239
- :param rettype: Kind of return (none, some, untyped).
240
249
  :param returns: True if return is declared in the signature.
241
250
  :param ignore: Configuration object for what to ignore.
242
251
  """
243
252
 
244
253
  def __init__(
245
254
  self,
246
- rettype: RetType = RetType.NONE,
247
- returns: bool = False,
255
+ returns: _Return | None = None,
248
256
  ignore: _Ignore | None = None,
249
257
  ) -> None:
250
- super().__init__(ignore or _Ignore())
251
- self._rettype = rettype
252
- self._returns = returns
258
+ super().__init__(returns, ignore or _Ignore())
253
259
 
254
260
  @classmethod
255
261
  def from_ast(
@@ -264,8 +270,8 @@ class Signature(_Stub):
264
270
  :return: Signature with args and return type.
265
271
  """
266
272
  rettype = RetType.from_ast(node.returns)
267
- returns = rettype == RetType.SOME
268
- signature = cls(rettype, returns, ignore)
273
+ returns = _Return(rettype == RetType.SOME, rettype)
274
+ signature = cls(returns, ignore)
269
275
  # noinspection PyUnresolvedReferences
270
276
  for i in [
271
277
  a if isinstance(a, Param) else Param(name=a.name)
@@ -282,18 +288,12 @@ class Signature(_Stub):
282
288
 
283
289
  return signature
284
290
 
285
- @property
286
- def rettype(self) -> RetType:
287
- """Return annotation kind (none, some, or untyped)."""
288
- return self._rettype
289
-
290
291
  def overload(self, rettype: RetType) -> None:
291
292
  """Set this signature's return type (for overloads).
292
293
 
293
294
  :param rettype: Return type for the overloaded variant.
294
295
  """
295
- self._rettype = rettype
296
- self._returns = rettype != RetType.NONE
296
+ self._returns = _Return(rettype != RetType.NONE, rettype)
297
297
 
298
298
 
299
299
  class Docstring(_Stub):
@@ -301,7 +301,6 @@ class Docstring(_Stub):
301
301
 
302
302
  :param string: Raw docstring text after normalization.
303
303
  :param returns: True if a return or yield section is present.
304
- :param ret_description_missing: True if return has no description.
305
304
  """
306
305
 
307
306
  @staticmethod
@@ -345,13 +344,10 @@ class Docstring(_Stub):
345
344
  def __init__(
346
345
  self,
347
346
  string: str | None = None,
348
- returns: bool = False,
349
- ret_description_missing: bool = False,
347
+ returns: _Return | None = None,
350
348
  ) -> None:
351
- super().__init__()
349
+ super().__init__(returns)
352
350
  self._string = string
353
- self._returns = returns
354
- self._ret_description_missing = ret_description_missing
355
351
 
356
352
  @classmethod
357
353
  def from_ast(cls, node: _ast.Const) -> Docstring:
@@ -362,16 +358,16 @@ class Docstring(_Stub):
362
358
  """
363
359
  indent_anomaly = cls._indent_anomaly(node.value)
364
360
  string = cls._normalize_docstring(node.value)
365
- # todo: we can start building return objects for more detailed
366
- # todo: checks that are in common with the params class
367
361
  match = _re.search(
368
362
  r":(?:returns?|yields?):\s*(.*)",
369
363
  string,
370
364
  _re.IGNORECASE,
371
365
  )
372
- returns = bool(match)
373
- ret_description_missing = not match or not match.group(1)
374
- docstring = cls(string, returns, ret_description_missing)
366
+ returns = _Return(
367
+ bool(match),
368
+ description_missing=not match or not match.group(1),
369
+ )
370
+ docstring = cls(string, returns)
375
371
  for match in _re.findall(
376
372
  r":([\w\s]+(?:\s\|\s[\w\s]+|\w+))([^\w\s])((?:.|\n)*?)(?=\n:|$)",
377
373
  string,
@@ -402,9 +398,8 @@ class Docstring(_Stub):
402
398
 
403
399
  Used when the function has a docstring but documents nothing.
404
400
  """
405
- return self._string is not None and not self._args and not self.returns
406
-
407
- @property
408
- def ret_description_missing(self) -> bool:
409
- """True if a return section exists but has no description."""
410
- return self._ret_description_missing
401
+ return (
402
+ self._string is not None
403
+ and not self._args
404
+ and not self.returns.returns
405
+ )
@@ -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.82.1"
11
+ __version__ = "0.82.2"
@@ -267,5 +267,10 @@ E = MessageMap(
267
267
  "maximum recursion depth exceeded",
268
268
  "recursion-error",
269
269
  ),
270
+ 904: Message(
271
+ "SIG904",
272
+ "duplicates found in mros",
273
+ "duplicates-found-in-mros",
274
+ ),
270
275
  },
271
276
  )
@@ -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.82.1"
14
+ current_version = "0.82.2"
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.82.1"
125
+ version = "0.82.2"
126
126
 
127
127
  [tool.poetry.dependencies]
128
128
  Sphinx = ">=7,<9"
@@ -239,7 +239,7 @@ name = "Security"
239
239
 
240
240
  [tool.vulture]
241
241
  exclude = [
242
- "tests/_templates.py",
242
+ "tests/base_test.py",
243
243
  "tests/conftest.py"
244
244
  ]
245
245
  make_whitelist = true
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
File without changes
File without changes
File without changes
File without changes