docsig 0.56.0__tar.gz → 0.58.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.56.0
3
+ Version: 0.58.0
4
4
  Summary: Check signature params for proper documentation
5
5
  Home-page: https://pypi.org/project/docsig/
6
6
  License: MIT
@@ -9,10 +9,9 @@ Author: jshwi
9
9
  Author-email: stephen@jshwisolutions.com
10
10
  Maintainer: jshwi
11
11
  Maintainer-email: stephen@jshwisolutions.com
12
- Requires-Python: >=3.8,<4.0
12
+ Requires-Python: >=3.8.1,<4.0.0
13
13
  Classifier: License :: OSI Approved :: MIT License
14
14
  Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.8
16
15
  Classifier: Programming Language :: Python :: 3.9
17
16
  Classifier: Programming Language :: Python :: 3.10
18
17
  Classifier: Programming Language :: Python :: 3.11
@@ -20,7 +19,6 @@ Classifier: Programming Language :: Python :: 3.12
20
19
  Requires-Dist: Sphinx (>=7.0.0,<8.0.0)
21
20
  Requires-Dist: arcon (>=0.4.0)
22
21
  Requires-Dist: astroid (>=3.0.1,<4.0.0)
23
- Requires-Dist: click (>=8.1.7,<9.0.0)
24
22
  Requires-Dist: pathspec (>=0.12.1,<0.13.0)
25
23
  Project-URL: Documentation, https://docsig.readthedocs.io/en/latest
26
24
  Project-URL: Repository, https://github.com/jshwi/docsig
@@ -162,6 +160,39 @@ Options can also be configured with the pyproject.toml file
162
160
  "SIG201",
163
161
  ]
164
162
 
163
+ Flake8
164
+ ******
165
+
166
+ ``docsig`` can also be used as a ``flake8`` plugin. Install ``flake8`` and
167
+ ensure your installation has registered `docsig`
168
+
169
+ .. code-block:: console
170
+
171
+ $ flake8 --version
172
+ 7.1.0 (docsig: 0.58.0, mccabe: 0.7.0, pycodestyle: 2.12.0, pyflakes: 3.2.0) CPython 3.8.13 on Darwin
173
+
174
+ And now use `flake8` to lint your files
175
+
176
+ .. code-block:: console
177
+
178
+ $ flake8 example.py
179
+ example.py:1:1: SIG202 includes parameters that do not exist (params-do-not-exist) 'function'
180
+
181
+ With ``flake8`` the pyproject.toml config will still be the base config, though the
182
+ `ini files <https://flake8.pycqa.org/en/latest/user/configuration.html#configuration-locations>`_ ``flake8`` gets it config from will override the pyproject.toml config.
183
+ For ``flake8`` all args and config options are prefixed with ``sig`` to
184
+ avoid any potential conflicts with other plugins
185
+
186
+ .. code-block:: ini
187
+
188
+ [flake8]
189
+ sig-check-dunders = True
190
+ sig-check-overridden = True
191
+ sig-check-protected = True
192
+
193
+ ..
194
+ end flake8
195
+
165
196
  API
166
197
  ***
167
198
 
@@ -221,11 +252,13 @@ pre-commit
221
252
 
222
253
  It can be added to your .pre-commit-config.yaml as follows:
223
254
 
255
+ Standalone
256
+
224
257
  .. code-block:: yaml
225
258
 
226
259
  repos:
227
260
  - repo: https://github.com/jshwi/docsig
228
- rev: v0.56.0
261
+ rev: v0.58.0
229
262
  hooks:
230
263
  - id: docsig
231
264
  args:
@@ -234,3 +267,20 @@ It can be added to your .pre-commit-config.yaml as follows:
234
267
  - "--check-overridden"
235
268
  - "--check-protected"
236
269
 
270
+ or integrated with ``flake8``
271
+
272
+ .. code-block:: yaml
273
+
274
+ repos:
275
+ - repo: https://github.com/PyCQA/flake8
276
+ rev: "7.1.0"
277
+ hooks:
278
+ - id: flake8
279
+ additional_dependencies:
280
+ - docsig==0.58.0
281
+ args:
282
+ - "--sig-check-class"
283
+ - "--sig-check-dunders"
284
+ - "--sig-check-overridden"
285
+ - "--sig-check-protected"
286
+
@@ -134,6 +134,39 @@ Options can also be configured with the pyproject.toml file
134
134
  "SIG201",
135
135
  ]
136
136
 
