Coverage for /Users/OORDCOR/Documents/code/bump-my-version/bumpversion/show.py: 18%

1"""Functions for displaying information about the version."""

2 import dataclasses

3 from io import StringIO

4 from pprint import pprint

5 from typing import Any, Optional

6

7 from bumpversion.bump import get_next_version

8 from bumpversion.config import Config

9 from bumpversion.exceptions import BadInputError

10 from bumpversion.ui import print_error, print_info

11 from bumpversion.utils import get_context, recursive_sort_dict

12

13

14 def output_default(value: dict) -> None:

15 """Output the value with key=value or just value if there is only one item."""

16 if len(value) == 1:

17 print_info(next(iter(value.values())))

18 else:

19 buffer = StringIO()

20 pprint(value, stream=buffer, sort_dicts=True) # noqa: T203

21 print_info(buffer.getvalue())

22

23

24 def output_yaml(value: dict) -> None:

25 """Output the value as yaml."""

26 from bumpversion.yaml_dump import dump

27

28 print_info(dump(recursive_sort_dict(value)))

29

30

31 def output_json(value: dict) -> None:

32 """Output the value as json."""

33 import json

34

35 def default_encoder(obj: Any) -> str:

36 if dataclasses.is_dataclass(obj):

37 return str(obj)

38 elif isinstance(obj, type):

39 return obj.__name__

40 raise TypeError(f"Object of type {type(obj), str(obj)} is not JSON serializable")

41

42 print_info(json.dumps(value, sort_keys=True, indent=2, default=default_encoder))

43

44

45 OUTPUTTERS = {

46 "yaml": output_yaml,

47 "json": output_json,

48 "default": output_default,

49}

50

51

52 def resolve_name(obj: Any, name: str, default: Any = None, err_on_missing: bool = False) -> Any:

53 """

54 Get a key or attr ``name`` from obj or default value.

55

56 Copied and modified from Django Template variable resolutions

57

58 Resolution methods:

59

60 - Mapping key lookup

61 - Attribute lookup

62 - Sequence index

63

64 Args:

65 obj: The object to access

66 name: A dotted name to the value, such as ``mykey.0.name``

67 default: If the name cannot be resolved from the object, return this value

68 err_on_missing: Raise a `BadInputError` if the name cannot be resolved

69

70 Returns:

71 The value at the resolved name or the default value.

72

73 Raises:

74 BadInputError: If we cannot resolve the name and `err_on_missing` is `True`

75

76 # noqa: DAR401

77 """

78 lookups = name.split(".")

79 current = obj

80 try: # catch-all for unexpected failures

81 for bit in lookups:

82 try: # dictionary lookup

83 current = current[bit]

84 # ValueError/IndexError are for numpy.array lookup on

85 # numpy < 1.9 and 1.9+ respectively

86 except (TypeError, AttributeError, KeyError, ValueError, IndexError):

87 try: # attribute lookup

88 current = getattr(current, bit)

89 except (TypeError, AttributeError):

90 # Reraise if the exception was raised by a @property

91 if bit in dir(current):

92 raise

93 try: # list-index lookup

94 current = current[int(bit)]

95 except (

96 IndexError, # list index out of range

97 ValueError, # invalid literal for int()

98 KeyError, # current is a dict without `int(bit)` key

99 TypeError,

100 ): # un-subscript-able object

101 return default

102 return current

103 except Exception as e: # noqa: BLE001 # pragma: no cover

104 if err_on_missing:

105 raise BadInputError(f"Could not resolve '{name}'") from e

106 else:

107 return default

108

109

110 def log_list(config: Config, version_part: Optional[str], new_version: Optional[str]) -> None:

111 """Output configuration with new version."""

112 ctx = get_context(config)

113 if version_part:

114 version = config.version_config.parse(config.current_version)

115 next_version = get_next_version(version, config, version_part, new_version)

116 next_version_str = config.version_config.serialize(next_version, ctx)

117

118 print_info(f"new_version={next_version_str}")

119

120 config_dict = recursive_sort_dict(config.model_dump(exclude={"scm_info", "parts"}))

121 for key, value in config_dict.items():

122 print_info(f"{key}={value}")

123

124

125 def do_show(*args, config: Config, format_: str = "default", increment: Optional[str] = None) -> None:

126 """Show current version or configuration information."""

127 config_dict = config.model_dump()

128 ctx = get_context(config)

129

130 if increment:

131 version = config.version_config.parse(config.current_version)

132 next_version = get_next_version(version, config, increment, None)

133 next_version_str = config.version_config.serialize(next_version, ctx)

134 config_dict["new_version"] = next_version_str

135

136 try:

137 if "all" in args or not args:

138 show_items = config_dict

139 else:

140 show_items = {key: resolve_name(config_dict, key) for key in args}

141

142 OUTPUTTERS.get(format_, OUTPUTTERS["default"])(show_items)

143 except BadInputError as e:

144 print_error(e.message)