render-engine 2026.5.4a2__tar.gz → 2026.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. render_engine-2026.6.1/.github/scripts/publish_prerelease.py +93 -0
  2. render_engine-2026.6.1/.github/workflows/publish-prerelease.yml +56 -0
  3. render_engine-2026.6.1/.github/workflows/publish.yml +55 -0
  4. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.gitignore +1 -1
  5. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/PKG-INFO +4 -5
  6. render_engine-2026.6.1/coverage-badge.svg +1 -0
  7. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/contributing/CONTRIBUTING.md +23 -15
  8. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/getting-started/layout.md +7 -6
  9. render_engine-2026.6.1/docs/docs/site.md +149 -0
  10. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/pyproject.toml +9 -9
  11. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/__version__.py +3 -3
  12. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/_base_object.py +4 -0
  13. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/archive.py +2 -1
  14. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/collection.py +3 -5
  15. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/data_object.py +0 -3
  16. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/page.py +0 -3
  17. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/site.py +70 -22
  18. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/themes.py +1 -1
  19. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine.egg-info/PKG-INFO +4 -5
  20. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine.egg-info/SOURCES.txt +4 -0
  21. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine.egg-info/requires.txt +3 -4
  22. render_engine-2026.6.1/src/render_engine.egg-info/scm_file_list.json +131 -0
  23. render_engine-2026.6.1/src/render_engine.egg-info/scm_version.json +8 -0
  24. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/tests/test_site.py +57 -0
  25. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/uv.lock +50 -68
  26. render_engine-2026.5.4a2/.github/workflows/publish.yml +0 -29
  27. render_engine-2026.5.4a2/coverage-badge.svg +0 -1
  28. render_engine-2026.5.4a2/docs/docs/site.md +0 -107
  29. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.all-contributorsrc +0 -0
  30. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.devcontainer/Dockerfile +0 -0
  31. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.devcontainer/devcontainer.json +0 -0
  32. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.devcontainer/setup.sh +0 -0
  33. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.github/FUNDING.yml +0 -0
  34. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.github/ISSUE_TEMPLATE/config.yaml +0 -0
  35. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.github/ISSUE_TEMPLATE/form_issue_template.yml +0 -0
  36. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  37. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.github/dependabot-bot.yml +0 -0
  38. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.github/dependabot.yml +0 -0
  39. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.github/labeler.yml +0 -0
  40. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.github/release.yml +0 -0
  41. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.github/workflows/devcontainer-ci.yml +0 -0
  42. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.github/workflows/labeler.yml +0 -0
  43. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.github/workflows/lint.yml +0 -0
  44. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.github/workflows/scorecard.yml +0 -0
  45. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.github/workflows/test.yml +0 -0
  46. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.github/workflows/todos.yml +0 -0
  47. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.github/workflows/ty.yml +0 -0
  48. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.markdownlint.json +0 -0
  49. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.pre-commit-config.yaml +0 -0
  50. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.readthedocs.yml +0 -0
  51. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/.vscode/tasks.json +0 -0
  52. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/README.md +0 -0
  53. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/SECURITY.md +0 -0
  54. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/archive.md +0 -0
  55. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/assets/create environment vs code.gif +0 -0
  56. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/assets/create-app-help.png +0 -0
  57. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/assets/create-codespace.gif +0 -0
  58. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/assets/launching a dev container.gif +0 -0
  59. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/assets/render-engine-init-help.png +0 -0
  60. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/assets/render-engine-init.png +0 -0
  61. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/collection.md +0 -0
  62. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/content_manager.md +0 -0
  63. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/contributing/CODE_OF_CONDUCT.md +0 -0
  64. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/contributing/SECURITY.md +0 -0
  65. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/contributing/environment_setup.md +0 -0
  66. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/contributing/gen_badge.md +0 -0
  67. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/contributing/github-actions-file-patterns.md +0 -0
  68. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/contributing/style-guide.md +0 -0
  69. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/contributing/testing.md +0 -0
  70. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/contributing/typechecking.md +0 -0
  71. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/custom_collections.md +0 -0
  72. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/data_object.md +0 -0
  73. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/feeds.md +0 -0
  74. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/getting-started/building-your-site.md +0 -0
  75. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/getting-started/creating-a-collection.md +0 -0
  76. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/getting-started/creating-a-page.md +0 -0
  77. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/getting-started/creating-your-app.md +0 -0
  78. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/getting-started/extending-render-engine.md +0 -0
  79. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/getting-started/getting-started.md +0 -0
  80. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/getting-started/installation.md +0 -0
  81. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/index.md +0 -0
  82. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/page.md +0 -0
  83. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/parsers.md +0 -0
  84. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/plugins.md +0 -0
  85. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/site_map.md +0 -0
  86. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/templates.md +0 -0
  87. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/docs/theme_management.md +0 -0
  88. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/docs/mkdocs.yml +0 -0
  89. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/justfile +0 -0
  90. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/noxfile.py +0 -0
  91. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/setup.cfg +0 -0
  92. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/.gitignore +0 -0
  93. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/__init__.py +0 -0
  94. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/blog.py +0 -0
  95. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/content_managers/__init__.py +0 -0
  96. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/content_managers/base_content_manager.py +0 -0
  97. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/content_managers/file_content_manager.py +0 -0
  98. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/engine.py +0 -0
  99. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/feeds.py +0 -0
  100. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/hookspecs.py +0 -0
  101. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/links.py +0 -0
  102. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/parsers/__init__.py +0 -0
  103. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/parsers/base_parsers.py +0 -0
  104. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/parsers/markdown.py +0 -0
  105. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/plugins.py +0 -0
  106. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/py.typed +0 -0
  107. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/render_engine_templates/__init__.py +0 -0
  108. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/render_engine_templates/archive.html +0 -0
  109. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/render_engine_templates/base.html +0 -0
  110. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/render_engine_templates/base_collection_path.md +0 -0
  111. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/render_engine_templates/base_templates/_archive.html +0 -0
  112. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/render_engine_templates/base_templates/_base.html +0 -0
  113. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/render_engine_templates/base_templates/_page.html +0 -0
  114. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/render_engine_templates/components/footer.html +0 -0
  115. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/render_engine_templates/components/page_title.html +0 -0
  116. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/render_engine_templates/page.html +0 -0
  117. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/render_engine_templates/redirect.html +0 -0
  118. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/render_engine_templates/rss2.0.xml +0 -0
  119. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/render_engine_templates/rss2.0_items.xml +0 -0
  120. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/render_engine_templates/sitemap.xml +0 -0
  121. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/render_engine_templates/sitemap_item.xml +0 -0
  122. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/site_map.py +0 -0
  123. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine/utils/__init__.py +0 -0
  124. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine.egg-info/dependency_links.txt +0 -0
  125. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/src/render_engine.egg-info/top_level.txt +0 -0
  126. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/tests/conftest.py +0 -0
  127. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/tests/test_archive.py +0 -0
  128. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/tests/test_base_object.py +0 -0
  129. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/tests/test_blog.py +0 -0
  130. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/tests/test_collections.py +0 -0
  131. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/tests/test_engine.py +0 -0
  132. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/tests/test_feeds/conftest_feed.py +0 -0
  133. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/tests/test_feeds/test_feeds.py +0 -0
  134. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/tests/test_file_content_manager.py +0 -0
  135. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/tests/test_page.py +0 -0
  136. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/tests/test_parsers_remove_2024_3_1.py +0 -0
  137. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/tests/test_plugins.py +0 -0
  138. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/tests/test_site_map.py +0 -0
  139. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/tests/test_templates/test_base_html.py +0 -0
  140. {render_engine-2026.5.4a2 → render_engine-2026.6.1}/tests/test_theme_manager.py +0 -0
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env -S uv run --script
2
+ # /// script
3
+ # requires-python = ">=3.10"
4
+ # dependencies = [
5
+ # "packaging==26.2",
6
+ # "rich-click==1.9.8",
7
+ # "rich==15.0.0",
8
+ # ]
9
+ # ///
10
+ """Compute the next CalVer prerelease version. Pure function: version in, version out.
11
+
12
+ Versioning rules (current version vs. today), per issue #1192:
13
+ - new year/month -> reset to current YYYY.M.1a1
14
+ - same YYYY.M, prerelease -> bump the a/b serial, keep the letter
15
+ - same YYYY.M, final -> next minor, start at a1
16
+ Output is always a prerelease (a/b suffix). Only the new version is printed to
17
+ stdout; the workflow handles reading the latest tag and creating the release.
18
+
19
+ Use `uv run --script` so uv installs the inline deps below instead of the
20
+ surrounding project environment:
21
+
22
+ uv run --script .github/scripts/publish_prerelease.py bump 2026.6.1b1
23
+ uv run --script .github/scripts/publish_prerelease.py selftest
24
+ """
25
+
26
+ from datetime import date
27
+
28
+ import rich_click as click
29
+ from packaging.version import Version
30
+ from rich.console import Console
31
+ from rich.table import Table
32
+
33
+ console = Console(stderr=True)
34
+
35
+
36
+ def next_version(latest: Version, today: date) -> str:
37
+ """Apply the issue #1192 rules. Always returns a prerelease string."""
38
+ if (latest.major, latest.minor) != (today.year, today.month):
39
+ return f"{today.year}.{today.month}.1a1"
40
+ if latest.is_prerelease:
41
+ letter, n = latest.pre # ("a" | "b", N)
42
+ return f"{today.year}.{today.month}.{latest.micro}{letter}{n + 1}"
43
+ return f"{today.year}.{today.month}.{latest.micro + 1}a1"
44
+
45
+
46
+ @click.group()
47
+ def cli() -> None:
48
+ """Compute CalVer prerelease versions."""
49
+
50
+
51
+ @cli.command()
52
+ @click.argument("current")
53
+ def bump(current: str) -> None:
54
+ """Print the next prerelease version after CURRENT (e.g. 2026.6.1b1)."""
55
+ click.echo(next_version(Version(current), date.today()))
56
+
57
+
58
+ @cli.command()
59
+ def selftest() -> None:
60
+ """Run offline checks of the version rules."""
61
+ today = date(2026, 6, 17)
62
+ cases = {
63
+ "2026.6.1b1": "2026.6.1b2", # continue beta serial
64
+ "2026.6.1a1": "2026.6.1a2", # continue alpha serial
65
+ "2026.6.1a9": "2026.6.1a10", # double-digit serial
66
+ "2026.6.1b10": "2026.6.1b11", # double-digit beta serial
67
+ "2026.6.1": "2026.6.2a1", # final -> next minor, a1
68
+ "2026.6.10": "2026.6.11a1", # double-digit minor final
69
+ "2026.5.4a2": "2026.6.1a1", # prior month -> reset to today
70
+ "2025.12.3a4": "2026.6.1a1", # prior year -> reset to today
71
+ }
72
+ table = Table(title=f"Version rules (today = {today})")
73
+ table.add_column("result")
74
+ table.add_column("latest", style="cyan")
75
+ table.add_column("next")
76
+ table.add_column("expected", style="dim")
77
+
78
+ failures = 0
79
+ for current, want in cases.items():
80
+ got = next_version(Version(current), today)
81
+ ok = got == want
82
+ failures += not ok
83
+ result = "[green]✓ OK[/]" if ok else "[red]✗ FAIL[/]"
84
+ table.add_row(result, current, f"[green]{got}[/]" if ok else f"[red]{got}[/]", want)
85
+
86
+ console.print(table)
87
+ if failures:
88
+ raise click.ClickException(f"{failures} failure(s)")
89
+ console.print("[bold green]ALL PASS[/]")
90
+
91
+
92
+ if __name__ == "__main__":
93
+ cli()
@@ -0,0 +1,56 @@
1
+ name: Publish prerelease on label
2
+
3
+ on:
4
+ pull_request:
5
+ types: [closed]
6
+ workflow_dispatch:
7
+ inputs:
8
+ dry_run:
9
+ description: "Compute the next version but do not create the release"
10
+ type: boolean
11
+ default: true
12
+
13
+ permissions:
14
+ contents: write
15
+
16
+ concurrency:
17
+ group: release-publish
18
+ cancel-in-progress: false # queue merges; every labeled merge should publish
19
+
20
+ jobs:
21
+ release:
22
+ # Run on a merged, labeled PR, or on any manual dispatch.
23
+ if: >
24
+ github.event_name == 'workflow_dispatch' ||
25
+ (github.event.pull_request.merged == true &&
26
+ contains(github.event.pull_request.labels.*.name, 'release'))
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
30
+ with:
31
+ persist-credentials: false # checkout only fetches the publish script
32
+ - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
33
+ with:
34
+ enable-cache: true
35
+
36
+ - name: Compute and publish prerelease
37
+ id: publish
38
+ env:
39
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40
+ # Merged-PR runs always publish; manual runs honor the dispatch toggle.
41
+ DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }}
42
+ run: |
43
+ # Latest release in the repo; the bump script computes the next one.
44
+ latest="$(gh release list --json name --jq '.[].name' \
45
+ | grep -E '^[0-9]{4}\.[0-9]+\.[0-9]+([ab][0-9]+)?$' \
46
+ | sort -V | tail -1)"
47
+ version="$(uv run --script .github/scripts/publish_prerelease.py bump "$latest")"
48
+ echo "Latest tag: $latest -> next: $version"
49
+ echo "version=$version" >> "$GITHUB_OUTPUT"
50
+
51
+ if [ "$DRY_RUN" = "true" ]; then
52
+ echo "DRY RUN — would publish $version"
53
+ else
54
+ gh release create "$version" --prerelease --generate-notes --title "$version"
55
+ gh workflow run publish.yml --ref "$version"
56
+ fi
@@ -0,0 +1,55 @@
1
+ name: Build distribution
2
+
3
+ on:
4
+ release:
5
+ types: [created, published, prereleased]
6
+ workflow_dispatch:
7
+
8
+ permissions: {}
9
+
10
+ jobs:
11
+ test:
12
+ uses: ./.github/workflows/test.yml
13
+ permissions:
14
+ contents: read
15
+
16
+ build:
17
+ needs: test
18
+ name: Deploy to PyPI
19
+ permissions:
20
+ contents: read
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - name: Checkout source
24
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
25
+ with:
26
+ persist-credentials: false
27
+
28
+ - name: Set up Python
29
+ uses: astral-sh/setup-uv@a9b33f02405f50e505f17ab8530e42a23200a5d1
30
+ with:
31
+ python-version: 3.14
32
+ enable-cache: false
33
+
34
+ - name: Build distributions
35
+ run: uv build
36
+ - name: Upload artifact
37
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
38
+ with:
39
+ path: ./dist
40
+ name: render-engine
41
+
42
+ publish-pypi:
43
+ needs: build
44
+ if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
45
+ runs-on: ubuntu-latest
46
+ permissions:
47
+ id-token: write
48
+ steps:
49
+ - name: Download artifact
50
+ uses: actions/download-artifact@484a0b528fb4d7bd804637ccb632e47a0e638317
51
+ with:
52
+ path: ./dist
53
+ name: render-engine
54
+ - name: Push to PyPI
55
+ uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b
@@ -170,4 +170,4 @@ untitled-site/
170
170
  __version__.py