137
+ Flake8
138
+ ******
139
+
140
+ ``docsig`` can also be used as a ``flake8`` plugin. Install ``flake8`` and
141
+ ensure your installation has registered `docsig`
142
+
143
+ .. code-block:: console
144
+
145
+ $ flake8 --version
146
+ 7.1.0 (docsig: 0.58.0, mccabe: 0.7.0, pycodestyle: 2.12.0, pyflakes: 3.2.0) CPython 3.8.13 on Darwin
147
+
148
+ And now use `flake8` to lint your files
149
+
150
+ .. code-block:: console
151
+
152
+ $ flake8 example.py
153
+ example.py:1:1: SIG202 includes parameters that do not exist (params-do-not-exist) 'function'
154
+
155
+ With ``flake8`` the pyproject.toml config will still be the base config, though the
156
+ `ini files <https://flake8.pycqa.org/en/latest/user/configuration.html#configuration-locations>`_ ``flake8`` gets it config from will override the pyproject.toml config.
157
+ For ``flake8`` all args and config options are prefixed with ``sig`` to
158
+ avoid any potential conflicts with other plugins
159
+
160
+ .. code-block:: ini
161
+
162
+ [flake8]
163
+ sig-check-dunders = True
164
+ sig-check-overridden = True
165
+ sig-check-protected = True
166
+
167
+ ..
168
+ end flake8
169
+
137
170
  API
138
171
  ***
139
172
 
@@ -193,11 +226,13 @@ pre-commit
193
226
 
194
227
  It can be added to your .pre-commit-config.yaml as follows:
195
228
 
229
+ Standalone
230
+
196
231
  .. code-block:: yaml
197
232
 
198
233
  repos:
199
234
  - repo: https://github.com/jshwi/docsig
200
- rev: v0.56.0
235
+ rev: v0.58.0
201
236
  hooks:
202
237
  - id: docsig
203
238
  args:
@@ -205,3 +240,20 @@ It can be added to your .pre-commit-config.yaml as follows:
205
240
  - "--check-dunders"
206
241
  - "--check-overridden"
207
242
  - "--check-protected"
243
+
244
+ or integrated with ``flake8``
245
+
246
+ .. code-block:: yaml
247
+
248
+ repos:
249
+ - repo: https://github.com/PyCQA/flake8
250
+ rev: "7.1.0"
251
+ hooks:
252
+ - id: flake8
253
+ additional_dependencies:
254
+ - docsig==0.58.0
255
+ args:
256
+ - "--sig-check-class"
257
+ - "--sig-check-dunders"
258
+ - "--sig-check-overridden"
259
+ - "--sig-check-protected"
@@ -5,6 +5,7 @@ docsig._core
5
5
 
6
6
  from __future__ import annotations as _
7
7
 
8
+ import sys as _sys
8
9
  from pathlib import Path as _Path
9
10
 
10
11
  from . import _decorators
@@ -13,8 +14,8 @@ from ._module import Modules as _Modules
13
14
  from ._module import Parent as _Parent
14
15
  from ._report import Failure as _Failure
15
16
  from ._report import Failures as _Failures
16
- from ._report import Report as _Report
17
17
  from ._utils import print_checks as _print_checks
18
+ from .messages import TEMPLATE as _TEMPLATE
18
19
  from .messages import Messages as _Messages
19
20
 
