ultralytics-actions 0.0.71__tar.gz → 0.0.73__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {ultralytics_actions-0.0.71/ultralytics_actions.egg-info → ultralytics_actions-0.0.73}/PKG-INFO +32 -15
  2. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/README.md +30 -14
  3. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/actions/__init__.py +1 -1
  4. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/actions/first_interaction.py +1 -2
  5. ultralytics_actions-0.0.73/actions/update_file_headers.py +191 -0
  6. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/actions/utils/github_utils.py +10 -5
  7. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/actions/utils/openai_utils.py +1 -1
  8. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/pyproject.toml +2 -0
  9. ultralytics_actions-0.0.73/tests/test_cli_commands.py +19 -0
  10. ultralytics_actions-0.0.73/tests/test_common_utils.py +63 -0
  11. ultralytics_actions-0.0.73/tests/test_dispatch_actions.py +146 -0
  12. ultralytics_actions-0.0.73/tests/test_first_interaction.py +116 -0
  13. ultralytics_actions-0.0.73/tests/test_github_utils.py +64 -0
  14. ultralytics_actions-0.0.73/tests/test_init.py +84 -0
  15. ultralytics_actions-0.0.73/tests/test_openai_utils.py +65 -0
  16. ultralytics_actions-0.0.73/tests/test_summarize_pr.py +73 -0
  17. ultralytics_actions-0.0.73/tests/test_summarize_release.py +127 -0
  18. ultralytics_actions-0.0.73/tests/test_update_markdown_codeblocks.py +106 -0
  19. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73/ultralytics_actions.egg-info}/PKG-INFO +32 -15
  20. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/ultralytics_actions.egg-info/SOURCES.txt +11 -0
  21. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/ultralytics_actions.egg-info/entry_points.txt +1 -0
  22. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/ultralytics_actions.egg-info/requires.txt +1 -0
  23. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/LICENSE +0 -0
  24. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/actions/dispatch_actions.py +0 -0
  25. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/actions/summarize_pr.py +0 -0
  26. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/actions/summarize_release.py +0 -0
  27. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/actions/update_markdown_code_blocks.py +0 -0
  28. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/actions/utils/__init__.py +0 -0
  29. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/actions/utils/common_utils.py +0 -0
  30. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/setup.cfg +0 -0
  31. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/tests/test_urls.py +0 -0
  32. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/ultralytics_actions.egg-info/dependency_links.txt +0 -0
  33. {ultralytics_actions-0.0.71 → ultralytics_actions-0.0.73}/ultralytics_actions.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ultralytics-actions
3
- Version: 0.0.71
3
+ Version: 0.0.73
4
4
  Summary: Ultralytics Actions for GitHub automation and PR management.
5
5
  Author-email: Glenn Jocher <glenn.jocher@ultralytics.com>
6
6
  Maintainer-email: Ultralytics <hello@ultralytics.com>
@@ -33,6 +33,7 @@ Requires-Dist: ruff>=0.9.1
33
33
  Requires-Dist: docformatter>=1.7.5
34
34
  Provides-Extra: dev
35
35
  Requires-Dist: pytest; extra == "dev"
36
+ Requires-Dist: pytest-cov; extra == "dev"
36
37
  Dynamic: license-file
37
38
 
38
39
  <a href="https://www.ultralytics.com/"><img src="https://raw.githubusercontent.com/ultralytics/assets/main/logo/Ultralytics_Logotype_Original.svg" width="320" alt="Ultralytics logo"></a>
