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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ build/ # Build artifacts from setuptools/build backends
.pytest_cache/ # Pytest cache directory
htmlcov/ # Coverage HTML reports (default output dir)
.coverage* # Coverage data files (default name pattern)
coverage.*
test-results/ # Directory for JUnit/Coverage XML reports (as configured in noxfile)

# Editor/IDE specific files
Expand Down
43 changes: 9 additions & 34 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@
)


MATCH_GENERATED_PRECOMMIT_SCRIPT: Path = SCRIPTS_FOLDER / "match-generated-precommit.py"
MATCH_GENERATED_PRECOMMIT_OPTIONS: tuple[str, ...] = GENERATE_DEMO_PROJECT_OPTIONS
LINT_FROM_DEMO_SCRIPT: Path = SCRIPTS_FOLDER / "lint-from-demo.py"
LINT_FROM_DEMO_OPTIONS: tuple[str, ...] = GENERATE_DEMO_PROJECT_OPTIONS


@nox.session(name="generate-demo-project", python=DEFAULT_TEMPLATE_PYTHON_VERSION)
Expand All @@ -53,7 +53,7 @@ def generate_demo_project(session: Session) -> None:
session.run("python", GENERATE_DEMO_PROJECT_SCRIPT, *GENERATE_DEMO_PROJECT_OPTIONS, *session.posargs)


@nox.session(name="clear-cache", python=DEFAULT_TEMPLATE_PYTHON_VERSION)
@nox.session(name="clear-cache", python=None)
def clear_cache(session: Session) -> None:
"""Clear the cache of generated project demos.

Expand All @@ -68,7 +68,7 @@ def clear_cache(session: Session) -> None:
def lint(session: Session):
"""Lint the template's own Python files and configurations."""
session.log("Installing linting dependencies for the template source...")
session.install("-e", ".", "--group", "dev", "--group", "lint")
session.install("-e", ".", "--group", "lint")

session.log(f"Running Ruff formatter check on template files with py{session.python}.")
session.run("ruff", "format")
Expand All @@ -77,19 +77,19 @@ def lint(session: Session):
session.run("ruff", "check", "--verbose", "--fix")


@nox.session(python=DEFAULT_TEMPLATE_PYTHON_VERSION, name="match-generated-precommit", tags=[])
def match_generated_precommit(session: Session):
@nox.session(python=DEFAULT_TEMPLATE_PYTHON_VERSION, name="lint-from-demo", tags=[])
def lint_from_demo(session: Session):
"""Lint the generated project's Python files and configurations."""
session.log("Installing linting dependencies for the generated project...")
session.install("-e", ".", "--group", "dev", "--group", "lint")
session.run("python", MATCH_GENERATED_PRECOMMIT_SCRIPT, *MATCH_GENERATED_PRECOMMIT_OPTIONS, *session.posargs)
session.run("python", LINT_FROM_DEMO_SCRIPT, *LINT_FROM_DEMO_OPTIONS, *session.posargs)


@nox.session(python=DEFAULT_TEMPLATE_PYTHON_VERSION)
def docs(session: Session):
"""Build the template documentation website."""
session.log("Installing documentation dependencies for the template docs...")
session.install("-e", ".", "--group", "dev", "--group", "docs")
session.install("-e", ".", "--group", "docs")

session.log(f"Building template documentation with py{session.python}.")
# Set path to allow Sphinx to import from template root if needed (e.g., __version__.py)
Expand Down Expand Up @@ -120,33 +120,8 @@ def test(session: Session) -> None:
session.log("Installing template testing dependencies...")
# Sync deps from template's own pyproject.toml, e.g., 'dev' group that includes 'pytest', 'cookiecutter'
session.install("-e", ".", "--group", "dev", "--group", "test")
session.run("pytest", "tests")

# Create a temporary directory for the generated project
temp_dir: Path = Path(tempfile.mkdtemp())
session.log(f"Rendering template into temporary directory: {temp_dir}")

# Run cookiecutter to generate a project
# Need to find cookiecutter executable - it's in the template dev env installed by uv sync.
cookiecutter_command: list[str] = ["uv", "run", "cookiecutter", "--no-input", "--output-dir", str(temp_dir), "."]

session.run(*cookiecutter_command, external=True)

# Navigate into the generated project directory
generated_project_dir = temp_dir / "test_project" # Use the slug defined in --extra-context
if not generated_project_dir.exists():
session.error(f"Generated project directory not found: {generated_project_dir}")

