docsig 0.53.1__tar.gz → 0.53.2__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.1
3
+ Version: 0.53.2
4
4
  Summary: Check signature params for proper documentation
5
5
  Home-page: https://pypi.org/project/docsig/
6
6
  License: MIT
@@ -90,7 +90,7 @@ Supports reStructuredText (``Sphinx``), ``NumPy``, and ``Google``
90
90
 
91
91
  Contributing
92
92
  ------------
93
- If you are interested in contributing to ``docsig`` please read about contributing `here <https://github.com/jshwi/docsig/blob/master/CONTRIBUTING.md>`__
93
+ If you are interested in contributing to ``docsig`` please read about contributing `here <https://docsig.readthedocs.io/en/latest/development/contributing.html>`__
94
94
 
95
95
  Installation
96
96
  ------------
@@ -199,17 +199,17 @@ API
199
199
  E102: includes parameters that do not exist (params-do-not-exist)
200
200
  1
201
201
 
202
- A full list of checks can be found `here <https://docsig.readthedocs.io/en/latest/docsig.html#docsig-messages>`__
202
+ A full list of checks can be found `here <https://docsig.readthedocs.io/en/latest/usage/messages.html>`__
203
203
 
204
204
  Message Control
205
205
  ***************
206
206
 
207
- `Documentation on message control <https://github.com/jshwi/docsig/blob/master/docs/examples/message-control.rst>`_
207
+ `Documentation on message control <https://docsig.readthedocs.io/en/latest/usage/message-control.html>`_
208
208
 
209
209
  Classes
210
210
  *******
211
211
 
212
- `Documenting classes <https://github.com/jshwi/docsig/blob/master/docs/examples/classes.rst>`_
212
+ `Documenting classes <https://docsig.readthedocs.io/en/latest/usage/configuration.html#classes>`_
213
213
 
214
214
  pre-commit
215
215
  **********
@@ -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.1
225
+ rev: v0.53.2
226
226
  hooks:
227
227
  - id: docsig
228
228
  args:
@@ -62,7 +62,7 @@ Supports reStructuredText (``Sphinx``), ``NumPy``, and ``Google``
62
62
 
63
63
  Contributing
64
64
  ------------
65
- If you are interested in contributing to ``docsig`` please read about contributing `here <https://github.com/jshwi/docsig/blob/master/CONTRIBUTING.md>`__
65
+ If you are interested in contributing to ``docsig`` please read about contributing `here <https://docsig.readthedocs.io/en/latest/development/contributing.html>`__
66
66
 
67
67
  Installation
68
68
  ------------
@@ -171,17 +171,17 @@ API
171
171
  E102: includes parameters that do not exist (params-do-not-exist)
172
172
  1
173
173
 
174
- A full list of checks can be found `here <https://docsig.readthedocs.io/en/latest/docsig.html#docsig-messages>`__
174
+ A full list of checks can be found `here <https://docsig.readthedocs.io/en/latest/usage/messages.html>`__
175
175
 
176
176
  Message Control
177
177
  ***************
178
178
 
179
- `Documentation on message control <https://github.com/jshwi/docsig/blob/master/docs/examples/message-control.rst>`_
179
+ `Documentation on message control <https://docsig.readthedocs.io/en/latest/usage/message-control.html>`_
180
180
 
181
181
  Classes
182
182
  *******
183
183
 
184
- `Documenting classes <https://github.com/jshwi/docsig/blob/master/docs/examples/classes.rst>`_
184
+ `Documenting classes <https://docsig.readthedocs.io/en/latest/usage/configuration.html#classes>`_
185
185
 
186
186
  pre-commit
187
187
  **********
@@ -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.1
197
+ rev: v0.53.2
198
198
  hooks:
199
199
  - id: docsig
200
200
  args:
@@ -11,6 +11,7 @@ import typing as _t
11
11
  import click as _click
12
12
 
13
13
  from ._module import Function as _Function
14
+ from ._stub import UNNAMED as _UNNAMED
14
15
  from ._stub import Param as _Param
15
16
  from ._stub import RetType as _RetType
16
17
  from ._utils import almost_equal as _almost_equal
@@ -65,10 +66,14 @@ class Failure(_t.List[str]):
65
66
  self._missing_class_docstring()
66
67
  self._missing_func_docstring()
67
68
  if func.docstring.string is not None:
68
- self._return_not_typed()
69
+ # make sure these come first as they alter the function
70
+ # docstring object before it is analysed further
71
+ self._duplicates()
69
72
  self._exists()
73
+
74
+ # all further analysis below
75
+ self._return_not_typed()
70
76
  self._missing()
71
- self._duplicates()
72
77
  self._extra_return()
73
78
  self._missing_return()
74
79
  self._property_return()
@@ -78,9 +83,9 @@ class Failure(_t.List[str]):
78
83
  doc = func.docstring.args.get(index)
79
84
  self._description_syntax(doc)
80
85
  self._indent_syntax(doc)
86
+ self._incorrect(doc)
81
87
  if arg != doc:
82
88
  self._order(arg, doc)
83
- self._incorrect(arg, doc)
84
89
  self._misspelled(arg, doc)
85
90
  self._not_equal(arg, doc)
86
91
 
@@ -105,7 +110,16 @@ class Failure(_t.List[str]):
105
110
  self._add(_E[101])
106
111
 
107
112
  def _exists(self) -> None:
113
+ # pop the parameters that do not exist so that they are excluded
114
+ # from further analysis, that way there are no additional, and
115
+ # redundant, errors
116
+ # this will ensure that both signature and docstring are equal
117
+ # in length, with all parameters that do not exist accounted for
108
118
  if len(self._func.docstring.args) > len(self._func.signature.args):
119
+ for count, __ in enumerate(self._func.docstring.args, 1):
120
+ if count > len(self._func.signature.args):
121
+ self._func.docstring.args.pop(count - 1)
122
+
109
123
  self._add(_E[102])
110
124
 
111
125
  def _missing_func_docstring(self) -> None:
@@ -121,7 +135,14 @@ class Failure(_t.List[str]):
121
135
  self._add(_E[103])
122
136
 
123
137
  def _duplicates(self) -> None:
138
+ # pop the duplicates so that they are considered a single
139
+ # parameter, that way there are no assumptions that the
140
+ # parameters must be out of order
124
141
  if self._func.docstring.args.duplicated:
142
+ for count, arg in enumerate(self._func.docstring.args):
143
+ if arg in self._func.docstring.args.duplicates:
144
+ self._func.docstring.args.pop(count)
145
+
125
146
  self._add(_E[106])
126
147
 
127
148
  def _extra_return(self) -> None:
@@ -151,13 +172,25 @@ class Failure(_t.List[str]):
151
172
  and not self._no_returns
152
173
  ):
153
174
  docstring = self._func.docstring.string
154
- if docstring is not None and "return" in docstring:
155
- hint = True
175
+ # do more than just search the docstring for the word return
176
+ # as return statements come last, so only search the last
177
+ # line
178
+ # params can also come last, so make sure it is not a param
179
+ # declaration
180
+ if docstring is not None:
181
+ lines = docstring.splitlines()
182
+ if len(lines) > 1:
183
+ if "return" in lines[-1] and ":param" not in lines[-1]:
184
+ hint = True
156
185
 
157
186
  self._add(_E[105], hint=hint)
158
187
 
159
- def _incorrect(self, sig: _Param, doc: _Param) -> None:
160
- if sig.name is None and doc.name is None:
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:
161
194
  self._add(_E[107])
162
195
 
163
196
  # final catch-all
@@ -14,6 +14,9 @@ from enum import Enum as _Enum
14
14
  import astroid as _ast
15
15
  import sphinx.ext.napoleon as _s
16
16
 
17
+ # no function will accidentally have this name
18
+ UNNAMED = -1000
19
+
17
20
 
18
21
  # noinspection PyTypeChecker
19
22
  class _GoogleDocstring(str):
@@ -131,19 +134,22 @@ class Param(_t.NamedTuple):
131
134
  class _Matches(_t.List[Param]):
132
135
  _pattern = _re.compile(":(.*?):")
133
136
 
134
- def __init__(self, string: str) -> None:
137
+ def __init__(self, string: str, indent_anomaly: bool) -> None:
135
138
  super().__init__()
136
139
  for line in string.splitlines():
137
140
  strip_line = line.lstrip()
138
141
  match = self._pattern.split(strip_line)[1:]
139
142
  if match:
140
- name = description = None
143
+ description = None
141
144
  kinds = match[0].split()
142
145
  if kinds:
143
146
  kind = DocType.from_str(kinds[0])
144
147
 
145
148
  if len(kinds) > 1:
146
149
  name = kinds[1]
150
+ else:
151
+ # name could not be parsed
152
+ name = UNNAMED
147
153
 
148
154
  if len(match) > 1:
149
155
  description = match[1]
@@ -153,7 +159,7 @@ class _Matches(_t.List[Param]):
153
159
  kind,
154
160
  name,
155
161
  description,
156
- len(line) - len(strip_line),
162
+ int(indent_anomaly),
157
163
  )
158
164
  )
159
165
 
@@ -165,6 +171,7 @@ class _Params(_t.List[Param]):
165
171
  super().__init__()
166
172
  self._ignore_args = ignore_args
167
173
  self._ignore_kwargs = ignore_kwargs
174
+ self._duplicates: list[Param] = []
168
175
 
169
176
  # pylint: disable=too-many-boolean-expressions
170
177
  def append(self, value: Param) -> None:
@@ -195,8 +202,26 @@ class _Params(_t.List[Param]):
195
202
 
196
203
  @property
197
204
  def duplicated(self) -> bool:
198
- """Boolean value for whether there are duplicate parameters."""
199
- return any(k for k, v in _Counter(self).items() if v > 1)
205
+ """Boolean value for whether there are duplicate parameters.
206
+
207
+ Ensure only the names of the parameters are needed to be
208
+ considered duplicates. It is not relevant whether the
209
+ descriptions match.
210
+ """
211
+ for k, v in _Counter(i.name for i in self).items():
212
+ if v > 1:
213
+ for i in self:
214
+ if i.name == k:
215
+ # record the duplicates for later analysis
216
+ self._duplicates.append(i)
217
+ return True
218
+
219
+ return False
220
+
221
+ @property
222
+ def duplicates(self) -> list[Param]:
223
+ """Duplicated parameters."""
224
+ return self._duplicates
200
225
 
201
226
 
202
227
  class _Stub:
@@ -288,6 +313,19 @@ class Docstring(_Stub):
288
313
  :param ignore_kwargs: Ignore kwargs prefixed with two asterisks.
289
314
  """
290
315
 
316
+ @staticmethod
317
+ def _indent_anomaly(string: str) -> bool:
318
+ leading_spaces = []
319
+ for line in string.splitlines():
320
+ match = _re.match(r"^\s*", line)
321
+ if match is not None:
322
+ spaces = len(match.group())
323
+ if spaces > 0:
324
+ leading_spaces.append(spaces)
325
+
326
+ # look for spaces in odd intervals
327
+ return bool(any(i % 2 != 0 for i in leading_spaces))
328
+
291
329
  def __init__(
292
330
  self,
293
331
  node: _ast.Const | None = None,
@@ -298,7 +336,7 @@ class Docstring(_Stub):
298
336
  self._string = None
299
337
  if node is not None:
300
338
  self._string = _RawDocstring(node.value)
301
- for i in _Matches(self._string):
339
+ for i in _Matches(self._string, self._indent_anomaly(node.value)):
302
340
  self._args.append(i)
303
341
 
304
342
  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.1"
11
+ __version__ = "0.53.2"
@@ -27,6 +27,7 @@ fail_under = 100
27
27
 
28
28
  [tool.coverage.run]
29
29
  omit = [
30
+ "docs/conf.py",
30
31
  "docsig/__main__.py",
31
32
  "whitelist.py"
32
33
  ]
@@ -65,7 +66,7 @@ maintainers = [
65
66
  name = "docsig"
66
67
  readme = "README.rst"
67
68
  repository = "https://github.com/jshwi/docsig"
68
- version = "0.53.1"
69
+ version = "0.53.2"
69
70
 
70
71
  [tool.poetry.dependencies]
71
72
  Sphinx = "^7.0.0"
@@ -78,14 +79,16 @@ python = "^3.8"
78
79
  [tool.poetry.dev-dependencies]
79
80
  bump2version = "^1.0.1"
80
81
  deptry = "^0.16.1"
82
+ furo = "^2024.4.27"
81
83
  ipython = "^8.12.0"
84
+ myst-parser = "^3.0.1"
82
85
  pre-commit = "^3.3.3"
83
86
  pyaud = "^7.5.0"
84
87
  pytest-randomly = "^3.13.0"
85
88
  pytest-sugar = "^1.0.0"
86
89
  pytest-xdist = "^3.6.1"
87
90
  restview = "^3.0.0"
88
- sphinx-immaterial = "^0.11.9"
91
+ sphinx-copybutton = "^0.5.2"
89
92
  sphinx-toolbox = "^3.5.0"
90
93
  templatest = "^0.10.1"
91
94
  tox = "^4.15.0"
@@ -95,10 +98,8 @@ docsig = "docsig.__main__:main"
95
98
 
96
99
  [tool.pyaud]
97
100
  audit = [
98
- "about-tests",
99
101
  "commit-policy",
100
102
  "copyright-year",
101
- "files",
102
103
  "format",
103
104
  "format-docs",
104
105
  "format-str",
@@ -106,13 +107,14 @@ audit = [
106
107
  "lint",
107
108
  "test",
108
109
  "typecheck",
109
- "unused"
110
+ "unused",
111
+ "whitelist"
110
112
  ]
111
113
  exclude = '''
112
114
  (?x)^(
113
115
  | docs\/conf\.py
114
116
  | whitelist\.py
115
- | scripts\/update_readme\.py
117
+ | scripts\/.*
116
118
  )$
117
119
  '''
118
120
 
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
File without changes