docsig 0.62.1__tar.gz → 0.64.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.1
3
+ Version: 0.64.0
4
4
  Summary: Check signature params for proper documentation
5
5
  Home-page: https://pypi.org/project/docsig/
6
6
  License: MIT
@@ -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.1, mccabe: 0.7.0, pycodestyle: 2.12.0, pyflakes: 3.2.0) CPython 3.8.13 on Darwin
183
+ 7.1.0 (docsig: 0.64.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.1
272
+ rev: v0.64.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.1
291
+ - docsig==0.64.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.1, mccabe: 0.7.0, pycodestyle: 2.12.0, pyflakes: 3.2.0) CPython 3.8.13 on Darwin
156
+ 7.1.0 (docsig: 0.64.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.1
245
+ rev: v0.64.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.1
264
+ - docsig==0.64.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,
@@ -118,23 +133,44 @@ def _run_check( # pylint: disable=too-many-arguments,too-many-locals
118
133
  )
119
134
 
120
135
 
121
- def _parse_ast( # pylint: disable=too-many-arguments
136
+ def _from_file(
137
+ root: _Path,
122
138
  messages: _Messages,
123
139
  ignore_args: bool,
124
140
  ignore_kwargs: bool,
125
141
  check_class_constructor,
126
- verbose: bool,
127
- root: _Path | None = None,
128
- string: str | None = None,
129
142
  ) -> _Parent:
130
- parent = _Parent()
131
143
  try:
132
- if root is not None:
133
- string = root.read_text(encoding="utf-8")
144
+ string = root.read_text(encoding="utf-8")
145
+ parent = _from_str(
146
+ messages=messages,
147
+ string=string,
148
+ root=root,
149
+ ignore_args=ignore_args,
150
+ ignore_kwargs=ignore_kwargs,
151
+ check_class_constructor=check_class_constructor,
152
+ )
153
+ except UnicodeDecodeError as err:
154
+ logger = _logging.getLogger(__package__)
155
+ logger.debug(_FILE_INFO, root, str(err).replace("\n", " "))
156
+ parent = _Parent(error=_Error.UNICODE)
134
157
 
135
- # empty string won't happen but keeps the
136
- # typechecker happy
137
- string = string or ""
158
+ if parent.error is not None and not root.name.endswith(".py"):
159
+ parent = _Parent()
160
+
161
+ return parent
162
+
163
+
164
+ def _from_str( # pylint: disable=too-many-arguments
165
+ string: str,
166
+ messages: _Messages,
167
+ ignore_args: bool,
168
+ ignore_kwargs: bool,
169
+ check_class_constructor,
170
+ root: _Path | None = None,
171
+ ) -> _Parent:
172
+ logger = _logging.getLogger(__package__)
173
+ try:
138
174
  parent = _Parent(
139
175
  _ast.parse(string),
140
176
  _Directives.from_text(string, messages),
@@ -143,29 +179,17 @@ def _parse_ast( # pylint: disable=too-many-arguments
143
179
  ignore_kwargs,
144
180
  check_class_constructor,
145
181
  )
146
- _vprint(
147
- _FILE_INFO.format(
148
- path=root or "stdin", msg="Parsing Python code successful"
149
- ),
150
- verbose,
151
- )
152
- return parent
153
- except (_ast.AstroidSyntaxError, UnicodeDecodeError) as err:
154
- msg = str(err).replace("\n", " ")
155
- 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
- parent = _Parent(error=_Error.SYNTAX)
159
-
160
- _vprint(
161
- _FILE_INFO.format(path=root or "stdin", msg=msg),
162
- verbose,
182
+ logger.debug(
183
+ _FILE_INFO, root or "stdin", "Parsing Python code successful"
163
184
  )
185
+ except _ast.AstroidSyntaxError as err:
186
+ logger.debug(_FILE_INFO, root or "stdin", str(err).replace("\n", " "))
187
+ parent = _Parent(error=_Error.SYNTAX)
164
188
 
165
189
  return parent
166
190
 
167
191
 
168
- def _get_failures( # pylint: disable=too-many-locals,too-many-arguments
192
+ def _get_failures( # pylint: disable=too-many-arguments
169
193
  module: _Parent,
170
194
  check_class: bool,
171
195
  check_class_constructor: bool,
@@ -209,8 +233,10 @@ def _get_failures( # pylint: disable=too-many-locals,too-many-arguments
209
233
 
210
234
  def _report(
211
235
  failures: _Failures, path: str | None = None, no_ansi: bool = False
212
- ) -> None:
236
+ ) -> int:
237
+ retcodes = [0]
213
238
  for failure in failures:
239
+ retcodes.append(failure.retcode)
214
240
  module = f"{path}:" if path is not None else ""
215
241
  header = f"{module}{failure.lineno} in {failure.name}"
216
242
  if not no_ansi and _sys.stdout.isatty():
@@ -229,6 +255,8 @@ def _report(
229
255
  if item.hint:
230
256
  print(f" hint: {item.hint}")
231
257
 
258
+ return max(retcodes)
259
+
232
260
 
233
261
  def runner( # pylint: disable=too-many-locals,too-many-arguments
234
262
  file: str | _Path,
@@ -246,7 +274,6 @@ def runner( # pylint: disable=too-many-locals,too-many-arguments
246
274
  ignore_typechecker: bool = False,
247
275
  check_protected_class_methods: bool = False,
248
276
  no_ansi: bool = False,
249
- verbose: bool = False,
250
277
  target: _Messages | None = None,
251
278
  ) -> _Failures:
252
279
  """Per path runner.
@@ -269,37 +296,31 @@ def runner( # pylint: disable=too-many-locals,too-many-arguments
269
296
  :param check_protected_class_methods: Check public methods belonging
270
297
  to protected classes.
271
298
  :param no_ansi: Disable ANSI output.
272
- :param verbose: increase output verbosity.
273
299
  :param target: List of errors to target.
274
300
  :return: Exit status for whether test failed or not.
275
301
  """
276
- failures = _Failures()
277
- module = _parse_ast(
302
+ module = _from_file(
303
+ _Path(file),
278
304
  disable or _Messages(),
279
305
  ignore_args,
280
306
  ignore_kwargs,
281
307
  check_class_constructor,
282
- verbose,
283
- root=_Path(file),
284
308
  )
285
- if module or module.error is not None:
286
- failures = _get_failures(
287
- module,
288
- check_class,
289
- check_class_constructor,
290
- check_dunders,
291
- check_nested,
292
- check_overridden,
293
- check_protected,
294
- check_property_returns,
295
- ignore_no_params,
296
- ignore_typechecker,
297
- check_protected_class_methods,
298
- no_ansi,
299
- target or _Messages(),
300
- )
301
-
302
- return failures
309
+ return _get_failures(
310
+ module,
311
+ check_class,
312
+ check_class_constructor,
313
+ check_dunders,
314
+ check_nested,
315
+ check_overridden,
316
+ check_protected,
317
+ check_property_returns,
318
+ ignore_no_params,
319
+ ignore_typechecker,
320
+ check_protected_class_methods,
321
+ no_ansi,
322
+ target or _Messages(),
323
+ )
303
324
 
304
325
 
305
326
  @_decorators.parse_msgs
@@ -367,6 +388,7 @@ def docsig( # pylint: disable=too-many-locals,too-many-arguments
367
388
  :param excludes: Files or dirs to exclude from checks.
368
389
  :return: Exit status for whether test failed or not.
369
390
  """
391
+ setup_logger(verbose)
370
392
  if list_checks:
371
393
  return int(bool(_print_checks())) # type: ignore
372
394
 
@@ -375,13 +397,12 @@ def docsig( # pylint: disable=too-many-locals,too-many-arguments
375
397
  patterns.append(exclude)
376
398
 
377
399
  if string is None:
378
- retcode = 0
400
+ retcodes = [0]
379
401
  paths = _Paths(
380
- *tuple(_Path(i) for i in path),
402
+ *path,
381
403
  patterns=patterns,
382
404
  excludes=excludes or [],
383
405
  include_ignored=include_ignored,
384
- verbose=verbose,
385
406
  )
386
407
  for file in paths:
387
408
  failures = runner(
@@ -400,41 +421,32 @@ def docsig( # pylint: disable=too-many-locals,too-many-arguments
400
421
  ignore_typechecker,
401
422
  check_protected_class_methods,
402
423
  no_ansi,
403
- verbose,
404
424
  target,
405
425
  )
406
- if failures:
407
- _report(failures, str(file), no_ansi)
408
- retcode = 1
426
+ retcodes.append(_report(failures, str(file), no_ansi))
409
427
 
410
- return retcode
428
+ return max(retcodes)
411
429
 
412
- module = _parse_ast(
430
+ module = _from_str(
431
+ string,
413
432
  disable or _Messages(),
414
433
  ignore_args,
415
434
  ignore_kwargs,
416
435
  check_class_constructor,
417
- verbose,
418
- string=string,
419
436
  )
420
- if module or module.error is not None:
421
- failures = _get_failures(
422
- module,
423
- check_class,
424
- check_class_constructor,
425
- check_dunders,
426
- check_nested,
427
- check_overridden,
428
- check_protected,
429
- check_property_returns,
430
- ignore_no_params,
431
- ignore_typechecker,
432
- check_protected_class_methods,
433
- no_ansi,
434
- target or _Messages(),
435
- )
436
- if failures:
437
- _report(failures, no_ansi=no_ansi)
438
- return 1
439
-
440
- return 0
437
+ failures = _get_failures(
438
+ module,
439
+ check_class,
440
+ check_class_constructor,
441
+ check_dunders,
442
+ check_nested,
443
+ check_overridden,
444
+ check_protected,
445
+ check_property_returns,
446
+ ignore_no_params,
447
+ ignore_typechecker,
448
+ check_protected_class_methods,
449
+ no_ansi,
450
+ target or _Messages(),
451
+ )
452
+ return _report(failures, no_ansi=no_ansi)
@@ -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,36 +76,31 @@ 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
- *paths: _Path,
83
+ *paths: _Path | str,
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
- self._excludes = excludes
90
+ self._excludes = excludes or []
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
- self._populate(path)
95
+ self._populate(_Path(path))
99
96
 
100
97
  for path in list(self):
101
98
  if str(path) != "." and (
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():
@@ -24,6 +24,7 @@ class Error(_Enum):
24
24
  """Represents an unrecoverable error."""
25
25
 
26
26
  SYNTAX = 1
27
+ UNICODE = 2
27
28
 
28
29
 
29
30
  class _Imports(_t.Dict[str, str]):
@@ -60,6 +61,9 @@ class Parent(_t.List["Parent"]):
60
61
  super().__init__()
61
62
  self._name = "module"
62
63
  self._error = error
64
+ self._ignore_args = ignore_args
65
+ self._ignore_kwargs = ignore_kwargs
66
+ self._check_class_constructor = check_class_constructor
63
67
  if node is None:
64
68
  if not isinstance(self, Function) and error is not None:
65
69
  self.append(Function(path, error=error))
@@ -67,28 +71,13 @@ class Parent(_t.List["Parent"]):
67
71
  self._name = node.name
68
72
  self._overloads = _Overloads()
69
73
  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
- )
78
-
79
- def _parse_imports(self, names: list[tuple[str, str | None]]) -> None:
80
- for name in names:
81
- original, alias = name
82
- self._imports[original] = alias or original
74
+ self._parse_ast(node, directives or _Directives(), path)
83
75
 
84
- def _parse_ast( # pylint: disable=protected-access,too-many-arguments
76
+ def _parse_ast(
85
77
  self,
86
78
  node: _ast.Module | _ast.ClassDef | _ast.FunctionDef | _ast.NodeNG,
87
79
  directives: _Directives,
88
80
  path: _Path | None = None,
89
- ignore_args: bool = False,
90
- ignore_kwargs: bool = False,
91
- check_class_constructor: bool = False,
92
81
  ) -> None:
93
82
  # need to keep track of `comments` as, even though they are
94
83
  # resolved in directives object, they are needed to notify user
@@ -104,7 +93,9 @@ class Parent(_t.List["Parent"]):
104
93
  comments.extend(parent_comments)
105
94
  disabled.extend(parent_disabled)
106
95
  if isinstance(subnode, (_ast.Import, _ast.ImportFrom)):
107
- self._parse_imports(subnode.names)
96
+ for name in subnode.names:
97
+ original, alias = name
98
+ self._imports[original] = alias or original
108
99
  elif isinstance(subnode, _ast.FunctionDef):
109
100
  func = Function(
110
101
  subnode,
@@ -112,9 +103,9 @@ class Parent(_t.List["Parent"]):
112
103
  directives,
113
104
  disabled,
114
105
  path,
115
- ignore_args,
116
- ignore_kwargs,
117
- check_class_constructor,
106
+ self._ignore_args,
107
+ self._ignore_kwargs,
108
+ self._check_class_constructor,
118
109
  self._imports,
119
110
  )
120
111
  if func.isoverloaded:
@@ -137,21 +128,14 @@ class Parent(_t.List["Parent"]):
137
128
  subnode,
138
129
  directives,
139
130
  path,
140
- ignore_args,
141
- ignore_kwargs,
142
- check_class_constructor,
131
+ self._ignore_args,
132
+ self._ignore_kwargs,
133
+ self._check_class_constructor,
143
134
  self._imports,
144
135
  )
145
136
  )
146
137
  else:
147
- self._parse_ast(
148
- subnode,
149
- directives,
150
- path,
151
- ignore_args,
152
- ignore_kwargs,
153
- check_class_constructor,
154
- )
138
+ self._parse_ast(subnode, directives, path)
155
139
 
156
140
  @property
157
141
  def isprotected(self) -> bool:
@@ -216,13 +200,14 @@ class Function(Parent):
216
200
  self._parent = node.parent.frame()
217
201
  self._decorators = node.decorators
218
202
  self._lineno = node.lineno
203
+ if self.ismethod and not self.isstaticmethod:
204
+ if node.args.posonlyargs:
205
+ node.args.posonlyargs.pop(0)
206
+ elif node.args.args:
207
+ node.args.args.pop(0)
208
+
219
209
  self._signature = self._signature.from_ast(
220
- node.args,
221
- node.returns,
222
- self.ismethod,
223
- self.isstaticmethod,
224
- ignore_args,
225
- ignore_kwargs,
210
+ node, ignore_args, ignore_kwargs
226
211
  )
227
212
  if self.isinit and not check_class_constructor:
228
213
  # docstring for __init__ is expected on the class
@@ -232,9 +217,7 @@ class Function(Parent):
232
217
  relevant_doc_node = node.doc_node
233
218
 
234
219
  if relevant_doc_node is not None:
235
- self._docstring = self.docstring.from_ast(
236
- relevant_doc_node, ignore_kwargs
237
- )
220
+ self._docstring = self.docstring.from_ast(relevant_doc_node)
238
221
 
239
222
  def __len__(self) -> int:
240
223
  """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,9 @@ 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
242
+ if self._func.error == _Error.UNICODE:
243
+ self._add(_E[902])
239
244
 
240
245
  @property
241
246
  def name(self) -> str:
@@ -247,6 +252,11 @@ class Failure(_t.List[Failed]):
247
252
  """Function line number."""
248
253
  return self._func.lineno
249
254
 
255
+ @property
256
+ def retcode(self) -> int:
257
+ """How to exit the program."""
258
+ return self._retcode
259
+
250
260
 
251
261
  class Failures(_t.List[Failure]):
252
262
  """Sequence of failed functions."""
@@ -112,15 +112,18 @@ class _Params(_t.List[Param]):
112
112
  self._ignore_kwargs = ignore_kwargs
113
113
  self._duplicates: list[Param] = []
114
114
 
115
- # pylint: disable=too-many-boolean-expressions
116
115
  def append(self, value: Param) -> None:
117
- if not value.isprotected and (
118
- value.kind == DocType.PARAM
119
- or (value.kind == DocType.ARG and not self._ignore_args)
120
- or (
121
- value.kind == DocType.KWARG
122
- and not self._ignore_kwargs
123
- 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
+ ),
124
127
  )
125
128
  ):
126
129
  super().append(value)
@@ -189,6 +192,7 @@ class _Stub:
189
192
  class Signature(_Stub):
190
193
  """Represents a function signature.
191
194
 
195
+ :param rettype: The return type.
192
196
  :param returns: Returns declared in signature.
193
197
  :param ignore_args: Ignore args prefixed with an asterisk.
194
198
  :param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
@@ -196,51 +200,40 @@ class Signature(_Stub):
196
200
 
197
201
  def __init__(
198
202
  self,
199
- returns: _ast.Module | str | None = None,
203
+ rettype: RetType = RetType.NONE,
204
+ returns: bool = False,
200
205
  ignore_args: bool = False,
201
206
  ignore_kwargs: bool = False,
202
207
  ) -> None:
203
208
  super().__init__(ignore_args, ignore_kwargs)
204
-
205
- self._rettype = RetType.from_ast(returns)
206
- self._returns = self._rettype == RetType.SOME
209
+ self._rettype = rettype
210
+ self._returns = returns
207
211
 
208
212
  @classmethod
209
- def from_ast( # pylint: disable=too-many-arguments
213
+ def from_ast(
210
214
  cls,
211
- arguments: _ast.Arguments,
212
- returns: _ast.Module | str,
213
- ismethod: bool = False,
214
- isstaticmethod: bool = False,
215
+ node: _ast.Module | _ast.ClassDef | _ast.FunctionDef,
215
216
  ignore_args: bool = False,
216
217
  ignore_kwargs: bool = False,
217
218
  ) -> Signature:
218
219
  """Parse signature from ast.
219
220
 
220
- :param arguments: Arguments provided to signature.
221
- :param returns: Returns declared in signature.
222
- :param ismethod: Whether this signature belongs to a method.
223
- :param isstaticmethod: Whether this signature belongs to a
224
- static method.
221
+ :param node: Abstract syntax tree.
225
222
  :param ignore_args: Ignore args prefixed with an asterisk.
226
223
  :param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
227
224
  :return: Instantiated signature object.
228
225
  """
229
- if ismethod and not isstaticmethod:
230
- if arguments.posonlyargs:
231
- arguments.posonlyargs.pop(0)
232
- elif arguments.args:
233
- arguments.args.pop(0)
234
-
235
- 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)
236
229
  for i in [
237
230
  a if isinstance(a, Param) else Param(name=a.name)
238
231
  for a in [
239
- *arguments.posonlyargs,
240
- *arguments.args,
241
- Param(DocType.ARG, name=arguments.vararg),
242
- *arguments.kwonlyargs,
243
- 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),
244
237
  ]
245
238
  if a is not None and a.name
246
239
  ]:
@@ -270,13 +263,11 @@ class Docstring(_Stub):
270
263
  """Represents a function docstring.
271
264
 
272
265
  :param string: The raw docstring.
273
- :param ignore_args: Ignore args prefixed with an asterisk.
274
- :param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
266
+ :param returns: Whether this docstring has a return.
275
267
  """
276
268
 
277
269
  @staticmethod
278
270
  def _indent_anomaly(string: str) -> bool:
279
- leading_spaces = []
280
271
  for line in string.splitlines():
281
272
  # only check params
282
273
  # description or anything else is out of scope
@@ -285,10 +276,9 @@ class Docstring(_Stub):
285
276
  if match is not None:
286
277
  spaces = len(match.group())
287
278
  if spaces > 0:
288
- leading_spaces.append(spaces)
279
+ return spaces % 2 != 0
289
280
 
290
- # look for spaces in odd intervals
291
- return bool(any(i % 2 != 0 for i in leading_spaces))
281
+ return False
292
282
 
293
283
  @staticmethod
294
284
  def _normalize_docstring(string: str) -> str:
@@ -307,30 +297,23 @@ class Docstring(_Stub):
307
297
  )
308
298
 
309
299
  def __init__(
310
- self,
311
- string: str | None = None,
312
- ignore_args: bool = False,
313
- ignore_kwargs: bool = False,
300
+ self, string: str | None = None, returns: bool = False
314
301
  ) -> None:
315
- super().__init__(ignore_args, ignore_kwargs)
302
+ super().__init__()
316
303
  self._string = string
317
- self._returns = self._string is not None and bool(
318
- _re.search(":returns?:", self._string)
319
- )
304
+ self._returns = returns
320
305
 
321
306
  @classmethod
322
- def from_ast(
323
- cls, node: _ast.Const, ignore_kwargs: bool = False
324
- ) -> Docstring:
307
+ def from_ast(cls, node: _ast.Const) -> Docstring:
325
308
  """Parse function docstring from ast.
326
309
 
327
310
  :param node: Docstring ast node.
328
- :param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
329
311
  :return: Instantiated docstring object.
330
312
  """
331
- string = cls._normalize_docstring(node.value)
332
- docstring = cls(string, ignore_kwargs)
333
313
  indent_anomaly = cls._indent_anomaly(node.value)
314
+ string = cls._normalize_docstring(node.value)
315
+ returns = bool(_re.search(r":returns?:", string))
316
+ docstring = cls(string, returns)
334
317
  for match in _re.findall(r":(.*?):((?:.|\n)*?)(?=\n:|$)", string):
335
318
  if match:
336
319
  kinds = match[0].split()
@@ -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.1"
11
+ __version__ = "0.64.0"
@@ -254,5 +254,11 @@ E = MessageMap(
254
254
  "parsing python code failed",
255
255
  "invalid-syntax",
256
256
  ),
257
+ 902: Message(
258
+ "SIG902",
259
+ "SIG902",
260
+ "failed to read file",
261
+ "unicode-decode-error",
262
+ ),
257
263
  }
258
264
  )
@@ -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.1"
14
+ current_version = "0.64.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.1"
134
+ version = "0.64.0"
135
135
 
136
136
  [tool.poetry.dependencies]
137
137
  Sphinx = "^7.0.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes