Skip to content

Commit 521cb62

Browse files
Merge remote-tracking branch 'origin/main' into mm/workflows
2 parents 329f050 + d140fe9 commit 521cb62

File tree

9 files changed

+129
-42
lines changed

9 files changed

+129
-42
lines changed

.github/utilities/ai_spam.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""Script for handling AI Spam label on pull requests.
2+
3+
Triggered when AI Spam label is added to a PR,
4+
it adds a comment and closes the PR.
5+
"""
6+
7+
import json
8+
import os
9+
10+
from github import Github
11+
12+
context_dict = json.loads(os.getenv("CONTEXT_GITHUB"))
13+
14+
repo_name = context_dict["repository"]
15+
g = Github(os.getenv("GITHUB_TOKEN"))
16+
repo = g.get_repo(repo_name)
17+
pr_number = context_dict["event"]["pull_request"]["number"]
18+
pr = repo.get_pull(pr_number)
19+
label_name = context_dict["event"]["label"]["name"]
20+
21+
if label_name == "AI Spam":
22+
comment_body = (
23+
"This pull request has been flagged with the **AI Spam** label.\n\n"
24+
"This PR is being closed."
25+
)
26+
pr.create_issue_comment(comment_body)
27+
pr.edit(state="closed")

.github/utilities/issue_assign.py

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
"""Script for the GitHub issue self-assign bot.
22
3-
It checks if a comment on an issue or PR includes the trigger
4-
phrase (as defined) and a mentioned user.
5-
If it does, it assigns the issue to the mentioned user.
6-
Users without write access can only have up to 2 open issues assigned.
7-
Users with write access (or admin) are exempt from this limit.
8-
If a non-write user already has 2 or more open issues, the bot
9-
comments on the issue with links to the currently assigned open issues.
3+
Checks if a comment on an issue or PR includes the trigger phrase and a mentioned user.
4+
If it does, it assigns or unassigns the issue to the mentioned user if they have
5+
permissions.
106
"""
117

128
import json
@@ -22,54 +18,75 @@
2218
repo = g.get_repo(repo)
2319
issue_number = context_dict["event"]["issue"]["number"]
2420
issue = repo.get_issue(number=issue_number)
25-
comment_body = context_dict["event"]["comment"]["body"]
21+
issue_labels = {label.name.lower() for label in issue.labels}
2622
pr = context_dict["event"]["issue"].get("pull_request")
23+
comment_body = context_dict["event"]["comment"]["body"]
2724
commenter = context_dict["event"]["comment"]["user"]["login"]
25+
commenter_permission = repo.get_collaborator_permission(commenter)
26+
has_write_permission = commenter_permission not in ["admin", "write"]
27+
28+
restricted_labels = {"meta-issue"}
2829

30+
# Assign tagged used to the issue if the comment includes the trigger phrase
2931
body = comment_body.lower()
30-
if "@aeon-actions-bot" in body and not pr:
31-
# Assign commenter if comment includes "assign me"
32-
if "assign me" in body:
33-
issue.add_to_assignees(commenter)
34-
# Assign tagged used to the issue if the comment includes the trigger phrase
35-
elif "assign" in body:
32+
if "@aeon-actions-bot" in body and "assign" in body and not pr:
33+
# Check if the issue has any restricted labels for auto assignment
34+
label_intersect = issue_labels & restricted_labels
35+
if len(label_intersect) > 0:
36+
issue.create_comment(
37+
f"This issue contains the following restricted label(s): "
38+
f"{', '.join(label_intersect)}. Cannot assign to users."
39+
)
40+
else:
41+
# collect any mentioned (@username) users
3642
mentioned_users = re.findall(r"@[a-zA-Z0-9_-]+", comment_body)
3743
mentioned_users = [user[1:] for user in mentioned_users]
3844
mentioned_users.remove("aeon-actions-bot")
45+
# Assign commenter if comment includes "assign me"
46+
if "assign me" in body:
47+
mentioned_users.append(commenter)
48+
mentioned_users = set(mentioned_users)
3949

50+
access_error = False
4051
for user in mentioned_users:
41-
user_obj = g.get_user(user)
42-
permission = repo.get_collaborator_permission(user_obj)
52+
# Can only assign others if the commenter has write access
53+
if user != commenter and not has_write_permission:
54+
if not access_error:
55+
issue.create_comment(
56+
"Cannot assign other users to issues without write access."
57+
)
58+
access_error = True
59+
continue
60+
61+
# If the user is already assigned to this issue, remove them
62+
if user in [assignee.login for assignee in issue.assignees]:
63+
issue.remove_from_assignees(user)
64+
continue
4365

44-
if permission in ["admin", "write"]:
66+
# If the commenter has write access, just assign
67+
if has_write_permission:
4568
issue.add_to_assignees(user)
4669
else:
47-
# First check if the user is already assigned to this issue
48-
if user in [assignee.login for assignee in issue.assignees]:
49-
continue
50-
5170
# search for open issues only
5271
query = f"repo:{repo.full_name} is:issue is:open assignee:{user}"
5372
issues_assigned_to_user = g.search_issues(query)
5473
assigned_count = issues_assigned_to_user.totalCount
5574

56-
if assigned_count >= 2:
75+
if assigned_count >= 3:
5776
# link to issue
5877
assigned_issues_list = [
5978
f"[#{assigned_issue.number}]({assigned_issue.html_url})"
6079
for assigned_issue in issues_assigned_to_user
6180
]
6281

63-
comment_message = (
64-
f"@{user}, you already have {assigned_count} "
65-
f"open issues assigned."
66-
"Users without write access are limited to self-assigning two"
67-
"issues.\n\n"
68-
"Here are the open issues assigned to you:\n"
82+
issue.create_comment(
83+
f"@{user}, already has {assigned_count} open issues assigned."
84+
"Users without write access are limited to self-assigning "
85+
"three issues.\n\n"
86+
"Here are the open issues assigned:\n"
6987
+ "\n".join(
7088
f"- {issue_link}" for issue_link in assigned_issues_list
7189
)
7290
)
73-
issue.create_comment(comment_message)
7491
else:
7592
issue.add_to_assignees(user)

.github/workflows/ai_spam.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: AI Spam Detection On PR
2+
3+
on:
4+
pull_request:
5+
types: [labeled]
6+
7+
concurrency:
8+
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
9+
cancel-in-progress: true
10+
11+
jobs:
12+
ai-spam-present:
13+
if: ${{ github.event.label.name == 'AI Spam' }}
14+
runs-on: ubuntu-24.04
15+
16+
steps:
17+
- name: Create app token
18+
uses: actions/create-github-app-token@v1
19+
id: app-token
20+
with:
21+
app-id: ${{ vars.PR_APP_ID }}
22+
private-key: ${{ secrets.PR_APP_KEY }}
23+
24+
- name: Checkout main
25+
uses: actions/checkout@v4
26+
with:
27+
sparse-checkout: .github/utilities
28+
29+
- name: Setup Python 3.11
30+
uses: actions/setup-python@v5
31+
with:
32+
python-version: "3.11"
33+
34+
- name: Install PyGithub
35+
run: pip install -Uq PyGithub
36+
37+
- name: Process AI Spam
38+
id: handle_spam
39+
run: python .github/utilities/ai_spam.py
40+
env:
41+
CONTEXT_GITHUB: ${{ toJson(github) }}
42+
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}

aeon/anomaly_detection/series/distance_based/_rockad.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ def _inner_fit(self, X: np.ndarray) -> None:
176176

177177
if self.power_transform:
178178
self.power_transformer_ = PowerTransformer()
179+
# todo check if this is still an issue with scikit-learn >= 1.7.0
180+
# when lower bound is raised
179181
try:
180182
Xtp = self.power_transformer_.fit_transform(Xt)
181183

aeon/anomaly_detection/series/distance_based/tests/test_rockad.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,6 @@ def test_rockad_incorrect_input():
7373
):
7474
ad = ROCKAD(stride=1, window_size=100)
7575
ad.fit(train_series)
76-
with pytest.warns(
77-
UserWarning, match=r"Power Transform failed and thus has been disabled."
78-
):
79-
ad = ROCKAD(stride=1, window_size=5)
80-
ad.fit(train_series)
8176
with pytest.raises(
8277
ValueError, match=r"window shape cannot be larger than input array shape"
8378
):

aeon/classification/sklearn/_continuous_interval_tree.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from sklearn.exceptions import NotFittedError
2222
from sklearn.utils import check_random_state
2323
from sklearn.utils.multiclass import check_classification_targets
24+
from sklearn.utils.validation import validate_data
2425

2526

2627
class _TreeNode:
@@ -374,7 +375,8 @@ def fit(self, X, y):
374375
"""
375376
# data processing
376377
X = self._check_X(X)
377-
X, y = self._validate_data(
378+
X, y = validate_data(
379+
self,
378380
X=X,
379381
y=y,
380382
ensure_min_samples=2,
@@ -464,8 +466,8 @@ def predict_proba(self, X):
464466

465467
# data processing
466468
X = self._check_X(X)
467-
X = self._validate_data(
468-
X=X, reset=False, force_all_finite="allow-nan", accept_sparse=False
469+
X = validate_data(
470+
self, X=X, reset=False, force_all_finite="allow-nan", accept_sparse=False
469471
)
470472

471473
dists = np.zeros((X.shape[0], self.n_classes_))

aeon/classification/sklearn/_rotation_forest_classifier.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from sklearn.tree import DecisionTreeClassifier
2020
from sklearn.utils import check_random_state
2121
from sklearn.utils.multiclass import check_classification_targets
22+
from sklearn.utils.validation import validate_data
2223

2324
from aeon.base._base import _clone_estimator
2425
from aeon.utils.validation import check_n_jobs
@@ -192,7 +193,7 @@ def predict_proba(self, X) -> np.ndarray:
192193

193194
# data processing
194195
X = self._check_X(X)
195-
X = self._validate_data(X=X, reset=False, accept_sparse=False)
196+
X = validate_data(self, X=X, reset=False, accept_sparse=False)
196197

197198
# replace missing values with 0 and remove useless attributes
198199
X = X[:, self._useful_atts]
@@ -299,7 +300,7 @@ def fit_predict_proba(self, X, y) -> np.ndarray:
299300
def _fit_rotf(self, X, y, save_transformed_data: bool = False):
300301
# data processing
301302
X = self._check_X(X)
302-
X, y = self._validate_data(X=X, y=y, ensure_min_samples=2, accept_sparse=False)
303+
X, y = validate_data(self, X=X, y=y, ensure_min_samples=2, accept_sparse=False)
303304
check_classification_targets(y)
304305

305306
self._n_jobs = check_n_jobs(self.n_jobs)

aeon/regression/sklearn/_rotation_forest_regressor.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from sklearn.exceptions import NotFittedError
2020
from sklearn.tree import DecisionTreeRegressor
2121
from sklearn.utils import check_random_state
22+
from sklearn.utils.validation import validate_data
2223

2324
from aeon.base._base import _clone_estimator
2425
from aeon.utils.validation import check_n_jobs
@@ -168,7 +169,7 @@ def predict(self, X) -> np.ndarray:
168169

169170
# data processing
170171
X = self._check_X(X)
171-
X = self._validate_data(X=X, reset=False, accept_sparse=False)
172+
X = validate_data(self, X=X, reset=False, accept_sparse=False)
172173

173174
# replace missing values with 0 and remove useless attributes
174175
X = X[:, self._useful_atts]
@@ -222,7 +223,7 @@ def fit_predict(self, X, y) -> np.ndarray:
222223
def _fit_rotf(self, X, y, save_transformed_data: bool = False):
223224
# data processing
224225
X = self._check_X(X)
225-
X, y = self._validate_data(X=X, y=y, ensure_min_samples=2, accept_sparse=False)
226+
X, y = validate_data(self, X=X, y=y, ensure_min_samples=2, accept_sparse=False)
226227

227228
self._label_average = np.mean(y)
228229

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ dependencies = [
5050
"numpy>=1.21.0,<2.3.0",
5151
"packaging>=20.0",
5252
"pandas>=2.0.0,<2.4.0",
53-
"scikit-learn>=1.0.0,<1.7.0",
53+
"scikit-learn>=1.0.0,<1.8.0",
5454
"scipy>=1.9.0,<1.16.0",
5555
"typing-extensions>=4.6.0",
5656
]

0 commit comments

Comments
 (0)