Skip to content

Commit d81ea91

Browse files
authored
Refactoring/498 centralize changelog code and preparation steps (#499)
* Add git utility class & test * Switch from decorator to annotated type so can be shared later * Remove conflicting git files as they are no longer used in code * Extract combined functionality into get_dependencies & modify for get_dependencies_from_latest_tag * Improve name of function & switch to verb tense without s * Move create_and_switch_to_branch to util.git * Remove unused function _is_valid_version * Add depth to see if resolves issues * Refactor Changelog modifications so clearer order of operations and have testing * Add changelog entry * Fix variable name & value as drifted from existing implementation * Fix comment * Switch to AfterValidator so pre-validation works as expected * Switch from string that we split into a list to a most sustainable list[str] * Rename UNRELEASED_TEXT to UNRELEASED_INITIAL_CONTENT * Update docstring per review to clarify new file * Remove unused tmp_path from fixtures * Set scope more explicitly and write comment once on test class * Rename test variables and put into class so relationship is clearer * Update comment to be more explicit * Update PTB dependencies
1 parent d8a216b commit d81ea91

File tree

18 files changed

+476
-347
lines changed

18 files changed

+476
-347
lines changed

.github/workflows/checks.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ jobs:
177177
steps:
178178
- name: SCM Checkout
179179
uses: actions/checkout@v4
180+
with:
181+
fetch-depth: 0
180182

181183
- name: Setup Python & Poetry Environment
182184
uses: ./.github/actions/python-environment

doc/changes/unreleased.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
# Unreleased
2+
3+
## Refactoring
4+
5+
* #498: Centralized changelog code relevant for `release:trigger` & robustly tested

exasol/toolbox/git.py

Lines changed: 0 additions & 14 deletions
This file was deleted.

exasol/toolbox/nox/_dependencies.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@
1212
licenses,
1313
packages_to_markdown,
1414
)
15-
from exasol.toolbox.util.dependencies.poetry_dependencies import (
16-
PoetryDependencies,
17-
PoetryToml,
18-
)
15+
from exasol.toolbox.util.dependencies.poetry_dependencies import get_dependencies
1916

2017

2118
class Audit:
@@ -86,12 +83,8 @@ def run(self, session: Session) -> None:
8683

8784
@nox.session(name="dependency:licenses", python=False)
8885
def dependency_licenses(session: Session) -> None:
89-
"""returns the packages and their licenses"""
90-
working_directory = Path()
91-
poetry_dep = PoetryToml.load_from_toml(working_directory=working_directory)
92-
dependencies = PoetryDependencies(
93-
groups=poetry_dep.groups, working_directory=working_directory
94-
).direct_dependencies
86+
"""Return the packages with their licenses"""
87+
dependencies = get_dependencies(working_directory=Path())
9588
package_infos = licenses()
9689
print(packages_to_markdown(dependencies=dependencies, packages=package_infos))
9790

exasol/toolbox/nox/_release.py

