docsig 0.62.0__tar.gz → 0.63.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.62.0
3
+ Version: 0.63.0
4
4
  Summary: Check signature params for proper documentation
5
5
  Home-page: https://pypi.org/project/docsig/
6
6
  License: MIT
@@ -20,7 +20,7 @@ Requires-Dist: Sphinx (>=7.0.0,<8.0.0)
20
20
  Requires-Dist: astroid (>=3.0.1,<4.0.0)
21
21
  Requires-Dist: pathspec (>=0.12.1,<0.13.0)
22
22
  Requires-Dist: tomli (>=2.0.1,<3.0.0)
23
- Requires-Dist: wcmatch (>=8.5.2,<10.0.0)
23
+ Requires-Dist: wcmatch (>=8.5.2,<11.0.0)
24
24
  Project-URL: Documentation, https://docsig.readthedocs.io/en/latest
25
25
  Project-URL: Repository, https://github.com/jshwi/docsig
26
26
  Description-Content-Type: text/x-rst
@@ -180,7 +180,7 @@ ensure your installation has registered `docsig`
180
180
  .. code-block:: console
181
181
 
182
182
  $ flake8 --version
183
- 7.1.0 (docsig: 0.62.0, mccabe: 0.7.0, pycodestyle: 2.12.0, pyflakes: 3.2.0) CPython 3.8.13 on Darwin
183
+ 7.1.0 (docsig: 0.63.0, mccabe: 0.7.0, pycodestyle: 2.12.0, pyflakes: 3.2.0) CPython 3.8.13 on Darwin
184
184
 
185
185
  And now use `flake8` to lint your files
186
186
 
@@ -269,7 +269,7 @@ Standalone
269
269
 
270
270
  repos:
271
271
  - repo: https://github.com/jshwi/docsig
272
- rev: v0.62.0
272
+ rev: v0.63.0
273
273
  hooks:
274
274
  - id: docsig
275
275
  args:
@@ -288,7 +288,7 @@ or integrated with ``flake8``
288
288
  hooks:
289
289
  - id: flake8
290
290
  additional_dependencies:
291
- - docsig==0.62.0
291
+ - docsig==0.63.0
292
292
  args:
293
293
  - "--sig-check-class"
294
294
  - "--sig-check-dunders"
@@ -153,7 +153,7 @@ ensure your installation has registered `docsig`
153
153
  .. code-block:: console
154
154
 
155
155
  $ flake8 --version
156
- 7.1.0 (docsig: 0.62.0, mccabe: 0.7.0, pycodestyle: 2.12.0, pyflakes: 3.2.0) CPython 3.8.13 on Darwin
156
+ 7.1.0 (docsig: 0.63.0, mccabe: 0.7.0, pycodestyle: 2.12.0, pyflakes: 3.2.0) CPython 3.8.13 on Darwin
157
157
 
158
158
  And now use `flake8` to lint your files
159
159
 
@@ -242,7 +242,7 @@ Standalone
242
242
 
243
243
  repos:
244
244
  - repo: https://github.com/jshwi/docsig
245
- rev: v0.62.0
245
+ rev: v0.63.0
246
246
  hooks:
247
247
  - id: docsig
248
248
  args:
@@ -261,7 +261,7 @@ or integrated with ``flake8``
261
261
  hooks:
262
262
  - id: flake8
263
263
  additional_dependencies:
264
- - docsig==0.62.0
264
+ - docsig==0.63.0
265
265
  args:
266
266
  - "--sig-check-class"
267
267
  - "--sig-check-dunders"
@@ -110,7 +110,7 @@ def parse_args(args: _t.Sequence[str] | None = None) -> _a.Namespace:
110
110
  :return: Parsed arguments.
