-
-
Notifications
You must be signed in to change notification settings - Fork 28
/
Copy pathmodels.py
192 lines (159 loc) · 6.9 KB
/
models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
"""Bump My Version configuration models."""
from __future__ import annotations
import re
from collections import defaultdict
from dataclasses import dataclass
from itertools import chain
from typing import TYPE_CHECKING, Dict, List, MutableMapping, Optional, Tuple, Union
from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings, SettingsConfigDict
from bumpversion.scm.models import SCMInfo # NOQA: TC001
from bumpversion.ui import get_indented_logger
from bumpversion.versioning.models import VersionComponentSpec # NOQA: TC001
if TYPE_CHECKING: # pragma: no-coverage
from bumpversion.versioning.models import VersionSpec
from bumpversion.versioning.version_config import VersionConfig
logger = get_indented_logger(__name__)
class FileChange(BaseModel):
"""A change to make to a file."""
parse: str
serialize: tuple
search: str
replace: str
regex: bool
ignore_missing_version: bool
ignore_missing_file: bool
filename: Optional[str] = None
glob: Optional[str] = None # Conflicts with filename. If both are specified, glob wins
glob_exclude: Optional[tuple] = None
key_path: Optional[str] = None # If specified, and has an appropriate extension, will be treated as a data file
include_bumps: Optional[tuple] = None
exclude_bumps: tuple = Field(default_factory=tuple)
def __hash__(self):
"""Return a hash of the model."""
return hash(tuple(sorted(self.model_dump().items())))
def get_search_pattern(self, context: MutableMapping) -> Tuple[re.Pattern, str]:
"""
Render the search pattern and return the compiled regex pattern and the raw pattern.
Args:
context: The context to use for rendering the search pattern
Returns:
A tuple of the compiled regex pattern and the raw pattern as a string.
"""
logger.debug("Rendering search pattern with context")
logger.indent()
# the default search pattern is escaped, so we can still use it in a regex
raw_pattern = self.search.format(**context)
default = re.compile(re.escape(raw_pattern), re.MULTILINE | re.DOTALL)
if not self.regex:
logger.debug("No RegEx flag detected. Searching for the default pattern: '%s'", default.pattern)
logger.dedent()
return default, raw_pattern
re_context = {key: re.escape(str(value)) for key, value in context.items()}
regex_pattern = self.search.format(**re_context)
try:
search_for_re = re.compile(regex_pattern, re.MULTILINE | re.DOTALL)
logger.debug("Searching for the regex: '%s'", search_for_re.pattern)
logger.dedent()
return search_for_re, raw_pattern
except re.error as e:
logger.error("Invalid regex '%s': %s.", default, e)
logger.debug("Invalid regex. Searching for the default pattern: '%s'", raw_pattern)
logger.dedent()
return default, raw_pattern
class Config(BaseSettings):
"""Bump Version configuration."""
current_version: Optional[str]
parse: str
serialize: tuple = Field(min_length=1)
search: str
replace: str
regex: bool
ignore_missing_version: bool
ignore_missing_files: bool
tag: bool
sign_tags: bool
tag_name: str
tag_message: Optional[str]
allow_dirty: bool
commit: bool
message: str
commit_args: Optional[str]
pep621_info: Optional[PEP621Info]
scm_info: Optional[SCMInfo]
parts: Dict[str, VersionComponentSpec]
moveable_tags: List[str] = Field(default_factory=list)
files: List[FileChange] = Field(default_factory=list)
setup_hooks: List[str] = Field(default_factory=list)
pre_commit_hooks: List[str] = Field(default_factory=list)
post_commit_hooks: List[str] = Field(default_factory=list)
included_paths: List[str] = Field(default_factory=list)
excluded_paths: List[str] = Field(default_factory=list)
model_config = SettingsConfigDict(env_prefix="bumpversion_")
_resolved_filemap: Optional[Dict[str, List[FileChange]]] = None
def add_files(self, filename: Union[str, List[str]]) -> None:
"""Add a filename to the list of files."""
if not filename:
return
filenames = [filename] if isinstance(filename, str) else filename
files = set(self.files)
for name in filenames:
files.add(
FileChange(
filename=name,
glob=None,
key_path=None,
parse=self.parse,
serialize=self.serialize,
search=self.search,
replace=self.replace,
regex=self.regex,
ignore_missing_version=self.ignore_missing_version,
ignore_missing_file=self.ignore_missing_files,
include_bumps=tuple(self.parts.keys()),
)
)
self.files = list(files)
self._resolved_filemap = None
@property
def resolved_filemap(self) -> Dict[str, List[FileChange]]:
"""Return the cached resolved filemap."""
if self._resolved_filemap is None:
self._resolved_filemap = self._resolve_filemap()
return self._resolved_filemap
def _resolve_filemap(self) -> Dict[str, List[FileChange]]:
"""Return a map of filenames to file configs, expanding any globs."""
from bumpversion.config.utils import resolve_glob_files
output = defaultdict(list)
new_files = []
for file_cfg in self.files:
if file_cfg.glob:
new_files.extend(resolve_glob_files(file_cfg))
else:
new_files.append(file_cfg)
for file_cfg in new_files:
output[file_cfg.filename].append(file_cfg)
return output
@property
def files_to_modify(self) -> List[FileChange]:
"""Return a list of files to modify."""
files_not_excluded = [filename for filename in self.resolved_filemap if filename not in self.excluded_paths]
inclusion_set = set(self.included_paths) | set(files_not_excluded)
return list(
chain.from_iterable(
file_cfg_list for key, file_cfg_list in self.resolved_filemap.items() if key in inclusion_set
)
)
@property
def version_config(self) -> "VersionConfig":
"""Return the version configuration."""
from bumpversion.versioning.version_config import VersionConfig
return VersionConfig(self.parse, self.serialize, self.search, self.replace, self.parts)
def version_spec(self, version: Optional[str] = None) -> "VersionSpec":
"""Return the version specification."""
from bumpversion.versioning.models import VersionSpec
return VersionSpec(self.parts)
@dataclass
class PEP621Info:
"""PEP 621 info, in particular, the static version number."""
version: Optional[str]