docsig 0.53.2__tar.gz → 0.54.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.53.2
3
+ Version: 0.54.0
4
4
  Summary: Check signature params for proper documentation
5
5
  Home-page: https://pypi.org/project/docsig/
6
6
  License: MIT
@@ -222,7 +222,7 @@ It can be added to your .pre-commit-config.yaml as follows:
222
222
 
223
223
  repos:
224
224
  - repo: https://github.com/jshwi/docsig
225
- rev: v0.53.2
225
+ rev: v0.54.0
226
226
  hooks:
227
227
  - id: docsig
228
228
  args:
@@ -194,7 +194,7 @@ It can be added to your .pre-commit-config.yaml as follows:
194
194
 
195
195
  repos:
196
196
  - repo: https://github.com/jshwi/docsig
197
- rev: v0.53.2
197
+ rev: v0.54.0
198
198
  hooks:
199
199
  - id: docsig
200
200
  args:
@@ -12,6 +12,7 @@ import click as _click
12
12
 
13
13
  from ._module import Function as _Function
14
14
  from ._stub import UNNAMED as _UNNAMED
15
+ from ._stub import VALID_DESCRIPTION as _VALID_DESCRIPTION
15
16
  from ._stub import Param as _Param
16
17
  from ._stub import RetType as _RetType
17
18
  from ._utils import almost_equal as _almost_equal
@@ -81,13 +82,41 @@ class Failure(_t.List[str]):
81
82
  for index in range(len(func)):
82
83
  arg = func.signature.args.get(index)
83
84
  doc = func.docstring.args.get(index)
85
+ self._no_description(doc)
84
86
  self._description_syntax(doc)
85
87
  self._indent_syntax(doc)
86
- self._incorrect(doc)
87
- if arg != doc:
88
- self._order(arg, doc)
89
- self._misspelled(arg, doc)
90
- self._not_equal(arg, doc)
88
+
89
+ if doc.name == _UNNAMED:
90
+ # if the parameter does not have a name, but exists,
91
+ # then it must be incorrectly documented
92
+ # prior implementation relied on the docstring
93
+ # parameter equalling the signature parameter
94
+ self._add(_E[107])
95
+
96
+ elif arg != doc:
97
+ if any(
98
+ arg.name == i.name for i in self._func.docstring.args
99
+ ) or any(
100
+ doc.name == i.name for i in self._func.signature.args
101
+ ):
102
+ # parameters out of order
103
+ self._add(_E[101])
104
+
105
+ elif (
106
+ arg.name is not None
107
+ and doc.name is not None
108
+ and not self._errors
109
+ and _almost_equal(
110
+ arg.name, doc.name, _MIN_MATCH, _MAX_MATCH
111
+ )
112
+ ):
113
+ # spelling error found in documented parameter
114
+ self._add(_E[112])
115
+
116
+ elif arg.name is not None and doc.name is not None:
117
+ # documented parameter not equal to its
118
+ # respective argument
119
+ self._add(_E[110])
91
120
 
92
121
  self.sort()
93
122
 
@@ -103,12 +132,6 @@ class Failure(_t.List[str]):
103
132
  if value not in self._disable and message not in self:
104
133
  super().append(message)
105
134
 
106
- def _order(self, sig: _Param, doc: _Param) -> None:
107
- if any(sig.name == i.name for i in self._func.docstring.args) or any(
108
- doc.name == i.name for i in self._func.signature.args
109
- ):
110
- self._add(_E[101])
111
-
112
135
  def _exists(self) -> None:
113
136
  # pop the parameters that do not exist so that they are excluded
114
137
  # from further analysis, that way there are no additional, and
@@ -132,6 +155,18 @@ class Failure(_t.List[str]):
132
155
 
133
156
  def _missing(self) -> None:
134
157
  if len(self._func.signature.args) > len(self._func.docstring.args):
