ultralytics-actions 0.0.71__py3-none-any.whl → 0.0.73__py3-none-any.whl

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.
actions/__init__.py CHANGED
@@ -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)
@@ -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`.
@@ -0,0 +1,17 @@
1
+ actions/__init__.py,sha256=Uq5k3qN5QggBhuBJT9-zO0ROzbZ9h5OCR7MQ-Xl8L0I,742
2
+ actions/dispatch_actions.py,sha256=vbA4w_B8vMXMen__ck2WoDsUFCELjXOQbpLzZCmqTXg,4240
3
+ actions/first_interaction.py,sha256=whphdBrWkcWRt6RgOeK2dUoGq3aBTqttQdokxVjkye4,16309
4
+ actions/summarize_pr.py,sha256=NCaDSbw4PVoRbPJzji_Ua2HadI2pn7QOE_dy3VK9_cc,10463
5
+ actions/summarize_release.py,sha256=OncODHx7XsmB-nPf-B1tnxUTcaJx6hM4JAMa9frypzM,7922
6
+ actions/update_file_headers.py,sha256=EltkXfBiWV53NYtAHLtTFRZdO-tXkgx2FPQr8Dp0aOU,6259
7
+ actions/update_markdown_code_blocks.py,sha256=9PL7YIQfApRNAa0que2hYHv7umGZTZoHlblesB0xFj4,8587
8
+ actions/utils/__init__.py,sha256=TXYvhFgDeAnosePM4jfOrEd6PlC7tWC-WMOgCB_T6Tw,728
9
+ actions/utils/common_utils.py,sha256=2eNwGJFigl9bBXcyWzdr8mr97Lrx7zFKWIFYugZcUJw,11736
10
+ actions/utils/github_utils.py,sha256=gdObDzOGmoLl8LXJ-fza7Dr2qLKfDsX2HbpcyFBEMtE,10097
11
+ actions/utils/openai_utils.py,sha256=09kW4K2LOc6KsWz5tijf2Piinhu3PIKPDVkRC3KyIxU,2943
12
+ ultralytics_actions-0.0.73.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
13
+ ultralytics_actions-0.0.73.dist-info/METADATA,sha256=GM-a9l23v5JNXoF837vLh92ZfYbfzg-iz2o_4XC1Les,11638
14
+ ultralytics_actions-0.0.73.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
15
+ ultralytics_actions-0.0.73.dist-info/entry_points.txt,sha256=zNOtw1Dh8ItViVO6vKoaCQtWSjk7jw-_MaqGByju4I4,440
16
+ ultralytics_actions-0.0.73.dist-info/top_level.txt,sha256=5apM5x80QlJcGbACn1v3fkmIuL1-XQCKcItJre7w7Tw,8
17
+ ultralytics_actions-0.0.73.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.0.0)
2
+ Generator: setuptools (80.4.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,5 +1,6 @@
1
1
  [console_scripts]
2
2
  ultralytics-actions-first-interaction = actions.first_interaction:main
3
+ ultralytics-actions-header = actions.update_file_headers:main
3
4
  ultralytics-actions-info = actions.utils:ultralytics_actions_info
4
5
  ultralytics-actions-summarize-pr = actions.summarize_pr:main
5
6
  ultralytics-actions-summarize-release = actions.summarize_release:main
@@ -1,16 +0,0 @@
1
- actions/__init__.py,sha256=KuXt92GxOFYtPimGijr5CdEXsLmxIG4MQWAaAgFd3ew,742
2
- actions/dispatch_actions.py,sha256=vbA4w_B8vMXMen__ck2WoDsUFCELjXOQbpLzZCmqTXg,4240
3
- actions/first_interaction.py,sha256=Yagh38abX638DNYr18HoiEEfCZOJfrqObhJIff54Sx0,16350
4
- actions/summarize_pr.py,sha256=NCaDSbw4PVoRbPJzji_Ua2HadI2pn7QOE_dy3VK9_cc,10463
5
- actions/summarize_release.py,sha256=OncODHx7XsmB-nPf-B1tnxUTcaJx6hM4JAMa9frypzM,7922
6
- actions/update_markdown_code_blocks.py,sha256=9PL7YIQfApRNAa0que2hYHv7umGZTZoHlblesB0xFj4,8587
7
- actions/utils/__init__.py,sha256=TXYvhFgDeAnosePM4jfOrEd6PlC7tWC-WMOgCB_T6Tw,728
8
- actions/utils/common_utils.py,sha256=2eNwGJFigl9bBXcyWzdr8mr97Lrx7zFKWIFYugZcUJw,11736
9
- actions/utils/github_utils.py,sha256=bpFMbpzPvtBiqtNvzISFkCp8_7EboMiD3aMA8RG_tTs,9785
10
- actions/utils/openai_utils.py,sha256=txbsEPQnIOieejatBuE6Yk7xR1fQ0erWOEs6cYgUQX4,2943
11
- ultralytics_actions-0.0.71.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
12
- ultralytics_actions-0.0.71.dist-info/METADATA,sha256=Q0n2vt5uFAB2167ciNanCwJk86uLv_6sry9l8mKgvNU,10930
13
- ultralytics_actions-0.0.71.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
14
- ultralytics_actions-0.0.71.dist-info/entry_points.txt,sha256=GowvOFplj0C7JmsjbKcbpgLpdf2r921pcaOQkAHWZRA,378
15
- ultralytics_actions-0.0.71.dist-info/top_level.txt,sha256=5apM5x80QlJcGbACn1v3fkmIuL1-XQCKcItJre7w7Tw,8
16
- ultralytics_actions-0.0.71.dist-info/RECORD,,