diff --git a/.github/workflows/prepare_release.yml b/.github/workflows/prepare_release.yml new file mode 100644 index 00000000..a755683b --- /dev/null +++ b/.github/workflows/prepare_release.yml @@ -0,0 +1,57 @@ +name: Release Prep + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch to merge release notes and code analysis into.' + required: true + default: 'main' + version: + description: + 'Version to use for the release. Must be in format: X.Y.Z.' + date: + description: + 'Date of the release. Must be in format YYYY-MM-DD.' + +jobs: + preparerelease: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install requests==2.31.0 + python -m pip install bandit==1.7.7 + python -m pip install .[test] + + - name: Generate release notes + env: + GH_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }} + run: > + python scripts/release_notes_generator.py + -v ${{ inputs.version }} + -d ${{ inputs.date }} + + - name: Save static code analysis + run: bandit -r . -x ./tests,./scripts,./build -f txt -o static_code_analysis.txt --exit-zero + + - name: Create pull request + id: cpr + uses: peter-evans/create-pull-request@v4 + with: + token: ${{ secrets.GH_ACCESS_TOKEN }} + commit-message: Prepare release for v${{ inputs.version }} + author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" + committer: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" + title: v${{ inputs.version }} Release Preparation + body: "This is an auto-generated PR to prepare the release." + branch: prepared-release + branch-suffix: short-commit-hash + base: ${{ inputs.branch }} diff --git a/.github/workflows/static_code_analysis.yml b/.github/workflows/static_code_analysis.yml deleted file mode 100644 index 6bb8de34..00000000 --- a/.github/workflows/static_code_analysis.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Static Code Analysis - -on: - release: - types: [published] - workflow_dispatch: - -jobs: - code-analysis: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up Python 3.10 - uses: actions/setup-python@v5 - with: - python-version: '3.10' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install bandit==1.7.7 - - name: Save code analysis - run: bandit -r . -x ./tests -f txt -o static_code_analysis.txt --exit-zero - - name: Create pull request - id: cpr - uses: peter-evans/create-pull-request@v4 - with: - token: ${{ secrets.GH_ACCESS_TOKEN }} - commit-message: Update static code analysis - author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" - committer: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" - title: Latest Code Analysis - body: "This is an auto-generated PR with the **latest** code analysis results." - branch: static-code-analysis - branch-suffix: short-commit-hash - base: main diff --git a/HISTORY.md b/HISTORY.md index aa094444..79ddf037 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,20 @@ # History +## v0.9.2 - 2025-01-09 + +### Maintenance + +* Create Prepare Release workflow - Issue [#364](https://github.com/sdv-dev/SDGym/issues/364) by @R-Palazzo + +### Bugs Fixed + +* Minimum tests failing because of broken action - Issue [#351](https://github.com/sdv-dev/SDGym/issues/351) by @amontanez24 + +### New Features + +* Add integration with 3rd party synthesizer (REalTabFormer) - Issue [#347](https://github.com/sdv-dev/SDGym/issues/347) by @cristid9 +* Add support for numpy 2.0.0 - Issue [#315](https://github.com/sdv-dev/SDGym/issues/315) by @R-Palazzo + ## v0.9.1 - 2024-08-29 ### Bugs Fixed diff --git a/scripts/release_notes_generator.py b/scripts/release_notes_generator.py new file mode 100644 index 00000000..3e95a107 --- /dev/null +++ b/scripts/release_notes_generator.py @@ -0,0 +1,128 @@ +"""Script to generate release notes.""" + +import argparse +import os +from collections import defaultdict + +import requests + +LABEL_TO_HEADER = { + 'feature request': 'New Features', + 'bug': 'Bugs Fixed', + 'internal': 'Internal', + 'maintenance': 'Maintenance', + 'customer success': 'Customer Success', + 'documentation': 'Documentation', + 'misc': 'Miscellaneous', +} +ISSUE_LABELS = [ + 'documentation', + 'maintenance', + 'internal', + 'bug', + 'feature request', + 'customer success', +] +NEW_LINE = '\n' +GITHUB_URL = 'https://api.github.com/repos/sdv-dev/sdgym' +GITHUB_TOKEN = os.getenv('GH_ACCESS_TOKEN') + + +def _get_milestone_number(milestone_title): + url = f'{GITHUB_URL}/milestones' + headers = {'Authorization': f'Bearer {GITHUB_TOKEN}'} + query_params = {'milestone': milestone_title, 'state': 'all', 'per_page': 100} + response = requests.get(url, headers=headers, params=query_params) + body = response.json() + if response.status_code != 200: + raise Exception(str(body)) + milestones = body + for milestone in milestones: + if milestone.get('title') == milestone_title: + return milestone.get('number') + raise ValueError(f'Milestone {milestone_title} not found in past 100 milestones.') + + +def _get_issues_by_milestone(milestone): + headers = {'Authorization': f'Bearer {GITHUB_TOKEN}'} + # get milestone number + milestone_number = _get_milestone_number(milestone) + url = f'{GITHUB_URL}/issues' + page = 1 + query_params = {'milestone': milestone_number, 'state': 'all'} + issues = [] + while True: + query_params['page'] = page + response = requests.get(url, headers=headers, params=query_params) + body = response.json() + if response.status_code != 200: + raise Exception(str(body)) + issues_on_page = body + if not issues_on_page: + break + issues.extend(issues_on_page) + page += 1 + return issues + + +def _get_issues_by_category(release_issues): + category_to_issues = defaultdict(list) + for issue in release_issues: + issue_title = issue['title'] + issue_number = issue['number'] + issue_url = issue['html_url'] + line = f'* {issue_title} - Issue [#{issue_number}]({issue_url})' + assignee = issue.get('assignee') + if assignee: + login = assignee['login'] + line += f' by @{login}' + # Check if any known label is marked on the issue + labels = [label['name'] for label in issue['labels']] + found_category = False + for category in ISSUE_LABELS: + if category in labels: + category_to_issues[category].append(line) + found_category = True + break + if not found_category: + category_to_issues['misc'].append(line) + return category_to_issues + + +def _create_release_notes(issues_by_category, version, date): + title = f'## v{version} - {date}' + release_notes = f'{title}{NEW_LINE}{NEW_LINE}' + for category in ISSUE_LABELS + ['misc']: + issues = issues_by_category.get(category) + if issues: + section_text = ( + f'### {LABEL_TO_HEADER[category]}{NEW_LINE}{NEW_LINE}' + f'{NEW_LINE.join(issues)}{NEW_LINE}{NEW_LINE}' + ) + release_notes += section_text + return release_notes + + +def update_release_notes(release_notes): + """Add the release notes for the new release to the ``HISTORY.md``.""" + file_path = 'HISTORY.md' + with open(file_path, 'r') as history_file: + history = history_file.read() + token = '# HISTORY\n\n' + split_index = history.find(token) + len(token) + 1 + header = history[:split_index] + new_notes = f'{header}{release_notes}{history[split_index:]}' + with open(file_path, 'w') as new_history_file: + new_history_file.write(new_notes) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-v', '--version', type=str, help='Release version number (ie. v1.0.1)') + parser.add_argument('-d', '--date', type=str, help='Date of release in format YYYY-MM-DD') + args = parser.parse_args() + release_number = args.version + release_issues = _get_issues_by_milestone(release_number) + issues_by_category = _get_issues_by_category(release_issues) + release_notes = _create_release_notes(issues_by_category, release_number, args.date) + update_release_notes(release_notes) diff --git a/static_code_analysis.txt b/static_code_analysis.txt index a76679c9..c441db63 100644 --- a/static_code_analysis.txt +++ b/static_code_analysis.txt @@ -1,4 +1,4 @@ -Run started:2024-08-29 15:51:04.711485 +Run started:2025-01-09 15:17:21.988086 Test results: >> Issue: [B403:blacklist] Consider possible security implications associated with pickle module. @@ -15,53 +15,53 @@ Test results: Severity: Low Confidence: High CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html) More Info: https://bandit.readthedocs.io/en/1.7.7/plugins/b101_assert_used.html - Location: ./sdgym/benchmark.py:152:8 -151 if isinstance(synthesizer, type): -152 assert issubclass( -153 synthesizer, BaselineSynthesizer -154 ), '`synthesizer` must be a synthesizer class' -155 synthesizer = synthesizer() + Location: ./sdgym/benchmark.py:151:8 +150 if isinstance(synthesizer, type): +151 assert issubclass(synthesizer, BaselineSynthesizer), ( +152 '`synthesizer` must be a synthesizer class' +153 ) +154 synthesizer = synthesizer() -------------------------------------------------- >> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Severity: Low Confidence: High CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html) More Info: https://bandit.readthedocs.io/en/1.7.7/plugins/b101_assert_used.html - Location: ./sdgym/benchmark.py:157:8 -156 else: -157 assert issubclass( -158 type(synthesizer), BaselineSynthesizer -159 ), '`synthesizer` must be an instance of a synthesizer class.' -160 + Location: ./sdgym/benchmark.py:156:8 +155 else: +156 assert issubclass(type(synthesizer), BaselineSynthesizer), ( +157 '`synthesizer` must be an instance of a synthesizer class.' +158 ) +159 -------------------------------------------------- >> Issue: [B608:hardcoded_sql_expressions] Possible SQL injection vector through string-based query construction. Severity: Medium Confidence: Low CWE: CWE-89 (https://cwe.mitre.org/data/definitions/89.html) More Info: https://bandit.readthedocs.io/en/1.7.7/plugins/b608_hardcoded_sql_expressions.html - Location: ./sdgym/benchmark.py:675:23 -674 # User data script to install the library -675 user_data_script = f"""#!/bin/bash -676 sudo apt update -y -677 sudo apt install python3-pip -y -678 echo "======== Install Dependencies ============" -679 sudo pip3 install sdgym -680 sudo pip3 install anyio -681 pip3 list -682 sudo apt install awscli -y -683 aws configure set aws_access_key_id {credentials.access_key} -684 aws configure set aws_secret_access_key {credentials.secret_key} -685 aws configure set region {session.region_name} -686 echo "======== Write Script ===========" -687 sudo touch ~/sdgym_script.py -688 echo "{script_content}" > ~/sdgym_script.py -689 echo "======== Run Script ===========" -690 sudo python3 ~/sdgym_script.py -691 echo "======== Complete ===========" -692 INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id) -693 aws ec2 terminate-instances --instance-ids $INSTANCE_ID -694 """ -695 + Location: ./sdgym/benchmark.py:674:23 +673 # User data script to install the library +674 user_data_script = f"""#!/bin/bash +675 sudo apt update -y +676 sudo apt install python3-pip -y +677 echo "======== Install Dependencies ============" +678 sudo pip3 install sdgym +679 sudo pip3 install anyio +680 pip3 list +681 sudo apt install awscli -y +682 aws configure set aws_access_key_id {credentials.access_key} +683 aws configure set aws_secret_access_key {credentials.secret_key} +684 aws configure set region {session.region_name} +685 echo "======== Write Script ===========" +686 sudo touch ~/sdgym_script.py +687 echo "{script_content}" > ~/sdgym_script.py +688 echo "======== Run Script ===========" +689 sudo python3 ~/sdgym_script.py +690 echo "======== Complete ===========" +691 INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id) +692 aws ec2 terminate-instances --instance-ids $INSTANCE_ID +693 """ +694 -------------------------------------------------- >> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module. @@ -96,7 +96,7 @@ Test results: -------------------------------------------------- Code scanned: - Total lines of code: 2735 + Total lines of code: 2769 Total lines skipped (#nosec): 0 Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0