20
21
  _DEFAULT_EXCLUDES = """\
@@ -111,6 +112,70 @@ def _run_check( # pylint: disable=too-many-arguments,too-many-locals
111
112
  )
112
113
 
113
114
 
115
+ def _get_failures( # pylint: disable=too-many-locals,too-many-arguments
116
+ module: _Parent,
117
+ check_class: bool,
118
+ check_class_constructor: bool,
119
+ check_dunders: bool,
120
+ check_nested: bool,
121
+ check_overridden: bool,
122
+ check_protected: bool,
123
+ check_property_returns: bool,
124
+ ignore_no_params: bool,
125
+ ignore_typechecker: bool,
126
+ check_protected_class_methods: bool,
127
+ no_ansi: bool,
128
+ target: _Messages,
129
+ ) -> _Failures:
130
+ failures = _Failures()
131
+ for top_level in module:
132
+ if (
133
+ not top_level.isprotected
134
+ or check_protected
135
+ or check_protected_class_methods
136
+ ):
137
+ _run_check(
138
+ top_level,
139
+ module,
140
+ check_class,
141
+ check_class_constructor,
142
+ check_dunders,
143
+ check_nested,
144
+ check_overridden,
145
+ check_protected,
146
+ check_property_returns,
147
+ ignore_no_params,
148
+ ignore_typechecker,
149
+ no_ansi,
150
+ target or _Messages(),
151
+ failures,
152
+ )
153
+
154
+ return failures
155
+
156
+
157
+ def _report(
158
+ failures: _Failures, path: str | None = None, no_ansi: bool = False
159
+ ) -> None:
160
+ for failure in failures:
161
+ header = f"{path}{failure.lineno} in {failure.name}"
162
+ if not no_ansi and _sys.stdout.isatty():
163
+ header = f"\033[35m{header}\033[0m"
164
+
165
+ print(header)
166
+ for item in failure:
167
+ print(
168
+ " "
169
+ + _TEMPLATE.format(
170
+ ref=item.ref,
171
+ description=item.description,
172
+ symbolic=item.symbolic,
173
+ )
174
+ )
175
+ if item.hint:
176
+ print(f" hint: {item.hint}")
177
+
178
+
114
179
  @_decorators.parse_msgs
115
180
  @_decorators.handle_deprecations
116
181
  @_decorators.validate_args
@@ -174,6 +239,7 @@ def docsig( # pylint: disable=too-many-locals,too-many-arguments
174
239
  checks.
175
240
  :return: Exit status for whether test failed or not.
176
241
  """
242
+ retcode = 0
177
243
  if list_checks:
178
244
  return int(bool(_print_checks())) # type: ignore
179
245
 
@@ -193,33 +259,24 @@ def docsig( # pylint: disable=too-many-locals,too-many-arguments
193
259
  no_ansi=no_ansi,
194
260
  verbose=verbose,
195
261
  )
196
- report = _Report()
197
262
  for module in modules:
198
- for top_level in module:
199
- if (
200
- not top_level.isprotected
201
- or check_protected
202
- or check_protected_class_methods
203
- ):
204
- failures = _Failures()
205
- _run_check(
206
- top_level,
207
- module,
208
- check_class,
209
- check_class_constructor,
210
- check_dunders,
211
- check_nested,
212
- check_overridden,
213
- check_protected,
214
- check_property_returns,
215
- ignore_no_params,
216
- ignore_typechecker,
217
- no_ansi,
218
- target or _Messages(),
219
- failures,
220
- )
221
- if failures:
222
- report[top_level.path].append(failures)
263
+ failures = _get_failures(
264
+ module,
265
+ check_class,
266
+ check_class_constructor,
267
+ check_dunders,
268
+ check_nested,
269
+ check_overridden,
270
+ check_protected,
271
+ check_property_returns,
272
+ ignore_no_params,
273
+ ignore_typechecker,
274
+ check_protected_class_methods,
275
+ no_ansi,
276
+ target or _Messages(),
277
+ )
278
+ if failures:
279
+ _report(failures, module.path, no_ansi=no_ansi)
280
+ retcode = 1
223
281
 
224
- report.print(no_ansi)
225
- return max(int(bool(report)), modules.retcode)
282
+ return max(retcode, modules.retcode)
@@ -14,7 +14,7 @@ def excepthook(no_ansi: bool = False) -> None:
14
14
 
15
15
  :param no_ansi: Disable ANSI output.
