diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..0bc53ef
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,20 @@
+## Summary
+
+
+
+## Changes
+
+
+
+-
+
+## Testing
+
+
+
+- [ ] Tests pass locally (`python -m pytest tests/ -v`)
+- [ ] Pre-commit hooks pass (`pre-commit run --all-files`)
+
+## Related Issues
+
+
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..056f1bb
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,27 @@
+version: 2
+
+updates:
+ # Python (pip) dependencies
+ - package-ecosystem: "pip"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ open-pull-requests-limit: 10
+ labels:
+ - "dependencies"
+ commit-message:
+ prefix: "chore(deps)"
+
+ # GitHub Actions
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ open-pull-requests-limit: 5
+ labels:
+ - "dependencies"
+ - "ci"
+ commit-message:
+ prefix: "ci(deps)"
diff --git a/.github/scripts/generate_pr_body.py b/.github/scripts/generate_pr_body.py
new file mode 100755
index 0000000..4db8604
--- /dev/null
+++ b/.github/scripts/generate_pr_body.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python3
+"""Generate PR body from git history."""
+
+import os
+import subprocess
+import sys
+
+
+def run_cmd(cmd: list[str]) -> str:
+ """
+ Run command and return stdout.
+
+ Note: Commands use validated inputs only. The BASE_REF is validated
+ in main() before being used in any git commands.
+ """
+ result = subprocess.run( # nosec B603 # noqa: S603
+ cmd,
+ capture_output=True,
+ text=True,
+ check=True,
+ shell=False, # Explicit: never use shell
+ )
+ return result.stdout.strip()
+
+
+def categorize_files(files: list[str]) -> dict[str, int]:
+ """Categorize changed files."""
+ categories = {
+ "src": 0,
+ "test": 0,
+ "doc": 0,
+ "ci": 0,
+ }
+
+ for f in files:
+ if f.startswith("src/"):
+ categories["src"] += 1
+ elif f.startswith("tests/"):
+ categories["test"] += 1
+ elif f.endswith("README.md") or (".github/" in f and f.endswith(".md")):
+ categories["doc"] += 1
+ elif ".github/workflows/" in f or ".github/dependabot" in f:
+ categories["ci"] += 1
+
+ return categories
+
+
+def build_summary_line(categories: dict[str, int], total: int) -> str:
+ """Build human-readable summary of changes."""
+ parts = []
+ if categories["src"] > 0:
+ parts.append(f"{categories['src']} source file(s)")
+ if categories["test"] > 0:
+ parts.append(f"{categories['test']} test file(s)")
+ if categories["doc"] > 0:
+ parts.append(f"{categories['doc']} doc file(s)")
+ if categories["ci"] > 0:
+ parts.append(f"{categories['ci']} CI file(s)")
+
+ if parts:
+ return f"This PR touches {', '.join(parts)} across {total} file(s) total."
+ return f"This PR modifies {total} file(s)."
+
+
+def main() -> int:
+ """Generate PR body from git history."""
+ base_ref = os.environ["BASE_REF"]
+
+ # Validate BASE_REF to prevent command injection
+ # Valid git refs: alphanumeric, hyphens, underscores, slashes, dots
+ # Reject anything suspicious
+ import re
+
+ if not re.match(r"^[a-zA-Z0-9/_.-]+$", base_ref):
+ print(f"ERROR: Invalid BASE_REF format: {base_ref}", file=sys.stderr)
+ return 1
+
+ # Additional safety: reject refs that could be command injection attempts
+ if base_ref.startswith("-") or ".." in base_ref:
+ print(f"ERROR: Suspicious BASE_REF detected: {base_ref}", file=sys.stderr)
+ return 1
+
+ # Get merge base
+ merge_base = run_cmd(["git", "merge-base", f"origin/{base_ref}", "HEAD"])
+
+ # Get commit messages
+ commits = run_cmd(["git", "log", "--pretty=format:- %s", f"{merge_base}..HEAD"])
+
+ # Get changed files
+ diff_stat = run_cmd(["git", "diff", "--stat", f"{merge_base}..HEAD"])
+ files = run_cmd(["git", "diff", "--name-only", f"{merge_base}..HEAD"]).split("\n")
+ files = [f for f in files if f] # Remove empty strings
+
+ # Categorize
+ categories = categorize_files(files)
+ summary = build_summary_line(categories, len(files))
+
+ # Build body
+ body = f"""## Summary
+
+{summary}
+
+## Changes
+
+{commits}
+
+
+Diff stats
+```
+{diff_stat}
+```
+
+
+
+## Testing
+
+- [ ] Tests pass locally (`python -m pytest tests/ -v`)
+- [ ] Pre-commit hooks pass (`pre-commit run --all-files`)
+
+## Related Issues
+
+
+"""
+
+ print(body)
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/.github/workflows/auto-populate-pr.yml b/.github/workflows/auto-populate-pr.yml
new file mode 100644
index 0000000..6548c50
--- /dev/null
+++ b/.github/workflows/auto-populate-pr.yml
@@ -0,0 +1,65 @@
+name: Auto-populate PR Body
+
+on:
+ pull_request:
+ types: [opened]
+
+permissions:
+ pull-requests: write
+
+jobs:
+ populate-body:
+ name: Generate PR Body
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.11"
+
+ - name: Fetch base branch
+ run: git fetch origin ${{ github.event.pull_request.base.ref }} --depth=1
+
+ - name: Check if PR body needs population
+ id: check
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ PR_NUMBER: ${{ github.event.pull_request.number }}
+ run: |
+ set -euo pipefail
+
+ CURRENT_BODY=$(gh pr view "$PR_NUMBER" --json body -q '.body')
+
+ # Strip HTML comments, headers, whitespace, checklist items
+ STRIPPED=$(echo "$CURRENT_BODY" \
+ | sed 's///g' \
+ | sed '/^## /d' \
+ | sed '/^- \[.\]/d' \
+ | sed '/^-$/d' \
+ | sed 's/^[[:space:]]*//; s/[[:space:]]*$//' \
+ | sed '/^$/d')
+
+ if [[ -n "$STRIPPED" ]]; then
+ echo "PR body already has custom content. Skipping auto-populate."
+ echo "skip=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "PR body is empty or template-only. Will generate content."
+ echo "skip=false" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: Generate and update PR body
+ if: steps.check.outputs.skip == 'false'
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ PR_NUMBER: ${{ github.event.pull_request.number }}
+ BASE_REF: ${{ github.event.pull_request.base.ref }}
+ run: |
+ set -euo pipefail
+
+ BODY=$(python3 .github/scripts/generate_pr_body.py)
+ gh pr edit "$PR_NUMBER" --body "$BODY"
+ echo "PR body has been auto-populated."
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..8733ab2
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,105 @@
+name: CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ lint:
+ name: Lint & Format
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.11"
+
+ - name: Cache pip packages
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/pip
+ key: pip-${{ runner.os }}-3.11-${{ hashFiles('requirements-dev.txt') }}
+ restore-keys: |
+ pip-${{ runner.os }}-3.11-
+
+ - name: Install dependencies
+ run: |
+ pip install --upgrade pip
+ pip install -r requirements-dev.txt
+
+ - name: Cache pre-commit hooks
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/pre-commit
+ key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
+ restore-keys: pre-commit-
+
+ - name: Run pre-commit
+ run: pre-commit run --all-files --show-diff-on-failure
+
+ - name: Lint check with ruff
+ run: ruff check src/ tests/ apps/
+
+ - name: Type check with mypy
+ run: mypy src/ apps/
+
+ - name: Security check with bandit
+ run: bandit -c pyproject.toml -r src/ apps/
+
+ test:
+ name: Test (Python ${{ matrix.python-version }}, ${{ matrix.os }})
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 15
+ needs: lint
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macos-latest]
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Cache pip packages
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/pip
+ key: pip-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('requirements*.txt') }}
+ restore-keys: |
+ pip-${{ runner.os }}-${{ matrix.python-version }}-
+ pip-${{ runner.os }}-
+
+ - name: Install dependencies
+ run: |
+ pip install --upgrade pip
+ pip install -r requirements.txt
+ pip install -r requirements-dev.txt
+ pip install -e .
+
+ - name: Run tests with coverage
+ run: |
+ set -euo pipefail
+ python -m pytest tests/ \
+ -v \
+ --cov=template_project \
+ --cov-fail-under=90 \
+ --cov-report=term-missing \
+ --cov-report=xml
+
+ - name: Upload coverage report
+ if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
+ uses: actions/upload-artifact@v4
+ with:
+ name: coverage-report
+ path: coverage.xml
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
new file mode 100644
index 0000000..35062b8
--- /dev/null
+++ b/.github/workflows/dependency-review.yml
@@ -0,0 +1,25 @@
+name: Dependency Review
+
+on:
+ pull_request:
+ paths:
+ - "requirements*.txt"
+ - "setup.py"
+ - "pyproject.toml"
+
+permissions:
+ contents: read
+
+jobs:
+ dependency-review:
+ name: Review Dependencies
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Dependency Review
+ uses: actions/dependency-review-action@v4
+ with:
+ fail-on-severity: high
+ comment-summary-in-pr: always
diff --git a/.github/workflows/pr-body.yml b/.github/workflows/pr-body.yml
new file mode 100644
index 0000000..e712263
--- /dev/null
+++ b/.github/workflows/pr-body.yml
@@ -0,0 +1,59 @@
+name: PR Body Validation
+
+on:
+ pull_request:
+ # Runs on 'edited' and 'reopened' only — the 'opened' event is handled by
+ # the auto-populate workflow, which edits the body and triggers 'edited'.
+ types: [edited, reopened]
+
+jobs:
+ validate-body:
+ name: Validate PR Body
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - name: Check PR body is not empty
+ run: |
+ set -euo pipefail
+
+ BODY=$(cat <<'BODY_EOF'
+ ${{ github.event.pull_request.body }}
+ BODY_EOF
+ )
+
+ # Strip HTML comments, whitespace, and section headers
+ CLEANED=$(echo "$BODY" | sed 's///g; /^## /d; /^- \[ \]/d; s/^[[:space:]]*//; s/[[:space:]]*$//; /^$/d; /^-$/d')
+
+ if [[ -z "$CLEANED" ]]; then
+ echo "::error::PR body is empty. Please provide a description of your changes."
+ echo ""
+ echo "A good PR description should include:"
+ echo " - What: A summary of the changes made"
+ echo " - Why: The motivation or issue being addressed"
+ echo " - How: Key implementation details (if non-obvious)"
+ echo " - Testing: How the changes were verified"
+ exit 1
+ fi
+
+ # Check minimum length (at least 20 characters of meaningful content)
+ CHAR_COUNT=${#CLEANED}
+ if [[ "$CHAR_COUNT" -lt 20 ]]; then
+ echo "::error::PR body is too short ($CHAR_COUNT chars). Please provide a meaningful description."
+ exit 1
+ fi
+
+ echo "PR body is present ($CHAR_COUNT chars)."
+
+ - name: Check for placeholder text
+ run: |
+ set -euo pipefail
+
+ BODY=$(cat <<'BODY_EOF'
+ ${{ github.event.pull_request.body }}
+ BODY_EOF
+ )
+
+ # Warn if common placeholder patterns are detected
+ if echo "$BODY" | grep -qiE '(TODO|FIXME|PLACEHOLDER|fill in|describe here|add description)'; then
+ echo "::warning::PR body may contain placeholder text. Please ensure all sections are filled in."
+ fi
diff --git a/.github/workflows/pr-policy.yml b/.github/workflows/pr-policy.yml
new file mode 100644
index 0000000..382437e
--- /dev/null
+++ b/.github/workflows/pr-policy.yml
@@ -0,0 +1,69 @@
+name: PR Policy
+
+on:
+ pull_request:
+ types: [opened, edited, synchronize, labeled, unlabeled, reopened]
+
+jobs:
+ title-convention:
+ name: Validate PR Title
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - name: Check conventional commit format
+ run: |
+ set -euo pipefail
+ TITLE="${{ github.event.pull_request.title }}"
+ PATTERN="^(feat|fix|docs|doc|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?!?: .+"
+ if [[ ! "$TITLE" =~ $PATTERN ]]; then
+ echo "::error::PR title does not follow Conventional Commits format."
+ echo ""
+ echo "Expected format: (): "
+ echo " Types: feat, fix, docs, doc, style, refactor, perf, test, build, ci, chore, revert"
+ echo ""
+ echo "Examples:"
+ echo " feat: add decrement CLI command"
+ echo " fix(core): handle empty config file"
+ echo " docs: update installation instructions"
+ echo ""
+ echo "Got: '$TITLE'"
+ exit 1
+ fi
+ echo "PR title follows Conventional Commits format."
+
+ label-check:
+ name: Require Label
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - name: Check for at least one label
+ run: |
+ set -euo pipefail
+ LABEL_COUNT=$(echo '${{ toJson(github.event.pull_request.labels) }}' | jq 'length')
+ if [[ "$LABEL_COUNT" -eq 0 ]]; then
+ echo "::error::PR must have at least one label before merging."
+ echo "Consider adding a label such as: bug, enhancement, documentation, maintenance, etc."
+ exit 1
+ fi
+ echo "PR has $LABEL_COUNT label(s)."
+
+ branch-naming:
+ name: Validate Branch Name
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - name: Check branch naming convention
+ run: |
+ set -euo pipefail
+ BRANCH="${{ github.head_ref }}"
+ PATTERN="^(feature|fix|bugfix|hotfix|docs|chore|refactor|test|ci|release|claude)/"
+ if [[ ! "$BRANCH" =~ $PATTERN ]]; then
+ echo "::warning::Branch name '$BRANCH' does not follow the recommended naming convention."
+ echo ""
+ echo "Recommended format: /"
+ echo " Types: feature, fix, bugfix, hotfix, docs, chore, refactor, test, ci, release"
+ echo ""
+ echo "Examples:"
+ echo " feature/add-decrement-cli"
+ echo " fix/empty-config-handling"
+ fi
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8b071b9..4a0792c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -4,7 +4,6 @@
# - Python linting + formatting (Ruff)
# - Typing checks (Mypy)
# - Security checks (Bandit)
-# - Docstring auto-formatting (Docformatter)
# - Docs build smoke test
# - Commit message enforcement
# - Shell script linting (shfmt + shellcheck)
@@ -44,14 +43,6 @@ repos:
- id: ruff-format
types_or: [python, pyi]
- # ---------- DOCSTRING AUTO-FIX (Docformatter) ----------
- - repo: https://github.com/PyCQA/docformatter
- rev: v1.7.7
- hooks:
- - id: docformatter
- args: [--in-place, --recursive, --wrap-summaries=88]
- types_or: [python]
-
# ---------- TYPES (MYPY) ----------
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.19.1
@@ -125,11 +116,7 @@ repos:
rev: v1.38.0
hooks:
- id: yamllint
- args:
- [
- -d,
- "{extends: default, rules: {line-length: {max: 120}, document-start: disable}}",
- ]
+ args: [--config-file=.yamllint]
# ---------- CI / PRE-COMMIT.CI CONFIG ----------
ci:
diff --git a/.yamllint b/.yamllint
new file mode 100644
index 0000000..efa8b92
--- /dev/null
+++ b/.yamllint
@@ -0,0 +1,34 @@
+# .yamllint
+extends: default
+
+rules:
+ # GitHub Actions REQUIRE unquoted true/false for certain keys
+ truthy:
+ allowed-values: ["true", "false", "on", "off", "yes", "no"]
+ check-keys: false # Critical: don't validate GitHub Actions boolean keys
+
+ # Realistic line lengths for CI workflows (URLs, commands)
+ line-length:
+ max: 160
+ allow-non-breakable-words: true
+ allow-non-breakable-inline-mappings: true
+
+ # Enforce newline at EOF (pre-commit already does this)
+ new-lines:
+ type: unix
+
+ # Ignore GitHub Actions schema-specific constructs
+ comments-indentation: disable
+ indentation:
+ indent-sequences: consistent
+
+# Apply stricter rules to project YAML (configs), relaxed for workflows
+yaml-files:
+ - '*.yaml'
+ - '*.yml'
+
+ignore: |
+ .git/
+ __pycache__/
+ .venv/
+ venv/
diff --git a/apps/decrement_hydra.py b/apps/decrement_hydra.py
index 3178380..fe7130e 100644
--- a/apps/decrement_hydra.py
+++ b/apps/decrement_hydra.py
@@ -1,26 +1,36 @@
"""Sample app decrementing a given number using Hydra to handle CLI."""
import hydra
+from omegaconf import DictConfig
-from PROJECT_NAME.add import add
+from template_project.add import add
def decrement(x: int) -> int:
- """Return the decrement of a given value.
+ """
+ Return the decrement of a given value.
Args:
x (int): value to be decremented
Returns:
int: The decremented value 'x-1'
+
"""
return add(x, -1)
@hydra.main(version_base=None, config_path="configs", config_name="decrement")
-def main(config):
+def main(config: DictConfig) -> None:
+ """
+ Decrement the given value from config and print the result.
+
+ Args:
+ config: Hydra config object with 'value' attribute
+
+ """
result = decrement(config.value)
- print(result)
+ print(result) # noqa: T201
if __name__ == "__main__":
diff --git a/apps/increment_fire.py b/apps/increment_fire.py
index 832e28d..9c52e16 100644
--- a/apps/increment_fire.py
+++ b/apps/increment_fire.py
@@ -2,17 +2,19 @@
import fire
-from PROJECT_NAME.add import add
+from template_project.add import add
def increment(x: int) -> int:
- """Return the increment of a given value.
+ """
+ Return the increment of a given value.
Args:
x (int): value to be incremented
Returns:
int: The incremented value 'x+1'
+
"""
return add(x, 1)
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 89506ea..5af147d 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -15,4 +15,4 @@ RUN apt update && \
echo "$USERNAME ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
USER $USERNAME
-WORKDIR /workspace/PROJECT_NAME
+WORKDIR /workspace/template_project
diff --git a/docker/compose.yaml b/docker/compose.yaml
index 7042e1c..0450e94 100644
--- a/docker/compose.yaml
+++ b/docker/compose.yaml
@@ -1,7 +1,7 @@
services:
- project_name:
- image: project_name
- container_name: project_name_container
+ template_project:
+ image: template_project
+ container_name: template_project_container
build:
context: .
args:
@@ -17,6 +17,6 @@ services:
capabilities: [gpu]
command: sleep infinity
volumes:
- - ..:/workspace/PROJECT_NAME
+ - ..:/workspace/template_project
- ${HOST_SSH_FOLDER}:/home/${HOST_USERNAME}/.ssh:ro
- ${HOST_GITCONFIG_FILE}:/home/${HOST_USERNAME}/.gitconfig:ro
diff --git a/docker/create.sh b/docker/create.sh
index 0a623b6..d8f50b8 100755
--- a/docker/create.sh
+++ b/docker/create.sh
@@ -12,4 +12,4 @@ source set_env_variables.sh
docker compose up -d
# Get an interactive bash session in the container
-docker compose exec project_name bash
+docker compose exec template_project bash
diff --git a/docker/exec.sh b/docker/exec.sh
index 2026e39..bda1519 100755
--- a/docker/exec.sh
+++ b/docker/exec.sh
@@ -9,4 +9,4 @@ cd "$(dirname "$0")" || exit
source set_env_variables.sh
# Get an interactive bash session
-docker compose exec project_name bash
+docker compose exec template_project bash
diff --git a/pyproject.toml b/pyproject.toml
index 8f6e425..572d89c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,21 +1,30 @@
[tool.ruff]
-# Core linting + formatting
line-length = 88
-target-version = ["py39", "py310", "py311", "py312"]
-fix = true
-select = ["ALL"]
+target-version = "py39"
+src = ["src", "apps", "tests"]
+
+[tool.ruff.lint]
+select = ["E", "F", "W", "B", "S", "D", "PLR"]
extend-ignore = [
- "E203", # Conflicts with line break before ':' in slices (Black style)
- "W503" # Line break before binary operator (Black style)
+ "E203", # Black-compatible slice formatting
+ "D203", # Google style: no blank line before class (keep D211)
+ "D212", # Google style: multi-line summary on second line (keep D213)
]
-src = ["src", "apps", "tests"]
-[tool.docformatter]
-# Auto-format docstrings
-wrap-summaries = 88
-pre-summary-newline = true
-make-summary-multi-line = true
-recursive = true
+[tool.ruff.lint.per-file-ignores]
+"tests/**/*.py" = [
+ "D100", "D101", "D102", # No docstring requirements in tests
+ "S101", # allow assert
+ "S602", # shell=True in subprocess (tests only)
+ "PLR2004", # Magic values in assertions are normal
+]
+
+[tool.ruff.format]
+quote-style = "double"
+indent-style = "space"
+skip-magic-trailing-comma = false
+line-ending = "auto"
+
[tool.mypy]
# Type checking
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..b964292
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,7 @@
+bandit
+mypy
+# Development dependencies
+pre-commit
+pytest
+pytest-cov
+ruff
diff --git a/requirements.txt b/requirements.txt
index 4f9c3aa..2292cab 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,10 @@
+bandit
fire
hydra-core
+mypy
pre-commit<4
pytest
+pytest-cov
+pytest-mock
+ruff
+types-PyYAML
diff --git a/setup.py b/setup.py
index 6c701d8..49e5985 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,9 @@
+"""Docstring for setup."""
+
from setuptools import find_packages, setup
setup(
- name="PROJECT_NAME",
+ name="template_project",
version="0.0.0",
description="",
author="",
diff --git a/src/PROJECT_NAME/__init__.py b/src/PROJECT_NAME/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/src/template_project/__init__.py b/src/template_project/__init__.py
new file mode 100644
index 0000000..c10b7ed
--- /dev/null
+++ b/src/template_project/__init__.py
@@ -0,0 +1 @@
+"""Docstring for template_project."""
diff --git a/src/PROJECT_NAME/add.py b/src/template_project/add.py
similarity index 87%
rename from src/PROJECT_NAME/add.py
rename to src/template_project/add.py
index aa39836..6124e82 100644
--- a/src/PROJECT_NAME/add.py
+++ b/src/template_project/add.py
@@ -2,7 +2,8 @@
def add(a: int, b: int) -> int:
- """Return the sum of two numbers.
+ """
+ Return the sum of two numbers.
Adds two numbers using internally the '+' operator.
@@ -12,6 +13,6 @@ def add(a: int, b: int) -> int:
Returns:
int: The value 'a+b'
- """
+ """
return a + b
diff --git a/tests/__init__.py b/tests/__init__.py
index e69de29..40c103b 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -0,0 +1 @@
+"""Docstring for apps/__init__.py."""
diff --git a/tests/apps/__init__.py b/tests/apps/__init__.py
new file mode 100644
index 0000000..56fced9
--- /dev/null
+++ b/tests/apps/__init__.py
@@ -0,0 +1 @@
+"""Docstring for tests.apps."""
diff --git a/tests/apps/test_decrement_hydra.py b/tests/apps/test_decrement_hydra.py
index 93720bb..1ffac58 100644
--- a/tests/apps/test_decrement_hydra.py
+++ b/tests/apps/test_decrement_hydra.py
@@ -1,3 +1,5 @@
+"""Tests for the decrement function."""
+
import subprocess
import tempfile
from pathlib import Path
@@ -5,9 +7,8 @@
apps_directory_path = Path(__file__).parents[2] / "apps"
-def test_increment_fire():
+def test_increment_fire() -> None:
"""Test the decrement_hydra.py script."""
-
with tempfile.TemporaryDirectory() as tmp_dir:
command = (
f"python {apps_directory_path / 'decrement_hydra.py'} "
diff --git a/tests/apps/test_increment_fire.py b/tests/apps/test_increment_fire.py
index 48081b1..6555aa2 100644
--- a/tests/apps/test_increment_fire.py
+++ b/tests/apps/test_increment_fire.py
@@ -1,12 +1,13 @@
+"""Tests for the increment function."""
+
import subprocess
from pathlib import Path
apps_directory_path = Path(__file__).parents[2] / "apps"
-def test_increment_fire():
+def test_increment_fire() -> None:
"""Test the increment_fire.py script."""
-
command = f"python {apps_directory_path / 'increment_fire.py'} --x 2"
res = int(subprocess.check_output(command, shell=True))
assert res == 3
diff --git a/tests/test_add.py b/tests/test_add.py
index 6bb8101..93be505 100644
--- a/tests/test_add.py
+++ b/tests/test_add.py
@@ -1,7 +1,8 @@
-from PROJECT_NAME.add import add
+"""Tests for the add function."""
+from template_project.add import add
-def test_add():
- """Test of the add module."""
+def test_add() -> None:
+ """Test of the add module."""
assert add(0, 1) == 1