111
111
  """
112
112
  parser = _ArgumentParser(
113
- description="Check signature params for proper documentation",
113
+ description="Check signature params for proper documentation"
114
114
  )
115
115
  parser.add_argument(
116
116
  "-V",
@@ -5,6 +5,7 @@ docsig._core
5
5
 
6
6
  from __future__ import annotations as _
7
7
 
8
+ import logging as _logging
8
9
  import sys as _sys
9
10
  from pathlib import Path as _Path
10
11
 
@@ -20,7 +21,6 @@ from ._module import Parent as _Parent
20
21
  from ._report import Failure as _Failure
21
22
  from ._report import Failures as _Failures
22
23
  from ._utils import print_checks as _print_checks
23
- from ._utils import vprint as _vprint
24
24
  from .messages import TEMPLATE as _TEMPLATE
25
25
  from .messages import Messages as _Messages
26
26
 
@@ -44,6 +44,21 @@ _DEFAULT_EXCLUDES = """\
44
44
  """
45
45
 
46
46
 
47
+ def setup_logger(verbose: bool) -> None:
48
+ """Setup docsig logger.
49
+
50
+ Only log if verbose mode is enabled.
51
+
52
+ :param verbose: Whether to enable verbose mode.
53
+ """
54
+ loglevel = _logging.DEBUG if verbose else _logging.INFO
55
+ logger = _logging.getLogger(__package__)
56
+ logger.setLevel(loglevel)
57
+ if not logger.handlers:
58
+ stream_handler = _logging.StreamHandler(_sys.stdout)
59
+ logger.addHandler(stream_handler)
60
+
61
+
47
62
  def _run_check( # pylint: disable=too-many-arguments,too-many-locals
48
63
  child: _Parent,
49
64
  parent: _Parent,
@@ -123,10 +138,10 @@ def _parse_ast( # pylint: disable=too-many-arguments
123
138
  ignore_args: bool,
124
139
  ignore_kwargs: bool,
125
140
  check_class_constructor,
126
- verbose: bool,
127
141
  root: _Path | None = None,
128
142
  string: str | None = None,
129
143
  ) -> _Parent:
144
+ logger = _logging.getLogger(__package__)
130
145
  parent = _Parent()
131
146
  try:
132
147
  if root is not None:
@@ -143,29 +158,21 @@ def _parse_ast( # pylint: disable=too-many-arguments
143
158
  ignore_kwargs,
144
159
  check_class_constructor,
145
160
  )
146
- _vprint(
147
- _FILE_INFO.format(
148
- path=root or "stdin", msg="Parsing Python code successful"
149
- ),
150
- verbose,
161
+ logger.debug(
162
+ _FILE_INFO, root or "stdin", "Parsing Python code successful"
151
163
  )
152
164
  return parent
153
165
  except (_ast.AstroidSyntaxError, UnicodeDecodeError) as err:
154
166
  msg = str(err).replace("\n", " ")
155
167
  if root is not None and root.name.endswith(".py"):
156
- # pass by silently for files that do not end with .py, may
157
- # result in a 123 syntax error exit status in the future
158
168
  parent = _Parent(error=_Error.SYNTAX)
159
169
 
160
- _vprint(
161
- _FILE_INFO.format(path=root or "stdin", msg=msg),
162
- verbose,
163
- )
170
+ logger.debug(_FILE_INFO, root or "stdin", msg)
164
171
 
165
172
  return parent
166
173
 
167
174
 
168
- def _get_failures( # pylint: disable=too-many-locals,too-many-arguments
175
+ def _get_failures( # pylint: disable=too-many-arguments
169
176
  module: _Parent,
170
177
  check_class: bool,
171
178
  check_class_constructor: bool,
@@ -209,8 +216,10 @@ def _get_failures( # pylint: disable=too-many-locals,too-many-arguments
209
216
 
210
217
  def _report(
211
218
  failures: _Failures, path: str | None = None, no_ansi: bool = False
212
- ) -> None:
219
+ ) -> int:
220
+ retcodes = []
213
221
  for failure in failures:
222
+ retcodes.append(failure.retcode)
214
223
  module = f"{path}:" if path is not None else ""
215
224
  header = f"{module}{failure.lineno} in {failure.name}"
216
225
  if not no_ansi and _sys.stdout.isatty():
@@ -229,6 +238,8 @@ def _report(
229
238
  if item.hint:
230
239
  print(f" hint: {item.hint}")
231
240
 
241
+ return max(retcodes)
242
+
232
243
 
233
244
  def runner( # pylint: disable=too-many-locals,too-many-arguments
234
245
  file: str | _Path,
@@ -246,7 +257,6 @@ def runner( # pylint: disable=too-many-locals,too-many-arguments
246
257
  ignore_typechecker: bool = False,
247
258
  check_protected_class_methods: bool = False,
248
259
  no_ansi: bool = False,
249
- verbose: bool = False,
250
260
  target: _Messages | None = None,
251
261
  ) -> _Failures:
252
262
  """Per path runner.