171
171
  .worktress
172
172
 
173
- output/*
173
+ output/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: render_engine
3
- Version: 2026.5.4a2
3
+ Version: 2026.6.1
4
4
  Summary: A Flexible Static Site Generator for Python
5
5
  Project-URL: homepage, https://github.com/render-engine/render-engine/
6
6
  Project-URL: repository, https://github.com/render-engine/render-engine/
@@ -8,15 +8,14 @@ Project-URL: documentation, https://render-engine.readthedocs.io/en/latest/
8
8
  Requires-Python: >=3.10
9
9
  Description-Content-Type: text/markdown
10
10
  Requires-Dist: jinja2==3.1.6
11
- Requires-Dist: more-itertools==10.8.0
11
+ Requires-Dist: more-itertools==11.1.0
12
12
  Requires-Dist: pluggy==1.6.0
13
13
  Requires-Dist: python-dateutil==2.9.0.post0
14
- Requires-Dist: python-frontmatter==1.1.0
14
+ Requires-Dist: python-frontmatter==1.3.0
15
15
  Requires-Dist: python-slugify==8.0.4
16
16
  Requires-Dist: render-engine-markdown==2023.12.1
17
- Requires-Dist: rich==14.3.3
17
+ Requires-Dist: rich==15.0.0
18
18
  Provides-Extra: extras
19
- Requires-Dist: render-engine-sitemap==2024.1.1a1; extra == "extras"
20
19
 
21
20
  > [!NOTE]
22
21
  > As of 2026.6.1 you will need to install render-engine-cli separately
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="114" height="20" role="img" aria-label="coverage: 93.05%"><title>coverage: 93.05%</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="114" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="53" height="20" fill="#4b0"/><rect width="114" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="315" y="150" fill-opacity=".8" filter="url(#blur)" textLength="510">coverage</text><text x="315" y="150" fill-opacity=".3" textLength="510">coverage</text></g><text x="315" y="140" textLength="510">coverage</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="865" y="150" fill-opacity=".8" filter="url(#blur)" textLength="430">93.05%</text><text x="865" y="150" fill-opacity=".3" textLength="430">93.05%</text></g><text x="865" y="140" textLength="430">93.05%</text></g></g></svg>
@@ -1,5 +1,16 @@
1
1
  # Contributing to Render Engine
2
2
 
3
+ While the Render Engine team welcomes contributions to our repositories we do ask that only humans contribute to them.
4
+ This does not mean that humans are not permitted to use LLMs in the course of their contributions, however any
5
+ contributions from accounts that are not owned by a human will be rejected immediately.
6
+
7
+ Please ensure that you are following all of the guidance in this document when contributing to Render Engine.
8
+ Specifically, please note the following:
9
+
10
+ 1. [Wait to be assigned an issue]
11
+ 2. [Formatting your PR]
12
+ 3. [AI attestation]
13
+
3
14
  This document will help you get started in contributing to the Render Engine codebase is open source and
4
15
  contributions are welcome.
5
16
 
@@ -37,7 +48,7 @@ If you see or experience a problem, please file an issue. Include any important
37
48
  - Plugins/Themes/Custom Parsers & Collections Installed
38
49
  - Commands/Code Used
39
50
  - Output
40
- - [AI Attestation][ai-attestation]
51
+ - [AI Attestation]
41
52
 
42
53
  ## Wait to be Assigned Issues
43
54
 
@@ -59,7 +70,9 @@ Once you've been [assigned an issue][being-assigned], you can begin working on a
59
70
  - fork and clone
60
71
  - create virtual environment and connect to virtual environment
61
72
  - create local installation with dev tooling
62
- `python -m pip install -e .[dev]`
73
+ `uv pip install -e . --group dev`
74
+ > **NOTE** If your change will require a documentation update the `docs` group should also be installed.
75
+ `uv pip install -e . --group dev --group docs`
63
76
  - run pre-commit (it will install all of its needed deps)
64
77
 
65
78
  ### Dockerfile
@@ -72,8 +85,10 @@ ensure the manual configuration is ran (You will still need to fork and clone).
72
85
  Render Engine uses nox to text against all supported versions. PRs will not be approved without tests passing on
73
86
  all supported versions.
74
87
 
75
- - 🚫 changes aren't breaking existing code (failing tests)
88
+ - 🚫 changes aren't breaking existing code (failing tests.)
76
89
  - 🚫 Ensure new dependencies are listed, justified, and approved to be added.
90
+ - 🚫 Ensure that type checking passes.
91
+ - 🚫 Ensure that all linting passes.
77
92
 
78
93
  ## TODOs
79
94
 
@@ -91,8 +106,8 @@ The label is removed automatically afterward and can be re-applied if new TODOs
91
106
 
92
107
  ## Contributing to Documentation
93
108
 
94
- Render Engine uses [readthedocs][readthedocs] in combination with [MkDocs][mkdocs],
95
- [Material for MkDocs][material], and [mkdocsstrings][mkdocsstrings] to generate documentation.
109
+ Render Engine uses [readthedocs][readthedocs] in combination with [MkDocs][uv[Material for MkDocs][material],
110
+ and [mkdocsstrings][mkdocsstrings] to generate documentation.
96
111
 
97
112
  - 📝 update docstrings for functions, methods, and classes.
98
113
  - 📷 add images and gifs where necessary. Assets should be stored in the `docs/docs/assets` folder.
@@ -122,15 +137,6 @@ uv sync --group docs
122
137
  ## Formatting your PR
123
138
 
124
139
  Render Engine uses a Pull Request Template that will help you include all the information needed to submit your PR.
125
-
126
- If you're starting from a blank PR be sure to include the following:
127
-
128
- - Summary
129
- - issue(s)/discussions being addressed
130
- - Documentation or tests added/updated
131
- - Any follow up tasks pending
132
- - [AI Attestation][ai-attestation]
133
-
134
140
  When submitting your PR, please be sure to use the template provided.
135
141
  **Not following this guideline will result in the immediate rejection of your PR.**
136
142
 
@@ -146,7 +152,7 @@ Low quality contributions or code that cannot be defended by the USER (not their
146
152
  will result in issues/prs being immediately closed.
147
153
  Multiple low quality contributions will result in your user account being banned from contribution.
148
154
 
149
- [ai-attestation]: #ai-attestation
155
+ [AI attestation]: #ai-attestation
150
156
  [gh-render-engine]: https://github.com/render-engine
151
157
  [wiki]: https://github.com/render-engine/.github/wiki
152
158
  [docs]: https://render-engine.readthedocs.org
@@ -161,3 +167,5 @@ Multiple low quality contributions will result in your user account being banned
161
167
  [code of conduct]: https://github.com/render-engine/render-engine/blob/main/.github/CODE_OF_CONDUCT.md
162
168
  [docs-contributing]: https://render-engine.readthedocs.io/en/latest/contributing/CONTRIBUTING/#instead-of-making-a-change-in-render-engines-code-consider
163
169
  [todo-to-issue]: https://github.com/marketplace/actions/todo-to-issue
170
+ [Wait to be assigned an issue]: #wait-to-be-assigned-issues
171
+ [Formatting your PR]: #formatting-your-pr
@@ -44,13 +44,14 @@ from render_engine_kjaymiller_theme import kjaymiller
44
44
  from render_engine_youtube_embed import YouTubeEmbed
45
45
 
46
46
  # create Site() as `app` or `site`
47
- # add your `output_path` (if not default)
48
- # add local `static_paths`
49
- # register plugins and themes
47
+ # setting your `output_path` (if not default)
48
+ # and local `static_paths`
49
+ app = Site(
50
+ output_path = "output",
51
+ static_paths = {"static"},
52
+ )
50
53
 
51
- app = Site()
52
- app.output_path = "output"
53
- app.static_paths.add("static")
54
+ # register plugins and themes
54
55
  app.register_plugins(YouTubeEmbed)
55
56
  app.register_theme(kjaymiller)
56
57
 
@@ -0,0 +1,149 @@
1
+ ---
2
+ title: "Site"
3
+ description: "Overview of the Site class in Render Engine, including attributes, functions for managing pages and collections, and rendering content."
4
+ date: August 22, 2024
5
+ tags: ["site", "pages", "collections", "rendering"]
6
+ ---
7
+
8
+ The site stores your pages and collections to be rendered.
9
+
10
+ **Attributes:**
11
+
12
+ | Name | Type | Description |
13
+ |------------------------|----------|----------------------------------------------------------------|
14
+ | `output_path` | `str` | path to write rendered content |
15
+ | `static_paths` | `set` | paths for static folders copied to output (recursive) |
16
+ | `site_vars` | `dict` | dictionary that will be passed into page template |
17
+ | `render_html_site_map` | `bool` | When True render the site map as an HTML file. Default: False. |
18
+ | `render_xml_site_map` | `bool` | When True render the site map as an XML file. Default: False. |
19
+ | `slug_only_urls` | `bool` | default value for pages for [`slug_only_url`] |
20
+
21
+ > !!! Note
22
+ These attributes can be set as class level attributes when subclassing `Site`, as parameters during instantiation,
23
+ or updated as attributes later on. If they are set as class level attributes those values will be used during
24
+ instantiation regardless of what is sent to the constructor.
25
+
26
+ ## Functions
27
+
28
+ ### `Site()`
29
+
30
+ ```python
31
+ from render_engine import Site
32
+
33
+ Site(
34
+ *,
35
+ output_path: str | Path = "output",
36
+ template_path: str | Path = "templates",
37
+ static_paths: set[str | Path] = {"static"},
38
+ plugin_settings: object | dict = SENTINEL,
39
+ render_html_site_map: bool = False,
40
+ render_xml_site_map: bool = False,
41
+ slug_only_urls: bool = False,
42
+ site_vars: object | dict = SENTINEL,
43
+ ) -> None:
44
+ pass
45
+ ```
46
+
47
+ The `Site` constructor takes the following keyword arguments:
48
+
49
+ <!-- markdownlint-disable MD056 -->
50
+ <!-- markdownlint-disable MD060 -->
51
+ | Name | Type | Description |
52
+ |------------------------|--------------|------------------------------------------------------------------------------------------|
53
+ | `output_path` | `str | Path` | Path to write rendered content. |
54
+ | `template_path` | `str | Path` | Path to location of template files. |
55
+ | `static_paths` | `str | Path` | Paths for static folders copied to output (recursive). |
56
+ | `plugin_settings` | `dict` | Dictionary caontaining plugin settings. |
57
+ | `render_html_site_map` | `bool` | When True render the site map as an HTML file. Default: False. |
58
+ | `render_xml_site_map` | `bool` | When True render the site map as an XML file. Default: False. |
59
+ | `slug_only_urls` | `bool` | Default value for Page objects rendering slub only URLS. Default: False |
60
+ | `site_vars` | `dict` | The site_vars dictionary containing data to be passed to all templates during rendering. |
61
+ <!-- markdownlint-enable MD056 -->
62
+ <!-- markdownlint-enable MD060 -->
63
+
64
+ ### `collection(Collection)`
65
+
66
+ Add the collection to the route list to be rendered later.
67
+
68
+ This is the primary way to add a collection to the site and
69
+ can either be called on an uninstantiated class or on the class definition as a decorator.
70
+
71
+ In most cases. You should use the decorator method.
72
+
73
+ ```Python
74
+ from render_engine import Site, Collection
75
+
76
+ site = Site()
77
+
78
+ @site.collection # works
79
+ class Pages(Collection):
80
+ pass
81
+
82
+
83
+ class Posts(Collection):
84
+ pass
85
+
86
+ site.collection(Posts) # also works
87
+ ```
88
+
89
+ ### `load_themes`
90
+
91
+ function for registering the themes with the theme_manager.
92
+ Used prior to rendering and cli-tasks
93
+
94
+ ### `page(Page)`
95
+
96
+ Add the page to the route list to be rendered later.
97
+ Also remaps `title` in case the user wants to use it in the template rendering.
98
+
99
+ This is the primary way to add a page to the site and can either be called
100
+ on an uninstantiated class or on the class definition as a decorator.
101
+
102
+ In most cases. You should use the decorator method.
103
+
104
+ ```Python
105
+ from render_engine import Site, Page
106
+
107
+ site = Site()
108
+
109
+ @site.page # works
110
+ class Home(Page):
111
+ pass
112
+
113
+ class About(Page):
114
+ pass
115
+
116
+ site.page(About) # also works
117
+ ```
118
+
119
+ ### `register_theme(theme)`
120
+
121
+ Overrides the ThemeManager register_theme method to add plugins to the site
122
+
123
+ ### `register_themes(*themes)`
124
+
125
+ Register multiple themes.
126
+
127
+ **Parameters:**
128
+
129
+ | Name | Type | Description | Default |
130
+ | --------- | ------- | ------------------------- | ------- |
131
+ | `*themes` | `Theme` | Theme objects to register | `()` |
132
+
133
+ ### `render()`
134
+
135
+ Render all pages and collections.
136
+
137
+ These are pages and collections that have been added to the site using
138
+ the [`Site.page`]
139
+ and [`Site.collection`] methods.
140
+
141
+ Render should be called after all pages and collections have been added to the site.
142
+
143
+ You can choose to call it manually in your file or
144
+ use the CLI command [`render-engine build`]
145
+
146
+ [`render-engine build`]: cli.md?id=build
147
+ [`site.collection`]: site.md?id=collection
148
+ [`site.page`]: site.md?id=page
149
+ [`slug_only_url`]: page.md#slug-only-urls
@@ -10,17 +10,17 @@ readme = "README.md"
10
10
  requires-python = ">=3.10"
11
11
  dependencies = [
12
12
  "jinja2==3.1.6",
13
- "more-itertools==10.8.0",
13
+ "more-itertools==11.1.0",
14
14
  "pluggy==1.6.0",
15
15
  "python-dateutil==2.9.0.post0",
16
- "python-frontmatter==1.1.0",
16
+ "python-frontmatter==1.3.0",
17
17
  "python-slugify==8.0.4",
18
18
  "render-engine-markdown==2023.12.1",
19
- "rich==14.3.3",
19
+ "rich==15.0.0",
20
20
  ]
21
21
 
22
22
  [project.optional-dependencies]
23
- extras = ["render-engine-sitemap==2024.1.1a1"]
23
+ extras = []
24
24
 
25
25
  [dependency-groups]
26
26
  dev = [
@@ -29,10 +29,10 @@ dev = [
29
29
  "httpx==0.28.1",
30
30
  # TODO: Update typing with new versions in 0.0.24
31
31
  # Issue URL: https://github.com/render-engine/render-engine/issues/1142
32
- "ty==0.0.14",
33
- "pre-commit==4.5.1",
34
- "pytest==9.0.2",
35
- "pytest-cov==7.0.0",
32
+ "ty==0.0.40",
33
+ "pre-commit==4.6.0",
34
+ "pytest==9.0.3",
35
+ "pytest-cov==7.1.0",
36
36
  "pytest-mock==3.15.1",
37
37
  "toml==0.10.2",
38
38
  ]
@@ -40,7 +40,7 @@ docs = [
40
40
  "mkdocs==1.6.1",
41
41
  "mkdocs-material==9.7.6",
42
42
  "mkdocs-multirepo-plugin==0.8.3",
43
- "mkdocstrings[python]==1.0.3",
43
+ "mkdocstrings[python]==1.0.4",
44
44
  "pymdown-extensions==10.21.3",
45
45
  ]
46
46
 
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2026.5.4a2'
22
- __version_tuple__ = version_tuple = (2026, 5, 4, 'a2')
21
+ __version__ = version = '2026.6.1'
22
+ __version_tuple__ = version_tuple = (2026, 6, 1)
23
23
 
24
- __commit_id__ = commit_id = 'g02bb1e0ce'
24
+ __commit_id__ = commit_id = 'g4f6a1146b'
@@ -5,6 +5,8 @@ from collections.abc import Callable
5
5
 
6
6
  from slugify import slugify
7
7
 
8
+ from render_engine.plugins import PluginManager
9
+
8
10
 
9
11
  class BaseObject:
10
12
  """
@@ -29,6 +31,8 @@ class BaseObject:
29
31
  skip_site_map: bool = False
30
32
  metadata: dict = dict()
31
33
  _path_name: str | None = None
34
+ site = None # This is a Site but circular imports so we can't actually type hint it.
35
+ plugin_manager: PluginManager | None
32
36
 
33
37
  # Using bool or None to have an unset state
34
38
  slug_only_url: bool | None = None
@@ -1,3 +1,4 @@
1
+ from collections.abc import Iterable
1
2
  from pathlib import Path
2
3
  from typing import Any
3
4
 
@@ -32,7 +33,7 @@ class Archive(BasePage):
32
33
  def __init__(
33
34
  self,
34
35
  title: str,
35
- pages: list[BasePage],
36
+ pages: Iterable[BasePage],
36
37
  template_vars: dict[str, Any],
37
38
  routes: list[str | Path],
38
39
  archive_index: int = 0,
@@ -15,7 +15,7 @@ from ._base_object import BaseObject
15
15
  from .archive import Archive
16
16
  from .content_managers import ContentManager, FileContentManager
17
17
  from .feeds import RSSFeed
18
- from .page import Page
18
+ from .page import BasePage, Page
19
19
  from .parsers import BasePageParser
20
20
  from .plugins import PluginManager
21
21
 
@@ -85,12 +85,10 @@ class Collection(BaseObject):
85
85
  parser_extras: dict[str, Any]
86
86
  required_themes: list[Callable]
87
87
  routes: list[str | Path] = ["./"]
88
- site = None
89
88
  sort_by: str | list = "_title"
90
89
  sort_reverse: bool = False
91
90
  template_vars: dict[str, Any]
92
91
  template: str | None
93
- plugin_manager: PluginManager | None
94
92
  ContentManager: type[ContentManager] = FileContentManager
95
93
  content_manager_extras: dict[str, Any]
96
94
 
@@ -204,7 +202,7 @@ class Collection(BaseObject):
204
202
 
205
203
  sorted_pages = list(self.sorted_pages)
206
204
  items_per_page = getattr(self, "items_per_page", len(sorted_pages))
207
- archives = [sorted_pages]
205
+ archives: list[Iterable[BasePage]] = [sorted_pages]
208
206
 
209
207
  if items_per_page != len(sorted_pages):
210
208
  paginated_archives = list(batched(sorted_pages, items_per_page))
@@ -293,7 +291,7 @@ class Collection(BaseObject):
293
291
  :param entry: The entry to process
294
292
  """
295
293
  if not isinstance(entry, RSSFeed) and not isinstance(entry, Archive):
296
- entry.plugin_manager: PluginManager = copy.deepcopy(self.plugin_manager)
294
+ entry.plugin_manager = copy.deepcopy(self.plugin_manager)
297
295
 
298
296
  # Circular imports. Need to be handled here.
299
297
  from .page import BasePage
@@ -4,7 +4,6 @@ from pathlib import Path
4
4
  from typing import Any, cast
5
5
 
6
6
  from render_engine._base_object import BaseObject
7
- from render_engine.plugins import PluginManager
8
7
 
9
8
 
10
9
  class DataObject(BaseObject):
@@ -16,8 +15,6 @@ class DataObject(BaseObject):
16
15
 
17
16
  routes: list[str | Path] = ["./"]
18
17
  path_name: str | Path = Path("data_object.json")
19
- plugin_manager: PluginManager | None
20
- site = None # This is a Site but circular imports so we can't actually type hint it.
21
18
 
22
19
  def __init__(self, serializer: Callable = json.dumps):
23
20
  """Ensure that the serializer method is static"""
@@ -9,7 +9,6 @@ from render_engine.themes import ThemeManager
9
9
 
10
10
  from ._base_object import BaseObject
11
11
  from .parsers import BasePageParser
12
- from .plugins import PluginManager
13
12
 
14
13
  logger = logging.getLogger("Page")
15
14
 
@@ -40,8 +39,6 @@ class BasePage(BaseObject):
40
39
  template: str | Template | None
41
40
  rendered_content: str | None
42
41
  _reference: str = "_slug"
43
- plugin_manager: PluginManager | None
44
- site = None # This is a Site but circular imports so we can't actually type hint it.
45
42
  no_prerender: bool = False
46
43
  collection: dict | None = None
47
44