16
16
  """
17
- if _e.get("DOCSIG_DEBUG", None) != "1":
17
+ if _e.get("DOCSIG_DEBUG") != "1":
18
18
  _sys.excepthook = lambda x, y, _: _pretty_print_error(
19
19
  x, str(y), no_ansi
20
20
  )
@@ -12,8 +12,6 @@ import typing as _t
12
12
  from pathlib import Path as _Path
13
13
 
14
14
  import astroid as _ast
15
- import click as _click
16
- from astroid import AstroidSyntaxError as _AstroidSyntaxError
17
15
  from pathspec import PathSpec as _PathSpec
18
16
  from pathspec.patterns import GitWildMatchPattern as _GitWildMatchPattern
19
17
 
@@ -427,13 +425,13 @@ class Modules(_t.List[Parent]): # pylint: disable=too-many-instance-attributes
427
425
  ),
428
426
  self._verbose,
429
427
  )
430
- except (_AstroidSyntaxError, UnicodeDecodeError) as err:
428
+ except (_ast.AstroidSyntaxError, UnicodeDecodeError) as err:
431
429
  msg = str(err).replace("\n", " ")
432
430
  if root is not None and root.name.endswith(".py"):
433
431
  # pass by silently for files that do not end with .py,
434
432
  # may result in a 123 syntax error exit status in the
435
433
  # future
436
- _click.echo(root, file=_sys.stderr)
434
+ print(root, file=_sys.stderr)
437
435
  _pretty_print_error(type(err), msg, no_ansi=self._no_ansi)
438
436
  self._retcode = 1
439
437
 
@@ -5,11 +5,8 @@ docsig._report
5
5
 
6
6
  from __future__ import annotations as _
7
7
 
8
- import sys as _sys
9
8
  import typing as _t
10
9
 
11
- import click as _click
12
-
13
10
  from ._module import Function as _Function
14
11
  from ._stub import UNNAMED as _UNNAMED
15
12
  from ._stub import VALID_DESCRIPTION as _VALID_DESCRIPTION
@@ -17,7 +14,6 @@ from ._stub import Param as _Param
17
14
  from ._stub import RetType as _RetType
18
15
  from ._utils import almost_equal as _almost_equal
19
16
  from ._utils import has_bad_return as _has_bad_return
20
- from .messages import TEMPLATE as _TEMPLATE
21
17
  from .messages import E as _E
22
18
  from .messages import Message as _Message
23
19
  from .messages import Messages as _Messages
@@ -26,7 +22,18 @@ _MIN_MATCH = 0.8
26
22
  _MAX_MATCH = 1.0
27
23
 
28
24
 
29
- class Failure(_t.List[str]):
25
+ class Failed(_t.NamedTuple):
26
+ """Report info object."""
27
+
28
+ name: str
29
+ ref: str
30
+ description: str
31
+ symbolic: str
32
+ lineno: int
33
+ hint: str | None = None
34
+
35
+
36
+ class Failure(_t.List[Failed]):
30
37
  """Compile and produce report.
31
38
 
32
39
  :param func: Function object.
@@ -47,7 +54,10 @@ class Failure(_t.List[str]):
47
54
  if target:
48
55
  self._func.messages.extend(i for i in _E.all if i not in target)
49
56
 
50
- self._func = func
57
+ self._name = self._func.name
58
+ if self._func.parent.name:
59
+ self._name = f"{self._func.parent.name}.{self._func.name}"
60
+
51
61
  self._check_property_returns = check_property_returns
52
62
  self._sig0xx_config()
53
63
  if self._func.docstring.string is None:
@@ -65,15 +75,16 @@ class Failure(_t.List[str]):
65
75
  self.sort()
66
76
 
67
77
  def _add(self, value: _Message, hint: bool = False, **kwargs) -> None:
68
- message = value.fstring(_TEMPLATE)
69
- if kwargs:
70
- message = message.format(**kwargs)
71
-
72
- if hint:
73
- message += f"\n hint: {value.hint}"
74
-
75
- if value not in self._func.messages and message not in self:
76
- super().append(message)
78
+ failed = Failed(
79
+ self._name,
80
+ value.ref,
81
+ value.description.format(**kwargs),
82
+ value.symbolic,
83
+ self._func.lineno,
84
+ value.hint if hint else None,
85
+ )
86
+ if value not in self._func.messages and failed not in self:
87
+ super().append(failed)
77
88
 
78
89
  def _sig0xx_config(self) -> None:
79
90
  for comment in self._func.comments:
@@ -171,7 +182,7 @@ class Failure(_t.List[str]):
171
182
  elif doc != sig:
172
183
  if (
173
184
  sig.name in self._func.docstring.args.names
174
- or doc.name in self.func.signature.args.names
185
+ or doc.name in self._func.signature.args.names
175
186
  ):
176
187
  # params-out-of-order
177
188
  self._add(_E[402])
@@ -218,42 +229,15 @@ class Failure(_t.List[str]):
218
229
  self._add(_E[505], hint=True)
219
230
 
220
231
  @property
221
- def func(self) -> _Function:
222
- """Function this failure belongs to."""
223
- return self._func
232
+ def name(self) -> str:
233
+ """Function name."""
234
+ return self._name
235
+
236
+ @property
237
+ def lineno(self) -> int:
238
+ """Function line number."""
239
+ return self._func.lineno
224
240
 
225
241
 
226
242
  class Failures(_t.List[Failure]):
227
243
  """Sequence of failed functions."""
228
-
229
-
230
- class Report(_t.Dict[str, _t.List[Failures]]):
231
- """Collect and display report."""
232
-
233
- def __getitem__(self, key: str) -> list[Failures]:
234
- if key not in super().__iter__():
235
- super().__setitem__(key, [])
236
-
237
- return super().__getitem__(key)
238
-
239
- def print(self, no_ansi: bool) -> None:
240
- """Display report summary if any checks have failed.
241
-
242
- :param no_ansi: Disable ANSI output.
243
- """
244
- for key, value in self.items():
245
- for failures in value:
246
- for failure in failures:
247
- header = f"{key}{failure.func.lineno}"
248
- function = failure.func.name
249
- if failure.func.parent.name:
250
- function = f"{failure.func.parent.name}.{function}"
251
-
252
- header += f" in {function}"
253
- _click.echo(
254
- "{}\n {}".format(
255
- _click.style(header, fg="magenta"),
256
- "\n ".join(failure),
257
- ),
258
- color=not no_ansi and _sys.stdout.isatty(),
259
- )
@@ -21,37 +21,6 @@ UNNAMED = -1000
21
21
  VALID_DESCRIPTION = " A valid description."
22
22
 
23
23
 
24
- # noinspection PyTypeChecker
25
- class _GoogleDocstring(str):
26
- def __new__(cls, string: str) -> _GoogleDocstring:
27
- return super().__new__(cls, str(_s.GoogleDocstring(string)))
28
-
29
-
30
- # noinspection PyTypeChecker
31
- class _NumpyDocstring(str):
32
- def __new__(cls, string: str) -> _NumpyDocstring:
33
- return super().__new__(cls, str(_s.NumpyDocstring(string)))
34
-
35
-
36
- # noinspection PyTypeChecker
37
- class _DocFmt(str):
38
- def __new__(cls, string: str) -> _DocFmt:
39
- return super().__new__(
40
- cls,
41
- _textwrap.dedent("\n".join(string.splitlines()[1:])).replace(
42
- "*", ""
43
- ),
44
- )
45
-
46
-
47
- # noinspection PyTypeChecker
48
- class _RawDocstring(str):
49
- def __new__(cls, string: str) -> _RawDocstring:
50
- return super().__new__(
51
- cls, _NumpyDocstring(_GoogleDocstring(_DocFmt(string)))
52
- )
53
-
54
-
55
24
  class RetType(_Enum):
56
25
  """Defines the possible types of a return annotation."""
57
26
 
@@ -356,6 +325,22 @@ class Docstring(_Stub):
356
325
  # description
357
326
  return any(i.endswith(":") for i in new.splitlines())
358
327
 
