docstring-format-checker 1.0.1__tar.gz → 1.2.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.4
2
2
  Name: docstring-format-checker
3
- Version: 1.0.1
3
+ Version: 1.2.0
4
4
  Summary: A CLI tool to check and validate Python docstring formatting and completeness
5
5
  Author: Chris Mahoney
6
6
  Author-email: Chris Mahoney <docstring-format-checker@data-science-extensions.com>
@@ -23,6 +23,7 @@ Requires-Dist: typer>=0.9.0
23
23
  Requires-Dist: tomli>=2.0.0 ; python_full_version < '3.11'
24
24
  Requires-Dist: rich>=13.0.0
25
25
  Requires-Dist: toolbox-python==1.*
26
+ Requires-Dist: pyfiglet==1.*
26
27
  Maintainer: Chris Mahoney
27
28
  Maintainer-email: Chris Mahoney <docstring-format-checker@data-science-extensions.com>
28
29
  Requires-Python: >=3.9
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "docstring-format-checker"
3
- version = "1.0.1"
3
+ version = "1.2.0"
4
4
  description = "A CLI tool to check and validate Python docstring formatting and completeness"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -32,6 +32,7 @@ dependencies = [
32
32
  "tomli>=2.0.0;python_version<'3.11'",
33
33
  "rich>=13.0.0",
34
34
  "toolbox-python==1.*",
35
+ "pyfiglet==1.*",
35
36
  ]
36
37
 
37
38
  [project.urls]
@@ -43,12 +43,14 @@
43
43
 
44
44
 
45
45
  # ## Python StdLib Imports ----
46
+ import os
46
47
  from functools import partial
47
48
  from pathlib import Path
48
49
  from textwrap import dedent
49
50
  from typing import Optional
50
51
 
51
52
  # ## Python Third Party Imports ----
53
+ import pyfiglet
52
54
  from rich.console import Console
53
55
  from rich.panel import Panel
54
56
  from rich.table import Table
@@ -160,29 +162,6 @@ def _version_callback(ctx: Context, param: CallbackParam, value: bool) -> None:
160
162
  raise Exit()
161
163
 
162
164
 
163
- def _help_callback_main(ctx: Context, param: CallbackParam, value: bool) -> None:
164
- """
165
- !!! note "Summary"
166
- Show help and exit.
167
-
168
- Params:
169
- ctx (Context):
170
- The context object.
171
- param (CallbackParam):
172
- The parameter object.
173
- value (bool):
174
- The boolean value indicating if the flag was set.
175
-
176
- Returns:
177
- (None):
178
- Nothing is returned.
179
- """
180
- if not value or ctx.resilient_parsing:
181
- return
182
- echo(ctx.get_help())
183
- raise Exit()
184
-
185
-
186
165
  def _example_callback(ctx: Context, param: CallbackParam, value: Optional[str]) -> None:
187
166
  """
188
167
  !!! note "Summary"
@@ -211,6 +190,7 @@ def _example_callback(ctx: Context, param: CallbackParam, value: Optional[str])
211
190
  else:
212
191
  console.print(_red(f"Error: Invalid example type '{value}'. Use 'config' or 'usage'."))
213
192
  raise Exit(1)
193
+ raise Exit()
214
194
 
215
195
 
216
196
  def _show_usage_examples_callback() -> None:
@@ -233,30 +213,33 @@ def _show_usage_examples_callback() -> None:
233
213
 
234
214
  examples_content: str = dedent(
235
215
  f"""