158
+ # append the parameters that are missing so that they are
159
+ # included in further analysis, that way there are no
160
+ # additional, and redundant, errors
161
+ # this will ensure that both signature and docstring are
162
+ # equal in length, with all parameters that are not
163
+ # documented accounted for
164
+ for count, arg in enumerate(self._func.signature.args, 1):
165
+ if count > len(self._func.docstring.args):
166
+ self._func.docstring.args.append(
167
+ _Param(arg.kind, arg.name, _VALID_DESCRIPTION, 0)
168
+ )
169
+ # params-missing
135
170
  self._add(_E[103])
136
171
 
137
172
  def _duplicates(self) -> None:
@@ -185,32 +220,10 @@ class Failure(_t.List[str]):
185
220
 
186
221
  self._add(_E[105], hint=hint)
187
222
 
188
- def _incorrect(self, doc: _Param) -> None:
189
- # if the parameter does not have a name, but exists, then it
190
- # must be incorrectly documented
191
- # prior implementation relied on the docstring parameter
192
- # equalling the signature parameter
193
- if doc.name == _UNNAMED:
194
- self._add(_E[107])
195
-
196
- # final catch-all
197
- def _not_equal(self, sig: _Param, doc: _Param) -> None:
198
- if sig.name is not None and doc.name is not None and not self._errors:
199
- self._add(_E[110])
200
-
201
223
  def _class_return(self) -> None:
202
224
  if self._func.docstring.returns and self._func.isinit:
203
225
  self._add(_E[111], hint=True)
204
226
 
205
- def _misspelled(self, sig: _Param, doc: _Param) -> None:
206
- if (
207
- sig.name is not None
208
- and doc.name is not None
209
- and not self._errors
210
- and _almost_equal(sig.name, doc.name, _MIN_MATCH, _MAX_MATCH)
211
- ):
212
- self._add(_E[112])
213
-
214
227
  def _description_syntax(self, doc: _Param) -> None:
215
228
  if doc.description is not None and not doc.description.startswith(" "):
216
229
  self._add(_E[115])
@@ -219,6 +232,10 @@ class Failure(_t.List[str]):
219
232
  if doc.indent > 0:
220
233
  self._add(_E[116])
221
234
 
235
+ def _no_description(self, doc: _Param) -> None:
236
+ if doc.description is None:
237
+ self._add(_E[117])
238
+
222
239
  def _invalid_directive(self) -> None:
223
240
  for comment in self._func.comments:
224
241
  if not comment.isvalid:
@@ -17,6 +17,9 @@ import sphinx.ext.napoleon as _s
17
17
  # no function will accidentally have this name
18
18
  UNNAMED = -1000
19
19
 
20
+ # an example of valid parameter description
21
+ VALID_DESCRIPTION = " A valid description."
22
+
20
23
 
21
24
  # noinspection PyTypeChecker
22
25
  class _GoogleDocstring(str):
@@ -134,7 +137,9 @@ class Param(_t.NamedTuple):
134
137
  class _Matches(_t.List[Param]):
135
138
  _pattern = _re.compile(":(.*?):")
136
139
 
137
- def __init__(self, string: str, indent_anomaly: bool) -> None:
140
+ def __init__(
141
+ self, string: str, indent_anomaly: bool, missing_descriptions: bool
142
+ ) -> None:
138
143
  super().__init__()
139
144
  for line in string.splitlines():
140
145
  strip_line = line.lstrip()
@@ -152,7 +157,9 @@ class _Matches(_t.List[Param]):
152
157
  name = UNNAMED
153
158
 
154
159
  if len(match) > 1:
155
- description = match[1]
160
+ second = match[1]
161
+ if second != "" or not missing_descriptions:
162
+ description = second
156
163
 