328
+ @staticmethod
329
+ def _normalize_docstring(string: str) -> str:
330
+ # convert google and numpy style docstrings to parse docstrings
331
+ # as restructured text
332
+ return str(
333
+ _s.NumpyDocstring(
334
+ str(
335
+ _s.GoogleDocstring(
336
+ _textwrap.dedent(
337
+ "\n".join(string.splitlines()[1:])
338
+ ).replace("*", "")
339
+ )
340
+ )
341
+ )
342
+ )
343
+
359
344
  def __init__(
360
345
  self,
361
346
  node: _ast.Const | None = None,
@@ -365,7 +350,7 @@ class Docstring(_Stub):
365
350
  super().__init__(ignore_args, ignore_kwargs)
366
351
  self._string = None
367
352
  if node is not None:
368
- self._string = _RawDocstring(node.value)
353
+ self._string = self._normalize_docstring(node.value)
369
354
  for i in _Matches(
370
355
  self._string,
371
356
  self._indent_anomaly(node.value),
@@ -378,7 +363,7 @@ class Docstring(_Stub):
378
363
  )
379
364
 
380
365
  @property
381
- def string(self) -> _RawDocstring | None:
366
+ def string(self) -> str | None:
382
367
  """The raw documentation string, if it exists, else None."""
383
368
  return self._string
384
369
 
@@ -9,8 +9,6 @@ import sys as _sys
9
9
  import typing as _t
10
10
  from difflib import SequenceMatcher as _SequenceMatcher
11
11
 
12
- import click as _click
13
-
14
12
  from .messages import TEMPLATE as _TEMPLATE
15
13
  from .messages import E as _E
16
14
 
@@ -35,7 +33,7 @@ def vprint(msg: str, verbose: bool = False) -> None:
35
33
  :param verbose: Whether verbose mode is enabled.
36
34
  """
37
35
  if verbose:
38
- _click.echo(msg)
36
+ print(msg)
39
37
 
40
38
 
41
39
  def pretty_print_error(
@@ -47,17 +45,17 @@ def pretty_print_error(
47
45
  :param msg: The exception message.
48
46
  :param no_ansi: Whether to in ANSI escape codes.
49
47
  """
50
- _click.echo(
51
- f"{_click.style(exception_type.__name__, fg='red', bold=True)}: {msg}",
52
- file=_sys.stderr,
53
- color=not no_ansi and _sys.stderr.isatty(),
54
- )
48
+ exception_type_name = exception_type.__name__
49
+ if not no_ansi and _sys.stdout.isatty():
50
+ exception_type_name = f"\033[1;31m{exception_type_name}\033[0m"
51
+
52
+ print(f"{exception_type_name}: {msg}", file=_sys.stderr)
55
53
 
56
54
 
57
55
  def print_checks() -> None:
58
56
  """Print all available checks."""
59
57
  for msg in _E.values():
60
- _click.echo(msg.fstring(_TEMPLATE))
58
+ print(msg.fstring(_TEMPLATE))
61
59
 
62
60
 
63
61
  def has_bad_return(string: str) -> bool:
@@ -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.56.0"
11
+ __version__ = "0.58.0"
@@ -14,6 +14,9 @@ UNKNOWN = "SIG000"
14
14
  #: Default template to format message strings.
15
15
  TEMPLATE = "{ref}: {description} ({symbolic})"
16
16
 
17
+ #: Flake8 template to format message strings.
18
+ FLAKE8 = "{ref} {description} ({symbolic})"
19
+
17
20
 
18
21
  class Message(_t.NamedTuple):
19
22
  """Represents an error message."""
@@ -0,0 +1,165 @@
1
+ """Flake8 implementation of docsig."""
2
+
3
+ import ast
4
+ import contextlib
5
+ import io
6
+ import re
7
+ import sys
8
+ import typing as t
9
+ from argparse import Namespace
10
+
11
+ import docsig
12
+
13
+ Flake8Error = t.Tuple[int, int, str, t.Type]
14
+
15
+
16
+ class Docsig:
17
+ """Flake8 implementation of docsig class.
18
+
19
+ :param tree: Ast module, which will not be used by flake8 will
20
+ provide.
21
+ :param filename: Filename to pass to docsig.
22
+ """
23
+
24
+ off_by_default = False
25
+ name = docsig.__name__
26
+ version = docsig.__version__
27
+ options_dict: t.Dict[str, bool] = {}
28
+
29
+ def __init__(self, tree: ast.Module, filename: str) -> None:
30
+ _tree = tree # noqa
31
+ self.filename = filename
32
+
33
+ # won't import flake8 type
34
+ # conflicts with this module name
35
+ # might require that flake8 actually be installed, which is not a
36
+ # requirement for this package
37
+ @classmethod
38
+ def add_options(cls, parser) -> None:
39
+ """Add flake8 commandline and config options.sig_
40
+
41
+ :param parser: Flake8 option manager.
42
+ """
43
+ parser.add_option(
44
+ "--sig-check-class",
45
+ action="store_true",
46
+ parse_from_config=True,
47
+ help="check class docstrings",
48
+ )
49
+ parser.add_option(
50
+ "--sig-check-class-constructor",
51
+ action="store_true",
52
+ parse_from_config=True,
53
+ help="check __init__ methods. Note: mutually incompatible with -c",
54
+ )
55
+ parser.add_option(
56
+ "--sig-check-dunders",
57
+ action="store_true",
58
+ parse_from_config=True,
59
+ help="check dunder methods",
60
+ )
61
+ parser.add_option(
62
+ "--sig-check-protected-class-methods",
63
+ action="store_true",
64
+ parse_from_config=True,
65
+ help="check public methods belonging to protected classes",
66
+ )
67
+ parser.add_option(
68
+ "--sig-check-nested",
69
+ action="store_true",
70
+ parse_from_config=True,
71
+ help="check nested functions and classes",
72
+ )
73
+ parser.add_option(
74
+ "--sig-check-overridden",
75
+ action="store_true",
76
+ parse_from_config=True,
77
+ help="check overridden methods",
78
+ )
79
+ parser.add_option(
80
+ "--sig-check-protected",
81
+ action="store_true",
82
+ parse_from_config=True,
83
+ help="check protected functions and classes",
84
+ )
85
+ parser.add_option(
86
+ "--sig-check-property-returns",
87
+ action="store_true",
88
+ parse_from_config=True,
89
+ help="check property return values",
90
+ )
91
+ parser.add_option(
92
+ "--sig-ignore-no-params",
93
+ action="store_true",
94
+ parse_from_config=True,
95
+ help="ignore docstrings where parameters are not documented",
96
+ )
97
+ parser.add_option(
98
+ "--sig-ignore-args",
99
+ action="store_true",
100
+ parse_from_config=True,
101
+ help="ignore args prefixed with an asterisk",
102
+ )
103
+ parser.add_option(
104
+ "--sig-ignore-kwargs",
105
+ action="store_true",
106
+ parse_from_config=True,
107
+ help="ignore kwargs prefixed with two asterisks",
108
+ )
109
+ parser.add_option(
110
+ "--sig-ignore-typechecker",
111
+ action="store_true",
112
+ parse_from_config=True,
113
+ help="ignore checking return values",
114
+ )
115
+
116
+ @classmethod
117
+ def parse_options(cls, options: Namespace) -> None:
118
+ """Parse flake8 options into am instance accessible dict.
119
+
120
+ :param options: Argparse namespace.
121
+ """
122
+ cls.options_dict = {
123
+ "check_class": options.sig_check_class,
124
+ "check_class_constructor": options.sig_check_class_constructor,
125
+ "check_dunders": options.sig_check_dunders,
126
+ "check_protected_class_methods": (
127
+ options.sig_check_protected_class_methods
128
+ ),
129
+ "check_nested": options.sig_check_nested,
130
+ "check_overridden": options.sig_check_overridden,
131
+ "check_protected": options.sig_check_protected,
132
+ "check_property_returns": options.sig_check_property_returns,
133
+ "ignore_no_params": options.sig_ignore_no_params,
134
+ "ignore_args": options.sig_ignore_args,
135
+ "ignore_kwargs": options.sig_ignore_kwargs,
136
+ "ignore_typechecker": options.sig_ignore_typechecker,
137
+ }
138
+
139
+ def run(self) -> t.Generator[Flake8Error, None, None]:
140
+ """Run docsig and possibly yield a flake8 error.
141
+
142
+ :return: Flake8 error, if there is one.
143
+ """
144
+ buffer = io.StringIO()
145
+ with contextlib.redirect_stdout(buffer):
146
+ sys.argv = [
147
+ __package__,
148
+ self.filename,
149
+ *[
150
+ f"--{k.replace('_', '-')}"
151
+ for k, v in self.options_dict.items()
152
+ if v
153
+ ],
154
+ ]
155
+ docsig.main()
156
+
157
+ results = re.split(r"^(?!\s)", buffer.getvalue(), flags=re.MULTILINE)
158
+ for result in results:
159
+ if not result:
160
+ continue
161
+
162
+ header, remainder = result.splitlines()[:2]
163
+ lineno, func_name = header.split(":", 1)[1].split(" in ", 1)
164
+ line = f"{remainder.lstrip().replace(':', '')} '{func_name}'"
165
+ yield int(lineno), 0, line, self.__class__
@@ -7,12 +7,61 @@ requires = [
7
7
  [tool.black]
8
8
  line-length = 79
9
9
 
10
+ [tool.bumpversion]
11
+ allow_dirty = true
12
+ commit = true
13
+ commit_args = "-sS"
14
+ current_version = "0.58.0"
15
+ message = "bump: version {current_version} → {new_version}"
16
+ sign_tags = true
17
+ tag = true
18
+
19
+ [[tool.bumpversion.files]]
20
+ filename = "docs/conf.py"
21
+
22
+ [[tool.bumpversion.files]]
23
+ filename = "docsig/_version.py"
24
+
25
+ [[tool.bumpversion.files]]
26
+ filename = "pyproject.toml"
27
+ replace = 'version = "{new_version}"'
28
+ search = 'version = "{current_version}"'
29
+
30
+ [[tool.bumpversion.files]]
31
+ filename = "package.json"
32
+ replace = '"version": "{new_version}"'
33
+ search = '"version": "{current_version}"'
34
+
35
+ [[tool.bumpversion.files]]
36
+ filename = "README.rst"
37
+ replace = "rev: v{new_version}"
38
+ search = "rev: v{current_version}"
39
+
40
+ [[tool.bumpversion.files]]
41
+ filename = "README.rst"
42
+ replace = "docsig: {new_version}"
43
+ search = "docsig: {current_version}"
44
+
45
+ [[tool.bumpversion.files]]
46
+ filename = "README.rst"
47
+ replace = "- docsig=={new_version}"
48
+ search = "- docsig=={current_version}"
49
+
50
+ [[tool.bumpversion.files]]
51
+ filename = "SECURITY.md"
52
+
53
+ [[tool.bumpversion.files]]
54
+ filename = "Makefile"
55
+ replace = "VERSION := {new_version}"
56
+ search = "VERSION := {current_version}"
57
+
10
58
  [tool.codespell]
11
59
  ignore-words-list = "astroid"
12
60
 
13
61
  [tool.coverage.report]
14
62
  exclude_lines = [
15
- "@_t.overload"
63
+ "@_t.overload",
64
+ "if __name__ == .__main__.:"
16
65
  ]
17
66
  fail_under = 100
18
67
 
@@ -24,7 +73,14 @@ omit = [
24
73
  ]
25
74
 
26
75
  [tool.deptry.per_rule_ignores]
27
- DEP004 = ["yaml"]
76
+ DEP004 = [
77
+ "flake8",
78
+ "git",
79
+ "pytest",
80
+ "tomli",
81
+ "tomli_w",
82
+ "yaml"
83
+ ]
28
84
 
29
85
  [tool.docformatter]
30
86
  in-place = true
@@ -76,26 +132,29 @@ maintainers = [
76
132
  name = "docsig"
77
133
  readme = "README.rst"
78
134
  repository = "https://github.com/jshwi/docsig"
79
- version = "0.56.0"
135
+ version = "0.58.0"
80
136
 
81
137
  [tool.poetry.dependencies]
82
138
  Sphinx = "^7.0.0"
83
139
  arcon = ">=0.4.0"
84
140
  astroid = "^3.0.1"
85
- click = "^8.1.7"
86
141
  pathspec = "^0.12.1"
87
- python = "^3.8"
142
+ python = "^3.8.1"
88
143
 
89
144
  [tool.poetry.group.dev.dependencies]
90
145
  black = "^24.4.2"
91
- bump2version = "^1.0.1"
146
+ bump-my-version = "^0.24.1"
92
147
  deptry = "^0.16.1"
93
148
  docformatter = "^1.7.5"
94
149
  flynt = "^1.0.1"
150
+ gitpython = "^3.1.43"
95
151
  isort = "^5.13.2"
96
152
  mypy = "^1.10.0"
97
153
  pre-commit = "^3.3.3"
98
154
  pylint = "^3.1.0"
155
+ tomli = "^2.0.1"
156
+ tomli-w = "^1.0.0"
157
+ towncrier = "^23.11.0"
99
158
  tox = "^4.15.0"
100
159
  vulture = "^2.11"
101
160
 
@@ -108,15 +167,22 @@ sphinx-copybutton = "^0.5.2"
108
167
  sphinx-markdown-builder = ">=0.5.5,<0.7.0"
109
168
  templatest = "^0.10.1"
110
169
 
170
+ [tool.poetry.group.flake8.dependencies]
171
+ flake8 = "^7.1.0"
172
+
111
173
  [tool.poetry.group.tests.dependencies]
112
174
  pytest = "^8.2.0"
113
175
  pytest-benchmark = "^4.0.0"
114
176
  pytest-cov = "^5.0.0"
177
+ pytest-gitignore = "^1.3"
115
178
  pytest-randomly = "^3.15.0"
116
179
  pytest-sugar = "^1.0.0"
117
180
  pytest-xdist = "^3.6.1"
118
181
  templatest = "^0.10.1"
119
182
 
183
+ [tool.poetry.plugins."flake8.extension"]
184
+ SIG = "docsig.plugin:Docsig"
185
+
120
186
  [tool.poetry.scripts]
121
187
  docsig = "docsig.__main__:main"
122
188
 
@@ -149,3 +215,32 @@ norecursedirs = [
149
215
  ".pytest_cache",
150
216
  "docs"
151
217
  ]
218
+
219
+ [tool.towncrier]
220
+ directory = "changelog"
221
+ filename = "CHANGELOG.md"
222
+ issue_format = "[#{issue}](https://github.com/jshwi/docsig/issues/{issue})"
223
+ package = "docsig"
224
+ start_string = "<!-- release notes start -->\n"
225
+ template = "changelog/_template.md"
226
+
227
+ [tool.towncrier.fragment.add]
228
+ name = "Added"
229
+
230
+ [tool.towncrier.fragment.change]
231
+ name = "Changed"
232
+
233
+ [tool.towncrier.fragment.deprecate]
234
+ name = "Deprecated"
235
+
236
+ [tool.towncrier.fragment.fix]
237
+ name = "Fixed"
238
+
239
+ [tool.towncrier.fragment.hack]
240
+ name = "Hacked"
241
+
242
+ [tool.towncrier.fragment.remove]
243
+ name = "Removed"
244
+
245
+ [tool.towncrier.fragment.security]
246
+ name = "Security"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes