docsig 0.57.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.57.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
@@ -19,7 +19,6 @@ Classifier: Programming Language :: Python :: 3.12
19
19
  Requires-Dist: Sphinx (>=7.0.0,<8.0.0)
20
20
  Requires-Dist: arcon (>=0.4.0)
21
21
  Requires-Dist: astroid (>=3.0.1,<4.0.0)
22
- Requires-Dist: click (>=8.1.7,<9.0.0)
23
22
  Requires-Dist: pathspec (>=0.12.1,<0.13.0)
24
23
  Project-URL: Documentation, https://docsig.readthedocs.io/en/latest
25
24
  Project-URL: Repository, https://github.com/jshwi/docsig
@@ -170,7 +169,7 @@ ensure your installation has registered `docsig`
170
169
  .. code-block:: console
171
170
 
172
171
  $ flake8 --version
173
- 7.1.0 (docsig: 0.57.0, mccabe: 0.7.0, pycodestyle: 2.12.0, pyflakes: 3.2.0) CPython 3.8.13 on Darwin
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
174
173
 
175
174
  And now use `flake8` to lint your files
176
175
 
@@ -253,11 +252,13 @@ pre-commit
253
252
 
254
253
  It can be added to your .pre-commit-config.yaml as follows:
255
254
 
255
+ Standalone
256
+
256
257
  .. code-block:: yaml
257
258
 
258
259
  repos:
259
260
  - repo: https://github.com/jshwi/docsig
260
- rev: v0.57.0
261
+ rev: v0.58.0
261
262
  hooks:
262
263
  - id: docsig
263
264
  args:
@@ -266,3 +267,20 @@ It can be added to your .pre-commit-config.yaml as follows:
266
267
  - "--check-overridden"
267
268
  - "--check-protected"
268
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
+
@@ -143,7 +143,7 @@ ensure your installation has registered `docsig`
143
143
  .. code-block:: console
144
144
 
145
145
  $ flake8 --version
146
- 7.1.0 (docsig: 0.57.0, mccabe: 0.7.0, pycodestyle: 2.12.0, pyflakes: 3.2.0) CPython 3.8.13 on Darwin
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
147
 
148
148
  And now use `flake8` to lint your files
149
149
 
@@ -226,11 +226,13 @@ pre-commit
226
226
 
227
227
  It can be added to your .pre-commit-config.yaml as follows:
228
228
 
229
+ Standalone
230
+
229
231
  .. code-block:: yaml
230
232
 
231
233
  repos:
232
234
  - repo: https://github.com/jshwi/docsig
233
- rev: v0.57.0
235
+ rev: v0.58.0
234
236
  hooks:
235
237
  - id: docsig
236
238
  args:
@@ -238,3 +240,20 @@ It can be added to your .pre-commit-config.yaml as follows:
238
240
  - "--check-dunders"
239
241
  - "--check-overridden"
240
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)
@@ -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.57.0"
11
+ __version__ = "0.58.0"
@@ -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.57.0"
14
+ current_version = "0.58.0"
15
15
  message = "bump: version {current_version} → {new_version}"
16
16
  sign_tags = true
17
17
  tag = true
@@ -42,6 +42,11 @@ filename = "README.rst"
42
42
  replace = "docsig: {new_version}"
43
43
  search = "docsig: {current_version}"
44
44
 
45
+ [[tool.bumpversion.files]]
46
+ filename = "README.rst"
47
+ replace = "- docsig=={new_version}"
48
+ search = "- docsig=={current_version}"
49
+
45
50
  [[tool.bumpversion.files]]
46
51
  filename = "SECURITY.md"
47
52
 
@@ -127,13 +132,12 @@ maintainers = [
127
132
  name = "docsig"
128
133
  readme = "README.rst"
129
134
  repository = "https://github.com/jshwi/docsig"
130
- version = "0.57.0"
135
+ version = "0.58.0"
131
136
 
132
137
  [tool.poetry.dependencies]
133
138
  Sphinx = "^7.0.0"
134
139
  arcon = ">=0.4.0"
135
140
  astroid = "^3.0.1"
136
- click = "^8.1.7"
137
141
  pathspec = "^0.12.1"
138
142
  python = "^3.8.1"
139
143
 
@@ -170,6 +174,7 @@ flake8 = "^7.1.0"
170
174
  pytest = "^8.2.0"
171
175
  pytest-benchmark = "^4.0.0"
172
176
  pytest-cov = "^5.0.0"
177
+ pytest-gitignore = "^1.3"
173
178
  pytest-randomly = "^3.15.0"
174
179
  pytest-sugar = "^1.0.0"
175
180
  pytest-xdist = "^3.6.1"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes