Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions .github/actions/update_python_requirements.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#!/usr/bin/env python3
"""
Generate Dependabot configuration for Python requirements files.
Scans the repository for requirements.txt files and creates a consolidated
dependabot.yml configuration with grouped updates.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
dependabot.yml configuration with grouped updates.
dependabot.yml configuration with grouped updates while preserving
non-pip ecosystem entries (e.g., github-actions).


HOWTO - Manual Testing:
-----------------------
1. Run the script from repository root:
$ cd /path/to/your/repo
$ python3 .github/actions/update_python_requirements.py

2. Verify the output:
$ cat .github/dependabot.yml

Check that:
- Paths start with "/" (e.g., /spk/python311/src)
- All expected requirements files are included
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- All expected requirements files are included
- All expected requirements files are included
- Non-pip ecosystems (github-actions) are preserved

- YAML structure looks correct

3. Validate YAML syntax:
$ python3 -c "import yaml; yaml.safe_load(open('.github/dependabot.yml'))"
$ echo $? # Should output: 0 (success)

4. (Optional) View found files during execution:
Temporarily uncomment the debug line in generate_dependabot_config()
to see which requirements files were discovered.

Requirements:
-------------
- Python 3.x
- PyYAML: pip install PyYAML
"""
import os
import glob
import itertools
import yaml

def find_repo_root(start_path):
"""
Find the repository root by looking for common indicators
like .git directory or spksrc directory.
Comment on lines +41 to +42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Find the repository root by looking for common indicators
like .git directory or spksrc directory.
Find the repository root by looking for .git directory.

"""
current_path = os.path.abspath(start_path)

while current_path != os.path.dirname(current_path):
if os.path.isdir(os.path.join(current_path, '.git')):
return current_path

if os.path.isdir(os.path.join(current_path, 'spksrc')):
return current_path

for indicator in ['.gitignore', 'README.md', 'LICENSE']:
if os.path.isfile(os.path.join(current_path, indicator)):
if os.path.isdir(os.path.join(current_path, 'spksrc')):
return current_path
Comment on lines +48 to +56
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return current_path
if os.path.isdir(os.path.join(current_path, 'spksrc')):
return current_path
for indicator in ['.gitignore', 'README.md', 'LICENSE']:
if os.path.isfile(os.path.join(current_path, indicator)):
if os.path.isdir(os.path.join(current_path, 'spksrc')):
return current_path
return current_path


current_path = os.path.dirname(current_path)

raise RuntimeError("Could not find repository root")


# Configuration
IGNORED_PACKAGES = ["pip", "Cython", "msgpack"]

REQUIREMENTS_PATTERNS = [
"spk/python*/crossenv/requirements-default.txt",
"spk/python*/src/requirements-abi3.txt",
"spk/python*/src/requirements-crossenv.txt",
"spk/python*/src/requirements-pure.txt",
"native/python*/src/requirements.txt"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"native/python*/src/requirements.txt"
"spk/python*-wheels/src/requirements-abi3.txt",
"spk/python*-wheels/src/requirements-crossenv.txt",
"spk/python*-wheels/src/requirements-pure.txt",
"native/python*/src/requirements.txt",
"spk/*/src/requirements-crossenv.txt",
"spk/*/src/requirements-pure.txt",

]


def create_ignore_list(packages):
"""Create the ignore list for Dependabot configuration."""
ignore_list = []
for package in packages:
ignore_list.append({
"dependency-name": package,
"update-types": [
"version-update:semver-major",
"version-update:semver-minor",
"version-update:semver-patch"
]
})
return ignore_list


def generate_dependabot_config(repo_root):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def generate_dependabot_config(repo_root):
def load_existing_config(config_path):
"""
Load existing dependabot.yml and extract non-pip ecosystem entries.
Returns:
list: Non-pip ecosystem update entries to preserve
"""
if not os.path.exists(config_path):
return []
with open(config_path) as f:
existing = yaml.safe_load(f)
if not existing or 'updates' not in existing:
return []
return [u for u in existing['updates'] if u.get('package-ecosystem') != 'pip']
def generate_dependabot_config(repo_root, preserve_ecosystems=None):

"""
Generate Dependabot configuration by scanning for Python requirements files.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Args:
repo_root: Path to repository root
preserve_ecosystems: List of existing non-pip ecosystem entries to preserve

Returns:
dict: Dependabot configuration structure
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
dict: Dependabot configuration structure
tuple: (Dependabot configuration dict, list of requirements files found)

"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""
"""
if preserve_ecosystems is None:
preserve_ecosystems = []

# Find all requirements files
paths = itertools.chain.from_iterable(
glob.glob(os.path.join(repo_root, pattern))
for pattern in REQUIREMENTS_PATTERNS
)

updates = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
updates = []
# Start with preserved non-pip ecosystems
updates = list(preserve_ecosystems)

requirements_found = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
requirements_found = []
requirements_found = []
seen_dirs = set()


for req_file in paths:
requirements_found.append(req_file)
filename = os.path.basename(req_file)