236
- {_green("dfc myfile.py")} Check a single Python file (list output)
237
- {_green("dfc src/")} Check all Python files in src/ directory
238
- {_green("dfc --output=table myfile.py")} Check with table output format
239
- {_green("dfc -o list myfile.py")} Check with list output format (default)
240
- {_green("dfc --check myfile.py")} Check and exit with error if issues found
241
- {_green("dfc --quiet myfile.py")} Check quietly, only show pass/fail
242
- {_green("dfc --quiet --check myfile.py")} Check quietly and exit with error if issues found
243
- {_green("dfc . --exclude '*/tests/*'")} Check current directory, excluding tests
244
- {_green("dfc . -c custom.toml")} Use custom configuration file
245
- {_green("dfc --example=config")} Show example configuration
246
- {_green("dfc -e usage")} Show usage examples (this help)
216
+ Execute the below commands in any terminal after installing the package.
217
+
218
+ {_blue("dfc myfile.py")} {_green("# Check a single Python file (list output)")}
219
+ {_blue("dfc myfile.py other_file.py")} {_green("# Check multiple Python files")}
220
+ {_blue("dfc src/")} {_green("# Check all Python files in src/ directory")}
221
+ {_blue("dfc -x src/app/__init__.py src/")} {_green("# Check all Python files in src/ directory, excluding one init file")}
222
+ {_blue("dfc --output=table myfile.py")} {_green("# Check with table output format")}
223
+ {_blue("dfc -o list myfile.py")} {_green("# Check with list output format (default)")}
224
+ {_blue("dfc --check myfile.py")} {_green("# Check and exit with error if issues found")}
225
+ {_blue("dfc --quiet myfile.py")} {_green("# Check quietly, only show pass/fail")}
226
+ {_blue("dfc --quiet --check myfile.py")} {_green("# Check quietly and exit with error if issues found")}
227
+ {_blue("dfc . --exclude '*/tests/*'")} {_green("# Check current directory, excluding tests")}
228
+ {_blue("dfc . -c custom.toml")} {_green("# Use custom configuration file")}
229
+ {_blue("dfc --example=config")} {_green("# Show example configuration")}
230
+ {_blue("dfc -e usage")} {_green("# Show usage examples (this help)")}
247
231
  """
248
232
  ).strip()
249
233
 
250
234
  panel = Panel(
251
235
  examples_content,
252
- title="Examples",
236
+ title="Usage Examples",
253
237
  title_align="left",
254
238
  border_style="dim",
255
239
  padding=(0, 1),
256
240
  )
257
241
 
258
242
  console.print(panel)
259
- raise Exit()
260
243
 
261
244
 
262
245
  def _show_config_example_callback() -> None:
@@ -278,73 +261,84 @@ def _show_config_example_callback() -> None:
278
261
  """
279
262
 
