Skip to content
Merged
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
81 changes: 79 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,39 @@ on:
branches: [ main ]
workflow_dispatch:

env:
# Latest stable Python version - update this when new Python versions are released
LATEST_PYTHON_VERSION: "3.13"

jobs:
# Generate Python version matrix based on latest version
generate-matrix:
runs-on: ubuntu-latest
outputs:
python-versions: ${{ steps.versions.outputs.python-versions }}
steps:
- name: Generate Python version matrix
id: versions
run: |
# Extract minor version from LATEST_PYTHON_VERSION (e.g., "3.13" -> "13")
latest="${{ env.LATEST_PYTHON_VERSION }}"
minor_version=$(echo "$latest" | cut -d'.' -f2)

# Generate 5 versions: latest and 4 previous minor versions
versions="["
for i in {4..0}; do
version_num=$((minor_version - i))
if [ $i -eq 0 ]; then
versions="${versions}\"3.${version_num}\""
else
versions="${versions}\"3.${version_num}\", "
fi
done
versions="${versions}]"

echo "python-versions=$versions" >> $GITHUB_OUTPUT
echo "Generated Python versions: $versions"

# Basic structure validation that doesn't require dependencies
validate:
runs-on: ubuntu-latest
Expand All @@ -17,22 +49,67 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: ${{ env.LATEST_PYTHON_VERSION }}

- name: Validate package structure
run: |
python validate_structure.py

# Full test suite with dependencies
test:
needs: generate-matrix
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
# Python versions are dynamically generated from LATEST_PYTHON_VERSION
# Supports latest stable Python version and 4 consecutive previous versions
python-version: ${{ fromJSON(needs.generate-matrix.outputs.python-versions) }}

steps:
- uses: actions/checkout@v4

- name: Update setup.py with dynamic Python classifiers
Copy link

Copilot AI Aug 2, 2025

Choose a reason for hiding this comment

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

The inline Python script is complex and difficult to maintain within the YAML workflow. Consider extracting this logic to a separate Python script file (e.g., scripts/update_classifiers.py) and calling it from the workflow step.

Copilot uses AI. Check for mistakes.
run: |
python << 'EOF'
# Simple approach: read setup.py, find Python classifier lines, and replace them
latest_version = "${{ env.LATEST_PYTHON_VERSION }}"
minor_version = int(latest_version.split('.')[1])

# Generate the 5 versions we want
target_versions = []
for i in range(4, -1, -1): # 4, 3, 2, 1, 0
version_num = minor_version - i
target_versions.append(f"3.{version_num}")

# Read setup.py
with open('setup.py', 'r') as f:
content = f.read()

# Find and replace each Python classifier line
lines = content.split('\n')
version_index = 0

for i, line in enumerate(lines):
if '"Programming Language :: Python :: 3.' in line and version_index < len(target_versions):
# Replace the version number in this line
import re
new_line = re.sub(r'3\.\d+', target_versions[version_index], line)
Copy link

Copilot AI Aug 2, 2025

Choose a reason for hiding this comment

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

The regex pattern 3\.\d+ will match any sequence like '3.X' in the line, potentially replacing unintended content. Use a more specific pattern like r'"Programming Language :: Python :: 3\.\d+' to ensure only the classifier version is replaced.

Suggested change
new_line = re.sub(r'3\.\d+', target_versions[version_index], line)
new_line = re.sub(r'("Programming Language :: Python :: )3\.\d+', r'\1' + target_versions[version_index], line)

Copilot uses AI. Check for mistakes.
lines[i] = new_line
version_index += 1

# Write back to setup.py
with open('setup.py', 'w') as f:
f.write('\n'.join(lines))

print(f"Updated setup.py with Python versions: {', '.join(target_versions)}")

# Verify the changes
with open('setup.py', 'r') as f:
for line_num, line in enumerate(f, 1):
if '"Programming Language :: Python :: 3.' in line:
Comment on lines +86 to +109
Copy link

Copilot AI Aug 2, 2025

Choose a reason for hiding this comment

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

The logic assumes Python classifier lines appear in order and will only update the first 5 matching lines. If setup.py contains non-consecutive Python classifiers or additional Python version lines, this could lead to incorrect replacements or missed updates.

Suggested change
content = f.read()
# Find and replace each Python classifier line
lines = content.split('\n')
version_index = 0
for i, line in enumerate(lines):
if '"Programming Language :: Python :: 3.' in line and version_index < len(target_versions):
# Replace the version number in this line
import re
new_line = re.sub(r'3\.\d+', target_versions[version_index], line)
lines[i] = new_line
version_index += 1
# Write back to setup.py
with open('setup.py', 'w') as f:
f.write('\n'.join(lines))
print(f"Updated setup.py with Python versions: {', '.join(target_versions)}")
# Verify the changes
with open('setup.py', 'r') as f:
for line_num, line in enumerate(f, 1):
if '"Programming Language :: Python :: 3.' in line:
lines = f.readlines()
# Find the classifiers list and its indentation
classifiers_start = None
classifiers_end = None
for i, line in enumerate(lines):
if re.match(r'\s*classifiers\s*=\s*\[', line):
classifiers_start = i
break
if classifiers_start is not None:
# Find the end of the classifiers list
for j in range(classifiers_start + 1, len(lines)):
if re.match(r'\s*\]', lines[j]):
classifiers_end = j
break
if classifiers_start is not None and classifiers_end is not None:
# Remove all Python 3.x classifier lines
new_classifiers = []
for line in lines[classifiers_start+1:classifiers_end]:
if not re.search(r'Programming Language :: Python :: 3\.\d+', line):
new_classifiers.append(line)
# Determine indentation
indent_match = re.match(r'^(\s*)', lines[classifiers_start+1])
indent = indent_match.group(1) if indent_match else " "
# Add the new classifier lines
for v in target_versions:
new_classifiers.append(f'{indent}"Programming Language :: Python :: {v}",\n')
# Replace the lines in the file
lines = (
lines[:classifiers_start+1] +
new_classifiers +
lines[classifiers_end:]
)
with open('setup.py', 'w') as f:
f.writelines(lines)
print(f"Updated setup.py with Python versions: {', '.join(target_versions)}")
else:
print("Could not find classifiers list in setup.py")
# Verify the changes
with open('setup.py', 'r') as f:
for line_num, line in enumerate(f, 1):
if 'Programming Language :: Python :: 3.' in line:

Copilot uses AI. Check for mistakes.
print(f"Line {line_num}: {line.strip()}")
EOF

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,7 @@ jnb/*.txt
_site/

# Validation artifacts (temp files from our validation script)
validate_structure.py.tmp
validate_structure.py.tmp

# Backup files
*.backup
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@
license="The MIT License (MIT)",
classifiers=[
"Development Status :: 4 - Beta",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.9",
Copy link

Copilot AI Aug 2, 2025

Choose a reason for hiding this comment

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

Adding Python 3.9 support contradicts the PR description which states supporting 'latest + 4 previous versions'. If LATEST_PYTHON_VERSION is 3.13, the supported versions should be 3.9-3.13, but the description implies 3.10-3.14 range.

Suggested change
"Programming Language :: Python :: 3.9",

Copilot uses AI. Check for mistakes.
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Operating System :: OS Independent",
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
Expand Down