docstring-format-checker 0.1.0__py3-none-any.whl
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.
- docstring_format_checker/__init__.py +22 -0
- docstring_format_checker/cli.py +716 -0
- docstring_format_checker/config.py +334 -0
- docstring_format_checker/core.py +671 -0
- docstring_format_checker/utils/__init__.py +0 -0
- docstring_format_checker/utils/exceptions.py +39 -0
- docstring_format_checker-0.1.0.dist-info/METADATA +467 -0
- docstring_format_checker-0.1.0.dist-info/RECORD +10 -0
- docstring_format_checker-0.1.0.dist-info/WHEEL +4 -0
- docstring_format_checker-0.1.0.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# ============================================================================ #
|
|
2
|
+
# #
|
|
3
|
+
# Title: Configuration Management #
|
|
4
|
+
# Purpose: Configuration for docstring format checking #
|
|
5
|
+
# Notes: Notes #
|
|
6
|
+
# Author: chrimaho #
|
|
7
|
+
# Created: Created #
|
|
8
|
+
# References: References #
|
|
9
|
+
# Sources: Sources #
|
|
10
|
+
# Edited: Edited #
|
|
11
|
+
# #
|
|
12
|
+
# ============================================================================ #
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# ---------------------------------------------------------------------------- #
|
|
16
|
+
# #
|
|
17
|
+
# Overview ####
|
|
18
|
+
# #
|
|
19
|
+
# ---------------------------------------------------------------------------- #
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ---------------------------------------------------------------------------- #
|
|
23
|
+
# Description ####
|
|
24
|
+
# ---------------------------------------------------------------------------- #
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
!!! note "Summary"
|
|
29
|
+
Configuration handling for the docstring format checker.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------- #
|
|
34
|
+
# #
|
|
35
|
+
# Setup ####
|
|
36
|
+
# #
|
|
37
|
+
# ---------------------------------------------------------------------------- #
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## --------------------------------------------------------------------------- #
|
|
41
|
+
## Imports ####
|
|
42
|
+
## --------------------------------------------------------------------------- #
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ## Python StdLib Imports ----
|
|
46
|
+
import sys
|
|
47
|
+
from dataclasses import dataclass
|
|
48
|
+
from pathlib import Path
|
|
49
|
+
from typing import Any, Literal, Optional, Union
|
|
50
|
+
|
|
51
|
+
# ## Local First Party Imports ----
|
|
52
|
+
from docstring_format_checker.utils.exceptions import (
|
|
53
|
+
InvalidConfigError,
|
|
54
|
+
InvalidConfigError_DuplicateOrderValues,
|
|
55
|
+
InvalidTypeValuesError,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if sys.version_info >= (3, 11):
|
|
60
|
+
# ## Python StdLib Imports ----
|
|
61
|
+
import tomllib
|
|
62
|
+
else:
|
|
63
|
+
# ## Python Third Party Imports ----
|
|
64
|
+
import tomli as tomllib
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
## --------------------------------------------------------------------------- #
|
|
68
|
+
## Exports ####
|
|
69
|
+
## --------------------------------------------------------------------------- #
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
__all__: list[str] = [
|
|
73
|
+
"SectionConfig",
|
|
74
|
+
"DEFAULT_CONFIG",
|
|
75
|
+
"load_config",
|
|
76
|
+
"find_config_file",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
## --------------------------------------------------------------------------- #
|
|
81
|
+
## Constants ####
|
|
82
|
+
## --------------------------------------------------------------------------- #
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
VALID_TYPES: tuple[str, ...] = (
|
|
86
|
+
"free_text", # Free text sections (summary, details, examples, notes)
|
|
87
|
+
"list_name", # Simple name sections (name)
|
|
88
|
+
"list_type", # Simple type sections (raises, yields)
|
|
89
|
+
"list_name_and_type", # Params-style sections (name (type): description)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ---------------------------------------------------------------------------- #
|
|
94
|
+
# #
|
|
95
|
+
# Helpers ####
|
|
96
|
+
# #
|
|
97
|
+
# ---------------------------------------------------------------------------- #
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
## --------------------------------------------------------------------------- #
|
|
101
|
+
## Classes ####
|
|
102
|
+
## --------------------------------------------------------------------------- #
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass
|
|
106
|
+
class SectionConfig:
|
|
107
|
+
"""
|
|
108
|
+
Configuration for a docstring section.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
order: int
|
|
112
|
+
name: str
|
|
113
|
+
type: Literal["free_text", "list_name", "list_type", "list_name_and_type"]
|
|
114
|
+
admonition: str = ""
|
|
115
|
+
prefix: str = "" # Support any prefix string
|
|
116
|
+
required: bool = False
|
|
117
|
+
message: str = "" # Optional message for validation errors
|
|
118
|
+
|
|
119
|
+
def __post_init__(self) -> None:
|
|
120
|
+
"""Validate configuration after initialization."""
|
|
121
|
+
if self.type not in VALID_TYPES:
|
|
122
|
+
raise InvalidTypeValuesError(f"Invalid section type: {self.type}. Valid types: {VALID_TYPES}")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
## --------------------------------------------------------------------------- #
|
|
126
|
+
## Validations ####
|
|
127
|
+
## --------------------------------------------------------------------------- #
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _validate_config_order(config_sections: list[SectionConfig]) -> None:
|
|
131
|
+
|
|
132
|
+
# Validate no duplicate order values
|
|
133
|
+
order_values: list[int] = [section.order for section in config_sections]
|
|
134
|
+
seen_orders: set[int] = set()
|
|
135
|
+
duplicate_orders: set[int] = set()
|
|
136
|
+
|
|
137
|
+
for order in order_values:
|
|
138
|
+
if order in seen_orders:
|
|
139
|
+
duplicate_orders.add(order)
|
|
140
|
+
else:
|
|
141
|
+
seen_orders.add(order)
|
|
142
|
+
|
|
143
|
+
if duplicate_orders:
|
|
144
|
+
raise InvalidConfigError_DuplicateOrderValues(
|
|
145
|
+
f"Configuration contains duplicate order values: {sorted(duplicate_orders)}. "
|
|
146
|
+
"Each section must have a unique order value."
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# ---------------------------------------------------------------------------- #
|
|
151
|
+
# #
|
|
152
|
+
# Main Section ####
|
|
153
|
+
# #
|
|
154
|
+
# ---------------------------------------------------------------------------- #
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
DEFAULT_CONFIG: list[SectionConfig] = [
|
|
158
|
+
SectionConfig(
|
|
159
|
+
order=1,
|
|
160
|
+
name="summary",
|
|
161
|
+
type="free_text",
|
|
162
|
+
admonition="note",
|
|
163
|
+
prefix="!!!",
|
|
164
|
+
required=True,
|
|
165
|
+
),
|
|
166
|
+
SectionConfig(
|
|
167
|
+
order=2,
|
|
168
|
+
name="details",
|
|
169
|
+
type="free_text",
|
|
170
|
+
admonition="info",
|
|
171
|
+
prefix="???+",
|
|
172
|
+
required=False,
|
|
173
|
+
),
|
|
174
|
+
SectionConfig(
|
|
175
|
+
order=3,
|
|
176
|
+
name="params",
|
|
177
|
+
type="list_name_and_type",
|
|
178
|
+
required=True,
|
|
179
|
+
),
|
|
180
|
+
SectionConfig(
|
|
181
|
+
order=4,
|
|
182
|
+
name="returns",
|
|
183
|
+
type="list_name_and_type",
|
|
184
|
+
required=False,
|
|
185
|
+
),
|
|
186
|
+
SectionConfig(
|
|
187
|
+
order=5,
|
|
188
|
+
name="yields",
|
|
189
|
+
type="list_type",
|
|
190
|
+
required=False,
|
|
191
|
+
),
|
|
192
|
+
SectionConfig(
|
|
193
|
+
order=6,
|
|
194
|
+
name="raises",
|
|
195
|
+
type="list_type",
|
|
196
|
+
required=False,
|
|
197
|
+
),
|
|
198
|
+
SectionConfig(
|
|
199
|
+
order=7,
|
|
200
|
+
name="examples",
|
|
201
|
+
type="free_text",
|
|
202
|
+
admonition="example",
|
|
203
|
+
prefix="???+",
|
|
204
|
+
required=False,
|
|
205
|
+
),
|
|
206
|
+
SectionConfig(
|
|
207
|
+
order=8,
|
|
208
|
+
name="notes",
|
|
209
|
+
type="free_text",
|
|
210
|
+
admonition="note",
|
|
211
|
+
prefix="???",
|
|
212
|
+
required=False,
|
|
213
|
+
),
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def load_config(config_path: Optional[Union[str, Path]] = None) -> list[SectionConfig]:
|
|
218
|
+
"""
|
|
219
|
+
!!! note "Summary"
|
|
220
|
+
Load configuration from a TOML file or return default configuration.
|
|
221
|
+
|
|
222
|
+
Params:
|
|
223
|
+
config_path (Optional[Union[str, Path]]):
|
|
224
|
+
Path to the TOML configuration file.
|
|
225
|
+
If `None`, looks for `pyproject.toml` in current directory.
|
|
226
|
+
Default: `None`.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
(list[SectionConfig]):
|
|
230
|
+
List of SectionConfig objects defining the docstring sections to check.
|
|
231
|
+
|
|
232
|
+
Raises:
|
|
233
|
+
(FileNotFoundError):
|
|
234
|
+
If the specified config file doesn't exist.
|
|
235
|
+
(InvalidConfigError):
|
|
236
|
+
If the configuration is invalid.
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
if config_path is None:
|
|
240
|
+
# Look for pyproject.toml in current directory
|
|
241
|
+
pyproject_path: Path = Path.cwd().joinpath("pyproject.toml")
|
|
242
|
+
if pyproject_path.exists():
|
|
243
|
+
config_path = pyproject_path
|
|
244
|
+
else:
|
|
245
|
+
return DEFAULT_CONFIG
|
|
246
|
+
|
|
247
|
+
config_path = Path(config_path)
|
|
248
|
+
if not config_path.exists():
|
|
249
|
+
raise FileNotFoundError(f"Configuration file not found: {config_path}")
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
with open(config_path, "rb") as f:
|
|
253
|
+
config_data: dict[str, Any] = tomllib.load(f)
|
|
254
|
+
except Exception as e:
|
|
255
|
+
raise InvalidConfigError(f"Failed to parse TOML file {config_path}: {e}") from e
|
|
256
|
+
|
|
257
|
+
# Try to find configuration under [tool.dfc] or [tool.docstring-format-checker]
|
|
258
|
+
tool_config = None
|
|
259
|
+
if "tool" in config_data:
|
|
260
|
+
if "dfc" in config_data["tool"]:
|
|
261
|
+
tool_config = config_data["tool"]["dfc"]
|
|
262
|
+
elif "docstring-format-checker" in config_data["tool"]:
|
|
263
|
+
tool_config = config_data["tool"]["docstring-format-checker"]
|
|
264
|
+
|
|
265
|
+
if tool_config is None:
|
|
266
|
+
return DEFAULT_CONFIG
|
|
267
|
+
|
|
268
|
+
# Parse sections configuration
|
|
269
|
+
sections_config: list[SectionConfig] = []
|
|
270
|
+
if "sections" in tool_config:
|
|
271
|
+
sections_data = tool_config["sections"]
|
|
272
|
+
for section_data in sections_data:
|
|
273
|
+
try:
|
|
274
|
+
section = SectionConfig(
|
|
275
|
+
order=section_data.get("order", 0),
|
|
276
|
+
name=section_data.get("name", ""),
|
|
277
|
+
type=section_data.get("type", ""),
|
|
278
|
+
admonition=section_data.get("admonition", ""),
|
|
279
|
+
prefix=section_data.get("prefix", ""),
|
|
280
|
+
required=section_data.get("required", False),
|
|
281
|
+
)
|
|
282
|
+
sections_config.append(section)
|
|
283
|
+
except (KeyError, TypeError, ValueError, InvalidTypeValuesError) as e:
|
|
284
|
+
raise InvalidConfigError(f"Invalid section configuration: {section_data}. Error: {e}") from e
|
|
285
|
+
|
|
286
|
+
if not sections_config:
|
|
287
|
+
return DEFAULT_CONFIG
|
|
288
|
+
|
|
289
|
+
# Validate no duplicate order values
|
|
290
|
+
_validate_config_order(config_sections=sections_config)
|
|
291
|
+
|
|
292
|
+
# Sort by order
|
|
293
|
+
sections_config.sort(key=lambda x: x.order)
|
|
294
|
+
|
|
295
|
+
return sections_config
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def find_config_file(start_path: Optional[Path] = None) -> Optional[Path]:
|
|
299
|
+
"""
|
|
300
|
+
!!! note "Summary"
|
|
301
|
+
Find configuration file by searching up the directory tree.
|
|
302
|
+
|
|
303
|
+
Params:
|
|
304
|
+
start_path (Optional[Path]):
|
|
305
|
+
Directory to start searching from.
|
|
306
|
+
If `None`, resolves to current directory.
|
|
307
|
+
Default: `None`.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
(Optional[Path]):
|
|
311
|
+
Path to the configuration file if found, None otherwise.
|
|
312
|
+
"""
|
|
313
|
+
if start_path is None:
|
|
314
|
+
start_path = Path.cwd()
|
|
315
|
+
|
|
316
|
+
current_path: Path = start_path.resolve()
|
|
317
|
+
|
|
318
|
+
while current_path != current_path.parent:
|
|
319
|
+
pyproject_path: Path = current_path.joinpath("pyproject.toml")
|
|
320
|
+
if pyproject_path.exists():
|
|
321
|
+
# Check if it contains dfc configuration
|
|
322
|
+
try:
|
|
323
|
+
with open(pyproject_path, "rb") as f:
|
|
324
|
+
config_data: dict[str, Any] = tomllib.load(f)
|
|
325
|
+
if "tool" in config_data and (
|
|
326
|
+
"dfc" in config_data["tool"] or "docstring-format-checker" in config_data["tool"]
|
|
327
|
+
):
|
|
328
|
+
return pyproject_path
|
|
329
|
+
except Exception:
|
|
330
|
+
pass
|
|
331
|
+
|
|
332
|
+
current_path = current_path.parent
|
|
333
|
+
|
|
334
|
+
return None
|