Skip to content
Draft
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
61 changes: 61 additions & 0 deletions .github/scripts/sync_dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""
Script to sync dependencies between the root pyproject.toml and the template pyproject.toml.
This script reads the dependencies from the template group in the root pyproject.toml
and updates the corresponding dependencies in the template pyproject.toml.
"""

import re
import tomlkit


def read_toml_file(file_path):
"""Read a TOML file and return its content as a dictionary."""
with open(file_path, 'r') as f:
return tomlkit.parse(f.read())


def update_template_pyproject():
"""Update the template pyproject.toml with dependencies from the root pyproject.toml."""
# Read the root pyproject.toml
root_pyproject = read_toml_file("pyproject.toml")

# Read the template pyproject.toml as text to preserve templating
with open("{{cookiecutter.project_slug}}/pyproject.toml", 'r') as f:
template_content = f.read()

# Extract dependencies from the root pyproject.toml template group
template_deps = root_pyproject.get("tool", {}).get("poetry", {}).get("group", {}).get("template", {}).get("dependencies", {})
template_dev_deps = root_pyproject.get("tool", {}).get("poetry", {}).get("group", {}).get("template-dev", {}).get("dependencies", {})

# Update regular dependencies in template pyproject.toml
for package, version in template_deps.items():
# Skip packages that are conditionally included (those with cookiecutter variables)
if package in ["django-htmx", "django-ninja", "django-ninja-crud", "psycopg", "sentry-sdk", "whitenoise"]:
continue

# For regular dependencies, update the version
if isinstance(version, dict) and "extras" in version:
# Handle dependencies with extras
version_str = f"{{extras = {version['extras']!r}, version = \"{version['version']}\"}}"
pattern = rf'{package}\s*=\s*{{[^}}]*}}'
template_content = re.sub(pattern, f"{package} = {version_str}", template_content)
else:
# Handle regular dependencies
version_str = str(version)
pattern = rf'{package}\s*=\s*"[^"]+"'
template_content = re.sub(pattern, f'{package} = "{version_str}"', template_content)

# Update dev dependencies in template pyproject.toml
for package, version in template_dev_deps.items():
pattern = rf'{package}\s*=\s*"[^"]+"'
template_content = re.sub(pattern, f'{package} = "{version}"', template_content)

# Write back the updated template pyproject.toml
with open("{{cookiecutter.project_slug}}/pyproject.toml", 'w') as f:
f.write(template_content)


if __name__ == "__main__":
update_template_pyproject()
print("Template dependencies have been synchronized successfully!")
49 changes: 49 additions & 0 deletions .github/workflows/sync-dependencies.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Sync Dependencies

on:
pull_request:
paths:
- 'pyproject.toml'
- '.github/workflows/sync-dependencies.yml'

jobs:
sync-dependencies:
name: Sync Template Dependencies
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
fetch-depth: 0

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install Poetry
run: pip install poetry

- name: Sync Dependencies
run: |
# Extract dependencies from root pyproject.toml template group
python .github/scripts/sync_dependencies.py

- name: Run Poetry Lock in template directory
run: |
cd {{cookiecutter.project_slug}}
poetry lock

- name: Commit and push changes if needed
run: |
git config --local user.email "[email protected]"
git config --local user.name "GitHub Action"

if [[ $(git diff --name-only | grep -c "{{cookiecutter.project_slug}}/pyproject.toml\|{{cookiecutter.project_slug}}/poetry.lock") -gt 0 ]]; then
git add {{cookiecutter.project_slug}}/pyproject.toml {{cookiecutter.project_slug}}/poetry.lock
git commit -m "Sync template dependencies from root" -m "Automated by sync-dependencies workflow"
git push
else
echo "No changes to commit"
fi
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,18 @@ To avoid these migration issues, the custom user model is created in an isolated



## Dependency Management

The template manages dependencies in two locations:
- Root `pyproject.toml`: Contains dependencies for the template itself
- `{{cookiecutter.project_slug}}/pyproject.toml`: Contains dependencies for projects generated from the template

To ensure consistent dependency updates, the template dependencies are synchronized through:
1. A "template" group in the root `pyproject.toml` that mirrors the dependencies in `{{cookiecutter.project_slug}}/pyproject.toml`
2. A GitHub workflow that automatically syncs dependencies from the root file to the template file on pull requests

This allows Dependabot to update all dependencies by targeting the root `pyproject.toml`, and those updates are automatically propagated to the template.


## Contribute
Contributions are welcome, feel free to suggest improvements.
22 changes: 22 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,28 @@ pytest = "^8.3.5"
pytest-cookies = "^0.7.0"
pre-commit = "^4.2.0"

[tool.poetry.group.template.dependencies]
# Dependencies for the template project (automatically synced to {{cookiecutter.project_slug}}/pyproject.toml)
dj-database-url = "2.2.0"
django = "5.0.6"
django-extensions = "3.2.3"
django-htmx = "1.23.0"
django-ninja = "1.2.0"
django-ninja-crud = "0.2.0"
gunicorn = "23.0.0"
psycopg = "3.2.9"
python-decouple = "3.8"
sentry-sdk = {extras = ["django"], version = "2.7.1"}
whitenoise = {extras = ["brotli"], version = "6.7.0"}

# Template dev dependencies
[tool.poetry.group.template-dev.dependencies]
model-bakery = "1.19.5"
pre-commit = "3.8.0"
pytest = "8.3.3"
pytest-django = "4.9.0"
pytest-mock = "3.14.0"


[tool.pytest.ini_options]
testpaths = "tests/"
Expand Down