280
263
  example_config: str = dedent(
281
- """
282
- # Example configuration for docstring-format-checker
283
- # Place this in your pyproject.toml file
284
-
285
- [tool.dfc]
286
- # or [tool.docstring-format-checker]
287
-
288
- [[tool.dfc.sections]]
289
- order = 1
290
- name = "summary"
291
- type = "free_text"
292
- admonition = "note"
293
- prefix = "!!!"
294
- required = true
295
-
296
- [[tool.dfc.sections]]
297
- order = 2
298
- name = "details"
299
- type = "free_text"
300
- admonition = "info"
301
- prefix = "???+"
302
- required = false
303
-
304
- [[tool.dfc.sections]]
305
- order = 3
306
- name = "params"
307
- type = "list_name_and_type"
308
- required = true
309
-
310
- [[tool.dfc.sections]]
311
- order = 4
312
- name = "returns"
313
- type = "list_name_and_type"
314
- required = false
315
-
316
- [[tool.dfc.sections]]
317
- order = 5
318
- name = "yields"
319
- type = "list_type"
320
- required = false
321
-
322
- [[tool.dfc.sections]]
323
- order = 6
324
- name = "raises"
325
- type = "list_type"
326
- required = false
327
-
328
- [[tool.dfc.sections]]
329
- order = 7
330
- name = "examples"
331
- type = "free_text"
332
- admonition = "example"
333
- prefix = "???+"
334
- required = false
335
-
336
- [[tool.dfc.sections]]
337
- order = 8
338
- name = "notes"
339
- type = "free_text"
340
- admonition = "note"
341
- prefix = "???"
342
- required = false
264
+ r"""
265
+ Place the below config in your `pyproject.toml` file.
266
+
267
+ [blue]\[tool.dfc][/blue]
268
+ [green]# or \[tool.docstring-format-checker][/green]
269
+ [blue]allow_undefined_sections = false[/blue]
270
+ [blue]require_docstrings = true[/blue]
271
+ [blue]check_private = true[/blue]
272
+ [blue]sections = [[/blue]
273
+ [blue]{ order = 1, name = "summary", type = "free_text", required = true, admonition = "note", prefix = "!!!" },[/blue]
274
+ [blue]{ order = 2, name = "details", type = "free_text", required = false, admonition = "abstract", prefix = "???+" },[/blue]
275
+ [blue]{ order = 3, name = "params", type = "list_name_and_type", required = false },[/blue]
276
+ [blue]{ order = 4, name = "raises", type = "list_type", required = false },[/blue]
277
+ [blue]{ order = 5, name = "returns", type = "list_name_and_type", required = false },[/blue]
278
+ [blue]{ order = 6, name = "yields", type = "list_type", required = false },[/blue]
279
+ [blue]{ order = 7, name = "examples", type = "free_text", required = false, admonition = "example", prefix = "???+" },[/blue]
280
+ [blue]{ order = 8, name = "notes", type = "free_text", required = false, admonition = "note", prefix = "???" },[/blue]
281
+ [blue]][/blue]
343
282
  """
344
283
  ).strip()
345
284
 
285
+ panel = Panel(
286
+ example_config,
287
+ title="Configuration Example",
288
+ title_align="left",
289
+ border_style="dim",
290
+ padding=(0, 1),
291
+ )
292
+
346
293
  # Print without Rich markup processing to avoid bracket interpretation
347
- console.print(example_config, markup=False)
294
+ console.print(panel)
295
+
296
+
297
+ def _help_callback_main(ctx: Context, param: CallbackParam, value: bool) -> None:
298
+ """
299
+ !!! note "Summary"
300
+ Show help and exit.
301
+
302
+ Params:
303
+ ctx (Context):
304
+ The context object.
305
+ param (CallbackParam):
306
+ The parameter object.
307
+ value (bool):
308
+ The boolean value indicating if the flag was set.
309
+
310
+ Returns:
311
+ (None):
312
+ Nothing is returned.
313
+ """
314
+
315
+ # Early exit if help flag is set
316
+ if not value or ctx.resilient_parsing:
317
+ return
318
+
319
+ # Determine terminal width for ASCII art
320
+ try:
321
+ terminal_width: int = os.get_terminal_size().columns
322
+ except OSError:
323
+ terminal_width = 80
324
+
325
+ # Determine title based on terminal width
326
+ title: str = "dfc" if terminal_width < 130 else "docstring-format-checker"
327
+
328
+ # Print ASCII art title
329
+ console.print(
330
+ pyfiglet.figlet_format(title, font="standard", justify="left", width=140),
331
+ style="magenta",
332
+ markup=False,
333
+ )
334
+
335
+ # Show help message
336
+ echo(ctx.get_help())
337
+
338
+ # Show usage and config examples
339
+ _show_usage_examples_callback()
340
+ _show_config_example_callback()
341
+
348
342
  raise Exit()
349
343
 
350
344
 
@@ -497,7 +491,7 @@ def _display_results(results: dict[str, list[DocstringError]], quiet: bool, outp
497
491
 
498
492
  # This will be the default behavior when no command is specified
499
493
  def check_docstrings(
500
- path: str,
494
+ paths: list[str],
501
495
  config: Optional[str] = None,
502
496
  exclude: Optional[list[str]] = None,
503
497
  quiet: bool = False,
@@ -509,8 +503,8 @@ def check_docstrings(
509
503
  Core logic for checking docstrings.
510
504
 
511
505
  Params:
512
- path (str):
513
- The path to the file or directory to check.
506
+ paths (list[str]):
507
+ The path(s) to the file(s) or directory(ies) to check.
514
508
  config (Optional[str]):
515
509
  The path to the configuration file.
516
510
  Default: `None`.
@@ -532,14 +526,20 @@ def check_docstrings(
532
526
  Nothing is returned.
533
527
  """
534
528
 
535
- target_path = Path(path)
536
-
537
- # Validate target path
538
- if not target_path.exists():
539
- console.print(_red(f"Error: Path does not exist: '{path}'"))
529
+ # Validate all target paths
530
+ path_objs: list[Path] = [Path(path) for path in paths]
531
+ target_paths: list[Path] = [p for p in path_objs if p.exists()]
532
+ invalid_paths: list[Path] = [p for p in path_objs if not p.exists()]
533
+
534
+ if len(invalid_paths) > 0:
535
+ console.print(
536
+ _red(f"[bold]Error: Paths do not exist:[/bold]"),
537
+ NEW_LINE,
538
+ NEW_LINE.join([f"- '{invalid_path}'" for invalid_path in invalid_paths]),
539
+ )
540
540
  raise Exit(1)
541
541
 
542
- # Load configuration
542
+ # Load configuration (use first path for config discovery if no config specified)
543
543
  try:
544
544
  if config:
545
545
  config_path = Path(config)
@@ -548,8 +548,9 @@ def check_docstrings(
548
548
  raise Exit(1)
549
549
  config_obj = load_config(config_path)
550
550
  else:
551
- # Try to find config file automatically
552
- found_config: Optional[Path] = find_config_file(target_path if target_path.is_dir() else target_path.parent)
551
+ # Try to find config file automatically using the first path
552
+ first_path: Path = target_paths[0]
553
+ found_config: Optional[Path] = find_config_file(first_path if first_path.is_dir() else first_path.parent)
553
554
  if found_config:
554
555
  config_obj: Config = load_config(found_config)
555
556
  else:
@@ -562,19 +563,27 @@ def check_docstrings(
562
563
  # Initialize checker
563
564
  checker = DocstringChecker(config_obj)
564
565
 
565
- # Check files
566
+ # Check all paths and collect results
567
+ all_results: dict[str, list[DocstringError]] = {}
568
+
566
569
  try:
567
- if target_path.is_file():
568
- errors: list[DocstringError] = checker.check_file(target_path)
569
- results: dict[str, list[DocstringError]] = {str(target_path): errors} if errors else {}
570
- else:
571
- results: dict[str, list[DocstringError]] = checker.check_directory(target_path, exclude_patterns=exclude)
570
+ for target_path in target_paths:
571
+ if target_path.is_file():
572
+ errors: list[DocstringError] = checker.check_file(target_path)
573
+ if errors:
574
+ all_results[str(target_path)] = errors
575
+ else:
576
+ directory_results: dict[str, list[DocstringError]] = checker.check_directory(
577
+ target_path, exclude_patterns=exclude
578
+ )
579
+ all_results.update(directory_results)
580
+
572
581
  except Exception as e:
573
582
  console.print(_red(f"Error during checking: {e}"))
574
583
  raise Exit(1)
575
584
 
576
585
  # Display results
577
- exit_code: int = _display_results(results, quiet, output, check)
586
+ exit_code: int = _display_results(all_results, quiet, output, check)
578
587
 
579
588
  # Always exit with error code if issues are found, regardless of check flag
580
589
  if exit_code != 0:
@@ -592,7 +601,7 @@ def check_docstrings(
592
601
  @app.callback(invoke_without_command=True)
593
602
  def main(
594
603
  ctx: Context,
595
- path: Optional[str] = Argument(None, help="Path to Python file or directory to check"),
604
+ paths: Optional[list[str]] = Argument(None, help="Path(s) to Python file(s) or directory(s) for DFC to check"),
596
605
  config: Optional[str] = Option(None, "--config", "-f", help="Path to configuration file (TOML format)"),
597
606
  exclude: Optional[list[str]] = Option(
598
607
  None,
@@ -654,8 +663,8 @@ def main(
654
663
  Params:
655
664
  ctx (Context):
656
665
  The context object for the command.
657
- path (Optional[str]):
658
- Path to Python file or directory to check.
666
+ paths (Optional[list[str]]):
667
+ Path(s) to Python file(s) or directory(ies) to check.
659
668
  config (Optional[str]):
660
669
  Path to configuration file (TOML format).
661
670
  exclude (Optional[list[str]]):
@@ -678,8 +687,8 @@ def main(
678
687
  Nothing is returned.
679
688
  """
680
689
 
681
- # If no path is provided, show help
682
- if path is None:
690
+ # If no paths are provided, show help
691
+ if not paths:
683
692
  echo(ctx.get_help())
684
693
  raise Exit(0)
685
694
 
@@ -689,7 +698,7 @@ def main(
689
698
  raise Exit(1)
690
699
 
691
700
  check_docstrings(
692
- path=path,
701
+ paths=paths,
693
702
  config=config,
694
703
  exclude=exclude,
695
704
  quiet=quiet,
@@ -704,7 +713,3 @@ def entry_point() -> None:
704
713
  Entry point for the CLI scripts defined in pyproject.toml.
705
714
  """
706
715
  app()
707
-
708
-
709
- if __name__ == "__main__":
710
- app()