Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
487b551
build: remove pydocstyle from the lint dependencies since we are just…
56kyle Jun 28, 2025
f6bed3e
build: move documentation dependencies into a separate docs dependenc…
56kyle Jun 28, 2025
269b42a
feat: replace GLOBAL_NOX_SESSIONS with a similar set IDEMPOTENT_NOX_S…
56kyle Jun 28, 2025
f317a39
feat: add vscode starting extensions and settings
56kyle Jun 28, 2025
2063c5f
feat: remove non-relevant extension recommendation
56kyle Jun 28, 2025
2595012
fix: add version to .cz.toml
56kyle Jun 28, 2025
f778c76
fix: adjust commitizen version handling
56kyle Jun 28, 2025
4163ffb
fix: add missing double quotes to .cz.toml
56kyle Jun 28, 2025
856fb7a
refactor: move the release nox session before the publish-python and …
56kyle Jun 29, 2025
cc09d0c
feat: remove unneeded venvs from noxfile.py
56kyle Jun 29, 2025
941a5df
refactor: rearrange parametrization order in test_github_workflows.py…
56kyle Jul 4, 2025
2be4c09
feat: remove github workflow check from generated .pre-commit-config.…
56kyle Jul 4, 2025
252149c
feat: add a basic prepare-release.py script and nox session
56kyle Jul 5, 2025
4eaddb4
fix: ensure the test-python.yml github workflow only runs the needed …
56kyle Jul 5, 2025
dfcbcf4
fix: replace .python-version with "pyproject.toml" for all workflows …
56kyle Jul 5, 2025
c0a93f8
fix: adjust nox session name in build-python.yml
56kyle Jul 5, 2025
4791d8e
feat: try replacing pyproject.toml with .python-version path from rep…
56kyle Jul 5, 2025
49290c5
refactor: replace cli pass through of repo path with a constant in th…
56kyle Jul 5, 2025
42660fd
feat: replace demo_name passthrough with add_rust_extension and gener…
56kyle Jul 5, 2025
9d514a2
fix: adjust path to python-version-file for most github actions workf…
56kyle Jul 5, 2025
2be3ae5
fix: remove unused github actions workflow and fix nox session names …
56kyle Jul 5, 2025
3c58b26
fix: adjust name of used nox session in typecheck-python.yml to match…
56kyle Jul 5, 2025
cfc5c69
feat: replace nox venv creation for type checking with uvx
56kyle Jul 5, 2025
08cc6f2
feat: adjust test matrix to not test all python versions on windows a…
56kyle Jul 5, 2025
637f49c
chore: attempt to fix typecheck nox session name
56kyle Jul 5, 2025
55f7fb7
chore: adjust logging in typecheck nox session
56kyle Jul 5, 2025
b8d0f29
fix: adjust typecheck-python.yml to match changes to nox session
56kyle Jul 5, 2025
d025737
feat: add a script and noxfile session for updating demos
56kyle Jul 6, 2025
71d3981
chore: rearrange kwarg order in nox session decorator
56kyle Jul 6, 2025
43edeb6
fix: add missing python arg to update-demo nox session
56kyle Jul 6, 2025
5f97c0c
fix: add missing dependencies for the update-demo nox session
56kyle Jul 6, 2025
62a39c2
fix: adjust kwargs passed to cruft update
56kyle Jul 6, 2025
356e688
fix: remove non-existent kwarg from cruft update call and fix type of…
56kyle Jul 6, 2025
2bf35a7
fix: replace args passed to update-demo.py script with altered args
56kyle Jul 6, 2025
4f636ea
chore: remove useless comments
56kyle Jul 6, 2025
26ae079
chore: remove with-rust demo update until it is properly setup
56kyle Jul 6, 2025
742c925
fix: add quotes around nox session call
56kyle Jul 6, 2025
2703dc5
fix: revert typecheck nox session back to creating a venv due to appa…
56kyle Jul 6, 2025
87a6776
fix: adjust nox session name in typecheck-python.yml to match old format
56kyle Jul 6, 2025
e97d5ee
fix: fix path syntax in build-python.yml
56kyle Jul 6, 2025
e3cbc29
fix: adjust workflow path reference in build-python.yml so that it ac…
56kyle Jul 6, 2025
dab1f8d
fix: add main to branches list for bump-version.yml
56kyle Jul 6, 2025
34a57b8
feat: add in scripts for setting up a release and some partially fini…
56kyle Jul 7, 2025
9490ead
feat: add a get-release-notes.py script and nox session along with ad…
56kyle Jul 8, 2025
28cf872
fix: adjust import to backport in util.py
56kyle Jul 8, 2025
23c99cf
build: add typing_extensions as a build dependency for the template p…
56kyle Jul 8, 2025
638a445
fix: adjust setup-release.py argparse usage
56kyle Jul 9, 2025
3fe6276
fix: replace python=None with False in nox file along adding the opti…
56kyle Jul 9, 2025
a77aa4d
fix: remove type alias to avoid needing typing_extensions
56kyle Jul 9, 2025
753f227
fix: remove cz from dependencies check due to command difference
56kyle Jul 9, 2025
5b41872
fix: add --with commitizen to uvx cz call
56kyle Jul 9, 2025
8cc5fcc
fix: replace --with with --from
56kyle Jul 9, 2025
25270e7
fix: adjust git command in release branch command
56kyle Jul 9, 2025
9a676e5
fix: adjust any uvx cz commands
56kyle Jul 9, 2025
241c8d6
fix: add missing capture_output flag to get_latest_release_notes
56kyle Jul 9, 2025
eb9b7dd
fix: add missing section to get_package_version command
56kyle Jul 9, 2025
910ef11
fix: replace --with with --from
56kyle Jul 9, 2025
4c9c5ce
fix: ensure a rev_range isn't passed to cz changelog if there hasn't …
56kyle Jul 9, 2025
0f3242a
fix: add missing --no-verify to automated commit
56kyle Jul 9, 2025
070054f
chore: add body.md to .gitignore
56kyle Jul 9, 2025
89c528b
fix: add missing nox command portion in bump-version.yml
56kyle Jul 9, 2025
f93aa52
feat: remove final job from bump-version.yml to avoid caching the tem…
56kyle Jul 9, 2025
e531aea
fix: add write permissions to allow for the gh release to work
56kyle Jul 11, 2025
3bb543e
fix: remove tag_name in bump-version.yml due to env var being used no…
56kyle Jul 12, 2025
54d3cba
fix: add a step to get the version from branch name and then use that…
56kyle Jul 12, 2025
43c6a14
feat: rename bump-version.yml to prepare-release.yml and rework logic…
56kyle Jul 12, 2025
31a98e1
fix: add missing syntax escaping for cookiecutter
56kyle Jul 12, 2025
df849a2
fix: replace nox usage for get-release-notes with the python script t…
56kyle Jul 12, 2025
c1a3e50
fix: replace body_path with body in the create release draft step of …
56kyle Jul 12, 2025
897b901
fix: add base64 encode and decode step to the changelog output to ens…
56kyle Jul 12, 2025
4a8e14f
feat: ensure v2 is used of action-gh-release
56kyle Jul 12, 2025
471482a
feat: revert back to writing out a body.md file
56kyle Jul 12, 2025
abd9719
feat: replace body usage with body_path and make use of github.workspace
56kyle Jul 12, 2025
ba54473
feat: ensure the nox session properly passes through the changelog path
56kyle Jul 12, 2025
b3e8970
feat: add a portion to release-python.yml to attach package files to …
56kyle Jul 14, 2025
bbc05bf
feat: change release-python.yml to operate on push to main or master …
56kyle Jul 17, 2025
8b33de7
docs: update the templates README.md to more accurately represent the…
56kyle Jul 17, 2025
3710aff
fix: add a fetch-depth of 0 and fetch-tags as true to prepare-release…
56kyle Jul 17, 2025
d7435dd
feat: add to the command for getting the release notes so that the he…
56kyle Jul 17, 2025
e75916b
chore: add S607 as a per-file-ignore for scripts/* in the template pr…
56kyle Jul 17, 2025
a801956
feat: delete explicit build-python.yml workflow and ensure that build…
56kyle Jul 17, 2025
4f97355
feat: add session args to setup_release nox session to allow for manu…
56kyle Jul 17, 2025
e43ac97
fix: ensure code is checked out and the built package is properly nam…
56kyle Jul 17, 2025
9ec1b64
feat: remove unused release nox session
56kyle Jul 18, 2025
f5a057a
feat: add testpypi as an index
56kyle Jul 18, 2025
050046c
fix: adjust nox session command in testpypi publish step
56kyle Jul 18, 2025
f513255
fix: replace --repository with --index in testpypi publish step
56kyle Jul 18, 2025
a98f2e3
fix: replace twine env variables with uv versions
56kyle Jul 18, 2025
cf93107
feat: replace manual pypi upload through nox session with github acti…
56kyle Jul 18, 2025
6b6fb54
fix: add missing oidc testpypi and pypi upload permissions
56kyle Jul 18, 2025
d45018b
fix: add missing permissions for release-python.yml create tag step
56kyle Jul 18, 2025
6d83d01
fix: adjust names of github actions steps and ensure we provide a glo…
56kyle Jul 18, 2025
db46728
fix: add v prefix to git tag command
56kyle Jul 18, 2025
30f78cf
fix: replace v addition to tag creation with v addition in get_tag job
56kyle Jul 18, 2025
38682dc
fix: add --clobber and --draft to the github release file attachment …
56kyle Jul 18, 2025
8296ef4
fix: add v prefix to the tag_name provided to the release draft creat…
56kyle Jul 18, 2025
802e9d5
fix: remove non existent --draft kwarg from gh release upload command…
56kyle Jul 18, 2025
dfba96c
Merge branch 'main' into develop
56kyle Jul 19, 2025
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ A Python project template robust enough to follow up [cookiecutter-hypermodern-p

I really believe this idea has a lot of good ideas and best practices, however, creating it is a ton of work.

There is definitely a lot left to do before this project is truly a daily driver, but I think there are just a few more pieces missing from this being at least useful in many cases.

If you have any interest in this project, please don't hesitate to reach out!
Any advice, support, PR's, etc. are welcome and would be greatly appreciated.

As it stands this project is getting rather close to being ready, but not quite yet. Mainly I want to ensure the github actions pipeline is steady, but once that is done it should be alright for use.

# Why does this project exist?

Unfortunately, the [Hypermodern Python Cookiecutter] is no longer maintained nor modern.
Expand Down
28 changes: 24 additions & 4 deletions noxfile.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Noxfile for the cookiecutter-robust-python template."""
import os
import shutil
import tempfile
from pathlib import Path

import nox
Expand Down Expand Up @@ -36,15 +35,15 @@

GENERATE_DEMO_SCRIPT: Path = SCRIPTS_FOLDER / "generate-demo.py"
GENERATE_DEMO_OPTIONS: tuple[str, ...] = (
*("--repo-folder", REPO_ROOT),
*("--demos-cache-folder", PROJECT_DEMOS_FOLDER),
*("--demo-name", DEFAULT_DEMO_NAME),
)


LINT_FROM_DEMO_SCRIPT: Path = SCRIPTS_FOLDER / "lint-from-demo.py"
LINT_FROM_DEMO_OPTIONS: tuple[str, ...] = GENERATE_DEMO_OPTIONS

UPDATE_DEMO_SCRIPT: Path = SCRIPTS_FOLDER / "update-demo.py"
UPDATE_DEMO_OPTIONS: tuple[str, ...] = GENERATE_DEMO_OPTIONS


@nox.session(name="generate-demo", python=DEFAULT_TEMPLATE_PYTHON_VERSION)
def generate_demo(session: Session) -> None:
Expand Down Expand Up @@ -123,6 +122,19 @@ def test(session: Session) -> None:
session.run("pytest", "tests")


@nox.parametrize(arg_names="add_rust_extension", arg_values_list=[False], ids=["no-rust"])
@nox.session(name="update-demo", python=DEFAULT_TEMPLATE_PYTHON_VERSION)
def update_demo(session: Session, add_rust_extension: bool) -> None:
session.log("Installing script dependencies for updating generated project demos...")
session.install("cookiecutter", "cruft", "platformdirs", "loguru", "typer")

session.log("Updating generated project demos...")
args: list[str] = [*UPDATE_DEMO_OPTIONS]
if add_rust_extension:
args.append("--add-rust-extension")
session.run("python", UPDATE_DEMO_SCRIPT, *args)


@nox.session(venv_backend="none")
def release_template(session: Session):
"""Run the release process for the TEMPLATE using Commitizen.
Expand Down Expand Up @@ -158,3 +170,11 @@ def release_template(session: Session):

session.log("Template version bumped and tag created locally via Commitizen/uvx.")
session.log("IMPORTANT: Push commits and tags to remote (`git push --follow-tags`) to trigger CD for the TEMPLATE.")


@nox.session(python=False)
def remove_demo_release(session: Session) -> None:
"""Deletes the latest demo release."""
session.run("git", "branch", "-d", f"release/{session.posargs[0]}", external=True)
session.run("git", "push", "--progress", "--porcelain", "origin", f"release/{session.posargs[0]}", external=True)

1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ docs = [
"sphinx-tabs>=3.4.7",
]
lint = [
"pydocstyle>=6.3.0",
"ruff>=0.11.9",
]
security = [
Expand Down
8 changes: 3 additions & 5 deletions scripts/generate-demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,15 @@

@cli.callback(invoke_without_command=True)
def main(
repo_folder: Annotated[Path, FolderOption("--repo-folder", "-r")],
demos_cache_folder: Annotated[Path, FolderOption("--demos-cache-folder", "-c")],
demo_name: Annotated[str, typer.Option("--demo-name", "-d")],
no_cache: Annotated[bool, typer.Option("--no-cache", "-n")] = False,
add_rust_extension: Annotated[bool, typer.Option("--add-rust-extension", "-r")] = False,
no_cache: Annotated[bool, typer.Option("--no-cache", "-n")] = False
) -> None:
"""Updates the poetry.lock file."""
try:
generate_demo(
repo_folder=repo_folder,
demos_cache_folder=demos_cache_folder,
demo_name=demo_name,
add_rust_extension=add_rust_extension,
no_cache=no_cache
)
except Exception as error:
Expand Down
8 changes: 3 additions & 5 deletions scripts/lint-from-demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,15 @@

@cli.callback(invoke_without_command=True)
def lint_from_demo(
repo_folder: Annotated[Path, FolderOption("--repo-folder", "-r")],
demos_cache_folder: Annotated[Path, FolderOption("--demos-cache-folder", "-c")],
demo_name: Annotated[str, typer.Option("--demo-name", "-d")],
no_cache: Annotated[bool, typer.Option("--no-cache", "-n")] = False,
add_rust_extension: Annotated[bool, typer.Option("--add-rust-extension", "-r")] = False,
no_cache: Annotated[bool, typer.Option("--no-cache", "-n")] = False
) -> None:
"""Runs precommit in a generated project and matches the template to the results."""
try:
with in_new_demo(
repo_folder=repo_folder,
demos_cache_folder=demos_cache_folder,
demo_name=demo_name,
add_rust_extension=add_rust_extension,
no_cache=no_cache
) as demo_path:
pre_commit.main.main(["run", "--all-files", "--hook-stage=manual", "--show-diff-on-failure"])
Expand Down
45 changes: 45 additions & 0 deletions scripts/update-demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import sys
from pathlib import Path
from typing import Annotated

import cruft
import typer
from cookiecutter.utils import work_in

from util import get_demo_name
from util import git
from util import FolderOption
from util import REPO_FOLDER


cli: typer.Typer = typer.Typer()


@cli.callback(invoke_without_command=True)
def update_demo(
demos_cache_folder: Annotated[Path, FolderOption("--demos-cache-folder", "-c")],
add_rust_extension: Annotated[bool, typer.Option("--add-rust-extension", "-r")] = False
) -> None:
"""Runs precommit in a generated project and matches the template to the results."""
try:
demo_name: str = get_demo_name(add_rust_extension=add_rust_extension)
demo_path: Path = demos_cache_folder / demo_name
typer.secho(f"Updating demo project at {demo_path=}.", fg="yellow")
with work_in(demo_path):
git("status", "--porcelain")
cruft.update(
project_dir=demo_path,
template_path=REPO_FOLDER,
extra_context={"project_name": demo_name, "add_rust_extension": add_rust_extension},
)
git("add", ".")
git("commit", "-m", "chore: update demo to the latest cookiecutter-robust-python", "--no-verify")
git("push")

except Exception as error:
typer.secho(f"error: {error}", fg="red")
sys.exit(1)


if __name__ == '__main__':
cli()
23 changes: 14 additions & 9 deletions scripts/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@

import cruft
import typer
from cookiecutter.main import cookiecutter
from cookiecutter.utils import work_in
from pygments.lexers import q
from typer.models import OptionInfo


REPO_FOLDER: Path = Path(__file__).resolve().parent.parent


FolderOption: partial[OptionInfo] = partial(
typer.Option, dir_okay=True, file_okay=False, resolve_path=True, path_type=Path
)
Expand Down Expand Up @@ -50,17 +52,15 @@ def run_command(command: str, *args: str) -> subprocess.CompletedProcess:

@contextmanager
def in_new_demo(
repo_folder: Path,
demos_cache_folder: Path,
demo_name: str,
add_rust_extension: bool,
no_cache: bool,
**kwargs: Any
) -> Generator[Path, None, None]:
"""Returns a context manager for working within a new demo."""
demo_path: Path = generate_demo(
repo_folder=repo_folder,
demos_cache_folder=demos_cache_folder,
demo_name=demo_name,
add_rust_extension=add_rust_extension,
no_cache=no_cache,
**kwargs
)
Expand All @@ -69,20 +69,20 @@ def in_new_demo(


def generate_demo(
repo_folder: Path,
demos_cache_folder: Path,
demo_name: str,
add_rust_extension: bool,
no_cache: bool,
**kwargs: Any
) -> Path:
"""Generates a demo project and returns its root path."""
demo_name: str = get_demo_name(add_rust_extension=add_rust_extension)
demos_cache_folder.mkdir(exist_ok=True)
if no_cache:
_remove_existing_demo(demo_path=demos_cache_folder / demo_name)
cruft.create(
template_git_url=str(repo_folder),
template_git_url=str(REPO_FOLDER),
output_dir=demos_cache_folder,
extra_context={"project_name": demo_name, **kwargs},
extra_context={"project_name": demo_name, "add_rust_extension": add_rust_extension, **kwargs},
no_input=True,
overwrite_if_exists=True
)
Expand All @@ -104,3 +104,8 @@ def _remove_existing_demo(demo_path: Path) -> None:

typer.secho(f"Removing existing demo project at {demo_path=}.", fg="yellow")
shutil.rmtree(demo_path, onerror=remove_readonly)


def get_demo_name(add_rust_extension: bool) -> str:
name_modifier: str = "maturin" if add_rust_extension else "python"
return f"robust-{name_modifier}-demo"
11 changes: 8 additions & 3 deletions tests/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
TYPE_CHECK_NOX_SESSIONS: list[str] = [f"typecheck-{python_version}" for python_version in PYTHON_VERSIONS]
TESTS_NOX_SESSIONS: list[str] = [f"tests-python-{python_version}" for python_version in PYTHON_VERSIONS]

GLOBAL_NOX_SESSIONS: list[str] = [
IDEMPOTENT_NOX_SESSIONS: list[str] = [
"pre-commit",
"lint-python",
"format-python",
Expand All @@ -31,8 +31,13 @@
"docs-build",
"build-python",
"build-container",
"publish-python",
"release",
"tox",
"coverage",
]
CONTEXT_DEPENDENT_NOX_SESSIONS: list[str] = [
"coverage",
"publish-python",
"release",
]

ALL_NOX_SESSIONS: list[str] = IDEMPOTENT_NOX_SESSIONS + CONTEXT_DEPENDENT_NOX_SESSIONS
5 changes: 2 additions & 3 deletions tests/integration_tests/test_robust_python_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
from pathlib import Path

import pytest
from pbr.options import TRUE_VALUES

from tests.constants import GLOBAL_NOX_SESSIONS
from tests.constants import IDEMPOTENT_NOX_SESSIONS


@pytest.mark.parametrize("session", GLOBAL_NOX_SESSIONS)
@pytest.mark.parametrize("session", IDEMPOTENT_NOX_SESSIONS)
def test_demo_project_nox_session(robust_demo: Path, session: str) -> None:
command: list[str] = ["nox", "-s", session]
try:
Expand Down
18 changes: 9 additions & 9 deletions tests/unit_tests/test_github_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@
from util import templates_matching


@pytest.mark.parametrize(
argnames="robust_demo__is_setup",
argvalues=[False],
indirect=True,
ids=["no-setup"]
)
@pytest.mark.parametrize(
argnames="robust_demo__add_rust_extension",
argvalues=[False, True],
indirect=True,
ids=["base", "maturin"]
)
@pytest.mark.parametrize(
argnames="robust_demo__is_setup",
argvalues=[False],
argnames="robust_file__path__relative",
argvalues=templates_matching(".github/workflows/*.yml"),
indirect=True,
ids=["no-setup"]
ids=lambda path: path.stem
)
class TestWorkflow:
@pytest.mark.parametrize(
argnames="robust_file__path__relative",
argvalues=templates_matching(".github/workflows/*.yml"),
indirect=True,
ids=lambda path: path.name
)
def test_workflow_basic_loading(self, robust_yaml: dict[str, Any]) -> None:
assert robust_yaml
18 changes: 1 addition & 17 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions {{cookiecutter.project_name}}/.cz.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
[tool.commitizen]
tag_format = "v$version"
version = "{{ cookiecutter.version }}"
version_scheme = "pep440"
version_provider = "pep621"

# Set to true to allow bumping to 0.y.z from 0.(y-1).z when commit types normally trigger major bump.
# Recommended for initial development phases before 1.0.0.
major_version_zero = true

write_version_files = [
"pyproject.toml:version",
]
commit_msg_file = ".git/COMMIT_EDITMSG"
retry_after_failure = true
update_changelog_on_bump = true
Expand Down
Loading