File: release.py

package info (click to toggle)
python-odmantic 1.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,488 kB
  • sloc: python: 8,646; sh: 110; javascript: 45; makefile: 34; xml: 13
file content (159 lines) | stat: -rw-r--r-- 5,135 bytes parent folder | download
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
# /// script
# requires-python = ">=3.13"
# dependencies = [
#     "semver>=2.13.0",
#     "typer>=0.4.1",
# ]
# ///

import datetime
import os
import tomllib
from enum import Enum

import typer
from click.types import Choice
from semver import VersionInfo


class BumpType(str, Enum):
    major = "major"
    minor = "minor"
    patch = "patch"


def get_current_version() -> VersionInfo:
    with open("./pyproject.toml", "rb") as f:
        pyproject = tomllib.load(f)
    version = pyproject["project"]["version"]
    return VersionInfo.parse(version)


def get_new_version(current_version: VersionInfo, bump_type: BumpType) -> VersionInfo:
    if bump_type == BumpType.major:
        return current_version.bump_major()
    if bump_type == BumpType.minor:
        return current_version.bump_minor()
    if bump_type == BumpType.patch:
        return current_version.bump_patch()
    raise NotImplementedError("Unhandled bump type")


PYPROJECT_PATH = "./pyproject.toml"


def update_pyproject(current_version: VersionInfo, new_version: VersionInfo) -> None:
    with open(PYPROJECT_PATH) as f:
        content = f.read()
    new_content = content.replace(
        f'version = "{current_version}"', f'version = "{new_version}"'
    )
    if content == new_content:
        typer.secho("Couldn't bump version in pyproject.toml", fg=typer.colors.RED)
        raise typer.Exit(1)
    with open(PYPROJECT_PATH, "w") as f:
        f.write(new_content)
    typer.secho("Version updated with success", fg=typer.colors.GREEN)


RELEASE_NOTE_PATH = "./__release_notes__.md"


def get_release_notes() -> str:
    with open("./CHANGELOG.md", "r") as f:
        while not f.readline().strip() == "## [Unreleased]":
            pass
        content = ""
        while not (line := f.readline().strip()).startswith("## "):
            content = content + line + "\n"
    return content


def save_release_notes(release_notes: str) -> None:
    if os.path.exists(RELEASE_NOTE_PATH):
        typer.secho(
            f"Release note file {RELEASE_NOTE_PATH} already exists", fg=typer.colors.RED
        )
        raise typer.Exit(1)
    with open(RELEASE_NOTE_PATH, "w") as f:
        f.write(release_notes)
    typer.secho("Release note file generated with success", fg=typer.colors.GREEN)


CHANGELOG_PATH = "./CHANGELOG.md"


def update_changelog(current_version: VersionInfo, new_version: VersionInfo) -> None:
    today = datetime.date.today()
    date_str = f"{today.year}-{today.month:02d}-{today.day:02d}"
    with open(CHANGELOG_PATH, "r") as f:
        content = f.read()
    # Add version header
    content = content.replace(
        "## [Unreleased]", ("## [Unreleased]\n\n" f"## [{new_version}] - {date_str}")
    )
    # Add version links
    content = content.replace(
        f"[unreleased]: https://github.com/art049/odmantic/compare/v{current_version}...HEAD",
        (
            f"[{new_version}]: https://github.com/art049/odmantic/compare/v{current_version}...v{new_version}\n"
            f"[unreleased]: https://github.com/art049/odmantic/compare/v{new_version}...HEAD"
        ),
    )
    with open(CHANGELOG_PATH, "w") as f:
        f.write(content)
    typer.secho("Changelog updated with success", fg=typer.colors.GREEN)


VERSION_FILE_PATH = "__version__.txt"


def create_version_file(new_version: VersionInfo) -> None:
    if os.path.exists(VERSION_FILE_PATH):
        typer.secho(
            f"Version file {VERSION_FILE_PATH} already exists", fg=typer.colors.RED
        )
        raise typer.Exit(1)
    with open(VERSION_FILE_PATH, "w") as f:
        f.write(str(new_version))


def summarize(
    current_version: VersionInfo,
    new_version: VersionInfo,
    bump_type: BumpType,
    release_notes: str,
) -> None:
    typer.secho("Release summary:", fg=typer.colors.BLUE, bold=True)
    typer.secho(f"    Version bump: {bump_type.upper()}", bold=True)
    typer.secho(f"    Version change: {current_version} -> {new_version}", bold=True)
    typer.confirm("Continue to release notes preview ?", abort=True, default=True)

    release_header = typer.style(
        f"RELEASE NOTE {new_version}\n\n", fg=typer.colors.BLUE, bold=True
    )
    typer.echo_via_pager(release_header + release_notes)
    typer.confirm("Continue ?", abort=True, default=True)


def main() -> None:
    current_version = get_current_version()
    typer.secho(f"Current version: {current_version}", bold=True)
    bump_type: BumpType = typer.prompt(
        typer.style("Release type ?", fg=typer.colors.BLUE, bold=True),
        type=Choice(list(BumpType.__members__)),
        default=BumpType.patch.value,
        show_choices=True,
    )
    new_version = get_new_version(current_version, bump_type)
    release_notes = get_release_notes()
    summarize(current_version, new_version, bump_type, release_notes)
    save_release_notes(release_notes)
    update_pyproject(current_version, new_version)
    update_changelog(current_version, new_version)
    create_version_file(new_version)
    typer.confirm("Additionnal release commit files staged ?", abort=True, default=True)


if __name__ == "__main__":
    typer.run(main)