@@ -42,12 +43,16 @@ Dynamic: license-file
42
43
  Welcome to the [Ultralytics Actions](https://github.com/ultralytics/actions) repository, your go-to solution for maintaining consistent code quality across Ultralytics Python and Swift projects. This GitHub Action is designed to automate the formatting of Python, Markdown, and Swift files, ensuring adherence to our coding standards and enhancing project maintainability.
43
44
 
44
45
  [![GitHub Actions Marketplace](https://img.shields.io/badge/Marketplace-Ultralytics_Actions-blue?style=flat&logo=github)](https://github.com/marketplace/actions/ultralytics-actions)
46
+
47
+ [![Actions CI](https://github.com/ultralytics/actions/actions/workflows/ci.yml/badge.svg)](https://github.com/ultralytics/actions/actions/workflows/ci.yml)
45
48
  [![Ultralytics Actions](https://github.com/ultralytics/actions/actions/workflows/format.yml/badge.svg)](https://github.com/ultralytics/actions/actions/workflows/format.yml)
49
+ [![codecov](https://codecov.io/github/ultralytics/actions/graph/badge.svg?token=DoizJ1WS6j)](https://codecov.io/github/ultralytics/actions)
50
+ [![PyPI version](https://badge.fury.io/py/ultralytics-actions.svg)](https://badge.fury.io/py/ultralytics-actions)
51
+ [![Downloads](https://static.pepy.tech/badge/ultralytics-actions)](https://www.pepy.tech/projects/ultralytics-actions)
52
+
46
53
  [![Ultralytics Discord](https://img.shields.io/discord/1089800235347353640?logo=discord&logoColor=white&label=Discord&color=blue)](https://discord.com/invite/ultralytics)
47
54
  [![Ultralytics Forums](https://img.shields.io/discourse/users?server=https%3A%2F%2Fcommunity.ultralytics.com&logo=discourse&label=Forums&color=blue)](https://community.ultralytics.com/)
48
55
  [![Ultralytics Reddit](https://img.shields.io/reddit/subreddit-subscribers/ultralytics?style=flat&logo=reddit&logoColor=white&label=Reddit&color=blue)](https://reddit.com/r/ultralytics)
49
- [![PyPI version](https://badge.fury.io/py/ultralytics-actions.svg)](https://badge.fury.io/py/ultralytics-actions)
50
- [![Downloads](https://static.pepy.tech/badge/ultralytics-actions)](https://www.pepy.tech/projects/ultralytics-actions)
51
56
 
52
57
  ## 📄 Actions Description
53
58
 
@@ -84,6 +89,11 @@ To integrate this action into your Ultralytics repository:
84
89
  2. **Add the Action:** Configure the Ultralytics Actions in your workflow file as shown below:
85
90
 
86
91
  ```yaml
92
+ # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
93
+
94
+ # Ultralytics Actions https://github.com/ultralytics/actions
95
+ # This workflow formats code and documentation in PRs to Ultralytics standards
96
+
87
97
  name: Ultralytics Actions
88
98
 
89
99
  on:
@@ -91,24 +101,31 @@ To integrate this action into your Ultralytics repository:
91
101
  types: [opened]
92
102
  pull_request:
93
103
  branches: [main]
94
- types: [opened, closed]
104
+ types: [opened, closed, synchronize, review_requested]
105
+
106
+ permissions:
107
+ contents: write # Modify code in PRs
108
+ pull-requests: write # Add comments and labels to PRs
109
+ issues: write # Add comments and labels to issues
95
110
 
96
111
  jobs:
97
- format:
98
- runs-on: ubuntu-latest # Use 'macos-latest' if 'swift: true'
112
+ actions:
113
+ runs-on: ubuntu-latest
99
114
  steps:
100
- - name: Run Ultralytics Formatting
115
+ - name: Run Ultralytics Actions
101
116
  uses: ultralytics/actions@main
102
117
  with:
103
- token: ${{ secrets.GITHUB_TOKEN }} # Automatically generated, do not modify
104
- labels: true # Autolabel issues and PRs using GPT-4.1 (requires 'openai_api_key')
105
- python: true # Format Python code and docstrings with Ruff and docformatter
106
- prettier: true # Format YAML, JSON, Markdown, and CSS with Prettier
107
- swift: false # Format Swift code with swift-format (requires 'runs-on: macos-latest')
118
+ token: ${{ secrets.GITHUB_TOKEN }} # Auto-generated token
119
+ labels: true # Auto-label issues/PRs using AI
120
+ python: true # Format Python with Ruff and docformatter
121
+ prettier: true # Format YAML, JSON, Markdown, CSS
122
+ swift: false # Format Swift (requires macos-latest)
123
+ dart: false # Format Dart/Flutter
108
124
  spelling: true # Check spelling with codespell
109
- links: true # Check for broken links with Lychee
110
- summary: true # Generate PR summary with GPT-4.1 (requires 'openai_api_key')
111
- openai_api_key: ${{ secrets.OPENAI_API_KEY }} # Add your OpenAI API key as a repository secret
125
+ links: true # Check broken links with Lychee
126
+ summary: true # Generate AI-powered PR summaries
127
+ openai_api_key: ${{ secrets.OPENAI_API_KEY }} # Powers PR summaries, labels and comments
128
+ brave_api_key: ${{ secrets.BRAVE_API_KEY }} # Used for broken link resolution
112
129
  ```
113
130
 
114
131
  3. **Customize:** Adjust the `runs-on` runner and the boolean flags (`labels`, `python`, `prettier`, `swift`, `spelling`, `links`, `summary`) based on your project's needs. Remember to add your `OPENAI_API_KEY` as a secret in your repository settings if you enable `labels` or `summary`.
@@ -5,12 +5,16 @@
5
5
  Welcome to the [Ultralytics Actions](https://github.com/ultralytics/actions) repository, your go-to solution for maintaining consistent code quality across Ultralytics Python and Swift projects. This GitHub Action is designed to automate the formatting of Python, Markdown, and Swift files, ensuring adherence to our coding standards and enhancing project maintainability.
6
6
 
7
7
  [![GitHub Actions Marketplace](https://img.shields.io/badge/Marketplace-Ultralytics_Actions-blue?style=flat&logo=github)](https://github.com/marketplace/actions/ultralytics-actions)
8
+
9
+ [![Actions CI](https://github.com/ultralytics/actions/actions/workflows/ci.yml/badge.svg)](https://github.com/ultralytics/actions/actions/workflows/ci.yml)
8
10
  [![Ultralytics Actions](https://github.com/ultralytics/actions/actions/workflows/format.yml/badge.svg)](https://github.com/ultralytics/actions/actions/workflows/format.yml)
11
+ [![codecov](https://codecov.io/github/ultralytics/actions/graph/badge.svg?token=DoizJ1WS6j)](https://codecov.io/github/ultralytics/actions)
12
+ [![PyPI version](https://badge.fury.io/py/ultralytics-actions.svg)](https://badge.fury.io/py/ultralytics-actions)
13
+ [![Downloads](https://static.pepy.tech/badge/ultralytics-actions)](https://www.pepy.tech/projects/ultralytics-actions)
14
+
9
15
  [![Ultralytics Discord](https://img.shields.io/discord/1089800235347353640?logo=discord&logoColor=white&label=Discord&color=blue)](https://discord.com/invite/ultralytics)
10
16
  [![Ultralytics Forums](https://img.shields.io/discourse/users?server=https%3A%2F%2Fcommunity.ultralytics.com&logo=discourse&label=Forums&color=blue)](https://community.ultralytics.com/)
11
17
  [![Ultralytics Reddit](https://img.shields.io/reddit/subreddit-subscribers/ultralytics?style=flat&logo=reddit&logoColor=white&label=Reddit&color=blue)](https://reddit.com/r/ultralytics)
12
- [![PyPI version](https://badge.fury.io/py/ultralytics-actions.svg)](https://badge.fury.io/py/ultralytics-actions)
13
- [![Downloads](https://static.pepy.tech/badge/ultralytics-actions)](https://www.pepy.tech/projects/ultralytics-actions)
14
18
 
15
19
  ## 📄 Actions Description
16
20
 
@@ -47,6 +51,11 @@ To integrate this action into your Ultralytics repository:
47
51
  2. **Add the Action:** Configure the Ultralytics Actions in your workflow file as shown below:
48
52
 
49
53
  ```yaml
54
+ # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
55
+
56
+ # Ultralytics Actions https://github.com/ultralytics/actions
57
+ # This workflow formats code and documentation in PRs to Ultralytics standards
58
+
50
59
  name: Ultralytics Actions
51
60
 
52
61
  on:
@@ -54,24 +63,31 @@ To integrate this action into your Ultralytics repository:
54
63
  types: [opened]
55
64
  pull_request:
56
65
  branches: [main]
57
- types: [opened, closed]
66
+ types: [opened, closed, synchronize, review_requested]
67
+
68
+ permissions:
69
+ contents: write # Modify code in PRs
70
+ pull-requests: write # Add comments and labels to PRs
71
+ issues: write # Add comments and labels to issues
58
72
 
59
73
  jobs:
60
- format:
61
- runs-on: ubuntu-latest # Use 'macos-latest' if 'swift: true'
74
+ actions:
75
+ runs-on: ubuntu-latest
62
76
  steps:
63
- - name: Run Ultralytics Formatting
77
+ - name: Run Ultralytics Actions
64
78
  uses: ultralytics/actions@main
65
79
  with:
66
- token: ${{ secrets.GITHUB_TOKEN }} # Automatically generated, do not modify
67
- labels: true # Autolabel issues and PRs using GPT-4.1 (requires 'openai_api_key')
68
- python: true # Format Python code and docstrings with Ruff and docformatter
69
- prettier: true # Format YAML, JSON, Markdown, and CSS with Prettier
70
- swift: false # Format Swift code with swift-format (requires 'runs-on: macos-latest')
80
+ token: ${{ secrets.GITHUB_TOKEN }} # Auto-generated token
81
+ labels: true # Auto-label issues/PRs using AI
82
+ python: true # Format Python with Ruff and docformatter
83
+ prettier: true # Format YAML, JSON, Markdown, CSS
84
+ swift: false # Format Swift (requires macos-latest)
85
+ dart: false # Format Dart/Flutter
71
86
  spelling: true # Check spelling with codespell
72
- links: true # Check for broken links with Lychee
73
- summary: true # Generate PR summary with GPT-4.1 (requires 'openai_api_key')
74
- openai_api_key: ${{ secrets.OPENAI_API_KEY }} # Add your OpenAI API key as a repository secret
87
+ links: true # Check broken links with Lychee
88
+ summary: true # Generate AI-powered PR summaries
89
+ openai_api_key: ${{ secrets.OPENAI_API_KEY }} # Powers PR summaries, labels and comments
90
+ brave_api_key: ${{ secrets.BRAVE_API_KEY }} # Used for broken link resolution
75
91
  ```
76
92
 
77
93
  3. **Customize:** Adjust the `runs-on` runner and the boolean flags (`labels`, `python`, `prettier`, `swift`, `spelling`, `links`, `summary`) based on your project's needs. Remember to add your `OPENAI_API_KEY` as a secret in your repository settings if you enable `labels` or `summary`.
@@ -22,4 +22,4 @@
22
22
  # ├── test_summarize_pr.py
23
23
  # └── ...
24
24
 
25
- __version__ = "0.0.71"
25
+ __version__ = "0.0.73"
@@ -249,8 +249,7 @@ mutation($discussionId: ID!, $body: String!) {
249
249
  """
250
250
  event.graphql_request(mutation, variables={"discussionId": node_id, "body": comment})
251
251
  else:
252
- url = f"{GITHUB_API_URL}/repos/{event.repository}/issues/{number}/comments"
253
- event.post(url, json={"body": comment}, headers=event.headers)
252
+ event.post(f"{GITHUB_API_URL}/repos/{event.repository}/issues/{number}/comments", json={"body": comment})
254
253
 
255
254
 
256
255
  def get_first_interaction_response(event, issue_type: str, title: str, body: str, username: str) -> str:
@@ -0,0 +1,191 @@
1
+ # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
+
3
+ import os
4
+ from pathlib import Path
5
+
6
+ from actions.utils import Action
7
+
8
+ # Base header text
9
+ HEADER = os.getenv("HEADER")
10
+
11
+ # Map file extensions to comment styles
12
+ COMMENT_MAP = {
13
+ # Python style
14
+ ".py": ("# ", None, None),
15
+ ".yml": ("# ", None, None),
16
+ ".yaml": ("# ", None, None),
17
+ ".toml": ("# ", None, None),
18
+ ".sh": ("# ", None, None), # Bash scripts
19
+ ".bash": ("# ", None, None), # Bash scripts
20
+ # C/C++/Java/JS style
21
+ ".c": ("// ", "/* ", " */"), # C files
22
+ ".cpp": ("// ", "/* ", " */"), # C++ files
23
+ ".h": ("// ", "/* ", " */"), # C/C++ header files
24
+ ".hpp": ("// ", "/* ", " */"), # C++ header files
25
+ ".swift": ("// ", "/* ", " */"),
26
+ ".js": ("// ", "/* ", " */"),
27
+ ".ts": ("// ", "/* ", " */"), # TypeScript files
28
+ ".dart": ("// ", "/* ", " */"), # Dart/Flutter files
29
+ ".rs": ("// ", "/* ", " */"), # Rust files
30
+ ".java": ("// ", "/* ", " */"), # Android Java
31
+ ".kt": ("// ", "/* ", " */"), # Android Kotlin
32
+ # CSS style
33
+ ".css": (None, "/* ", " */"),
34
+ # HTML/XML style
35
+ ".html": (None, "<!-- ", " -->"),
36
+ ".xml": (None, "<!-- ", " -->"), # Android XML
37
+ # MATLAB style
38
+ ".m": ("% ", None, None),
39
+ }
40
+
41
+ # Ignore these Paths (do not update their headers)
42
+ IGNORE_PATHS = [
43
+ ".idea",
44
+ ".venv",
45
+ "env",
46
+ "node_modules",
47
+ ".git",
48
+ "__pycache__",
49
+ "mkdocs_github_authors.yaml",
50
+ # Build and distribution directories
51
+ "dist",
52
+ "build",
53
+ ".eggs",
54
+ "site", # mkdocs build directory
55
+ # Generated code
56
+ "generated",
57
+ "auto_gen",
58
+ # Lock files
59
+ "lock",
60
+ # Minified files
61
+ ".min.js",
62
+ ".min.css",
63
+ ]
64
+
65
+
66
+ def update_file(file_path, prefix, block_start, block_end, base_header):
67
+ """Update file with the correct header and proper spacing."""
68
+ try:
69
+ with open(file_path, encoding="utf-8") as f:
70
+ lines = f.readlines()
71
+ except Exception as e:
72
+ print(f"Error reading {file_path}: {e}")
73
+ return False
74
+
75
+ if not lines:
76
+ return False
77
+
78
+ # Format the header based on comment style
79
+ if prefix:
80
+ formatted_header = f"{prefix}{base_header}\n"
81
+ elif block_start and block_end:
82
+ formatted_header = f"{block_start}{base_header}{block_end}\n"
83
+ else:
84
+ formatted_header = f"# {base_header}\n"
85
+
86
+ # Keep shebang line if it exists
87
+ start_idx = 0
88
+ if lines and lines[0].startswith("#!"):
89
+ start_idx = 1
90
+
91
+ modified = False
92
+ new_lines = lines[:start_idx]
93
+ remaining_lines = lines[start_idx:]
94
+
95
+ # If first line is already the exact header we want
96
+ if remaining_lines and remaining_lines[0] == formatted_header:
97
+ # Check if spacing is correct
98
+ new_lines.append(remaining_lines[0])
99
+ if len(remaining_lines) > 1:
100
+ second_line = remaining_lines[1].strip()
101
+ if second_line == "" or second_line in ["#", "//", "/*", "*", "<!--", "%"]:
102
+ # Spacing is correct, append the rest
103
+ new_lines.extend(remaining_lines[1:])
104
+ else:
105
+ # Add blank line
106
+ new_lines.append("\n")
107
+ new_lines.extend(remaining_lines[1:])
108
+ modified = True
109
+ else:
110
+ # Only header exists, no need for blank line
111
+ pass
112
+ # Check if first line has AGPL but is not the exact header
113
+ elif remaining_lines and "AGPL" in remaining_lines[0] and remaining_lines[0] != formatted_header:
114
+ # Replace with proper header
115
+ new_lines.append(formatted_header)
116
+ modified = True
117
+
118
+ # Check if second line is blank or commented
119
+ if len(remaining_lines) > 1:
120
+ second_line = remaining_lines[1].strip()
121
+ if second_line == "" or second_line in ["#", "//", "/*", "*", "<!--", "%"]:
122
+ # Keep existing blank/comment line
123
+ new_lines.append(remaining_lines[1])
124
+ new_lines.extend(remaining_lines[2:])
125
+ else:
126
+ # Add blank line
127
+ new_lines.append("\n")
128
+ new_lines.extend(remaining_lines[1:])
129
+ else:
130
+ # Only header line, no need for blank line after
131
+ pass
132
+ # No header found, add it
133
+ else:
134
+ # Add header at the beginning
135
+ new_lines.append(formatted_header)
136
+ # Add blank line if content follows
137
+ if remaining_lines and remaining_lines[0].strip():
138
+ new_lines.append("\n")
139
+ new_lines.extend(remaining_lines)
140
+ modified = True
141
+
142
+ if modified:
143
+ try:
144
+ with open(file_path, "w", encoding="utf-8") as f:
145
+ f.writelines(new_lines)
146
+ return True
147
+ except Exception as e:
148
+ print(f"Error writing {file_path}: {e}")
149
+ return False
150
+
151
+ return False
152
+
153
+
154
+ def main(*args, **kwargs):
155
+ """Automates file header updates for all files in the specified directory."""
156
+ event = Action(*args, **kwargs)
157
+
158
+ if "ultralytics" in event.repository.lower():
159
+ if event.is_repo_private() and event.repository.startswith("ultralytics/"):
160
+ from datetime import datetime
161
+
162
+ notice = f"Copyright © 2014-{datetime.now().year}"
163
+ header = f"Ultralytics Inc. 🚀 {notice} - CONFIDENTIAL - https://ultralytics.com - All Rights Reserved"
164
+ else:
165
+ header = "Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license"
166
+ elif HEADER and HEADER.lower() not in {"true", "false", "none"}:
167
+ header = HEADER
168
+ else:
169
+ return
170
+
171
+ directory = Path.cwd()
172
+ total = changed = unchanged = 0
173
+ for ext, comment_style in COMMENT_MAP.items():
174
+ prefix, block_start, block_end = comment_style
175
+
176
+ for file_path in directory.rglob(f"*{ext}"):
177
+ if any(part in str(file_path) for part in IGNORE_PATHS):
178
+ continue
179
+
180
+ total += 1
181
+ if update_file(file_path, prefix, block_start, block_end, header):
182
+ print(f"Updated: {file_path.relative_to(directory)}")
183
+ changed += 1
184
+ else:
185
+ unchanged += 1
186
+
187
+ print(f"Headers: {total}, Updated: {changed}, Unchanged: {unchanged}")
188
+
189
+
190
+ if __name__ == "__main__":
191
+ main()
@@ -41,7 +41,7 @@ class Action:
41
41
  self.eyes_reaction_id = None
42
42
  self.verbose = verbose
43
43
 
44
- def _request(self, method: str, url: str, headers=None, expected_status=None, **kwargs):
44
+ def _request(self, method: str, url: str, headers=None, expected_status=None, hard=False, **kwargs):
45
45
  """Unified request handler with error checking."""
46
46
  headers = headers or self.headers
47
47
  expected_status = expected_status or self._default_status[method.lower()]
@@ -55,11 +55,11 @@ class Action:
55
55
  if not success:
56
56
  try:
57
57
  error_detail = response.json()
58
- print(f" Error: {error_detail.get('message', 'Unknown error')}")
59
- except:
60
- print(f" Error: {response.text[:100]}...")
58
+ print(f" Error: {error_detail.get('message', 'Unknown error')}")
59
+ except Exception as e:
60
+ print(f" Error: {response.text[:100]}... {e}")
61
61
 
62
- if not success:
62
+ if not success and hard:
63
63
  response.raise_for_status()
64
64
 
65
65
  return response
@@ -91,6 +91,10 @@ class Action:
91
91
  return json.loads(Path(event_path).read_text())
92
92
  return {}
93
93
 
94
+ def is_repo_private(self) -> bool:
95
+ """Checks if the repository is public using event data or GitHub API if needed."""
96
+ return self.event_data.get("repository", {}).get("private")
97
+
94
98
  def get_username(self) -> str | None:
95
99
  """Gets username associated with the GitHub token."""
96
100
  response = self.post(GITHUB_GRAPHQL_URL, json={"query": "query { viewer { login } }"})
@@ -152,6 +156,7 @@ class Action:
152
156
  "github.event_name": self.event_name,
153
157
  "github.event.action": self.event_data.get("action"),
154
158
  "github.repository": self.repository,
159
+ "github.repository.private": self.is_repo_private(),
155
160
  "github.event.pull_request.number": self.pr.get("number"),
156
161
  "github.event.pull_request.head.repo.full_name": self.pr.get("head", {}).get("repo", {}).get("full_name"),
157
162
  "github.actor": os.environ.get("GITHUB_ACTOR"),
@@ -50,7 +50,7 @@ def get_completion(
50
50
  "temperature": temperature,
51
51
  }
52
52
 
53
- r = requests.post(url, headers=headers, json=data)
53
+ r = requests.post(url, json=data, headers=headers)
54
54
  r.raise_for_status()
55
55
  content = r.json()["choices"][0]["message"]["content"].strip()
56
56
  content = remove_outer_codeblocks(content)
@@ -73,6 +73,7 @@ dependencies = [
73
73
  [project.optional-dependencies]
74
74
  dev = [
75
75
  "pytest",
76
+ "pytest-cov",
76
77
  ]
77
78
 
78
79
  [project.urls]
@@ -87,6 +88,7 @@ ultralytics-actions-first-interaction = "actions.first_interaction:main"
87
88
  ultralytics-actions-summarize-pr = "actions.summarize_pr:main"
88
89
  ultralytics-actions-summarize-release = "actions.summarize_release:main"
89
90
  ultralytics-actions-update-markdown-code-blocks = "actions.update_markdown_code_blocks:main"
91
+ ultralytics-actions-header = "actions.update_file_headers:main"
90
92
  ultralytics-actions-info = "actions.utils:ultralytics_actions_info"
91
93
 
92
94
  [tool.setuptools]
@@ -0,0 +1,19 @@
1
+ # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
+
3
+
4
+ # Import CLI command modules
5
+ from actions import (
6
+ first_interaction,
7
+ summarize_pr,
8
+ summarize_release,
9
+ update_markdown_code_blocks,
10
+ )
11
+
12
+
13
+ def test_importable_modules():
14
+ """Test that all modules can be imported without errors."""
15
+ # This is a simple test to ensure modules can be imported successfully
16
+ assert hasattr(first_interaction, "main")
17
+ assert hasattr(summarize_pr, "main")
18
+ assert hasattr(summarize_release, "main")
19
+ assert hasattr(update_markdown_code_blocks, "main")
@@ -0,0 +1,63 @@
1
+ # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
+
3
+ from unittest.mock import MagicMock, patch
4
+
5
+ from actions.utils.common_utils import (
6
+ allow_redirect,
7
+ brave_search,
8
+ clean_url,
9
+ remove_html_comments,
10
+ )
11
+
12
+
13
+ def test_remove_html_comments():
14
+ """Test removing HTML comments from strings."""
15
+ test_str = "Before <!-- Comment --> After"
16
+ assert remove_html_comments(test_str) == "Before After"
17
+
18
+ # Multiline comment
19
+ test_str = "Before\n<!-- Comment\nline 2\nline 3 -->\nAfter"
20
+ assert remove_html_comments(test_str) == "Before\n\nAfter"
21
+
22
+ # No comments
23
+ test_str = "No comments here"
24
+ assert remove_html_comments(test_str) == "No comments here"
25
+
26
+
27
+ def test_clean_url():
28
+ """Test cleaning URL strings."""
29
+ # Test removing quotes and trailing characters
30
+ assert clean_url('"https://example.com"') == "https://example.com"
31
+ assert clean_url("'https://example.com'") == "https://example.com"
32
+ assert clean_url("https://example.com.") == "https://example.com"
33
+ assert clean_url("https://example.com,") == "https://example.com"
34
+
35
+ # Test git URLs
36
+ assert clean_url("git+https://github.com/user/repo.git@main") == "https://github.com/user/repo"
37
+
38
+
39
+ def test_allow_redirect():
40
+ """Test allowing URL redirects based on rules."""
41
+ # Should not allow - start ignores
42
+ assert not allow_redirect("https://youtu.be/xyz", "https://youtube.com")
43
+
44
+ # Should not allow - end ignores
45
+ assert not allow_redirect("https://example.com", "https://example.com/404")
46
+
47
+ # Empty end URL
48
+ assert not allow_redirect("https://example.com", "")
49
+
50
+
51
+ @patch("requests.get")
52
+ def test_brave_search(mock_get):
53
+ """Test Brave search API integration."""
54
+ mock_response = MagicMock()
55
+ mock_response.status_code = 200
56
+ mock_response.json.return_value = {
57
+ "web": {"results": [{"url": "https://example.com"}, {"url": "https://example.org"}]}
58
+ }
59
+ mock_get.return_value = mock_response
60
+
61
+ results = brave_search("test query", "test-api-key", count=2)
62
+ assert results == ["https://example.com", "https://example.org"]
63
+ mock_get.assert_called_once()