docstring-format-checker 1.4.0__tar.gz → 1.5.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.4.0
3
+ Version: 1.5.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>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "docstring-format-checker"
3
- version = "1.4.0"
3
+ version = "1.5.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"
@@ -168,6 +168,13 @@ disable = [
168
168
  "W0718", # broad-exception-caught
169
169
  ]
170
170
 
171
+ [tool.complexipy]
172
+ paths = "src/docstring_format_checker"
173
+ max-complexity-allowed = 15
174
+ quiet = false
175
+ ignore-complexity = false
176
+ sort = "asc"
177
+
171
178
  [tool.bump_version.replacements]
172
179
  files = [
173
180
  { file = "src/docstring_format_checker/__init__.py", pattern = "__version__ = \"{VERSION}\"" },
@@ -178,7 +185,7 @@ files = [
178
185
  [tool.dfc]
179
186
  allow_undefined_sections = false
180
187
  require_docstrings = true
181
- check_private = false
188
+ check_private = true
182
189
  sections = [
183
190
  { order=1, name="summary", type="free_text", required=true, admonition="note", prefix="!!!" },
184
191
  { order=2, name="details", type="free_text", required=false, admonition="abstract", prefix="???+" },
@@ -414,7 +414,18 @@ def _display_results(
414
414
 
415
415
 
416
416
  def _count_errors_and_files(results: dict[str, list[DocstringError]]) -> dict[str, int]:
417
- """Count total errors, functions, and files from results."""
417
+ """
418
+ !!! note "Summary"
419
+ Count total errors, functions, and files from results.
420
+
421
+ Params:
422
+ results (dict[str, list[DocstringError]]):
423
+ Dictionary mapping file paths to lists of errors.
424
+
425
+ Returns:
426
+ (dict[str, int]):
427
+ Dictionary containing total_errors, total_functions, and total_files.
428
+ """
418
429
  total_individual_errors: int = 0
419
430
  total_functions: int = 0
420
431
 
@@ -422,7 +433,7 @@ def _count_errors_and_files(results: dict[str, list[DocstringError]]) -> dict[st
422
433
  total_functions += len(errors)
423
434
  for error in errors:
424
435
  if "; " in error.message:
425
- individual_errors = [msg.strip() for msg in error.message.split("; ") if msg.strip()]
436
+ individual_errors: list[str] = [msg.strip() for msg in error.message.split("; ") if msg.strip()]
426
437
  total_individual_errors += len(individual_errors)
427
438
  else:
428
439
  total_individual_errors += 1
@@ -431,17 +442,31 @@ def _count_errors_and_files(results: dict[str, list[DocstringError]]) -> dict[st
431
442
 
432
443
 
433
444
  def _display_quiet_summary(error_stats: dict[str, int]) -> None:
434
- """Display summary in quiet mode."""
445
+ """
446
+ !!! note "Summary"
447
+ Display summary in quiet mode.
448
+
449
+ Params:
450
+ error_stats (dict[str, int]):
451
+ Dictionary containing total_errors, total_functions, and total_files.
452
+ """
435
453
  functions_text = (
436
454
  "1 function" if error_stats["total_functions"] == 1 else f"{error_stats['total_functions']} functions"
437
455
  )
438
- files_text = "1 file" if error_stats["total_files"] == 1 else f"{error_stats['total_files']} files"
456
+ files_text: str = "1 file" if error_stats["total_files"] == 1 else f"{error_stats['total_files']} files"
439
457
 
440
458
  console.print(_red(f"{NEW_LINE}Found {error_stats['total_errors']} error(s) in {functions_text} over {files_text}"))
441
459
 
442
460
 
443
461
  def _display_table_output(results: dict[str, list[DocstringError]]) -> None:
444
- """Display results in table format."""
462
+ """
463
+ !!! note "Summary"
464
+ Display results in table format.
465
+
466
+ Params:
467
+ results (dict[str, list[DocstringError]]):
468
+ Dictionary mapping file paths to lists of errors.
469
+ """
445
470
  table = Table(show_header=True, header_style="bold magenta")
446
471
  table.add_column("File", style="cyan", no_wrap=False)
447
472
  table.add_column("Line", justify="right", style="white")
@@ -464,32 +489,94 @@ def _display_table_output(results: dict[str, list[DocstringError]]) -> None:
464
489
  console.print(table)
465
490
 
466
491
 
492
+ def _create_error_header(error: DocstringError) -> str:
493
+ """
494
+ !!! note "Summary"
495
+ Create formatted header for a single error.
496
+
497
+ Params:
498
+ error (DocstringError):
499
+ The error to create a header for.
500
+
501
+ Returns:
502
+ (str):
503
+ Formatted header string with line number, item type, and name.
504
+ """
505
+ if error.line_number > 0:
506
+ return f" [red]Line {error.line_number}[/red] - {error.item_type} '{error.item_name}':"
507
+ else:
508
+ return f" {_red('Error')} - {error.item_type} '{error.item_name}':"
509
+
510
+
511
+ def _split_error_messages(message: str) -> list[str]:
512
+ """
513
+ !!! note "Summary"
514
+ Split compound error message into individual messages.
515
+
516
+ Params:
517
+ message (str):
518
+ The error message to split.
519
+
520
+ Returns:
521
+ (list[str]):
522
+ List of individual error messages.
523
+ """
524
+ if "; " in message:
525
+ return [msg.strip() for msg in message.split("; ") if msg.strip()]
526
+ else:
527
+ return [message.strip()]
528
+
529
+
530
+ def _format_error_output(error: DocstringError) -> list[str]:
531
+ """
532
+ !!! note "Summary"
533
+ Format single error for display output.
534
+
535
+ Params:
536
+ error (DocstringError):
537
+ The error to format.
538
+
539
+ Returns:
540
+ (list[str]):
541
+ List of formatted lines to print.
542
+ """
543
+ lines: list[str] = [_create_error_header(error)]
544
+ individual_errors: list[str] = _split_error_messages(error.message)
545
+ for individual_error in individual_errors:
546
+ lines.append(f" - {individual_error}")
547
+ return lines
548
+
549
+
467
550
  def _display_list_output(results: dict[str, list[DocstringError]]) -> None:
468
- """Display results in list format."""
551
+ """
552
+ !!! note "Summary"
553
+ Display results in list format.
554
+
555
+ Params:
556
+ results (dict[str, list[DocstringError]]):
557
+ Dictionary mapping file paths to lists of errors.
558
+ """
469
559
  for file_path, errors in results.items():
470
560
  console.print(f"{NEW_LINE}{_cyan(file_path)}")
471
561
  for error in errors:
472
- # Print header with line number, item type and name
473
- if error.line_number > 0:
474
- console.print(f" [red]Line {error.line_number}[/red] - {error.item_type} '{error.item_name}':")
475
- else:
476
- console.print(f" {_red('Error')} - {error.item_type} '{error.item_name}':")
477
-
478
- # Format and print error messages
479
- if "; " in error.message:
480
- individual_errors = [msg.strip() for msg in error.message.split("; ") if msg.strip()]
481
- for individual_error in individual_errors:
482
- console.print(f" - {individual_error}")
483
- else:
484
- console.print(f" - {error.message.strip()}")
562
+ output_lines: list[str] = _format_error_output(error)
563
+ for line in output_lines:
564
+ console.print(line)
485
565
 
486
566
 
487
567
  def _display_final_summary(error_stats: dict[str, int]) -> None:
488
- """Display the final summary line."""
489
- functions_text = (
568
+ """
569
+ !!! note "Summary"
570
+ Display the final summary line.
571
+
572
+ Params:
573
+ error_stats (dict[str, int]):
574
+ Dictionary containing total_errors, total_functions, and total_files.
575
+ """
576
+ functions_text: str = (
490
577
  "1 function" if error_stats["total_functions"] == 1 else f"{error_stats['total_functions']} functions"
491
578
  )
492
- files_text = "1 file" if error_stats["total_files"] == 1 else f"{error_stats['total_files']} files"
579
+ files_text: str = "1 file" if error_stats["total_files"] == 1 else f"{error_stats['total_files']} files"
493
580
 
494
581
  console.print(_red(f"{NEW_LINE}Found {error_stats['total_errors']} error(s) in {functions_text} over {files_text}"))
495
582
 
@@ -538,23 +625,38 @@ def check_docstrings(
538
625
  Nothing is returned.
539
626
  """
540
627
  # Validate and process input paths
541
- target_paths = _validate_and_process_paths(paths)
628
+ target_paths: list[Path] = _validate_and_process_paths(paths)
542
629
 
543
630
  # Load and validate configuration
544
- config_obj = _load_and_validate_config(config, target_paths)
631
+ config_obj: Config = _load_and_validate_config(config, target_paths)
545
632
 
546
633
  # Initialize checker and process all paths
547
634
  checker = DocstringChecker(config_obj)
548
- all_results = _process_all_paths(checker, target_paths, exclude)
635
+ all_results: dict[str, list[DocstringError]] = _process_all_paths(checker, target_paths, exclude)
549
636
 
550
637
  # Display results and handle exit
551
- exit_code = _display_results(all_results, quiet, output, check)
638
+ exit_code: int = _display_results(all_results, quiet, output, check)
552
639
  if exit_code != 0:
553
640
  raise Exit(exit_code)
554
641
 
555
642
 
556
643
  def _validate_and_process_paths(paths: list[str]) -> list[Path]:
557
- """Validate input paths and return valid paths."""
644
+ """
645
+ !!! note "Summary"
646
+ Validate input paths and return valid paths.
647
+
648
+ Params:
649
+ paths (list[str]):
650
+ List of path strings to validate.
651
+
652
+ Raises:
653
+ (Exit):
654
+ If any paths do not exist.
655
+
656
+ Returns:
657
+ (list[Path]):
658
+ List of valid Path objects.
659
+ """
558
660
  path_objs: list[Path] = [Path(path) for path in paths]
559
661
  target_paths: list[Path] = [p for p in path_objs if p.exists()]
560
662
  invalid_paths: list[Path] = [p for p in path_objs if not p.exists()]
@@ -571,7 +673,24 @@ def _validate_and_process_paths(paths: list[str]) -> list[Path]:
571
673
 
572
674
 
573
675
  def _load_and_validate_config(config: Optional[str], target_paths: list[Path]) -> Config:
574
- """Load and validate configuration from file or auto-discovery."""
676
+ """
677
+ !!! note "Summary"
678
+ Load and validate configuration from file or auto-discovery.
679
+
680
+ Params:
681
+ config (Optional[str]):
682
+ Optional path to configuration file.
683
+ target_paths (list[Path]):
684
+ List of target paths for auto-discovery.
685
+
686
+ Raises:
687
+ (Exit):
688
+ If configuration loading fails.
689
+
690
+ Returns:
691
+ (Config):
692
+ Loaded configuration object.
693
+ """
575
694
  try:
576
695
  if config:
577
696
  return _load_explicit_config(config)
@@ -583,7 +702,22 @@ def _load_and_validate_config(config: Optional[str], target_paths: list[Path]) -
583
702
 
584
703
 
585
704
  def _load_explicit_config(config: str) -> Config:
586
- """Load configuration from explicitly specified path."""
705
+ """
706
+ !!! note "Summary"
707
+ Load configuration from explicitly specified path.
708
+
709
+ Params:
710
+ config (str):
711
+ Path to configuration file.
712
+
713
+ Raises:
714
+ (Exit):
715
+ If configuration file does not exist.
716
+
717
+ Returns:
718
+ (Config):
719
+ Loaded configuration object.
720
+ """
587
721
  config_path = Path(config)
588
722
  if not config_path.exists():
589
723
  console.print(_red(f"Error: Configuration file does not exist: {config}"))
@@ -592,9 +726,20 @@ def _load_explicit_config(config: str) -> Config:
592
726
 
593
727
 
594
728
  def _load_auto_discovered_config(target_paths: list[Path]) -> Config:
595
- """Load configuration from auto-discovery or defaults."""
729
+ """
730
+ !!! note "Summary"
731
+ Load configuration from auto-discovery or defaults.
732
+
733
+ Params:
734
+ target_paths (list[Path]):
735
+ List of target paths to search for configuration.
736
+
737
+ Returns:
738
+ (Config):
739
+ Loaded configuration object from found config or defaults.
740
+ """
596
741
  first_path: Path = target_paths[0]
597
- search_path = first_path if first_path.is_dir() else first_path.parent
742
+ search_path: Path = first_path if first_path.is_dir() else first_path.parent
598
743
  found_config: Optional[Path] = find_config_file(search_path)
599
744
 
600
745
  if found_config:
@@ -606,17 +751,38 @@ def _load_auto_discovered_config(target_paths: list[Path]) -> Config:
606
751
  def _process_all_paths(
607
752
  checker: DocstringChecker, target_paths: list[Path], exclude: Optional[list[str]]
608
753
  ) -> dict[str, list[DocstringError]]:
609
- """Process all target paths and collect docstring errors."""
754
+ """
755
+ !!! note "Summary"
756
+ Process all target paths and collect docstring errors.
757
+
758
+ Params:
759
+ checker (DocstringChecker):
760
+ The checker instance to use.
761
+ target_paths (list[Path]):
762
+ List of paths to check (files or directories).
763
+ exclude (Optional[list[str]]):
764
+ Optional list of exclusion patterns.
765
+
766
+ Raises:
767
+ (Exit):
768
+ If an error occurs during checking.
769
+
770
+ Returns:
771
+ (dict[str, list[DocstringError]]):
772
+ Dictionary mapping file paths to lists of errors.
773
+ """
610
774
  all_results: dict[str, list[DocstringError]] = {}
611
775
 
612
776
  try:
613
777
  for target_path in target_paths:
614
778
  if target_path.is_file():
615
- errors = checker.check_file(target_path)
779
+ errors: list[DocstringError] = checker.check_file(target_path)
616
780
  if errors:
617
781
  all_results[str(target_path)] = errors
618
782
  else:
619
- directory_results = checker.check_directory(target_path, exclude_patterns=exclude)
783
+ directory_results: dict[str, list[DocstringError]] = checker.check_directory(
784
+ target_path, exclude_patterns=exclude
785
+ )
620
786
  all_results.update(directory_results)
621
787
  except Exception as e:
622
788
  console.print(_red(f"Error during checking: {e}"))
@@ -352,7 +352,22 @@ def load_config(config_path: Optional[Union[str, Path]] = None) -> Config:
352
352
 
353
353
 
354
354
  def _resolve_config_path(config_path: Optional[Union[str, Path]]) -> Optional[Path]:
355
- """Resolve configuration file path."""
355
+ """
356
+ !!! note "Summary"
357
+ Resolve configuration file path.
358
+
359
+ Params:
360
+ config_path (Optional[Union[str, Path]]):
361
+ Optional path to configuration file.
362
+
363
+ Raises:
364
+ (FileNotFoundError):
365
+ If specified config file does not exist.
366
+
367
+ Returns:
368
+ (Optional[Path]):
369
+ Resolved Path object or None if no config found.
370
+ """
356
371
  if config_path is None:
357
372
  # Look for pyproject.toml in current directory
358
373
  pyproject_path: Path = Path.cwd().joinpath("pyproject.toml")
@@ -370,7 +385,22 @@ def _resolve_config_path(config_path: Optional[Union[str, Path]]) -> Optional[Pa
370
385
 
371
386
 
372
387
  def _parse_toml_file(config_path: Path) -> dict[str, Any]:
373
- """Parse TOML configuration file."""
388
+ """
389
+ !!! note "Summary"
390
+ Parse TOML configuration file.
391
+
392
+ Params:
393
+ config_path (Path):
394
+ Path to TOML file to parse.
395
+
396
+ Raises:
397
+ (InvalidConfigError):
398
+ If TOML parsing fails.
399
+
400
+ Returns:
401
+ (dict[str, Any]):
402
+ Parsed TOML data as dictionary.
403
+ """
374
404
  try:
375
405
  with open(config_path, "rb") as f:
376
406
  return tomllib.load(f)
@@ -379,7 +409,18 @@ def _parse_toml_file(config_path: Path) -> dict[str, Any]:
379
409
 
380
410
 
381
411
  def _extract_tool_config(config_data: dict[str, Any]) -> Optional[dict[str, Any]]:
382
- """Extract tool configuration from TOML data."""
412
+ """
413
+ !!! note "Summary"
414
+ Extract tool configuration from TOML data.
415
+
416
+ Params:
417
+ config_data (dict[str, Any]):
418
+ Parsed TOML data dictionary.
419
+
420
+ Returns:
421
+ (Optional[dict[str, Any]]):
422
+ Tool configuration dictionary or None if not found.
423
+ """
383
424
  if "tool" not in config_data:
384
425
  return None
385
426
 
@@ -393,7 +434,18 @@ def _extract_tool_config(config_data: dict[str, Any]) -> Optional[dict[str, Any]
393
434
 
394
435
 
395
436
  def _parse_global_config(tool_config: dict[str, Any]) -> GlobalConfig:
396
- """Parse global configuration flags."""
437
+ """
438
+ !!! note "Summary"
439
+ Parse global configuration flags.
440
+
441
+ Params:
442
+ tool_config (dict[str, Any]):
443
+ Tool configuration dictionary.
444
+
445
+ Returns:
446
+ (GlobalConfig):
447
+ Parsed global configuration object.
448
+ """
397
449
  return GlobalConfig(
398
450
  allow_undefined_sections=tool_config.get("allow_undefined_sections", False),
399
451
  require_docstrings=tool_config.get("require_docstrings", True),
@@ -402,7 +454,18 @@ def _parse_global_config(tool_config: dict[str, Any]) -> GlobalConfig:
402
454
 
403
455
 
404
456
  def _parse_sections_config(tool_config: dict[str, Any]) -> list[SectionConfig]:
405
- """Parse sections configuration."""
457
+ """
458
+ !!! note "Summary"
459
+ Parse sections configuration.
460
+
461
+ Params:
462
+ tool_config (dict[str, Any]):
463
+ Tool configuration dictionary.
464
+
465
+ Returns:
466
+ (list[SectionConfig]):
467
+ List of section configuration objects or defaults.
468
+ """
406
469
  if "sections" not in tool_config:
407
470
  return DEFAULT_SECTIONS
408
471