google-docstring-parser 0.0.6__tar.gz → 0.0.8__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.
Files changed (15) hide show
  1. {google_docstring_parser-0.0.6 → google_docstring_parser-0.0.8}/PKG-INFO +1 -1
  2. {google_docstring_parser-0.0.6 → google_docstring_parser-0.0.8}/google_docstring_parser/google_docstring_parser.py +147 -69
  3. {google_docstring_parser-0.0.6 → google_docstring_parser-0.0.8}/google_docstring_parser/type_validation.py +26 -1
  4. {google_docstring_parser-0.0.6 → google_docstring_parser-0.0.8}/google_docstring_parser.egg-info/PKG-INFO +1 -1
  5. {google_docstring_parser-0.0.6 → google_docstring_parser-0.0.8}/pyproject.toml +2 -2
  6. {google_docstring_parser-0.0.6 → google_docstring_parser-0.0.8}/tools/check_docstrings.py +17 -12
  7. {google_docstring_parser-0.0.6 → google_docstring_parser-0.0.8}/LICENSE +0 -0
  8. {google_docstring_parser-0.0.6 → google_docstring_parser-0.0.8}/README.md +0 -0
  9. {google_docstring_parser-0.0.6 → google_docstring_parser-0.0.8}/google_docstring_parser/__init__.py +0 -0
  10. {google_docstring_parser-0.0.6 → google_docstring_parser-0.0.8}/google_docstring_parser.egg-info/SOURCES.txt +0 -0
  11. {google_docstring_parser-0.0.6 → google_docstring_parser-0.0.8}/google_docstring_parser.egg-info/dependency_links.txt +0 -0
  12. {google_docstring_parser-0.0.6 → google_docstring_parser-0.0.8}/google_docstring_parser.egg-info/requires.txt +0 -0
  13. {google_docstring_parser-0.0.6 → google_docstring_parser-0.0.8}/google_docstring_parser.egg-info/top_level.txt +0 -0
  14. {google_docstring_parser-0.0.6 → google_docstring_parser-0.0.8}/setup.cfg +0 -0
  15. {google_docstring_parser-0.0.6 → google_docstring_parser-0.0.8}/tools/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-docstring-parser
3
- Version: 0.0.6
3
+ Version: 0.0.8
4
4
  Summary: A lightweight, efficient parser for Google-style Python docstrings that converts them into structured dictionaries.
5
5
  Author: Vladimir Iglovikov
6
6
  Maintainer: Vladimir Iglovikov
@@ -19,10 +19,13 @@ This module provides functions to parse Google-style docstrings into structured
19
19
  from __future__ import annotations
20
20
 
21
21
  import re
22
- from typing import Any
22
+ from typing import TYPE_CHECKING, Any
23
23
 
24
24
  from docstring_parser import parse
25
25
 
