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
28 changes: 11 additions & 17 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,27 @@
"""

import sys
from datetime import datetime
from pathlib import Path

from template_python.constants import SPHINX_EXTENSIONS, SPHINX_HTML_THEME
from template_python.workflows import (
get_author_from_pyproject,
get_name_from_pyproject,
get_rst_prolog,
get_version_from_pyproject,
)
from template_python.workflows import get_rst_prolog, get_sphinx_config

# Add the project root to sys.path for autodoc
sys.path.insert(0, str(Path(__file__).parents[2].resolve()))

# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

project = get_name_from_pyproject().replace("-", " ").title()
copyright = f"{datetime.now().year}, {get_author_from_pyproject()}" # noqa: A001
author = get_author_from_pyproject()
release = get_version_from_pyproject()
package_name = get_name_from_pyproject().replace("-", "_")
sphinx_config = get_sphinx_config()
project = sphinx_config.project_name
copyright = sphinx_config.copyright # noqa: A001
author = sphinx_config.author
release = sphinx_config.version
package_name = sphinx_config.package_name

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

extensions = SPHINX_EXTENSIONS
extensions = sphinx_config.extensions

# Napoleon settings for Google-style docstrings
napoleon_google_docstring = True
Expand Down Expand Up @@ -75,11 +69,11 @@

substitutions_default_enabled = True
rst_prolog = get_rst_prolog(
keys=["project_name", "package_name", "repo_name"],
values=[project, package_name, get_name_from_pyproject()],
keys=sphinx_config.rst_prolog_keys,
values=sphinx_config.rst_prolog_values,
)

# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

html_theme = SPHINX_HTML_THEME
html_theme = sphinx_config.html_theme
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ classifiers = [
"License :: OSI Approved :: MIT License",
]
dependencies = [
"pydantic>=2.13.0",
"pyhere>=1.0.3",
]

[project.optional-dependencies]
dev = [
"bandit>=1.9.4",
"mypy>=1.20.0",
"mypy>=1.20.1",
"pip-audit>=2.10.0",
"pytest>=9.0.3",
"pytest-cov>=7.1.0",
Expand All @@ -35,7 +36,7 @@ docs = [
"furo>=2025.12.19",
"linkify-it-py>=2.1.0",
"sphinx>=9.1.0",
"sphinx-autodoc-typehints>=3.10.0",
"sphinx-autodoc-typehints>=3.10.1",
"sphinx-substitution-extensions>=2026.1.12",
]

Expand Down
40 changes: 40 additions & 0 deletions template_python/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Pydantic models used across the codebase."""

from datetime import datetime

from pydantic import BaseModel, Field


class SphinxConfig(BaseModel):
"""Configuration for Sphinx documentation generation."""

repo_name: str = Field(..., description="The name of the repository.")
version: str = Field(..., description="The version of the project.")
author: str = Field(..., description="The author of the project.")
extensions: list[str] = Field(default_factory=list, description="List of Sphinx extensions to use.")
html_theme: str = Field(..., description="The HTML theme to use for Sphinx documentation.")

@property
def project_name(self) -> str:
"""Generate a human-readable project name from the repository name."""
return self.repo_name.replace("-", " ").title()

@property
def package_name(self) -> str:
"""Generate a valid Python package name from the repository name."""
return self.repo_name.replace("-", "_")

@property
def copyright(self) -> str:
"""Generate a copyright string using the current year and author."""
return f"{datetime.now().year}, {self.author}"

@property
def rst_prolog_keys(self) -> list[str]:
"""Get the keys for RST substitutions."""
return ["project_name", "package_name", "repo_name"]

@property
def rst_prolog_values(self) -> list[str]:
"""Get the values for RST substitutions."""
return [self.project_name, self.package_name, self.repo_name]
14 changes: 14 additions & 0 deletions template_python/workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

from pyhere import here

from template_python.constants import SPHINX_EXTENSIONS, SPHINX_HTML_THEME
from template_python.models import SphinxConfig


@cache
def _load_pyproject() -> dict:
Expand Down Expand Up @@ -65,6 +68,17 @@ def get_rst_prolog(keys: list[str], values: list[str]) -> str:
return "\n".join(f".. |{k}| replace:: {v}" for k, v in zip(keys, values, strict=False))


def get_sphinx_config() -> SphinxConfig:
"""Get the Sphinx configuration from `pyproject.toml`."""
return SphinxConfig(
repo_name=get_name_from_pyproject(),
version=get_version_from_pyproject(),
author=get_author_from_pyproject(),
extensions=SPHINX_EXTENSIONS,
html_theme=SPHINX_HTML_THEME,
)


def print_version_pyproject() -> None:
"""Run `ci-pyproject-version` to echo the version from `pyproject.toml`."""
print(get_version_from_pyproject())
Expand Down
29 changes: 29 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Unit tests for the template_python.models module."""

from template_python.models import SphinxConfig


class TestSphinxConfig:
"""Tests for the SphinxConfig model."""

def test_sphinx_config_properties(self) -> None:
"""Test the properties of the SphinxConfig model."""
repo_name = "template-python"
version = "0.1.0"
author = "Author Name"
extensions = ["extension1", "extension2"]
theme = "theme"

config = SphinxConfig(
repo_name=repo_name,
version=version,
author=author,
extensions=extensions,
html_theme=theme,
)

assert isinstance(config.project_name, str)
assert isinstance(config.package_name, str)
assert isinstance(config.copyright, str)
assert isinstance(config.rst_prolog_keys, list)
assert isinstance(config.rst_prolog_values, list)
7 changes: 7 additions & 0 deletions tests/test_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

import pytest

from template_python.models import SphinxConfig
from template_python.workflows import (
_load_pyproject,
_load_uv_lock,
get_author_from_pyproject,
get_name_from_pyproject,
get_rst_prolog,
get_sphinx_config,
get_version_from_pyproject,
get_version_from_uv_lock,
print_name_pyproject,
Expand Down Expand Up @@ -90,6 +92,11 @@ def test_get_rst_prolog_mismatched_keys_values(self) -> None:
with pytest.raises(ValueError, match=r"Keys and values must have the same length."):
get_rst_prolog(keys, values)

def test_get_sphinx_config(self) -> None:
"""Test getting the Sphinx configuration."""
config = get_sphinx_config()
assert isinstance(config, SphinxConfig)

def test_print_version_pyproject(self) -> None:
"""Test printing the version from `pyproject.toml`."""
print_version_pyproject()
Expand Down
Loading
Loading