Skip to content

Commit 4248080

Browse files
Release v0.3.0 (#477)
* release prep * changelog 08/06 * remove noqa * changelog
1 parent dc5ffb8 commit 4248080

File tree

8 files changed

+281
-69
lines changed

8 files changed

+281
-69
lines changed

.pre-commit-config.yaml

-2
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,3 @@ repos:
6060
- id: pydocstyle
6161
args: ["--config=setup.cfg"]
6262
exclude: "tests/__init__.py"
63-
64-
exclude: "build_tools/.*"

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ We strive to provide a broad library of time series algorithms including the
1313
latest advances, offer efficient implementations using numba, and interfaces with other
1414
time series packages to provide a single framework for algorithm comparison.
1515

16-
The latest `aeon` release is `v0.2.0`. You can view the full changelog [here](https://www.aeon-toolkit.org/en/latest/changelog.html).
16+
The latest `aeon` release is `v0.3.0`. You can view the full changelog
17+
[here](https://www.aeon-toolkit.org/en/latest/changelog.html).
1718

1819
```diff
1920
- The deprecation policy is currently suspended, be careful with the version bounds used when including aeon as a dependency.

aeon/__init__.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# -*- coding: utf-8 -*-
2-
32
"""aeon."""
43

5-
__version__ = "0.2.0"
4+
__version__ = "0.3.0"
65

76
__all__ = ["show_versions"]
87

build_tools/changelog.py

+131-59
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
"""Myst Markdown changelog generator."""
33

44
import os
5-
from collections import defaultdict
6-
from typing import Dict, List
5+
from collections import OrderedDict
6+
from typing import Dict, List, Tuple
77

88
import httpx
99
from dateutil import parser
@@ -12,15 +12,14 @@
1212
"Accept": "application/vnd.github.v3+json",
1313
}
1414

15-
if os.getenv("GITHUB_TOKEN") is not None:
16-
HEADERS["Authorization"] = f"token {os.getenv('GITHUB_TOKEN')}"
17-
1815
OWNER = "aeon-toolkit"
1916
REPO = "aeon"
2017
GITHUB_REPOS = "https://api.github.com/repos"
18+
EXCLUDED_USERS = ["github-actions[bot]"]
2119

22-
def fetch_merged_pull_requests(page: int = 1) -> List[Dict]: # noqa
23-
"""Fetch a page of pull requests"""
20+
21+
def fetch_merged_pull_requests(page: int = 1) -> List[Dict]:
22+
"""Fetch a page of pull requests."""
2423
params = {
2524
"base": "main",
2625
"state": "closed",
@@ -37,7 +36,8 @@ def fetch_merged_pull_requests(page: int = 1) -> List[Dict]: # noqa
3736
return [pr for pr in r.json() if pr["merged_at"]]
3837

3938

40-
def fetch_latest_release(): # noqa
39+
def fetch_latest_release() -> Dict:
40+
"""Fetch latest release."""
4141
response = httpx.get(
4242
f"{GITHUB_REPOS}/{OWNER}/{REPO}/releases/latest", headers=HEADERS
4343
)
@@ -48,11 +48,11 @@ def fetch_latest_release(): # noqa
4848
raise ValueError(response.text, response.status_code)
4949

5050

51-
def fetch_pull_requests_since_last_release() -> List[Dict]: # noqa
52-
"""Fetch pull requests and filter based on merged date"""
51+
def fetch_pull_requests_since_last_release() -> List[Dict]:
52+
"""Fetch pull requests and filter based on merged date."""
5353
release = fetch_latest_release()
5454
published_at = parser.parse(release["published_at"])
55-
print( # noqa
55+
print( # noqa: T201
5656
f"Latest release {release['tag_name']} was published at {published_at}"
5757
)
5858

@@ -64,13 +64,16 @@ def fetch_pull_requests_since_last_release() -> List[Dict]: # noqa
6464
all_pulls.extend(
6565
[p for p in pulls if parser.parse(p["merged_at"]) > published_at]
6666
)
67-
is_exhausted = any(parser.parse(p["merged_at"]) < published_at for p in pulls) or len(pulls) == 0
67+
is_exhausted = (
68+
any(parser.parse(p["merged_at"]) < published_at for p in pulls)
69+
or len(pulls) == 0
70+
)
6871
page += 1
6972
return all_pulls
7073

7174

72-
def github_compare_tags(tag_left: str, tag_right: str = "HEAD"): # noqa
73-
"""Compare commit between two tags"""
75+
def github_compare_tags(tag_left: str, tag_right: str = "HEAD") -> Dict:
76+
"""Compare commit between two tags."""
7477
response = httpx.get(
7578
f"{GITHUB_REPOS}/{OWNER}/{REPO}/compare/{tag_left}...{tag_right}"
7679
)
@@ -80,87 +83,156 @@ def github_compare_tags(tag_left: str, tag_right: str = "HEAD"): # noqa
8083
raise ValueError(response.text, response.status_code)
8184

8285

83-
EXCLUDED_USERS = ["github-actions[bot]"]
84-
85-
def render_contributors(prs: List, fmt: str = "myst"): # noqa
86-
"""Find unique authors and print a list in given format"""
86+
def render_contributors(prs: list, fmt: str = "myst", n_prs: int = -1):
87+
"""Find unique authors and print a list in given format."""
8788
authors = sorted({pr["user"]["login"] for pr in prs}, key=lambda x: x.lower())
8889

8990
header = "Contributors\n"
9091
if fmt == "github":
91-
print(f"### {header}") # noqa
92-
print(", ".join(f"@{user}" for user in authors if user not in EXCLUDED_USERS)) # noqa
92+
print(f"### {header}") # noqa: T201
93+
print( # noqa: T201
94+
", ".join(f"@{user}" for user in authors if user not in EXCLUDED_USERS)
95+
)
9396
elif fmt == "myst":
94-
print(f"## {header}") # noqa
95-
print(",\n".join("{user}" + f"`{user}`" for user in authors if user not in EXCLUDED_USERS)) # noqa
97+
print(f"## {header}") # noqa: T201
98+
print( # noqa: T201
99+
"The following have contributed to this release through a collective "
100+
f"{n_prs} GitHub Pull Requests:\n"
101+
)
102+
print( # noqa: T201
103+
",\n".join(
104+
"{user}" + f"`{user}`" for user in authors if user not in EXCLUDED_USERS
105+
)
106+
)
107+
108+
109+
def assign_pr_category(
110+
assigned: Dict, categories: List[List], pr_idx: int, pr_labels: List, pkg_title: str
111+
):
112+
"""Assign a PR to a category."""
113+
has_category = False
114+
for cat in categories:
115+
if not set(cat[1]).isdisjoint(set(pr_labels)):
116+
has_category = True
117+
118+
if cat[0] not in assigned[pkg_title]:
119+
assigned[pkg_title][cat[0]] = []
120+
121+
assigned[pkg_title][cat[0]].append(pr_idx)
122+
123+
if not has_category:
124+
if "Other" not in assigned[pkg_title]:
125+
assigned[pkg_title]["Other"] = []
96126

127+
assigned[pkg_title]["Other"].append(pr_idx)
97128

98-
def assign_prs(prs, categs: List[Dict[str, List[str]]]): # noqa
99-
"""Assign PR to categories based on labels"""
100-
assigned = defaultdict(list)
129+
130+
def assign_prs(
131+
prs: List[Dict], packages: List[List], categories: List[List]
132+
) -> Tuple[Dict, int]:
133+
"""Assign all PRs to packages and categories based on labels."""
134+
assigned = {}
135+
prs_removed = 0
101136

102137
for i, pr in enumerate(prs):
103-
for cat in categs:
104-
pr_labels = [label["name"] for label in pr["labels"]]
105-
if cat["title"] != "Not Included" and "no changelog" in pr_labels:
106-
continue
107-
if not set(cat["labels"]).isdisjoint(set(pr_labels)):
108-
assigned[cat["title"]].append(i)
109-
110-
assigned["Other"] = list(
111-
set(range(len(prs))) - {i for _, l in assigned.items() for i in l}
112-
)
138+
pr_labels = [label["name"] for label in pr["labels"]]
139+
140+
if "no changelog" in pr_labels:
141+
prs_removed += 1
142+
continue
143+
144+
has_package = False
145+
for pkg in packages:
146+
if not set(pkg[1]).isdisjoint(set(pr_labels)):
147+
has_package = True
148+
149+
if pkg[0] not in assigned:
150+
assigned[pkg[0]] = {}
151+
152+
assign_pr_category(assigned, categories, i, pr_labels, pkg[0])
153+
154+
if not has_package:
155+
if "Other" not in assigned:
156+
assigned["Other"] = OrderedDict()
113157

114-
if "Not Included" in assigned:
115-
assigned.pop("Not Included")
158+
assign_pr_category(assigned, categories, i, pr_labels, "Other")
116159

117-
return assigned
160+
# order assignments
161+
assigned = OrderedDict({k: v for k, v in sorted(assigned.items())})
162+
if "Other" in assigned:
163+
assigned.move_to_end("Other")
118164

165+
for key in assigned:
166+
assigned[key] = OrderedDict({k: v for k, v in sorted(assigned[key].items())})
167+
if "Other" in assigned[key]:
168+
assigned[key].move_to_end("Other")
119169

120-
def render_row(pr): # noqa
170+
return assigned, prs_removed
171+
172+
173+
def render_row(pr: Dict): # noqa
121174
"""Render a single row with PR in Myst Markdown format"""
122-
print( # noqa
175+
print( # noqa: T201
123176
"-",
124177
pr["title"],
125178
"({pr}" + f"`{pr['number']}`)",
126179
"{user}" + f"`{pr['user']['login']}`",
127180
)
128181

129182

130-
def render_changelog(prs, assigned): # noqa
131-
# sourcery skip: use-named-expression
132-
"""Render changelog"""
133-
for title, _ in assigned.items():
134-
pr_group = [prs[i] for i in assigned[title]]
135-
if pr_group:
136-
print(f"\n## {title}\n") # noqa
183+
def render_changelog(prs: List[Dict], assigned: Dict):
184+
"""Render changelog."""
185+
for pkg_title, group in assigned.items():
186+
print(f"\n## {pkg_title}") # noqa: T201
187+
188+
for cat_title, pr_idx in group.items():
189+
print(f"\n### {cat_title}\n") # noqa: T201
190+
pr_group = [prs[i] for i in pr_idx]
137191

138192
for pr in sorted(pr_group, key=lambda x: parser.parse(x["merged_at"])):
139193
render_row(pr)
140194

141195

142196
if __name__ == "__main__":
197+
# don't commit the actual token, it will get revoked!
198+
os.environ["GITHUB_TOKEN"] = ""
199+
200+
if os.getenv("GITHUB_TOKEN") is not None and os.getenv("GITHUB_TOKEN") != "":
201+
HEADERS["Authorization"] = f"token {os.getenv('GITHUB_TOKEN')}"
202+
203+
# if you edit these, consider editing the PR template as well
204+
packages = [
205+
["Annotation", ["annotation"]],
206+
["Benchmarking", ["benchmarking"]],
207+
["Classification", ["classification"]],
208+
["Clustering", ["clustering"]],
209+
["Distances", ["distances"]],
210+
["Forecasting", ["forecasting"]],
211+
["Regression", ["regression"]],
212+
["Transformations", ["transformations"]],
213+
]
143214
categories = [
144-
{"title": "Enhancements", "labels": ["enhancement"]},
145-
{"title": "Fixes", "labels": ["bug"]},
146-
{"title": "Maintenance", "labels": ["maintenance"]},
147-
{"title": "Refactored", "labels": ["refactor"]},
148-
{"title": "Documentation", "labels": ["documentation"]},
149-
{"title": "Not Included", "labels": ["no changelog"]}, # this is deleted
215+
["Bug Fixes", ["bug"]],
216+
["Documentation", ["documentation"]],
217+
["Enhancements", ["enhancement"]],
218+
["Maintenance", ["maintenance"]],
219+
["Refactored", ["refactor"]],
150220
]
151221

152222
pulls = fetch_pull_requests_since_last_release()
153-
print(f"Found {len(pulls)} merged PRs since last release") # noqa
154-
assigned = assign_prs(pulls, categories)
223+
print(f"Found {len(pulls)} merged PRs since last release") # noqa: T201
224+
225+
assigned, prs_removed = assign_prs(pulls, packages, categories)
226+
155227
render_changelog(pulls, assigned)
156-
print() # noqa
157-
render_contributors(pulls)
228+
print() # noqa: T201
229+
render_contributors(pulls, fmt="myst", n_prs=len(pulls) - prs_removed)
158230

159231
release = fetch_latest_release()
160232
diff = github_compare_tags(release["tag_name"])
161233
if diff["total_commits"] != len(pulls):
162234
raise ValueError(
163235
"Something went wrong and not all PR were fetched. "
164-
f'There are {len(pulls)} PRs but {diff["total_commits"]} in the diff. '
236+
f"There are {len(pulls)} PRs but {diff['total_commits']} in the diff. "
165237
"Please verify that all PRs are included in the changelog."
166-
) # noqa
238+
)

docs/changelog.md

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
# Changelog
22

3-
All notable changes to this project will be documented in this file. We keep track of changes in this file since v0.4.0. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and we adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html>). The source code for all [releases](https://github.com/aeon-toolkit/aeon/releases>) is available on GitHub.
3+
All notable changes to this project will be documented in this file. The format is
4+
based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and we adhere
5+
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html>). The source code for
6+
all [releases](https://github.com/aeon-toolkit/aeon/releases>) is available on GitHub.
47

5-
To stay up-to-date with aeon releases, subscribe to aeon [here](https://libraries.io/pypi/aeon>) or follow us on [Twitter](https://twitter.com/aeon_toolbox>).
8+
To stay up-to-date with aeon releases, subscribe to aeon
9+
[here](https://libraries.io/pypi/aeon>) or follow us on
10+
[Twitter](https://twitter.com/aeon_toolbox>).
611

7-
For upcoming changes and next releases, see our [milestones](https://github.com/aeon-toolkit/aeon/milestones). For our long-term plan, see our [roadmap](roadmap).
12+
For upcoming changes and next releases, see our
13+
[milestones](https://github.com/aeon-toolkit/aeon/milestones). For our long-term plan,
14+
see our [roadmap](roadmap).
815

16+
- [Version 0.3.0](changelogs/v0.3.md)
917
- [Version 0.2.0](changelogs/v0.2.md)
1018
- [Version 0.1.0](changelogs/v0.1.md)

docs/changelogs/v0.2.md

-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ Following this release the deprecation policy remains suspended. Future releases
9595
- [MNT] Remove any reference of pykalman ({pr}`380`) {user}`hadifawaz1999`
9696
- [MNT] Unstable extras ({pr}`365`) {user}`MatthewMiddlehurst`
9797
- [MNT] Remove the test_methods_have_no_side_effects for inceptionTime classifier from _config ({pr}`338`) {user}`hadifawaz1999`
98-
- [MNT] Cleanup the forecasting tests ({pr}`192`) {user}`lmmentel`
9998

10099
## Contributors
101100

0 commit comments

Comments
 (0)