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.
- {docsig-0.56.0 → docsig-0.58.0}/PKG-INFO +55 -5
- {docsig-0.56.0 → docsig-0.58.0}/README.rst +53 -1
- {docsig-0.56.0 → docsig-0.58.0}/docsig/_core.py +86 -29
- {docsig-0.56.0 → docsig-0.58.0}/docsig/_hooks.py +1 -1
- {docsig-0.56.0 → docsig-0.58.0}/docsig/_module.py +2 -4
- {docsig-0.56.0 → docsig-0.58.0}/docsig/_report.py +35 -51
- {docsig-0.56.0 → docsig-0.58.0}/docsig/_stub.py +18 -33
- {docsig-0.56.0 → docsig-0.58.0}/docsig/_utils.py +7 -9
- {docsig-0.56.0 → docsig-0.58.0}/docsig/_version.py +1 -1
- {docsig-0.56.0 → docsig-0.58.0}/docsig/messages.py +3 -0
- docsig-0.58.0/docsig/plugin.py +165 -0
- {docsig-0.56.0 → docsig-0.58.0}/pyproject.toml +101 -6
- {docsig-0.56.0 → docsig-0.58.0}/LICENSE +0 -0
- {docsig-0.56.0 → docsig-0.58.0}/docsig/__init__.py +0 -0
- {docsig-0.56.0 → docsig-0.58.0}/docsig/__main__.py +0 -0
- {docsig-0.56.0 → docsig-0.58.0}/docsig/_config.py +0 -0
- {docsig-0.56.0 → docsig-0.58.0}/docsig/_decorators.py +0 -0
- {docsig-0.56.0 → docsig-0.58.0}/docsig/_directives.py +0 -0
- {docsig-0.56.0 → docsig-0.58.0}/docsig/_main.py +0 -0
- {docsig-0.56.0 → docsig-0.58.0}/docsig/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: docsig
|
|
3
|
-
Version: 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.
|
|
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.
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
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"
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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.
|
|
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
|
|
222
|
-
"""Function
|
|
223
|
-
return self.
|
|
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 =
|
|
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) ->
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
58
|
+
print(msg.fstring(_TEMPLATE))
|
|
61
59
|
|
|
62
60
|
|
|
63
61
|
def has_bad_return(string: str) -> bool:
|
|
@@ -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 = [
|
|
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.
|
|
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
|
-
|
|
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
|