Lines changed: 14 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,8 @@
1313
_version,
1414
)
1515
from exasol.toolbox.nox.plugin import NoxTasks
16-
from exasol.toolbox.release import (
17-
extract_release_notes,
18-
new_changelog,
19-
new_changes,
20-
new_unreleased,
21-
)
16+
from exasol.toolbox.util.git import Git
17+
from exasol.toolbox.util.release.changelog import Changelogs
2218
from exasol.toolbox.util.version import (
2319
ReleaseTypes,
2420
Version,
@@ -61,32 +57,12 @@ def _create_parser() -> argparse.ArgumentParser:
6157
return parser
6258

6359

64-
def _is_valid_version(old: Version, new: Version) -> bool:
65-
return new >= old
66-
67-
6860
def _update_project_version(session: Session, version: Version) -> Version:
6961
session.run("poetry", "version", f"{version}")
7062
_version(session, Mode.Fix)
7163
return version
7264

7365

74-
def _update_changelog(version: Version) -> tuple[Path, Path, Path]:
75-
unreleased = Path(PROJECT_CONFIG.root) / "doc" / "changes" / "unreleased.md"
76-
changelog = Path(PROJECT_CONFIG.root) / "doc" / "changes" / f"changes_{version}.md"
77-
changes = Path(PROJECT_CONFIG.root) / "doc" / "changes" / f"changelog.md"
78-
79-
changelog_content = extract_release_notes(unreleased)
80-
changelog.write_text(new_changelog(version, changelog_content))
81-
82-
unreleased.write_text(new_unreleased())
83-
84-
changes_content = new_changes(changes, version)
85-
changes.write_text(changes_content)
86-
87-
return changelog, changes, unreleased
88-
89-
9066
def _add_files_to_index(session: Session, files: list[Path]) -> None:
9167
for file in files:
9268
session.run("git", "add", f"{file}")
@@ -131,40 +107,40 @@ def run(*args: str):
131107
@nox.session(name="release:prepare", python=False)
132108
def prepare_release(session: Session) -> None:
133109
"""
134-
Prepares the project for a new release.
110+
Prepare the project for a new release.
135111
"""
136112
parser = _create_parser()
137113
args = parser.parse_args(session.posargs)
138-
139114
new_version = Version.upgrade_version_from_poetry(args.type)
140115

141116
if not args.no_branch and not args.no_add:
142-
session.run("git", "switch", "-c", f"release/prepare-{new_version}")
143-
144-
pm = NoxTasks.plugin_manager(PROJECT_CONFIG)
117+
Git.create_and_switch_to_branch(f"release/prepare-{new_version}")
145118

146119
_ = _update_project_version(session, new_version)
147-
changelog, changes, unreleased = _update_changelog(new_version)
148120

121+
changelogs = Changelogs(
122+
changes_path=PROJECT_CONFIG.doc / "changes", version=new_version
123+
)
124+
changelogs.update_changelogs_for_release()
125+
changed_files = changelogs.get_changed_files()
126+
127+
pm = NoxTasks.plugin_manager(PROJECT_CONFIG)
149128
pm.hook.prepare_release_update_version(
150129
session=session, config=PROJECT_CONFIG, version=new_version
151130
)
152131

153132
if args.no_add:
154133
return
155134

156-
files = [
157-
changelog,
158-
unreleased,
159-
changes,
135+
changed_files += [
160136
PROJECT_CONFIG.root / "pyproject.toml",
161137
PROJECT_CONFIG.version_file,
162138
]
163139
results = pm.hook.prepare_release_add_files(session=session, config=PROJECT_CONFIG)
164-
files += [f for plugin_response in results for f in plugin_response]
140+
changed_files += [f for plugin_response in results for f in plugin_response]
165141
_add_files_to_index(
166142
session,
167-
files,
143+
changed_files,
168144
)
169145
session.run("git", "commit", "-m", f"Prepare release {new_version}")
170146

exasol/toolbox/release/__init__.py

Lines changed: 0 additions & 85 deletions
This file was deleted.

exasol/toolbox/util/dependencies/poetry_dependencies.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import subprocess
4+
import tempfile
45
from pathlib import Path
56
from typing import Optional
67

@@ -12,6 +13,7 @@
1213
from tomlkit import TOMLDocument
1314

1415
from exasol.toolbox.util.dependencies.shared_models import Package
16+
from exasol.toolbox.util.git import Git
1517

1618

1719
class PoetryGroup(BaseModel):
@@ -21,6 +23,7 @@ class PoetryGroup(BaseModel):
2123
toml_section: Optional[str]
2224

2325

26+
PYPROJECT_TOML = "pyproject.toml"
2427
TRANSITIVE_GROUP = PoetryGroup(name="transitive", toml_section=None)
2528

2629

@@ -31,7 +34,7 @@ class PoetryToml(BaseModel):
3134

3235
@classmethod
3336
def load_from_toml(cls, working_directory: Path) -> PoetryToml:
34-
file_path = working_directory / "pyproject.toml"
37+
file_path = working_directory / PYPROJECT_TOML
3538
if not file_path.exists():
3639
raise ValueError(f"File not found: {file_path}")
3740

@@ -142,3 +145,21 @@ def all_dependencies(self) -> dict[str, list[Package]]:
142145
transitive_dependencies.append(dep)
143146

144147
return direct_dependencies | {TRANSITIVE_GROUP.name: transitive_dependencies}
148+
149+
150+
def get_dependencies(working_directory: Path) -> dict[str, list[Package]]:
151+
poetry_dep = PoetryToml.load_from_toml(working_directory=working_directory)
152+
return PoetryDependencies(
153+
groups=poetry_dep.groups, working_directory=working_directory
154+
).direct_dependencies
155+
156+
157+
def get_dependencies_from_latest_tag() -> dict[str, list[Package]]:
158+
latest_tag = Git.get_latest_tag()
159+
with tempfile.TemporaryDirectory() as path:
160+
tmpdir = Path(path)
161+
162+
Git.copy_remote_file_locally(latest_tag, "poetry.lock", tmpdir)
163+
Git.copy_remote_file_locally(latest_tag, PYPROJECT_TOML, tmpdir)
164+
165+
return get_dependencies(working_directory=tmpdir)

exasol/toolbox/util/dependencies/shared_models.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
from __future__ import annotations
22

3+
from typing import Annotated
4+
35
from packaging.version import Version
46
from pydantic import (
7+
AfterValidator,
58
BaseModel,
69
ConfigDict,
7-
field_validator,
810
)
911

12+
VERSION_TYPE = Annotated[str, AfterValidator(lambda v: Version(v))]
13+
1014

1115
class Package(BaseModel):
1216
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
1317

1418
name: str
15-
version: Version
16-
17-
@field_validator("version", mode="before")
18-
def convert_version(cls, v: str) -> Version:
19-
return Version(v)
19+
version: VERSION_TYPE
2020

2121
@property
2222
def normalized_name(self) -> str:

exasol/toolbox/util/git.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import subprocess # nosec
2+
from functools import wraps
3+
from pathlib import Path
4+
5+
6+
def run_command(func):
7+
@wraps(func)
8+
def wrapper(*args, **kwargs) -> str:
9+
command_list = func(*args, **kwargs)
10+
output = subprocess.run(
11+
command_list, capture_output=True, text=True, check=True
12+
) # nosec
13+
return output.stdout.strip()
14+
15+
return wrapper
16+
17+
18+
class Git:
19+
@staticmethod
20+
@run_command
21+
def get_latest_tag():
22+
"""
23+
Get the latest tag from the git repository.
24+
"""
25+
return ["git", "describe", "--tags", "--abbrev=0"]
26+
27+
@staticmethod
28+
@run_command
29+
def read_file_from_tag(tag: str, remote_file: str):
30+
"""
31+
Read the contents of the specified file `remote_file` at the point in time
32+
specified by git tag `tag`.
33+
"""
34+
return ["git", "cat-file", "blob", f"{tag}:{remote_file}"]
35+
36+
@staticmethod
37+
def copy_remote_file_locally(
38+
tag: str, remote_file: str, destination_directory: Path
39+
) -> None:
40+
"""
41+
Copy the contents of the specified file `remote_file` at the point in time
42+
specified by git tag `tag` and copy it into the local `destination_directory/remote_file`.
43+
"""
44+
contents = Git.read_file_from_tag(tag=tag, remote_file=remote_file)
45+
(destination_directory / remote_file).write_text(contents)
46+
47+
@staticmethod
48+
@run_command
49+
def create_and_switch_to_branch(branch_name: str):
50+
return ["git", "switch", "-c", branch_name]

exasol/toolbox/util/release/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)