Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New check_pypi_version() function #247

Merged
merged 11 commits into from
Oct 16, 2024
42 changes: 2 additions & 40 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,49 +41,11 @@ jobs:
shell: python
run: |
import os
import requests
import toml

# Load version and package name from pyproject.toml
pyproject = toml.load('pyproject.toml')
package_name = pyproject['project']['name']
local_version = pyproject['project'].get('version', 'dynamic')

# If version is dynamic, extract it from the specified file
if local_version == 'dynamic':
version_attr = pyproject['tool']['setuptools']['dynamic']['version']['attr']
module_path, attr_name = version_attr.rsplit('.', 1)
with open(f"{module_path.replace('.', '/')}/__init__.py") as f:
local_version = next(line.split('=')[1].strip().strip("'\"") for line in f if line.startswith(attr_name))

print(f"Local Version: {local_version}")

# Get online version from PyPI
response = requests.get(f"https://pypi.org/pypi/{package_name}/json")
online_version = response.json()['info']['version'] if response.status_code == 200 else None
print(f"Online Version: {online_version or 'Not Found'}")

# Determine if a new version should be published
publish = False
if online_version:
local_ver = tuple(map(int, local_version.split('.')))
online_ver = tuple(map(int, online_version.split('.')))
major_diff = local_ver[0] - online_ver[0]
minor_diff = local_ver[1] - online_ver[1]
patch_diff = local_ver[2] - online_ver[2]

publish = (
(major_diff == 0 and minor_diff == 0 and 0 < patch_diff <= 2) or
(major_diff == 0 and minor_diff == 1 and local_ver[2] == 0) or
(major_diff == 1 and local_ver[1] == 0 and local_ver[2] == 0)
)
else:
publish = True # First release

from actions.utils import check_pypi_version
local_version, online_version, publish = check_pypi_version()
os.system(f'echo "increment={publish}" >> $GITHUB_OUTPUT')
os.system(f'echo "current_tag=v{local_version}" >> $GITHUB_OUTPUT')
os.system(f'echo "previous_tag=v{online_version}" >> $GITHUB_OUTPUT')

if publish:
print('Ready to publish new version to PyPI βœ….')
id: check_pypi
Expand Down
30 changes: 15 additions & 15 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,21 @@ runs:
shell: bash
continue-on-error: true

# Autolabel Issues and PRs (run before commit changes in case commit fails) ----------------------------------------
- name: Autolabel Issues and PRs
if: inputs.labels == 'true' && (github.event.action == 'opened' || github.event.action == 'created')
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
GITHUB_TOKEN: ${{ inputs.token }}
FIRST_ISSUE_RESPONSE: ${{ inputs.first_issue_response }}
FIRST_PR_RESPONSE: ${{ inputs.first_pr_response }}
OPENAI_API_KEY: ${{ inputs.openai_api_key }}
OPENAI_MODEL: ${{ inputs.openai_model }}
run: |
ultralytics-actions-first-interaction
shell: bash
continue-on-error: true