@@ -269,7 +279,6 @@ def runner( # pylint: disable=too-many-locals,too-many-arguments
269
279
  :param check_protected_class_methods: Check public methods belonging
270
280
  to protected classes.
271
281
  :param no_ansi: Disable ANSI output.
272
- :param verbose: increase output verbosity.
273
282
  :param target: List of errors to target.
274
283
  :return: Exit status for whether test failed or not.
275
284
  """
@@ -279,7 +288,6 @@ def runner( # pylint: disable=too-many-locals,too-many-arguments
279
288
  ignore_args,
280
289
  ignore_kwargs,
281
290
  check_class_constructor,
282
- verbose,
283
291
  root=_Path(file),
284
292
  )
285
293
  if module or module.error is not None:
@@ -367,6 +375,7 @@ def docsig( # pylint: disable=too-many-locals,too-many-arguments
367
375
  :param excludes: Files or dirs to exclude from checks.
368
376
  :return: Exit status for whether test failed or not.
369
377
  """
378
+ setup_logger(verbose)
370
379
  if list_checks:
371
380
  return int(bool(_print_checks())) # type: ignore
372
381
 
@@ -375,13 +384,12 @@ def docsig( # pylint: disable=too-many-locals,too-many-arguments
375
384
  patterns.append(exclude)
376
385
 
377
386
  if string is None:
378
- retcode = 0
387
+ retcodes = [0]
379
388
  paths = _Paths(
380
389
  *tuple(_Path(i) for i in path),
381
390
  patterns=patterns,
382
391
  excludes=excludes or [],
383
392
  include_ignored=include_ignored,
384
- verbose=verbose,
385
393
  )
386
394
  for file in paths:
387
395
  failures = runner(
@@ -400,21 +408,18 @@ def docsig( # pylint: disable=too-many-locals,too-many-arguments
400
408
  ignore_typechecker,
401
409
  check_protected_class_methods,
402
410
  no_ansi,
403
- verbose,
404
411
  target,
405
412
  )
406
413
  if failures:
407
- _report(failures, str(file), no_ansi)
408
- retcode = 1
414
+ retcodes.append(_report(failures, str(file), no_ansi))
409
415
 
410
- return retcode
416
+ return max(retcodes)
411
417
 
412
418
  module = _parse_ast(
413
419
  disable or _Messages(),
414
420
  ignore_args,
415
421
  ignore_kwargs,
416
422
  check_class_constructor,
417
- verbose,
418
423
  string=string,
419
424
  )
420
425
  if module or module.error is not None:
@@ -434,7 +439,6 @@ def docsig( # pylint: disable=too-many-locals,too-many-arguments
434
439
  target or _Messages(),
435
440
  )
436
441
  if failures:
437
- _report(failures, no_ansi=no_ansi)
438
- return 1
442
+ return _report(failures, no_ansi=no_ansi)
439
443
 
440
444
  return 0
@@ -5,6 +5,7 @@ docsig._files
5
5
 
6
6
  from __future__ import annotations as _
7
7
 
8
+ import logging as _logging
8
9
  import os as _os
9
10
  import re as _re
10
11
  import typing as _t
@@ -14,9 +15,7 @@ from pathspec import PathSpec as _PathSpec
14
15
  from pathspec.patterns import GitWildMatchPattern as _GitWildMatchPattern
15
16
  from wcmatch.pathlib import Path as _WcPath
16
17
 
17
- from ._utils import vprint as _vprint
18
-
19
- FILE_INFO = "{path}: {msg}"
18
+ FILE_INFO = "%s: %s"
20
19
 
21
20
 
22
21
  class _Gitignore(_PathSpec):
@@ -68,7 +67,7 @@ def _glob(path: _Path, pattern: str) -> bool:
68
67
  return _WcPath(str(path)).globmatch(pattern) # type: ignore
69
68
 
70
69
 
71
- class Paths(_t.List[_Path]): # pylint: disable=too-many-instance-attributes
70
+ class Paths(_t.List[_Path]):
72
71
  """Collect a list of valid paths.
73
72
 
74
73
  :param paths: Path(s) to parse ``Module``(s) from.
@@ -77,23 +76,21 @@ class Paths(_t.List[_Path]): # pylint: disable=too-many-instance-attributes
77
76
  :param excludes: Files or dirs to exclude from checks.
78
77
  :param include_ignored: Check files even if they match a gitignore
79
78
  pattern.
80
- :param verbose: increase output verbosity.
81
79
  """
82
80
 
83
- def __init__( # pylint: disable=too-many-arguments
81
+ def __init__(
84
82
  self,
85
83
  *paths: _Path,
86
84
  patterns: list[str],
87
85
  excludes: list[str],
88
86
  include_ignored: bool = False,
89
- verbose: bool = False,
90
87
  ) -> None:
91
88
  super().__init__()
92
89
  self._patterns = patterns
93
90
  self._excludes = excludes
94
91
  self._include_ignored = include_ignored
95
- self._verbose = verbose
96
92
  self._gitignore = _Gitignore()
93
+ self._logger = _logging.getLogger(__package__)
97
94
  for path in paths:
98
95
  self._populate(path)
99
96
 
@@ -102,11 +99,8 @@ class Paths(_t.List[_Path]): # pylint: disable=too-many-instance-attributes
102
99
  any(_re.match(i, str(path)) for i in self._patterns)
103
100
  or any(_glob(path, i) for i in self._excludes)
104
101
  ):
105
- _vprint(
106
- FILE_INFO.format(
107
- path=path, msg="in exclude list, skipping"
108
- ),
109
- self._verbose,
102
+ self._logger.debug(
103
+ FILE_INFO, path, "in exclude list, skipping"
110
104
  )
111
105
  self.remove(path)
112
106
 
@@ -117,10 +111,7 @@ class Paths(_t.List[_Path]): # pylint: disable=too-many-instance-attributes
117
111
  raise FileNotFoundError(root)
118
112
 
119
113
  if not self._include_ignored and self._gitignore.match_file(root):
120
- _vprint(
121
- FILE_INFO.format(path=root, msg="in gitignore, skipping"),
122
- self._verbose,
123
- )
114
+ self._logger.debug(FILE_INFO, root, "in gitignore, skipping")
124
115
  return
125
116
 
126
117
  if root.is_file():
@@ -60,6 +60,9 @@ class Parent(_t.List["Parent"]):
60
60
  super().__init__()
61
61
  self._name = "module"
62
62
  self._error = error
63
+ self._ignore_args = ignore_args
64
+ self._ignore_kwargs = ignore_kwargs
65
+ self._check_class_constructor = check_class_constructor
63
66
  if node is None:
64
67
  if not isinstance(self, Function) and error is not None:
65
68
  self.append(Function(path, error=error))
@@ -67,28 +70,18 @@ class Parent(_t.List["Parent"]):
67
70
  self._name = node.name
68
71
  self._overloads = _Overloads()
69
72
  self._imports = imports or _Imports()
70
- self._parse_ast(
71
- node,
72
- directives or _Directives(),
73
- path,
74
- ignore_args,
75
- ignore_kwargs,
76
- check_class_constructor,
77
- )
73
+ self._parse_ast(node, directives or _Directives(), path)
78
74
 
79
75
  def _parse_imports(self, names: list[tuple[str, str | None]]) -> None:
80
76
  for name in names:
81
77
  original, alias = name
82
78
  self._imports[original] = alias or original
83
79
 
84
- def _parse_ast( # pylint: disable=protected-access,too-many-arguments
80
+ def _parse_ast(
85
81
  self,
86
82
  node: _ast.Module | _ast.ClassDef | _ast.FunctionDef | _ast.NodeNG,
87
83
  directives: _Directives,
88
84
  path: _Path | None = None,
89
- ignore_args: bool = False,
90
- ignore_kwargs: bool = False,
91
- check_class_constructor: bool = False,
92
85
  ) -> None:
93
86
  # need to keep track of `comments` as, even though they are
94
87
  # resolved in directives object, they are needed to notify user
@@ -112,9 +105,9 @@ class Parent(_t.List["Parent"]):
112
105
  directives,
113
106
  disabled,
114
107
  path,
115
- ignore_args,
116
- ignore_kwargs,
117
- check_class_constructor,
108
+ self._ignore_args,
109
+ self._ignore_kwargs,
110
+ self._check_class_constructor,
118
111
  self._imports,
119
112
  )
120
113
  if func.isoverloaded:
@@ -137,21 +130,14 @@ class Parent(_t.List["Parent"]):
137
130
  subnode,
138
131
  directives,
139
132
  path,
140
- ignore_args,
141
- ignore_kwargs,
142
- check_class_constructor,
133
+ self._ignore_args,
134
+ self._ignore_kwargs,
135
+ self._check_class_constructor,
143
136
  self._imports,
144
137
  )
145
138
  )
146
139
  else:
147
- self._parse_ast(
148
- subnode,
149
- directives,
150
- path,
151
- ignore_args,
152
- ignore_kwargs,
153
- check_class_constructor,
154
- )
140
+ self._parse_ast(subnode, directives, path)
155
141
 
156
142
  @property
157
143
  def isprotected(self) -> bool:
@@ -216,13 +202,14 @@ class Function(Parent):
216
202
  self._parent = node.parent.frame()
217
203
  self._decorators = node.decorators
218
204
  self._lineno = node.lineno
205
+ if self.ismethod and not self.isstaticmethod:
206
+ if node.args.posonlyargs:
207
+ node.args.posonlyargs.pop(0)
208
+ elif node.args.args:
209
+ node.args.args.pop(0)
210
+
219
211
  self._signature = self._signature.from_ast(
220
- node.args,
221
- node.returns,
222
- self.ismethod,
223
- self.isstaticmethod,
224
- ignore_args,
225
- ignore_kwargs,
212
+ node, ignore_args, ignore_kwargs
226
213
  )
227
214
  if self.isinit and not check_class_constructor:
228
215
  # docstring for __init__ is expected on the class
@@ -232,9 +219,7 @@ class Function(Parent):
232
219
  relevant_doc_node = node.doc_node
233
220
 
234
221
  if relevant_doc_node is not None:
235
- self._docstring = self.docstring.from_ast(
236
- relevant_doc_node, ignore_kwargs
237
- )
222
+ self._docstring = self.docstring.from_ast(relevant_doc_node)
238
223
 
239
224
  def __len__(self) -> int:
240
225
  """Length of the longest sequence of args."""
@@ -51,6 +51,7 @@ class Failure(_t.List[Failed]):
51
51
  ignore_typechecker: bool,
52
52
  ) -> None:
53
53
  super().__init__()
54
+ self._retcode = 0
54
55
  self._func = func
55
56
  if target:
56
57
  self._func.messages.extend(i for i in _E.all if i not in target)
@@ -79,6 +80,7 @@ class Failure(_t.List[Failed]):
79
80
  self.sort()
80
81
 
81
82
  def _add(self, value: _Message, hint: bool = False, **kwargs) -> None:
83
+ self._retcode = 1
82
84
  failed = Failed(
83
85
  self._name,
84
86
  value.ref,
@@ -236,6 +238,7 @@ class Failure(_t.List[Failed]):
236
238
  # invalid-syntax
237
239
  if self._func.error == _Error.SYNTAX:
238
240
  self._add(_E[901])
241
+ self._retcode = 123
239
242
 
240
243
  @property
241
244
  def name(self) -> str:
@@ -247,6 +250,11 @@ class Failure(_t.List[Failed]):
247
250
  """Function line number."""
248
251
  return self._func.lineno
249
252
 
253
+ @property
254
+ def retcode(self) -> int:
255
+ """How to exit the program."""
256
+ return self._retcode
257
+
250
258
 
251
259
  class Failures(_t.List[Failure]):
252
260
  """Sequence of failed functions."""
@@ -15,7 +15,7 @@ import astroid as _ast
15
15
  import sphinx.ext.napoleon as _s
16
16
 
17
17
  # no function will accidentally have this name
18
- UNNAMED = -1000
18
+ UNNAMED = "-1000"
19
19
 
20
20
  # an example of valid parameter description
21
21
  VALID_DESCRIPTION = " A valid description."
@@ -103,43 +103,6 @@ class Param(_t.NamedTuple):
103
103
  return str(self.name).startswith("_")
104
104
 
105
105
 
106
- class _Matches(_t.List[Param]):
107
- _pattern = _re.compile(":(.*?):")
108
-
109
- def __init__(
110
- self, string: str, indent_anomaly: bool, missing_descriptions: bool
111
- ) -> None:
112
- super().__init__()
113
- for line in string.splitlines():
114
- strip_line = line.lstrip()
115
- match = self._pattern.split(strip_line)[1:]
116
- if match:
117
- description = None
118
- kinds = match[0].split()
119
- if kinds:
120
- kind = DocType.from_str(kinds[0])
121
-
122
- if len(kinds) > 1:
123
- name = kinds[1]
124
- else:
125
- # name could not be parsed
126
- name = UNNAMED
127
-
128
- if len(match) > 1:
129
- second = match[1]
130
- if second != "" or not missing_descriptions:
131
- description = second
132
-
133
- super().append(
134
- Param(
135
- kind,
136
- name,
137
- description,
138
- int(indent_anomaly),
139
- )
140
- )
141
-
142
-
143
106
  class _Params(_t.List[Param]):
144
107
  def __init__(
145
108
  self, ignore_args: bool = False, ignore_kwargs: bool = False
@@ -149,15 +112,18 @@ class _Params(_t.List[Param]):
149
112
  self._ignore_kwargs = ignore_kwargs
150
113
  self._duplicates: list[Param] = []
151
114
 
152
- # pylint: disable=too-many-boolean-expressions
153
115
  def append(self, value: Param) -> None:
154
- if not value.isprotected and (
155
- value.kind == DocType.PARAM
156
- or (value.kind == DocType.ARG and not self._ignore_args)
157
- or (
158
- value.kind == DocType.KWARG
159
- and not self._ignore_kwargs
160
- and not any(i.kind == DocType.KWARG for i in self)
116
+ if not value.isprotected and any(
117
+ (
118
+ value.kind == DocType.PARAM,
119
+ (value.kind == DocType.ARG and not self._ignore_args),
120
+ (
121
+ value.kind == DocType.KWARG
122
+ and not (
123
+ self._ignore_kwargs
124
+ or any(i.kind == DocType.KWARG for i in self)
125
+ )
126
+ ),
161
127
  )
162
128
  ):
163
129
  super().append(value)
@@ -226,6 +192,7 @@ class _Stub:
226
192
  class Signature(_Stub):
227
193
  """Represents a function signature.
228
194
 
195
+ :param rettype: The return type.
229
196
  :param returns: Returns declared in signature.
230
197
  :param ignore_args: Ignore args prefixed with an asterisk.
231
198
  :param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
@@ -233,51 +200,40 @@ class Signature(_Stub):
233
200
 
234
201
  def __init__(
235
202
  self,
236
- returns: _ast.Module | str | None = None,
203
+ rettype: RetType = RetType.NONE,
204
+ returns: bool = False,
237
205
  ignore_args: bool = False,
238
206
  ignore_kwargs: bool = False,
239
207
  ) -> None:
240
208
  super().__init__(ignore_args, ignore_kwargs)
241
-
242
- self._rettype = RetType.from_ast(returns)
243
- self._returns = self._rettype == RetType.SOME
209
+ self._rettype = rettype
210
+ self._returns = returns
244
211
 
245
212
  @classmethod
246
- def from_ast( # pylint: disable=too-many-arguments
213
+ def from_ast(
247
214
  cls,
248
- arguments: _ast.Arguments,
249
- returns: _ast.Module | str,
250
- ismethod: bool = False,
251
- isstaticmethod: bool = False,
215
+ node: _ast.Module | _ast.ClassDef | _ast.FunctionDef,
252
216
  ignore_args: bool = False,
253
217
  ignore_kwargs: bool = False,
254
218
  ) -> Signature:
255
219
  """Parse signature from ast.
256
220
 
257
- :param arguments: Arguments provided to signature.
258
- :param returns: Returns declared in signature.
259
- :param ismethod: Whether this signature belongs to a method.
260
- :param isstaticmethod: Whether this signature belongs to a
261
- static method.
221
+ :param node: Abstract syntax tree.
262
222
  :param ignore_args: Ignore args prefixed with an asterisk.
263
223
  :param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
264
224
  :return: Instantiated signature object.
265
225
  """
266
- if ismethod and not isstaticmethod:
267
- if arguments.posonlyargs:
268
- arguments.posonlyargs.pop(0)
269
- elif arguments.args:
270
- arguments.args.pop(0)
271
-
272
- signature = cls(returns, ignore_args, ignore_kwargs)
226
+ rettype = RetType.from_ast(node.returns)
227
+ returns = rettype == RetType.SOME
228
+ signature = cls(rettype, returns, ignore_args, ignore_kwargs)
273
229
  for i in [
274
230
  a if isinstance(a, Param) else Param(name=a.name)
275
231
  for a in [
276
- *arguments.posonlyargs,
277
- *arguments.args,
278
- Param(DocType.ARG, name=arguments.vararg),
279
- *arguments.kwonlyargs,
280
- Param(DocType.KWARG, name=arguments.kwarg),
232
+ *node.args.posonlyargs,
233
+ *node.args.args,
234
+ Param(DocType.ARG, name=node.args.vararg),
235
+ *node.args.kwonlyargs,
236
+ Param(DocType.KWARG, name=node.args.kwarg),
281
237
  ]
282
238
  if a is not None and a.name
283
239
  ]:
@@ -307,13 +263,11 @@ class Docstring(_Stub):
307
263
  """Represents a function docstring.
308
264
 
309
265
  :param string: The raw docstring.
310
- :param ignore_args: Ignore args prefixed with an asterisk.
311
- :param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
266
+ :param returns: Whether this docstring has a return.
312
267
  """
313
268
 
314
269
  @staticmethod
315
270
  def _indent_anomaly(string: str) -> bool:
316
- leading_spaces = []
317
271
  for line in string.splitlines():
318
272
  # only check params
319
273
  # description or anything else is out of scope
@@ -322,28 +276,9 @@ class Docstring(_Stub):
322
276
  if match is not None:
323
277
  spaces = len(match.group())
324
278
  if spaces > 0:
325
- leading_spaces.append(spaces)
279
+ return spaces % 2 != 0
326
280
 
327
- # look for spaces in odd intervals
328
- return bool(any(i % 2 != 0 for i in leading_spaces))
329
-
330
- @staticmethod
331
- def _missing_descriptions(string: str) -> bool:
332
- # find out if parameter is missing a description
333
- new = ""
334
- for line in string.strip().splitlines()[2:]:
335
- line = line.lstrip()
336
- if not line.startswith(":"):
337
- # it is not a parameter, it is a next line description
338
- # append the next entry to the same line
339
- new = f"{new[:-1]} "
340
- new += f"{line}\n"
341
- if not new:
342
- return True
343
-
344
- # if it ends with a colon, it's a parameter without a
345
- # description
346
- return any(i.endswith(":") for i in new.splitlines())
281
+ return False
347
282
 
348
283
  @staticmethod
349
284
  def _normalize_docstring(string: str) -> str:
@@ -362,35 +297,35 @@ class Docstring(_Stub):
362
297
  )
363
298
 
364
299
  def __init__(
365
- self,
366
- string: str | None = None,
367
- ignore_args: bool = False,
368
- ignore_kwargs: bool = False,
300
+ self, string: str | None = None, returns: bool = False
369
301
  ) -> None:
370
- super().__init__(ignore_args, ignore_kwargs)
302
+ super().__init__()
371
303
  self._string = string
372
- self._returns = self._string is not None and bool(
373
- _re.search(":returns?:", self._string)
374
- )
304
+ self._returns = returns
375
305
 
376
306
  @classmethod
377
- def from_ast(
378
- cls, node: _ast.Const, ignore_kwargs: bool = False
379
- ) -> Docstring:
307
+ def from_ast(cls, node: _ast.Const) -> Docstring:
380
308
  """Parse function docstring from ast.
381
309
 
382
310
  :param node: Docstring ast node.
383
- :param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
384
311
  :return: Instantiated docstring object.
385
312
  """
313
+ indent_anomaly = cls._indent_anomaly(node.value)
386
314
  string = cls._normalize_docstring(node.value)
387
- docstring = cls(string, ignore_kwargs)
388
- for i in _Matches(
389
- string,
390
- cls._indent_anomaly(node.value),
391
- cls._missing_descriptions(node.value),
392
- ):
393
- docstring.args.append(i)
315
+ returns = bool(_re.search(r":returns?:", string))
316
+ docstring = cls(string, returns)
317
+ for match in _re.findall(r":(.*?):((?:.|\n)*?)(?=\n:|$)", string):
318
+ if match:
319
+ kinds = match[0].split()
320
+ if kinds:
321
+ docstring.args.append(
322
+ Param(
323
+ DocType.from_str(kinds[0]),
324
+ UNNAMED if len(kinds) == 1 else kinds[1],
325
+ match[1] or None,
326
+ int(indent_anomaly),
327
+ )
328
+ )
394
329
 
395
330
  return docstring
396
331
 
@@ -26,16 +26,6 @@ def almost_equal(str1: str, str2: str, mini: float, maxi: float) -> bool:
26
26
  return mini < _SequenceMatcher(a=str1, b=str2).ratio() < maxi
27
27
 
28
28
 
29
- def vprint(msg: str, verbose: bool = False) -> None:
30
- """Print messages only if verbose is true.
31
-
32
- :param msg: Message to print.
33
- :param verbose: Whether verbose mode is enabled.
34
- """
35
- if verbose:
36
- print(msg)
37
-
38
-
39
29
  def pretty_print_error(
40
30
  exception_type: _t.Type[BaseException], msg: str, no_ansi: bool
41
31
  ) -> None:
@@ -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.62.0"
11
+ __version__ = "0.63.0"
@@ -6,7 +6,7 @@ from argparse import Namespace
6
6
 
7
7
  from ._config import get_config as _get_config
8
8
  from ._config import merge_configs as _merge_configs
9
- from ._core import runner
9
+ from ._core import runner, setup_logger
10
10
  from ._version import __version__
11
11
  from .messages import FLAKE8, E
12
12
 
@@ -141,10 +141,11 @@ class Docsig:
141
141
  ref=E[5].ref,
142
142
  description=E[5].description,
143
143
  symbolic=E[5].symbolic,
144
- ),
144
+ )
145
145
  )
146
146
  yield 0, 0, line, self.__class__
147
147
  else:
148
+ setup_logger(self.a.verbose)
148
149
  results = runner(
149
150
  self.filename,
150
151
  check_class=self.a.check_class,
@@ -161,7 +162,6 @@ class Docsig:
161
162
  ignore_args=self.a.ignore_args,
162
163
  ignore_kwargs=self.a.ignore_kwargs,
163
164
  ignore_typechecker=self.a.ignore_typechecker,
164
- verbose=self.a.verbose,
165
165
  )
166
166
  for result in results:
167
167
  for info in result:
@@ -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.62.0"
14
+ current_version = "0.63.0"
15
15
  message = "bump: version {current_version} → {new_version}"
16
16
  sign_tags = true
17
17
  tag = true
@@ -131,7 +131,7 @@ maintainers = [
131
131
  name = "docsig"
132
132
  readme = "README.rst"
133
133
  repository = "https://github.com/jshwi/docsig"
134
- version = "0.62.0"
134
+ version = "0.63.0"
135
135
 
136
136
  [tool.poetry.dependencies]
137
137
  Sphinx = "^7.0.0"
@@ -139,7 +139,7 @@ astroid = "^3.0.1"
139
139
  pathspec = "^0.12.1"
140
140
  python = "^3.8.1"
141
141
  tomli = "^2.0.1"
142
- wcmatch = ">=8.5.2,<10.0.0"
142
+ wcmatch = ">=8.5.2,<11.0.0"
143
143
 
144
144
  [tool.poetry.group.dev.dependencies]
145
145
  black = "^24.4.2"
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