157
164
  super().append(
158
165
  Param(
@@ -326,6 +333,24 @@ class Docstring(_Stub):
326
333
  # look for spaces in odd intervals
327
334
  return bool(any(i % 2 != 0 for i in leading_spaces))
328
335
 
336
+ @staticmethod
337
+ def _missing_descriptions(string: str) -> bool:
338
+ # find out if parameter is missing a description
339
+ new = ""
340
+ for line in string.strip().splitlines()[2:]:
341
+ line = line.lstrip()
342
+ if not line.startswith(":"):
343
+ # it is not a parameter, it is a next line description
344
+ # append the next entry to the same line
345
+ new = f"{new[:-1]} "
346
+ new += f"{line}\n"
347
+ if not new:
348
+ return True
349
+
350
+ # if it ends with a colon, it's a parameter without a
351
+ # description
352
+ return any(i.endswith(":") for i in new.splitlines())
353
+
329
354
  def __init__(
330
355
  self,
331
356
  node: _ast.Const | None = None,
@@ -336,7 +361,11 @@ class Docstring(_Stub):
336
361
  self._string = None
337
362
  if node is not None:
338
363
  self._string = _RawDocstring(node.value)
339
- for i in _Matches(self._string, self._indent_anomaly(node.value)):
364
+ for i in _Matches(
365
+ self._string,
366
+ self._indent_anomaly(node.value),
367
+ self._missing_descriptions(node.value),
368
+ ):
340
369
  self._args.append(i)
341
370
 
342
371
  self._returns = self._string is not None and 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.53.2"
11
+ __version__ = "0.54.0"
@@ -1,26 +1,6 @@
1
1
  """
2
2
  docsig.messages
3
3
  ===============
4
- | E101: parameters out of order
5
- | E102: includes parameters that do not exist
6
- | E103: parameters missing
7
- | E104: return statement documented for None
8
- | E105: return missing from docstring
9
- | E106: duplicate parameters found
10
- | E107: parameter appears to be incorrectly documented
11
- | E108: return statement documented for property
12
- | E109: cannot determine whether a return statement should exist or not
13
- | E110: documented parameter not equal to its respective argument
14
- | E111: return statement documented for class
15
- | E112: spelling error found in documented parameter
16
- | E113: function is missing a docstring
17
- | E114: class is missing a docstring
18
- | E115: syntax error in description
19
- | E116: param not indented correctly
20
- | E201: unknown module comment directive '{directive}'
21
- | E202: unknown inline comment directive '{directive}'
22
- | E203: unknown module comment option for {directive} '{option}'
23
- | E204: unknown inline comment option for {directive} '{option}'
24
4
  """
25
5
 
26
6
  from __future__ import annotations
@@ -157,7 +137,7 @@ E = MessageMap(
157
137
  ),
158
138
  109: Message(
159
139
  "E109",
160
- "cannot determine whether a return statement should exist or not",
140
+ "cannot determine whether a return statement should exist",
161
141
  "confirm-return-needed",
162
142
  "annotate type to indicate whether return documentation needed",
163
143
  ),
@@ -197,6 +177,11 @@ E = MessageMap(
197
177
  "param not indented correctly",
198
178
  "incorrect-indent",
199
179
  ),
180
+ 117: Message(
181
+ "E117",
182
+ "description missing from parameter",
183
+ "description-missing",
184
+ ),
200
185
  # E2xx: Config
201
186
  201: Message(
202
187
  "E201",
@@ -5,15 +5,6 @@ requires = [
5
5
  ]
6
6
 
7
7
  [tool.black]
8
- exclude = '''
9
- /(
10
- | \.git
11
- | \.mypy_cache
12
- | _build
13
- | build
14
- | dist
15
- )/
16
- '''
17
8
  line-length = 79
18
9
 
19
10
  [tool.codespell]
@@ -32,10 +23,21 @@ omit = [
32
23
  "whitelist.py"
33
24
  ]
34
25
 
26
+ [tool.deptry.per_rule_ignores]
27
+ DEP004 = ["yaml"]
28
+
29
+ [tool.docformatter]
30
+ in-place = true
31
+ wrap-summaries = 72
32
+
35
33
  [tool.docsig]
36
34
  check-class = true
37
35
  check-protected-class-methods = true
38
36
 
37
+ [tool.flynt]
38
+ line-length = 79
39
+ transform-concats = true
40
+
39
41
  [tool.isort]
40
42
  ensure_newline_before_comments = true
41
43
  force_grid_wrap = 0
@@ -45,6 +47,14 @@ multi_line_output = 3
45
47
  profile = "black"
46
48
  use_parentheses = true
47
49
 
50
+ [tool.mypy]
51
+ exclude = [
52
+ "whitelist\\.py"
53
+ ]
54
+ ignore_missing_imports = true
55
+ install_types = true
56
+ non_interactive = true
57
+
48
58
  [tool.poetry]
49
59
  authors = [
50
60
  "jshwi <stephen@jshwisolutions.com>"
@@ -66,7 +76,7 @@ maintainers = [
66
76
  name = "docsig"
67
77
  readme = "README.rst"
68
78
  repository = "https://github.com/jshwi/docsig"
69
- version = "0.53.2"
79
+ version = "0.54.0"
70
80
 
71
81
  [tool.poetry.dependencies]
72
82
  Sphinx = "^7.0.0"
@@ -76,57 +86,62 @@ click = "^8.1.7"
76
86
  pathspec = "^0.12.1"
77
87
  python = "^3.8"
78
88
 
79
- [tool.poetry.dev-dependencies]
89
+ [tool.poetry.group.dev.dependencies]
90
+ black = "^24.4.2"
80
91
  bump2version = "^1.0.1"
81
92
  deptry = "^0.16.1"
93
+ docformatter = "^1.7.5"
94
+ flynt = "^1.0.1"
95
+ isort = "^5.13.2"
96
+ mypy = "^1.10.0"
97
+ pre-commit = "^3.3.3"
98
+ pylint = "^3.1.0"
99
+ tox = "^4.15.0"
100
+ vulture = "^2.11"
101
+
102
+ [tool.poetry.group.docs.dependencies]
82
103
  furo = "^2024.4.27"
83
- ipython = "^8.12.0"
84
104
  myst-parser = "^3.0.1"
85
- pre-commit = "^3.3.3"
86
- pyaud = "^7.5.0"
87
- pytest-randomly = "^3.13.0"
105
+ pytest = "^8.2.0"
106
+ pyyaml = "^6.0.1"
107
+ sphinx-copybutton = "^0.5.2"
108
+ sphinx-markdown-builder = ">=0.5.5,<0.7.0"
109
+ templatest = "^0.10.1"
110
+
111
+ [tool.poetry.group.tests.dependencies]
112
+ pytest = "^8.2.0"
113
+ pytest-benchmark = "^4.0.0"
114
+ pytest-cov = "^5.0.0"
115
+ pytest-randomly = "^3.15.0"
88
116
  pytest-sugar = "^1.0.0"
89
117
  pytest-xdist = "^3.6.1"
90
- restview = "^3.0.0"
91
- sphinx-copybutton = "^0.5.2"
92
- sphinx-toolbox = "^3.5.0"
93
118
  templatest = "^0.10.1"
94
- tox = "^4.15.0"
95
119
 
96
120
  [tool.poetry.scripts]
97
121
  docsig = "docsig.__main__:main"
98
122
 
99
- [tool.pyaud]
100
- audit = [
101
- "commit-policy",
102
- "copyright-year",
103
- "format",
104
- "format-docs",
105
- "format-str",
106
- "imports",
107
- "lint",
108
- "test",
109
- "typecheck",
110
- "unused",
111
- "whitelist"
123
+ [tool.pylint.options]
124
+ disable = [
125
+ "consider-using-f-string",
126
+ "fixme"
112
127
  ]
113
- exclude = '''
114
- (?x)^(
115
- | docs\/conf\.py
116
- | whitelist\.py
117
- | scripts\/.*
118
- )$
119
- '''
128
+ ignore-patterns = [
129
+ 'conf\.py',
130
+ 'whitelist\.py'
131
+ ]
132
+ min-similarity-lines = 9
120
133
 
121
134
  [tool.pytest.ini_options]
122
135
  addopts = [
123
136
  "--color=yes",
124
137
  "--cov-report=term-missing",
125
138
  "--durations=5",
126
- "-n=auto",
127
139
  "-vv"
128
140
  ]
129
141
  filterwarnings = "ignore::DeprecationWarning"
142
+ markers = [
143
+ "benchmark: Marks tests as benchmarks"
144
+ ]
130
145
  norecursedirs = [
131
146
  ".git",
132
147
  ".idea",
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
File without changes