Skip to content

Commit 0ed45ce

Browse files
authored
Merge pull request #19 from 56kyle/develop
Develop
2 parents 59d4b04 + dfba96c commit 0ed45ce

33 files changed

+584
-378
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ A Python project template robust enough to follow up [cookiecutter-hypermodern-p
66

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

9-
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.
10-
119
If you have any interest in this project, please don't hesitate to reach out!
1210
Any advice, support, PR's, etc. are welcome and would be greatly appreciated.
1311

12+
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.
13+
1414
# Why does this project exist?
1515

1616
Unfortunately, the [Hypermodern Python Cookiecutter] is no longer maintained nor modern.

noxfile.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"""Noxfile for the cookiecutter-robust-python template."""
22
import os
33
import shutil
4-
import tempfile
54
from pathlib import Path
65

76
import nox
@@ -36,15 +35,15 @@
3635

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

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

44+
UPDATE_DEMO_SCRIPT: Path = SCRIPTS_FOLDER / "update-demo.py"
45+
UPDATE_DEMO_OPTIONS: tuple[str, ...] = GENERATE_DEMO_OPTIONS
46+
4847

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

125124

125+
@nox.parametrize(arg_names="add_rust_extension", arg_values_list=[False], ids=["no-rust"])
126+
@nox.session(name="update-demo", python=DEFAULT_TEMPLATE_PYTHON_VERSION)
127+
def update_demo(session: Session, add_rust_extension: bool) -> None:
128+
session.log("Installing script dependencies for updating generated project demos...")
129+
session.install("cookiecutter", "cruft", "platformdirs", "loguru", "typer")
130+
131+
session.log("Updating generated project demos...")
132+
args: list[str] = [*UPDATE_DEMO_OPTIONS]
133+
if add_rust_extension:
134+
args.append("--add-rust-extension")
135+
session.run("python", UPDATE_DEMO_SCRIPT, *args)
136+
137+
126138
@nox.session(venv_backend="none")
127139
def release_template(session: Session):
128140
"""Run the release process for the TEMPLATE using Commitizen.
@@ -158,3 +170,11 @@ def release_template(session: Session):
158170

159171
session.log("Template version bumped and tag created locally via Commitizen/uvx.")
160172
session.log("IMPORTANT: Push commits and tags to remote (`git push --follow-tags`) to trigger CD for the TEMPLATE.")
173+
174+
175+
@nox.session(python=False)
176+
def remove_demo_release(session: Session) -> None:
177+
"""Deletes the latest demo release."""
178+
session.run("git", "branch", "-d", f"release/{session.posargs[0]}", external=True)
179+
session.run("git", "push", "--progress", "--porcelain", "origin", f"release/{session.posargs[0]}", external=True)
180+

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ docs = [
3030
"sphinx-tabs>=3.4.7",
3131
]
3232
lint = [
33-
"pydocstyle>=6.3.0",
3433
"ruff>=0.11.9",
3534
]
3635
security = [

scripts/generate-demo.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,15 @@
1515

1616
@cli.callback(invoke_without_command=True)
1717
def main(
18-
repo_folder: Annotated[Path, FolderOption("--repo-folder", "-r")],
1918
demos_cache_folder: Annotated[Path, FolderOption("--demos-cache-folder", "-c")],
20-
demo_name: Annotated[str, typer.Option("--demo-name", "-d")],
21-
no_cache: Annotated[bool, typer.Option("--no-cache", "-n")] = False,
19+
add_rust_extension: Annotated[bool, typer.Option("--add-rust-extension", "-r")] = False,
20+
no_cache: Annotated[bool, typer.Option("--no-cache", "-n")] = False
2221
) -> None:
2322
"""Updates the poetry.lock file."""
2423
try:
2524
generate_demo(
26-
repo_folder=repo_folder,
2725
demos_cache_folder=demos_cache_folder,
28-
demo_name=demo_name,
26+
add_rust_extension=add_rust_extension,
2927
no_cache=no_cache
3028
)
3129
except Exception as error:

scripts/lint-from-demo.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,15 @@
1616

1717
@cli.callback(invoke_without_command=True)
1818
def lint_from_demo(
19-
repo_folder: Annotated[Path, FolderOption("--repo-folder", "-r")],
2019
demos_cache_folder: Annotated[Path, FolderOption("--demos-cache-folder", "-c")],
21-
demo_name: Annotated[str, typer.Option("--demo-name", "-d")],
22-
no_cache: Annotated[bool, typer.Option("--no-cache", "-n")] = False,
20+
add_rust_extension: Annotated[bool, typer.Option("--add-rust-extension", "-r")] = False,
21+
no_cache: Annotated[bool, typer.Option("--no-cache", "-n")] = False
2322
) -> None:
2423
"""Runs precommit in a generated project and matches the template to the results."""
2524
try:
2625
with in_new_demo(
27-
repo_folder=repo_folder,
2826
demos_cache_folder=demos_cache_folder,
29-
demo_name=demo_name,
27+
add_rust_extension=add_rust_extension,
3028
no_cache=no_cache
3129
) as demo_path:
3230
pre_commit.main.main(["run", "--all-files", "--hook-stage=manual", "--show-diff-on-failure"])

scripts/update-demo.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import sys
2+
from pathlib import Path
3+
from typing import Annotated
4+
5+
import cruft
6+
import typer
7+
from cookiecutter.utils import work_in
8+
9+
from util import get_demo_name
10+
from util import git
11+
from util import FolderOption
12+
from util import REPO_FOLDER
13+
14+
15+
cli: typer.Typer = typer.Typer()
16+
17+
18+
@cli.callback(invoke_without_command=True)
19+
def update_demo(
20+
demos_cache_folder: Annotated[Path, FolderOption("--demos-cache-folder", "-c")],
21+
add_rust_extension: Annotated[bool, typer.Option("--add-rust-extension", "-r")] = False
22+
) -> None:
23+
"""Runs precommit in a generated project and matches the template to the results."""
24+
try:
25+
demo_name: str = get_demo_name(add_rust_extension=add_rust_extension)
26+
demo_path: Path = demos_cache_folder / demo_name
27+
typer.secho(f"Updating demo project at {demo_path=}.", fg="yellow")
28+
with work_in(demo_path):
29+
git("status", "--porcelain")
30+
cruft.update(
31+
project_dir=demo_path,
32+
template_path=REPO_FOLDER,
33+
extra_context={"project_name": demo_name, "add_rust_extension": add_rust_extension},
34+
)
35+
git("add", ".")
36+
git("commit", "-m", "chore: update demo to the latest cookiecutter-robust-python", "--no-verify")
37+
git("push")
38+
39+
except Exception as error:
40+
typer.secho(f"error: {error}", fg="red")
41+
sys.exit(1)
42+
43+
44+
if __name__ == '__main__':
45+
cli()

scripts/util.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313

1414
import cruft
1515
import typer
16-
from cookiecutter.main import cookiecutter
1716
from cookiecutter.utils import work_in
1817
from pygments.lexers import q
1918
from typer.models import OptionInfo
2019

2120

21+
REPO_FOLDER: Path = Path(__file__).resolve().parent.parent
22+
23+
2224
FolderOption: partial[OptionInfo] = partial(
2325
typer.Option, dir_okay=True, file_okay=False, resolve_path=True, path_type=Path
2426
)
@@ -50,17 +52,15 @@ def run_command(command: str, *args: str) -> subprocess.CompletedProcess:
5052

5153
@contextmanager
5254
def in_new_demo(
53-
repo_folder: Path,
5455
demos_cache_folder: Path,
55-
demo_name: str,
56+
add_rust_extension: bool,
5657
no_cache: bool,
5758
**kwargs: Any
5859
) -> Generator[Path, None, None]:
5960
"""Returns a context manager for working within a new demo."""
6061
demo_path: Path = generate_demo(
61-
repo_folder=repo_folder,
6262
demos_cache_folder=demos_cache_folder,
63-
demo_name=demo_name,
63+
add_rust_extension=add_rust_extension,
6464
no_cache=no_cache,
6565
**kwargs
6666
)
@@ -69,20 +69,20 @@ def in_new_demo(
6969

7070

7171
def generate_demo(
72-
repo_folder: Path,
7372
demos_cache_folder: Path,
74-
demo_name: str,
73+
add_rust_extension: bool,
7574
no_cache: bool,
7675
**kwargs: Any
7776
) -> Path:
7877
"""Generates a demo project and returns its root path."""
78+
demo_name: str = get_demo_name(add_rust_extension=add_rust_extension)
7979
demos_cache_folder.mkdir(exist_ok=True)
8080
if no_cache:
8181
_remove_existing_demo(demo_path=demos_cache_folder / demo_name)
8282
cruft.create(
83-
template_git_url=str(repo_folder),
83+
template_git_url=str(REPO_FOLDER),
8484
output_dir=demos_cache_folder,
85-
extra_context={"project_name": demo_name, **kwargs},
85+
extra_context={"project_name": demo_name, "add_rust_extension": add_rust_extension, **kwargs},
8686
no_input=True,
8787
overwrite_if_exists=True
8888
)
@@ -104,3 +104,8 @@ def _remove_existing_demo(demo_path: Path) -> None:
104104

105105
typer.secho(f"Removing existing demo project at {demo_path=}.", fg="yellow")
106106
shutil.rmtree(demo_path, onerror=remove_readonly)
107+
108+
109+
def get_demo_name(add_rust_extension: bool) -> str:
110+
name_modifier: str = "maturin" if add_rust_extension else "python"
111+
return f"robust-{name_modifier}-demo"

tests/constants.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
TYPE_CHECK_NOX_SESSIONS: list[str] = [f"typecheck-{python_version}" for python_version in PYTHON_VERSIONS]
2323
TESTS_NOX_SESSIONS: list[str] = [f"tests-python-{python_version}" for python_version in PYTHON_VERSIONS]
2424

25-
GLOBAL_NOX_SESSIONS: list[str] = [
25+
IDEMPOTENT_NOX_SESSIONS: list[str] = [
2626
"pre-commit",
2727
"lint-python",
2828
"format-python",
@@ -31,8 +31,13 @@
3131
"docs-build",
3232
"build-python",
3333
"build-container",
34-
"publish-python",
35-
"release",
3634
"tox",
3735
"coverage",
3836
]
37+
CONTEXT_DEPENDENT_NOX_SESSIONS: list[str] = [
38+
"coverage",
39+
"publish-python",
40+
"release",
41+
]
42+
43+
ALL_NOX_SESSIONS: list[str] = IDEMPOTENT_NOX_SESSIONS + CONTEXT_DEPENDENT_NOX_SESSIONS

tests/integration_tests/test_robust_python_demo.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44
from pathlib import Path
55

66
import pytest
7-
from pbr.options import TRUE_VALUES
87

9-
from tests.constants import GLOBAL_NOX_SESSIONS
8+
from tests.constants import IDEMPOTENT_NOX_SESSIONS
109

1110

12-
@pytest.mark.parametrize("session", GLOBAL_NOX_SESSIONS)
11+
@pytest.mark.parametrize("session", IDEMPOTENT_NOX_SESSIONS)
1312
def test_demo_project_nox_session(robust_demo: Path, session: str) -> None:
1413
command: list[str] = ["nox", "-s", session]
1514
try:

tests/unit_tests/test_github_workflows.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,24 @@
66
from util import templates_matching
77

88

9+
@pytest.mark.parametrize(
10+
argnames="robust_demo__is_setup",
11+
argvalues=[False],
12+
indirect=True,
13+
ids=["no-setup"]
14+
)
915
@pytest.mark.parametrize(
1016
argnames="robust_demo__add_rust_extension",
1117
argvalues=[False, True],
1218
indirect=True,
1319
ids=["base", "maturin"]
1420
)
1521
@pytest.mark.parametrize(
16-
argnames="robust_demo__is_setup",
17-
argvalues=[False],
22+
argnames="robust_file__path__relative",
23+
argvalues=templates_matching(".github/workflows/*.yml"),
1824
indirect=True,
19-
ids=["no-setup"]
25+
ids=lambda path: path.stem
2026
)
2127
class TestWorkflow:
22-
@pytest.mark.parametrize(
23-
argnames="robust_file__path__relative",
24-
argvalues=templates_matching(".github/workflows/*.yml"),
25-
indirect=True,
26-
ids=lambda path: path.name
27-
)
2828
def test_workflow_basic_loading(self, robust_yaml: dict[str, Any]) -> None:
2929
assert robust_yaml

0 commit comments

Comments
 (0)