# CRITICAL: Dependabot requires relative path from repo root with leading "/"
relative_dir = "/" + os.path.relpath(os.path.dirname(req_file), repo_root)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Avoid duplicate directory+file combinations
dir_file_key = (relative_dir, filename)
if dir_file_key in seen_dirs:
continue
seen_dirs.add(dir_file_key)
requirements_found.append(req_file)

updates.append({
"package-ecosystem": "pip",
"directory": relative_dir,
"requirements-file": filename,
"schedule": {
"interval": "weekly"
},
"groups": {
"all-python-deps": {
"patterns": ["*"]
}
},
"ignore": create_ignore_list(IGNORED_PACKAGES)
})

return {
"version": 2,
"updates": updates
}, requirements_found


def main():
"""Main execution function."""
# Find repository root
script_dir = os.path.dirname(os.path.abspath(__file__))
repo_root = find_repo_root(script_dir)

print(f"Repository root: {repo_root}")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Load existing config to preserve non-pip ecosystems
config_path = os.path.join(repo_root, ".github", "dependabot.yml")
preserved = load_existing_config(config_path)
print(f"Preserving {len(preserved)} non-pip ecosystem entries")

# Generate configuration
dependabot_config, requirements_found = generate_dependabot_config(repo_root)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
dependabot_config, requirements_found = generate_dependabot_config(repo_root)
dependabot_config, requirements_found = generate_dependabot_config(repo_root, preserved)


print(f"Found {len(requirements_found)} requirements files")
if len(requirements_found) == 0:
print("WARNING: No requirements files found. Check REQUIREMENTS_PATTERNS.")
return

# Write configuration
output_path = os.path.join(repo_root, ".github", "dependabot.yml")
os.makedirs(os.path.dirname(output_path), exist_ok=True)
Comment on lines +151 to +152
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
output_path = os.path.join(repo_root, ".github", "dependabot.yml")
os.makedirs(os.path.dirname(output_path), exist_ok=True)
os.makedirs(os.path.dirname(config_path), exist_ok=True)


with open(output_path, "w") as f:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
with open(output_path, "w") as f:
with open(config_path, "w") as f:

yaml.dump(dependabot_config, f, sort_keys=False, default_flow_style=False, indent=2)

print(f"Generated dependabot.yml with {len(dependabot_config['updates'])} update configurations")
print(f"Output: {output_path}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
print(f"Output: {output_path}")
print(f"Output: {config_path}")



if __name__ == "__main__":
main()
58 changes: 58 additions & 0 deletions .github/workflows/python-requirements-updater.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Update Python Requirements Config

on:
workflow_dispatch:
schedule:
- cron: '0 2 * * 1' # Every Monday at 2 AM UTC
push:
paths:
- 'spk/python*/crossenv/requirements-default.txt'
- 'spk/python*/src/requirements-*.txt'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- 'spk/python*/src/requirements-*.txt'
- 'spk/python*/src/requirements-*.txt'
- 'spk/python*-wheels/src/requirements-*.txt'
- 'spk/*/src/requirements-crossenv.txt'
- 'spk/*/src/requirements-pure.txt'

- 'native/python*/src/requirements.txt'
- '.github/actions/update_python_requirements.py'
- '.github/workflows/python-requirements-updater.yml'

jobs:
update-config:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
python-version: '3.11'
python-version: '3.x'


- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install PyYAML

- name: Generate dependabot configuration
run: |
python .github/actions/update_python_requirements.py

- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
commit-message: "chore: update dependabot config for Python requirements"
branch: auto/python-requirements-config
title: "chore: Update Python requirements configuration"
body: |
## Python Requirements Configuration Update

This PR updates the `.github/dependabot.yml` configuration file based on the current Python requirements files in the repository.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This PR updates the `.github/dependabot.yml` configuration file based on the current Python requirements files in the repository.
This PR updates the \`.github/dependabot.yml\` configuration file based on the current Python requirements files in the repository.


### Scanned Patterns
- `spk/python*/crossenv/requirements-default.txt`
- `spk/python*/src/requirements-*.txt`
- `native/python*/src/requirements.txt`
Comment on lines +48 to +50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- `spk/python*/crossenv/requirements-default.txt`
- `spk/python*/src/requirements-*.txt`
- `native/python*/src/requirements.txt`
- \`spk/python*/crossenv/requirements-default.txt\`
- \`spk/python*/src/requirements-*.txt\`
- \`spk/python*-wheels/src/requirements-*.txt\`
- \`spk/*/src/requirements-crossenv.txt\`
- \`spk/*/src/requirements-pure.txt\`
- \`native/python*/src/requirements.txt\`


### Configuration
- **Schedule**: Weekly updates
- **Grouping**: All Python dependencies grouped together per package
- **Ignored packages**: pip, Cython, msgpack
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- **Ignored packages**: pip, Cython, msgpack
- **Ignored packages**: pip, Cython, msgpack
- **Preserved**: Non-pip ecosystems (github-actions)


---
*Auto-generated by [update_python_requirements.py](.github/actions/update_python_requirements.py)*