# Commit Changes ---------------------------------------------------------------------------------------------------
- name: Commit and Push Changes
if: (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && github.event.action != 'closed'
Expand All @@ -209,21 +224,6 @@ runs:
shell: bash
continue-on-error: false

# Autolabel Issues and PRs ----------------------------------------------------------------------------------------
- name: Autolabel Issues and PRs
if: inputs.labels == 'true' && (github.event.action == 'opened' || github.event.action == 'created')
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
GITHUB_TOKEN: ${{ inputs.token }}
FIRST_ISSUE_RESPONSE: ${{ inputs.first_issue_response }}
FIRST_PR_RESPONSE: ${{ inputs.first_pr_response }}
OPENAI_API_KEY: ${{ inputs.openai_api_key }}
OPENAI_MODEL: ${{ inputs.openai_model }}
run: |
ultralytics-actions-first-interaction
shell: bash
continue-on-error: true

# Broken links -----------------------------------------------------------------------------------------------------
- name: Broken Link Checker
if: inputs.links == 'true' && github.event.action != 'closed'
Expand Down
8 changes: 1 addition & 7 deletions actions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,4 @@
# β”œβ”€β”€ test_summarize_pr.py
# └── ...

from .first_interaction import main as first_interaction_main
from .summarize_pr import main as summarize_pr_main
from .summarize_release import main as summarize_release_main
from .update_markdown_code_blocks import main as update_markdown_code_blocks_main

__all__ = ["first_interaction_main", "summarize_pr_main", "summarize_release_main", "update_markdown_code_blocks_main"]
__version__ = "0.0.5"
__version__ = "0.0.6"
24 changes: 12 additions & 12 deletions actions/first_interaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@


def get_event_content() -> Tuple[int, str, str, str, str, str, str]:
"""Extracts the number, node_id, title, body, username, and issue_type."""
"""Extracts key information from GitHub event data for issues, pull requests, or discussions."""
with open(GITHUB_EVENT_PATH) as f:
data = json.load(f)
action = data["action"] # 'opened', 'closed', 'created' (discussion), etc.
Expand All @@ -50,7 +50,7 @@ def get_event_content() -> Tuple[int, str, str, str, str, str, str]:


def update_issue_pr_content(number: int, node_id: str, issue_type: str):
"""Updates the title and body of the issue, pull request, or discussion."""
"""Updates the title and body of an issue, pull request, or discussion with predefined content."""
new_title = "Content Under Review"
new_body = """This post has been flagged for review by [Ultralytics Actions](https://ultralytics.com/actions) due to possible spam, abuse, or off-topic content. For more information please see our:

Expand Down Expand Up @@ -79,7 +79,7 @@ def update_issue_pr_content(number: int, node_id: str, issue_type: str):


def close_issue_pr(number: int, node_id: str, issue_type: str):
"""Closes the issue, pull request, or discussion."""
"""Closes the specified issue, pull request, or discussion using the GitHub API."""
if issue_type == "discussion":
mutation = """
mutation($discussionId: ID!) {
Expand All @@ -98,7 +98,7 @@ def close_issue_pr(number: int, node_id: str, issue_type: str):


def lock_issue_pr(number: int, node_id: str, issue_type: str):
"""Locks the issue, pull request, or discussion."""
"""Locks an issue, pull request, or discussion to prevent further interactions."""
if issue_type == "discussion":
mutation = """
mutation($lockableId: ID!, $lockReason: LockReason) {
Expand All @@ -119,7 +119,7 @@ def lock_issue_pr(number: int, node_id: str, issue_type: str):


def block_user(username: str):
"""Blocks a user from the organization."""
"""Blocks a user from the organization using the GitHub API."""
url = f"{GITHUB_API_URL}/orgs/{REPO_NAME.split('/')[0]}/blocks/{username}"
r = requests.put(url, headers=GITHUB_HEADERS)
print(f"{'Successful' if r.status_code == 204 else 'Fail'} user block for {username}: {r.status_code}")
Expand All @@ -128,7 +128,7 @@ def block_user(username: str):
def get_relevant_labels(
issue_type: str, title: str, body: str, available_labels: Dict, current_labels: List
) -> List[str]:
"""Uses OpenAI to determine the most relevant labels."""
"""Determines relevant labels for GitHub issues/PRs using OpenAI, considering title, body, and existing labels."""
# Remove mutually exclusive labels like both 'bug' and 'question' or inappropriate labels like 'help wanted'
for label in ["help wanted", "TODO"]: # normal case
available_labels.pop(label, None) # remove as should only be manually added
Expand Down Expand Up @@ -212,7 +212,7 @@ def get_label_ids(labels: List[str]) -> List[str]:


def apply_labels(number: int, node_id: str, labels: List[str], issue_type: str):
"""Applies the given labels to the issue, pull request, or discussion."""
"""Applies specified labels to a GitHub issue, pull request, or discussion using the appropriate API."""
if "Alert" in labels:
create_alert_label()

Expand Down Expand Up @@ -243,21 +243,21 @@ def apply_labels(number: int, node_id: str, labels: List[str], issue_type: str):


def create_alert_label():
"""Creates the 'Alert' label in the repository if it doesn't exist."""
"""Creates the 'Alert' label in the repository if it doesn't exist, with a red color and description."""
alert_label = {"name": "Alert", "color": "FF0000", "description": "Potential spam, abuse, or off-topic."}
requests.post(f"{GITHUB_API_URL}/repos/{REPO_NAME}/labels", json=alert_label, headers=GITHUB_HEADERS)


def is_org_member(username: str) -> bool:
"""Checks if a user is a member of the organization."""
"""Checks if a user is a member of the organization using the GitHub API."""
org_name = REPO_NAME.split("/")[0]
url = f"{GITHUB_API_URL}/orgs/{org_name}/members/{username}"
r = requests.get(url, headers=GITHUB_HEADERS)
return r.status_code == 204 # 204 means the user is a member


def add_comment(number: int, node_id: str, comment: str, issue_type: str):
"""Adds a comment to the issue, pull request, or discussion."""
"""Adds a comment to the specified issue, pull request, or discussion using the GitHub API."""
if issue_type == "discussion":
mutation = """
mutation($discussionId: ID!, $body: String!) {
Expand All @@ -276,7 +276,7 @@ def add_comment(number: int, node_id: str, comment: str, issue_type: str):


def get_first_interaction_response(issue_type: str, title: str, body: str, username: str, number: int) -> str:
"""Generates a custom response using LLM based on the issue/PR content and instructions."""
"""Generates a custom LLM response for GitHub issues, PRs, or discussions based on content."""
issue_discussion_response = f"""
πŸ‘‹ Hello @{username}, thank you for submitting a `{REPO_NAME}` πŸš€ {issue_type.capitalize()}. To help us address your concern efficiently, please ensure you've provided the following information:

Expand Down Expand Up @@ -370,7 +370,7 @@ def get_first_interaction_response(issue_type: str, title: str, body: str, usern


def main():
"""Runs autolabel action and adds custom response for new issues/PRs/Discussions."""
"""Executes autolabeling and custom response generation for new GitHub issues, PRs, and discussions."""
number, node_id, title, body, username, issue_type, action = get_event_content()
available_labels = get_github_data("labels")
label_descriptions = {label["name"]: label.get("description", "") for label in available_labels}
Expand Down
6 changes: 3 additions & 3 deletions actions/summarize_pr.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@


def generate_pr_summary(repo_name, diff_text):
"""Generates a professionally written yet accessible summary of a PR using OpenAI's API."""
"""Generates a concise, professional summary of a PR using OpenAI's API for Ultralytics repositories."""
if not diff_text:
diff_text = "**ERROR: DIFF IS EMPTY, THERE ARE ZERO CODE CHANGES IN THIS PR."
ratio = 3.3 # about 3.3 characters per token
Expand All @@ -45,7 +45,7 @@ def generate_pr_summary(repo_name, diff_text):


def update_pr_description(repo_name, pr_number, new_summary):
"""Updates the original PR description with a new summary, replacing an existing summary if found."""
"""Updates the PR description with a new summary, replacing existing summary if present."""
# Fetch the current PR description
pr_url = f"{GITHUB_API_URL}/repos/{repo_name}/pulls/{pr_number}"
pr_response = requests.get(pr_url, headers=GITHUB_HEADERS)
Expand All @@ -64,7 +64,7 @@ def update_pr_description(repo_name, pr_number, new_summary):


def main():
"""Summarize PR."""
"""Summarize a pull request and update its description with an AI-generated summary."""
diff = get_pr_diff(PR_NUMBER)

# Generate PR summary
Expand Down
12 changes: 6 additions & 6 deletions actions/summarize_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@


def get_release_diff(repo_name: str, previous_tag: str, latest_tag: str) -> str:
"""Get the diff between two tags."""
"""Retrieves the differences between two specified Git tags in a GitHub repository."""
url = f"{GITHUB_API_URL}/repos/{repo_name}/compare/{previous_tag}...{latest_tag}"
r = requests.get(url, headers=GITHUB_HEADERS_DIFF)
return r.text if r.status_code == 200 else f"Failed to get diff: {r.content}"


def get_prs_between_tags(repo_name: str, previous_tag: str, latest_tag: str) -> list:
"""Get PRs merged between two tags using the compare API."""
"""Retrieves and processes pull requests merged between two specified tags in a GitHub repository."""
url = f"{GITHUB_API_URL}/repos/{repo_name}/compare/{previous_tag}...{latest_tag}"
r = requests.get(url, headers=GITHUB_HEADERS)
r.raise_for_status()
Expand Down Expand Up @@ -68,7 +68,7 @@ def get_prs_between_tags(repo_name: str, previous_tag: str, latest_tag: str) ->


def get_new_contributors(repo: str, prs: list) -> set:
"""Identify genuinely new contributors in the current release."""
"""Identify new contributors who made their first merged PR in the current release."""
new_contributors = set()
for pr in prs:
author = pr["author"]
Expand All @@ -85,7 +85,7 @@ def get_new_contributors(repo: str, prs: list) -> set:


def generate_release_summary(diff: str, prs: list, latest_tag: str, previous_tag: str, repo_name: str) -> str:
"""Generate a summary for the release."""
"""Generate a concise release summary with key changes, purpose, and impact for a new Ultralytics version."""
pr_summaries = "\n\n".join(
[f"PR #{pr['number']}: {pr['title']} by @{pr['author']}\n{pr['body'][:1000]}" for pr in prs]
)
Expand Down Expand Up @@ -139,15 +139,15 @@ def generate_release_summary(diff: str, prs: list, latest_tag: str, previous_tag


def create_github_release(repo_name: str, tag_name: str, name: str, body: str) -> int:
"""Create a release on GitHub."""
"""Creates a GitHub release with specified tag, name, and body content for the given repository."""
url = f"{GITHUB_API_URL}/repos/{repo_name}/releases"
data = {"tag_name": tag_name, "name": name, "body": body, "draft": False, "prerelease": False}
r = requests.post(url, headers=GITHUB_HEADERS, json=data)
return r.status_code


def get_previous_tag() -> str:
"""Get the previous tag from git tags."""
"""Retrieves the previous Git tag, excluding the current tag, using the git describe command."""
cmd = ["git", "describe", "--tags", "--abbrev=0", "--exclude", CURRENT_TAG]
try:
return subprocess.run(cmd, check=True, text=True, capture_output=True).stdout.strip()
Expand Down
16 changes: 8 additions & 8 deletions actions/update_markdown_code_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,29 @@


def extract_code_blocks(markdown_content):
"""Extract Python code blocks with ``` followed by "python", "py", or "{ .py .annotate }"."""
"""Extracts Python code blocks from markdown content using regex pattern matching."""
pattern = r"^( *)```(?:python|py|\{[ ]*\.py[ ]*\.annotate[ ]*\})\n(.*?)\n\1```"
code_block_pattern = re.compile(pattern, re.DOTALL | re.MULTILINE)
return code_block_pattern.findall(markdown_content)


def remove_indentation(code_block, num_spaces):
"""Removes `num_spaces` leading spaces from each line in `code_block` and returns the modified string."""
"""Removes specified leading spaces from each line in a code block to adjust indentation."""
lines = code_block.split("\n")
stripped_lines = [line[num_spaces:] if len(line) >= num_spaces else line for line in lines]
return "\n".join(stripped_lines)


def add_indentation(code_block, num_spaces):
"""Adds `num_spaces` leading spaces to each non-empty line in `code_block`."""
"""Adds specified number of leading spaces to non-empty lines in a code block."""
indent = " " * num_spaces
lines = code_block.split("\n")
indented_lines = [indent + line if line.strip() != "" else line for line in lines]
return "\n".join(indented_lines)


def format_code_with_ruff(temp_dir):
"""Formats all Python code files in the `temp_dir` directory using the 'ruff' linter tool."""
"""Formats Python code files in the specified directory using ruff linter and docformatter tools."""
try:
# Run ruff format
subprocess.run(
Expand Down Expand Up @@ -86,14 +86,14 @@ def format_code_with_ruff(temp_dir):


def generate_temp_filename(file_path, index):
"""Generates a unique temporary filename based on the file path and index."""
"""Generates a unique temporary filename using a hash of the file path and index."""
unique_string = f"{file_path.parent}_{file_path.stem}_{index}"
unique_hash = hashlib.md5(unique_string.encode()).hexdigest()
return f"temp_{unique_hash}.py"


def process_markdown_file(file_path, temp_dir, verbose=False):
"""Reads a markdown file, extracts Python code blocks, saves them to temp files, and updates the file."""
"""Processes a markdown file, extracting Python code blocks for formatting and updating the original file."""
try:
markdown_content = Path(file_path).read_text()
code_blocks = extract_code_blocks(markdown_content)
Expand All @@ -119,7 +119,7 @@ def process_markdown_file(file_path, temp_dir, verbose=False):


def update_markdown_file(file_path, markdown_content, temp_files):
"""Updates the markdown file with formatted code blocks."""
"""Updates a markdown file with formatted Python code blocks extracted and processed externally."""
for num_spaces, original_code_block, temp_file_path in temp_files:
try:
with open(temp_file_path) as temp_file:
Expand All @@ -143,7 +143,7 @@ def update_markdown_file(file_path, markdown_content, temp_files):


def main(root_dir=Path.cwd(), verbose=False):
"""Processes all markdown files in a specified directory and its subdirectories."""
"""Processes markdown files, extracts and formats Python code blocks, and updates the original files."""
root_path = Path(root_dir)
markdown_files = list(root_path.rglob("*.md"))
temp_dir = Path("temp_code_blocks")
Expand Down
2 changes: 2 additions & 0 deletions actions/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
GITHUB_TOKEN,
PR_NUMBER,
REPO_NAME,
check_pypi_version,
get_github_data,
get_pr_diff,
graphql_request,
Expand All @@ -32,4 +33,5 @@
"OPENAI_API_KEY",
"OPENAI_MODEL",
"get_completion",
"check_pypi_version",
)
Loading
Loading