session.log(f"Changing to generated project directory: {generated_project_dir}")
session.cd(generated_project_dir)

session.log("Installing generated project dependencies using uv sync...")
session.install("-e", ".", external=True)

session.log("Running generated project's default checks...")
session.run("nox")

session.log(f"Cleaning up temporary directory: {temp_dir}")
shutil.rmtree(temp_dir)


@nox.session(venv_backend="none")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@


@cli.callback(invoke_without_command=True)
def match_generated_precommit(
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")],
Expand All @@ -30,8 +30,10 @@ def match_generated_precommit(
no_cache=no_cache
) as demo_path:
pre_commit.main.main(["run", "--all-files", "--hook-stage=manual", "--show-diff-on-failure"])
retrocookie(instance_path=demo_path, commits=["HEAD"])
git("checkout", "HEAD", "--", "{{cookiecutter.project_name}}/pyproject.toml")
try:
retrocookie(instance_path=demo_path, commits=["HEAD"])
finally:
git("checkout", "HEAD", "--", "{{cookiecutter.project_name}}/pyproject.toml")
except Exception as error:
typer.secho(f"error: {error}", fg="red")
sys.exit(1)
Expand Down
1 change: 1 addition & 0 deletions {{cookiecutter.project_name}}/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ build/ # Build artifacts from setuptools/build backends
.pytest_cache/ # Pytest cache directory
htmlcov/ # Coverage HTML reports (default output dir)
.coverage* # Coverage data files (default name pattern)
coverage.*
test-results/ # Directory for JUnit/Coverage XML reports (as configured in noxfile)

# Editor/IDE specific files
Expand Down
44 changes: 17 additions & 27 deletions {{cookiecutter.project_name}}/noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,11 @@ def precommit(session: Session) -> None:
activate_virtualenv_in_precommit_hooks(session)


@nox.session(python=DEFAULT_PYTHON_VERSION, name="format-python", tags=[FORMAT, PYTHON])
@nox.session(python=None, name="format-python", tags=[FORMAT, PYTHON])
def format_python(session: Session) -> None:
"""Run Python code formatter (Ruff format)."""
session.log("Installing formatting dependencies...")
session.install("-e", ".", "--group", "dev")

session.log(f"Running Ruff formatter check with py{session.python}.")
session.run("ruff", "format", *session.posargs)
session.run("uvx", "ruff", "format", *session.posargs)


{% if cookiecutter.add_rust_extension == "y" -%}
Expand All @@ -92,14 +89,11 @@ def format_rust(session: Session) -> None:


{% endif -%}
@nox.session(python=DEFAULT_PYTHON_VERSION, name="lint-python", tags=[LINT, PYTHON])
@nox.session(python=None, name="lint-python", tags=[LINT, PYTHON])
def lint_python(session: Session) -> None:
"""Run Python code linters (Ruff check, Pydocstyle rules)."""
session.log("Installing linting dependencies...")
session.install("-e", ".", "--group", "dev")

session.log(f"Running Ruff check with py{session.python}.")
session.run("ruff", "check", "--fix", "--verbose")
session.run("uvx", "ruff", "check", "--fix", "--verbose")


{% if cookiecutter.add_rust_extension == "y" -%}
Expand All @@ -117,23 +111,20 @@ def lint_rust(session: Session) -> None:
def typecheck(session: Session) -> None:
"""Run static type checking (Pyright) on Python code."""
session.log("Installing type checking dependencies...")
session.install("-e", ".", "--group", "dev")
session.install("pyright")

session.log(f"Running Pyright check with py{session.python}.")
session.run("pyright")


@nox.session(python=DEFAULT_PYTHON_VERSION, name="security-python", tags=[SECURITY, PYTHON, CI])
@nox.session(python=None, name="security-python", tags=[SECURITY, PYTHON, CI])
def security_python(session: Session) -> None:
"""Run code security checks (Bandit) on Python code."""
session.log("Installing security dependencies...")
session.install("-e", ".", "--group", "dev")

session.log(f"Running Bandit static security analysis with py{session.python}.")
session.run("bandit", "-r", PACKAGE_NAME, "-c", "bandit.yml", "-ll")
session.run("uvx", "bandit", "-r", PACKAGE_NAME, "-c", "bandit.yml", "-ll")

session.log(f"Running pip-audit dependency security check with py{session.python}.")
session.run("pip-audit")
session.run("uvx", "pip-audit")


{% if cookiecutter.add_rust_extension == 'y' -%}
Expand All @@ -155,11 +146,12 @@ def tests_python(session: Session) -> None:
session.log(f"Running test suite with py{session.python}.")
test_results_dir = Path("test-results")
test_results_dir.mkdir(parents=True, exist_ok=True)
junitxml_file = test_results_dir / f"test-results-py{session.python}.xml"
junitxml_file = test_results_dir / f"test-results-py{session.python.replace('.', '')}.xml"

session.run(
"pytest",
"--cov={}".format(PACKAGE_NAME),
"--cov-append",
"--cov-report=term",
"--cov-report=xml",
f"--junitxml={junitxml_file}",
Expand Down Expand Up @@ -201,7 +193,7 @@ def build_python(session: Session) -> None:
{% if cookiecutter.add_rust_extension == "y" -%}
session.run("maturin", "develop", "--uv")
{% else -%}
session.run("uv", "build", "--sdist", "--wheel", "--outdir", "dist/", external=True)
session.run("uv", "build", "--sdist", "--wheel", "--out-dir", "dist/", external=True)
{% endif -%}

session.log("Built packages in ./dist directory:")
Expand Down Expand Up @@ -248,17 +240,15 @@ def build_container(session: Session) -> None:
session.log(f"Container image {project_image_name}:latest built locally.")


@nox.session(python=DEFAULT_PYTHON_VERSION, name="publish-python", tags=[RELEASE])
@nox.session(python=None, name="publish-python", tags=[RELEASE])
def publish_python(session: Session) -> None:
"""Publish sdist and wheel packages to PyPI via uv publish.

Requires packages to be built first (`nox -s build-python` or `nox -s build`).
Requires TWINE_USERNAME/TWINE_PASSWORD or TWINE_API_KEY environment variables set (usually in CI).
"""
session.install("twine")

session.log("Checking built packages with Twine.")
session.run("twine", "check", "dist/*")
session.run("uvx", "twine", "check", "dist/*")

session.log("Publishing packages to PyPI.")
session.run("uv", "publish", "dist/*", external=True)
Expand All @@ -275,7 +265,7 @@ def publish_rust(session: Session) -> None:


{% endif -%}
@nox.session(venv_backend="none", tags=[RELEASE])
@nox.session(python=None, tags=[RELEASE])
def release(session: Session) -> None:
"""Run the release process using Commitizen.

Expand All @@ -290,15 +280,15 @@ def release(session: Session) -> None:
session.skip("Git not available.")

session.log("Checking Commitizen availability via uvx.")
session.run("cz", "--version", success_codes=[0])
session.run("uvx", "--from=commitizen", "cz", "version", success_codes=[0])

increment = session.posargs[0] if session.posargs else None
session.log(
"Bumping version and tagging release (increment: %s).",
increment if increment else "default",
)

cz_bump_args = ["uvx", "cz", "bump", "--changelog"]
cz_bump_args = ["uvx", "--from=commitizen", "cz", "bump", "--changelog"]

if increment:
cz_bump_args.append(f"--increment={increment}")
Expand All @@ -310,7 +300,7 @@ def release(session: Session) -> None:
session.log("IMPORTANT: Push commits and tags to remote (`git push --follow-tags`) to trigger CD pipeline.")


@nox.session(venv_backend="none")
@nox.session(python=None)
def tox(session: Session) -> None:
"""Run the 'tox' test matrix.

Expand Down
4 changes: 2 additions & 2 deletions {{cookiecutter.project_name}}/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ description = "{{cookiecutter.project_name}}"
authors = [
{ name = "{{cookiecutter.author}}", email = "{{cookiecutter.email}}" },
]
license = { text = "{{cookiecutter.license}}" }
license = "{{cookiecutter.license}}"
license-files = ["LICENSE"]
readme = "README.md"
requires-python = ">={{cookiecutter.min_python_version}},<4.0"
keywords = [
Expand All @@ -14,7 +15,6 @@ keywords = [
classifiers = [
"Programming Language :: Python :: {{cookiecutter.min_python_version}}",
"Programming Language :: Python :: 3 :: Only",
"License :: OSI Approved :: {{cookiecutter.license}}",
]
dependencies = [
"loguru>=0.7.3",
Expand Down