26
+ if TYPE_CHECKING:
27
+ from docstring_parser.common import Docstring
28
+
26
29
  from google_docstring_parser.type_validation import (
27
30
  InvalidTypeAnnotationError,
28
31
  check_text_for_bare_collections,
@@ -369,60 +372,84 @@ def _parse_references(reference_content: str) -> list[dict[str, str]]:
369
372
  return references
370
373
 
371
374
 
372
- def _is_continuation_line(line: str, all_lines: list[str]) -> bool:
373
- """Check if a line is a continuation of a previous line.
375
+ def _validate_type_with_error_handling(type_str: str, result: dict[str, Any], collect_errors: bool) -> None:
376
+ """Validate a type annotation and handle any errors.
377
+
378
+ This function validates type annotations and handles errors differently based on the collect_errors flag:
379
+ - When collect_errors is True: Errors are added to result["errors"] list instead of being raised
380
+ - When collect_errors is False: Errors are raised immediately as InvalidTypeAnnotationError
374
381
 
375
382
  Args:
376
- line (str): The line to check
377
- all_lines (list[str]): All lines in the section
383
+ type_str (str): The type annotation to validate
384
+ result (dict[str, Any]): The result dictionary to add errors to when collect_errors is True
385
+ collect_errors (bool): Whether to collect errors in result["errors"] (True) or raise them (False)
378
386
 
379
387
  Returns:
380
- bool: True if the line is a continuation line, False otherwise
381
- """
382
- line_index = all_lines.index(line)
383
- if line_index == 0:
384
- return False
385
-
386
- current_indent = len(line) - len(line.lstrip())
387
- prev_line = all_lines[line_index - 1]
388
- prev_indent = len(prev_line) - len(prev_line.lstrip())
388
+ None
389
389
 
390
- return current_indent > prev_indent and not line.lstrip().startswith("-")
390
+ Raises:
391
+ InvalidTypeAnnotationError: If type validation fails and collect_errors is False
392
+ """
393
+ try:
394
+ validate_type_annotation(type_str)
395
+ if "[" in type_str and "]" in type_str:
396
+ check_text_for_bare_collections(type_str)
397
+ except InvalidTypeAnnotationError as e:
398
+ if collect_errors:
399
+ result["errors"].append(str(e))
400
+ else:
401
+ raise
391
402
 
392
403
 
393
- def _process_args_section(args: list[dict[str, str | None]], *, validate_types: bool) -> None:
394
- """Process the Args section of a docstring.
404
+ def _process_args_with_validation(
405
+ sections: dict[str, str],
406
+ parsed: Docstring,
407
+ result: dict[str, Any],
408
+ validate_types: bool,
409
+ collect_errors: bool,
410
+ ) -> None:
411
+ """Process the Args section with type validation.
395
412
 
396
413
  Args:
397
- args (list[dict[str, str | None]]): List of argument dictionaries
414
+ sections (dict[str, str]): The sections dictionary
415
+ parsed (Docstring): The parsed docstring object
416
+ result (dict[str, Any]): The result dictionary to update
398
417
  validate_types (bool): Whether to validate type annotations
399
-
400
- Returns:
401
- None
418
+ collect_errors (bool): Whether to collect errors or raise them
402
419
  """
403
- if not validate_types:
420
+ if "Args" not in sections:
421
+ return
422
+
423
+ args = [
424
+ {
425
+ "name": arg.arg_name.rstrip() if arg.arg_name is not None else None,
426
+ "type": arg.type_name.rstrip() if arg.type_name is not None else None,
427
+ "description": arg.description.rstrip() if arg.description is not None else None,
428
+ }
429
+ for arg in parsed.params
430
+ ]
431
+
432
+ if not args:
404
433
  return
405
434
 
406
- # Validate type annotations and check for bare nested collections
407
435
  for arg in args:
408
436
  if arg["type"] and validate_types:
409
- validate_type_annotation(arg["type"])
410
-
411
- # Check for nested types - if this is a complex type like Dict[str, List],
412
- # the bare 'List' would be caught here
413
- if "[" in arg["type"] and "]" in arg["type"]:
414
- check_text_for_bare_collections(arg["type"])
437
+ _validate_type_with_error_handling(arg["type"], result, collect_errors)
438
+ result["Args"] = args
415
439
 
416
440
 
417
- def _process_returns_section(sections: dict[str, str], *, validate_types: bool) -> dict[str, str]:
441
+ def _parse_returns_section(sections: dict[str, str], *, validate_types: bool) -> dict[str, str] | str:
418
442
  """Process the Returns section of a docstring.
419
443
 
420
444
  Args:
421
- sections (dict[str, str]): Dictionary of docstring sections
445
+ sections (dict[str, str]): The sections dictionary
422
446
  validate_types (bool): Whether to validate type annotations
423
447
 
424
448
  Returns:
425
- dict[str, str]: Dictionary containing 'type' and 'description' keys for the return value
449
+ dict[str, str] | str: Either:
450
+ - A dictionary with 'type' and 'description' keys
451
+ - The string 'None' if the section only contains 'None'
452
+ - An empty dict if no return information is found
426
453
  """
427
454
  if (
428
455
  "Returns" not in sections
@@ -434,28 +461,78 @@ def _process_returns_section(sections: dict[str, str], *, validate_types: bool)
434
461
  return_type = return_match[1]
435
462
  return_desc = return_match[2].strip()
436
463
 
437
- # If type exists -> description must exist
438
- # If type is None -> description must be empty
439
- if (return_type and not return_desc) or (not return_type and return_desc):
440
- return {}
464
+ # Special case: Returns section just contains "None"
465
+ if not return_type and return_desc == "None":
466
+ return "None"
441
467
 
468
+ # Validate type if present
442
469
  if return_type and validate_types:
443
- # Validate the return type
444
470
  validate_type_annotation(return_type)
445
471
 
446
- # Check for nested types in return type
472
+ # Check for nested types
447
473
  if "[" in return_type and "]" in return_type:
448
474
  check_text_for_bare_collections(return_type)
449
475
 
450
476
  return {"type": return_type, "description": return_desc.rstrip()}
451
477
 
452
478
 
453
- def parse_google_docstring(docstring: str, *, validate_types: bool = True) -> dict[str, Any]:
479
+ def _process_returns_with_validation(
480
+ sections: dict[str, str],
481
+ result: dict[str, Any],
482
+ validate_types: bool,
483
+ collect_errors: bool,
484
+ ) -> None:
485
+ """Process the Returns section with type validation.
486
+
487
+ Args:
488
+ sections (dict[str, str]): The sections dictionary
489
+ result (dict[str, Any]): The result dictionary to update
490
+ validate_types (bool): Whether to validate type annotations
491
+ collect_errors (bool): Whether to collect errors or raise them
492
+ """
493
+ if "Returns" not in sections:
494
+ return
495
+
496
+ try:
497
+ returns = _parse_returns_section(sections, validate_types=validate_types)
498
+ if isinstance(returns, dict) and returns.get("type") and validate_types:
499
+ _validate_type_with_error_handling(returns["type"], result, collect_errors)
500
+ result["Returns"] = returns
501
+ except InvalidTypeAnnotationError as e:
502
+ if collect_errors:
503
+ result["errors"].append(str(e))
504
+ else:
505
+ raise
506
+
507
+
508
+ def _process_references_section(sections: dict[str, str], result: dict[str, Any]) -> None:
509
+ """Process the References section.
510
+
511
+ Args:
512
+ sections (dict[str, str]): The sections dictionary
513
+ result (dict[str, Any]): The result dictionary to update
514
+ """
515
+ for ref_section in ["References", "Reference"]:
516
+ if ref_section in sections:
517
+ # Reference errors should always be raised
518
+ result[ref_section] = _parse_references(sections[ref_section])
519
+ # Don't add this section to the general sections mapping later
520
+ sections.pop(ref_section, None)
521
+ break
522
+
523
+
524
+ def parse_google_docstring(
525
+ docstring: str,
526
+ *,
527
+ validate_types: bool = True,
528
+ collect_errors: bool = True,
529
+ ) -> dict[str, Any]:
454
530
  """Parse a Google-style docstring.
455
531
 
456
532
  Args:
457
533
  docstring (str): The docstring to parse
458
534
  validate_types (bool): Whether to validate type annotations
535
+ collect_errors (bool): Whether to collect errors in the result dictionary instead of raising them
459
536
 
460
537
  Returns:
461
538
  dict[str, Any]: Dictionary containing the parsed docstring information with the following keys:
@@ -463,17 +540,27 @@ def parse_google_docstring(docstring: str, *, validate_types: bool = True) -> di
463
540
  - Args (list[dict[str, str | None]], optional): List of argument dictionaries
464
541
  - Returns (dict[str, str], optional): Return type and description
465
542
  - References/Reference (list[dict[str, str]], optional): List of references
543
+ - errors (list[str], optional): List of validation errors if any (only if collect_errors is True)
466
544
  - Other sections are included as is
545
+
546
+ Raises:
547
+ InvalidTypeAnnotationError: If type validation is enabled and an invalid type is found
548
+ (only if collect_errors is False)
549
+ ReferenceFormatError: If a reference format is invalid
467
550
  """
468
551
  if not docstring:
469
552
  return {}
470
553
 
554
+ # Initialize result dictionary with description and errors if needed
555
+ result: dict[str, Any] = {
556
+ "Description": "",
557
+ }
558
+ if collect_errors:
559
+ result["errors"] = []
560
+
471
561
  # Clean up the docstring
472
562
  docstring = docstring.strip()
473
563
 
474
- # Initialize result dictionary with only description
475
- result: dict[str, Any] = {"Description": ""}
476
-
477
564
  # Extract sections and parse docstring
478
565
  sections = _extract_sections(docstring)
479
566
  parsed = parse(docstring)
@@ -482,35 +569,26 @@ def parse_google_docstring(docstring: str, *, validate_types: bool = True) -> di
482
569
  if parsed.description:
483
570
  result["Description"] = parsed.description.rstrip()
484
571
 
485
- # Process args (only if present)
486
- if "Args" in sections and (
487
- args := [
488
- {
489
- "name": arg.arg_name.rstrip() if arg.arg_name is not None else None,
490
- "type": arg.type_name.rstrip() if arg.type_name is not None else None,
491
- "description": arg.description.rstrip() if arg.description is not None else None,
492
- }
493
- for arg in parsed.params
494
- ]
495
- ):
496
- _process_args_section(args, validate_types=validate_types)
497
- result["Args"] = args
572
+ # Process args with validation
573
+ _process_args_with_validation(sections, parsed, result, validate_types, collect_errors)
498
574
 
499
- # Process returns only if present
500
- if "Returns" in sections:
501
- result["Returns"] = _process_returns_section(sections, validate_types=validate_types)
575
+ # Process returns with validation
576
+ _process_returns_with_validation(sections, result, validate_types, collect_errors)
502
577
 
503
578
  # Process references section
504
- for ref_section in ["References", "Reference"]:
505
- if ref_section in sections:
506
- result[ref_section] = _parse_references(sections[ref_section])
507
- # Don't add this section to the general sections mapping later
508
- sections.pop(ref_section, None)
509
- break
579
+ _process_references_section(sections, result)
510
580
 
511
581
  # Add other sections directly using dict union
512
- return result | {
513
- section: content.rstrip()
514
- for section, content in sections.items()
515
- if section not in ["Description", "Args", "Returns"]
516
- }
582
+ result.update(
583
+ {
584
+ section: content.rstrip()
585
+ for section, content in sections.items()
586
+ if section not in ["Description", "Args", "Returns"]
587
+ },
588
+ )
589
+
590
+ # Remove errors key if no errors
591
+ if collect_errors and not result["errors"]:
592
+ del result["errors"]
593
+
594
+ return result
@@ -177,7 +177,7 @@ def validate_type_annotation(type_annotation: str) -> None:
177
177
 
178
178
  # Check for bare collection types without arguments - exact match only
179
179
  if is_bare_collection(type_annotation):
180
- error_msg = f"Collection type '{type_annotation}' must include element types (e.g., {type_annotation}[str])"
180
+ error_msg = f"Collection '{type_annotation}' must include element types (e.g., {type_annotation}[str])"
181
181
  raise InvalidTypeAnnotationError(error_msg)
182
182
 
183
183
  # Check for nested types in complex type annotations
@@ -466,6 +466,26 @@ def _check_for_bare_collection(tokens: list[str], i: int, token: str) -> None:
466
466
  raise InvalidTypeAnnotationError(error_msg)
467
467
 
468
468
 
469
+ def _is_bare_collection_in_nested_type(token: str, tokens: list[str], i: int, bracket_stack: list[str]) -> bool:
470
+ """Check if a collection type is used without type arguments in a nested type.
471
+
472
+ Args:
473
+ token (str): The current token
474
+ tokens (list[str]): The list of all tokens
475
+ i (int): The current token index
476
+ bracket_stack (list[str]): The stack of open brackets
477
+
478
+ Returns:
479
+ bool: True if the collection is used without type arguments in a nested type
480
+ """
481
+ is_collection: bool = token in COLLECTIONS_REQUIRING_ARGS
482
+ has_brackets: bool = bool(bracket_stack)
483
+ has_next_token: bool = i < len(tokens) - 1
484
+ next_token_not_bracket: bool = tokens[i + 1] != OPEN_BRACKET if has_next_token else False
485
+
486
+ return is_collection and has_brackets and has_next_token and next_token_not_bracket
487
+
488
+
469
489
  def _check_tokens_for_collection_type_usage(tokens: list[str]) -> None:
470
490
  """Check tokens for proper collection type usage.
471
491
 
@@ -492,6 +512,11 @@ def _check_tokens_for_collection_type_usage(tokens: list[str]) -> None:
492
512
  elif token in (CLOSE_BRACKET, CLOSE_PAREN, CLOSE_BRACE):
493
513
  _check_for_closing_bracket(token, bracket_stack, collection_stack)
494
514
 
515
+ # Check for bare collections in nested types
516
+ elif _is_bare_collection_in_nested_type(token, tokens, i, bracket_stack):
517
+ error_msg = f"Invalid nested type: collection type '{token}' requires element types"
518
+ raise InvalidTypeAnnotationError(error_msg)
519
+
495
520
  # Check for unclosed brackets at the end
496
521
  if bracket_stack:
497
522
  raise BracketValidationError(BracketValidationError.UNCLOSED_BRACKETS)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-docstring-parser
3
- Version: 0.0.6
3
+ Version: 0.0.8
4
4
  Summary: A lightweight, efficient parser for Google-style Python docstrings that converts them into structured dictionaries.
5
5
  Author: Vladimir Iglovikov
6
6
  Maintainer: Vladimir Iglovikov
@@ -5,7 +5,7 @@ requires = [ "setuptools>=45", "wheel" ]
5
5
 
6
6
  [project]
7
7
  name = "google-docstring-parser"
8
- version = "0.0.6"
8
+ version = "0.0.8"
9
9
 
10
10
  description = "A lightweight, efficient parser for Google-style Python docstrings that converts them into structured dictionaries."
11
11
  readme = "README.md"
@@ -107,6 +107,7 @@ lint.ignore = [
107
107
  "D107",
108
108
  "EM101",
109
109
  "EM102",
110
+ "FBT001",
110
111
  ]
111
112
 
112
113
  # Allow fix for all enabled rules (when `--fix`) is provided.
@@ -117,7 +118,6 @@ lint.per-file-ignores = { "__init__.py" = [
117
118
  ], "tools/*.py" = [
118
119
  "T201",
119
120
  "BLE001",
120
- "FBT001",
121
121
  "FBT002",
122
122
  "ANN201",
123
123
  ] }
@@ -22,7 +22,7 @@ from google_docstring_parser.google_docstring_parser import (
22
22
 
23
23
  # Default configuration
24
24
  DEFAULT_CONFIG = {
25
- "paths": ["."],
25
+ "paths": [], # Empty by default, so no directories are scanned unless explicitly specified
26
26
  "require_param_types": False,
27
27
  "check_references": True,
28
28
  "exclude_files": [],
@@ -268,23 +268,20 @@ def check_returns_section_name(docstring: str) -> list[str]:
268
268
 
269
269
 
270
270
  def check_returns_type(docstring_dict: dict[str, Any]) -> list[str]:
271
- """Check Returns type in a docstring.
272
-
273
- Args:
274
- docstring_dict (dict[str, Any]): Parsed docstring dictionary
275
-
276
- Returns:
277
- list[str]: List of error messages for invalid return types
278
- """
271
+ """Check Returns type in a docstring."""
279
272
  errors = []
280
- if docstring_dict.get("Returns"):
281
- returns = docstring_dict["Returns"]
273
+ if returns := docstring_dict.get("Returns"):
282
274
  # Special case: Returns section just contains "None"
283
275
  if isinstance(returns, str) and returns.strip() == "None":
284
276
  return errors
285
277
 
286
- if isinstance(returns, dict) and not returns.get("type"):
278
+ if not isinstance(returns, dict):
279
+ errors.append("Returns section must be either 'None' or have a type annotation")
280
+ return errors
281
+
282
+ if not returns.get("type"):
287
283
  errors.append("Returns section is missing type annotation")
284
+
288
285
  return errors
289
286
 
290
287
 
@@ -706,6 +703,14 @@ def main() -> None:
706
703
  print(f" Check references: {check_references}")
707
704
  print(f" Exclude files: {exclude_files}")
708
705
 
706
+ # Check if paths is empty
707
+ if not paths:
708
+ print(
709
+ "No paths specified for checking. Please specify paths as command line "
710
+ "arguments or configure them in pyproject.toml under [tool.docstring_checker] section.",
711
+ )
712
+ sys.exit(0)
713
+
709
714
  if all_errors := _process_paths(
710
715
  paths,
711
716
  exclude_files,