render-engine 2025.11.1a1__tar.gz → 2026.1.1a2__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.
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.github/workflows/devcontainer-ci.yml +2 -2
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.github/workflows/labeler.yml +1 -1
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.github/workflows/lint.yml +2 -2
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.github/workflows/publish.yml +1 -1
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.github/workflows/scorecard.yml +3 -3
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.github/workflows/test.yml +3 -3
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/PKG-INFO +1 -1
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/collection.md +2 -1
- render_engine-2026.1.1a2/docs/docs/content_manager.md +85 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/mkdocs.yml +1 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/requirements.txt +35 -35
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/__version__.py +3 -3
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/_base_object.py +1 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/collection.py +43 -24
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/content_managers/base_content_manager.py +18 -1
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/content_managers/file_content_manager.py +30 -1
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/page.py +23 -18
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/site.py +5 -8
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine.egg-info/PKG-INFO +1 -1
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine.egg-info/SOURCES.txt +2 -0
- render_engine-2026.1.1a2/tests/test_file_content_manager.py +78 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/tests/test_plugins.py +6 -6
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/tests/test_templates/test_base_html.py +2 -2
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.all-contributorsrc +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.devcontainer/Dockerfile +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.devcontainer/devcontainer.json +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.devcontainer/setup.sh +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.github/FUNDING.yml +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.github/ISSUE_TEMPLATE/config.yaml +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.github/ISSUE_TEMPLATE/form_issue_template.yml +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.github/dependabot-bot.yml +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.github/dependabot.yml +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.github/labeler.yml +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.github/release.yml +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.gitignore +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.markdownlint.json +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.pre-commit-config.yaml +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.readthedocs.yml +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.vscode/tasks.json +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/CONTRIBUTING.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/README.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/SECURITY.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/.markdownlint.json +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/archive.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/assets/create environment vs code.gif +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/assets/create-app-help.png +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/assets/create-codespace.gif +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/assets/launching a dev container.gif +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/assets/render-engine-init-help.png +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/assets/render-engine-init.png +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/cli.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/contributing/CODE_OF_CONDUCT.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/contributing/CONTRIBUTING.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/contributing/environment_setup.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/custom_collections.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/feeds.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/getting-started/building-your-site.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/getting-started/creating-a-collection.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/getting-started/creating-a-page.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/getting-started/creating-your-app.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/getting-started/getting-started.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/getting-started/installation.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/getting-started/layout.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/index.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/page.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/parsers.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/plugins.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/site.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/site_map.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/templates.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/docs/theme_management.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/docs/requirements.txt +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/noxfile.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/pyproject.toml +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/setup.cfg +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/.gitignore +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/__init__.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/__main__.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/archive.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/blog.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/content_managers/__init__.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/engine.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/extras/__init__.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/feeds.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/hookspecs.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/links.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/parsers/markdown.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/plugins.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/py.typed +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/__init__.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/archive.html +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/base.html +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/base_collection_path.md +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/base_templates/_archive.html +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/base_templates/_base.html +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/base_templates/_page.html +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/components/footer.html +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/components/page_title.html +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/page.html +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/rss2.0.xml +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/rss2.0_items.xml +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/sitemap.xml +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/render_engine_templates/sitemap_item.xml +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/site_map.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/themes.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine/utils/__init__.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine.egg-info/dependency_links.txt +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine.egg-info/requires.txt +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/src/render_engine.egg-info/top_level.txt +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/tests/conftest.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/tests/test_archive.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/tests/test_base_object.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/tests/test_blog.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/tests/test_collections.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/tests/test_engine.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/tests/test_feeds/conftest_feed.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/tests/test_feeds/test_feeds.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/tests/test_page.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/tests/test_parsers_remove_2024_3_1.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/tests/test_site.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/tests/test_site_map.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/tests/test_theme_manager.py +0 -0
- {render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/uv.lock +0 -0
{render_engine-2025.11.1a1 → render_engine-2026.1.1a2}/.github/workflows/devcontainer-ci.yml
RENAMED
|
@@ -19,9 +19,9 @@ jobs:
|
|
|
19
19
|
build:
|
|
20
20
|
runs-on: ubuntu-latest
|
|
21
21
|
steps:
|
|
22
|
-
- uses: actions/checkout@
|
|
22
|
+
- uses: actions/checkout@v6.0.1
|
|
23
23
|
- name: Use Node.js 20.x
|
|
24
|
-
uses: actions/setup-node@
|
|
24
|
+
uses: actions/setup-node@v6
|
|
25
25
|
with:
|
|
26
26
|
node-version: 20.x
|
|
27
27
|
- run: npm install -g @devcontainers/cli
|
|
@@ -13,7 +13,7 @@ jobs:
|
|
|
13
13
|
name: Format and Lint Python
|
|
14
14
|
runs-on: ubuntu-latest
|
|
15
15
|
steps:
|
|
16
|
-
- uses: actions/checkout@
|
|
16
|
+
- uses: actions/checkout@v6.0.1
|
|
17
17
|
with:
|
|
18
18
|
fetch-depth: 0
|
|
19
19
|
- name: Install Python
|
|
@@ -34,7 +34,7 @@ jobs:
|
|
|
34
34
|
name: Lint Markdown
|
|
35
35
|
runs-on: ubuntu-latest
|
|
36
36
|
steps:
|
|
37
|
-
- uses: actions/checkout@
|
|
37
|
+
- uses: actions/checkout@v6.0.1
|
|
38
38
|
- name: Markdown Lint base-files
|
|
39
39
|
uses: DavidAnson/markdownlint-cli2-action@v20
|
|
40
40
|
with:
|
|
@@ -32,7 +32,7 @@ jobs:
|
|
|
32
32
|
|
|
33
33
|
steps:
|
|
34
34
|
- name: "Checkout code"
|
|
35
|
-
uses: actions/checkout@
|
|
35
|
+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.1
|
|
36
36
|
with:
|
|
37
37
|
persist-credentials: false
|
|
38
38
|
|
|
@@ -59,7 +59,7 @@ jobs:
|
|
|
59
59
|
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
|
60
60
|
# format to the repository Actions tab.
|
|
61
61
|
- name: "Upload artifact"
|
|
62
|
-
uses: actions/upload-artifact@
|
|
62
|
+
uses: actions/upload-artifact@v6
|
|
63
63
|
with:
|
|
64
64
|
name: SARIF
|
|
65
65
|
path: results.sarif
|
|
@@ -68,6 +68,6 @@ jobs:
|
|
|
68
68
|
# Upload the results to GitHub's code scanning dashboard (optional).
|
|
69
69
|
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
|
|
70
70
|
- name: "Upload to code-scanning"
|
|
71
|
-
uses: github/codeql-action/upload-sarif@
|
|
71
|
+
uses: github/codeql-action/upload-sarif@v4
|
|
72
72
|
with:
|
|
73
73
|
sarif_file: results.sarif
|
|
@@ -23,7 +23,7 @@ jobs:
|
|
|
23
23
|
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
24
24
|
fail-fast: true
|
|
25
25
|
steps:
|
|
26
|
-
- uses: actions/checkout@
|
|
26
|
+
- uses: actions/checkout@v6.0.1
|
|
27
27
|
- uses: actions/setup-python@v5
|
|
28
28
|
with:
|
|
29
29
|
python-version: ${{ matrix.python-version }}
|
|
@@ -44,7 +44,7 @@ jobs:
|
|
|
44
44
|
fail-fast: true
|
|
45
45
|
runs-on: ${{ matrix.os }}
|
|
46
46
|
steps:
|
|
47
|
-
- uses: actions/checkout@
|
|
47
|
+
- uses: actions/checkout@v6.0.1
|
|
48
48
|
- uses: actions/setup-python@v5
|
|
49
49
|
with:
|
|
50
50
|
python-version: "3.12"
|
|
@@ -56,7 +56,7 @@ jobs:
|
|
|
56
56
|
run: |
|
|
57
57
|
python -m pytest
|
|
58
58
|
- name: coverage
|
|
59
|
-
uses: actions/upload-artifact@
|
|
59
|
+
uses: actions/upload-artifact@v6
|
|
60
60
|
with:
|
|
61
61
|
name: code-coverage-report-${{ matrix.os }}
|
|
62
62
|
path: .coverage
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: render_engine
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2026.1.1a2
|
|
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/
|
|
@@ -46,7 +46,8 @@ sort_reverse: bool = False
|
|
|
46
46
|
title: str
|
|
47
47
|
template: str | None
|
|
48
48
|
archive_template str | None: The template to use for the archive pages.
|
|
49
|
-
|
|
49
|
+
ContentManager: type[ContentManager] | None = FileContentManager: The `ContentManager` to use.
|
|
50
|
+
content_manager_extras: dict[str, Any]: Configuration options to send to the `ContentManager` during instantiation.
|
|
50
51
|
```
|
|
51
52
|
|
|
52
53
|
## Attributes
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Enhancing Functionality with Content Managers"
|
|
3
|
+
description: "Guide to using creating Content Managers to use alternate content storage systems."
|
|
4
|
+
date: August 22, 2024
|
|
5
|
+
tags: ["content_manager", "render-engine", "customization"]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Content Managers are a way to control how and where your content is stored.
|
|
9
|
+
|
|
10
|
+
## Introduction
|
|
11
|
+
|
|
12
|
+
Render Engine `Collection` uses a `ContentManager` to manage the storage of content for site generation. By default
|
|
13
|
+
content is stored in the file system with each piece of content existing in a discrete file. With an alternate
|
|
14
|
+
`ContentManager` the content can be stored in a database, JSON file, or other alternate data store.
|
|
15
|
+
|
|
16
|
+
## Selecting a `ContentManager`
|
|
17
|
+
|
|
18
|
+
The `ContentManager` for a given [`Collection`](collection.md) is controlled by the `ContentManager` attribute. When
|
|
19
|
+
the class is instantiated the `ContentManager` is also instantiated with any `content_manager_extras` being passed
|
|
20
|
+
as arguments. To access the `ContentManager` of a given `Collection` use the `content_manager` attribute.
|
|
21
|
+
|
|
22
|
+
## Creating a `ContentManager`
|
|
23
|
+
|
|
24
|
+
To create a `ContentManager` create a sub-class of `ContentManager` that implements the following methods:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
@property
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def pages(self) -> Iterable:
|
|
30
|
+
"""The Page objects managed by the content manager"""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def create_entry(self, filepath: Path = None, editor: str = None, metadata: dict = None, content: str = None):
|
|
35
|
+
"""Create a new entry"""
|
|
36
|
+
...
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### `pages`
|
|
40
|
+
|
|
41
|
+
The `pages` property is how the `Collection` accesses its content. It is a method that _must_ be implemented by
|
|
42
|
+
every `ContentManager`. An example `pages` implementation (from the
|
|
43
|
+
[`FileContentManger`](https://github.com/render-engine/render-engine/blob/main/src/render_engine/content_managers/file_content_manager.py)) is:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
@property
|
|
47
|
+
def pages(self) -> Iterable:
|
|
48
|
+
if self._pages is None:
|
|
49
|
+
self._pages = [self.collection.get_page(page) for page in self.iter_content_path()]
|
|
50
|
+
yield from self._pages
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### `create_entry`
|
|
54
|
+
|
|
55
|
+
The `create_entry` method is used by the Render Engine CLI tool to add new entries. It is responsible for adding
|
|
56
|
+
the new entry to the datastore and, if an `editor` is specified, giving the user the ability to edit the new entry
|
|
57
|
+
prior to committing the entry to the datastore.
|
|
58
|
+
|
|
59
|
+
#### Arguments
|
|
60
|
+
|
|
61
|
+
- `filepath: Path`: The path on the filesystem to store the new entry.
|
|
62
|
+
- `editor: str`: The text editor to open for editing the new entry.
|
|
63
|
+
- `content: str`: The initial content for the new entry.
|
|
64
|
+
- `metadata: dict`: The metadata for the new entry.
|
|
65
|
+
|
|
66
|
+
!!! Note
|
|
67
|
+
Not every `ContentManager` actually needs all the arguments that are passed.
|
|
68
|
+
|
|
69
|
+
### `find_entry`
|
|
70
|
+
|
|
71
|
+
The `find_entry` method is used to find a specific page in a `Collection`. It will search through all the pages
|
|
72
|
+
known to the `ContentManager` until it finds one that has all the attributes specified. Even if multiple entries
|
|
73
|
+
would satisfy the criteria, only the first found will be returned.
|
|
74
|
+
|
|
75
|
+
### `update_entry`
|
|
76
|
+
|
|
77
|
+
The `update_entry` is used to update an existing entry in a `ContentManager`.
|
|
78
|
+
|
|
79
|
+
Parameters:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
page: Page # The Page to update
|
|
83
|
+
content: str = None # Updated content
|
|
84
|
+
**kwargs: dict # The other attributes for the updated page
|
|
85
|
+
```
|
|
@@ -4,30 +4,30 @@
|
|
|
4
4
|
#
|
|
5
5
|
# pip-compile --extra=dev --output-file=requirements.txt pyproject.toml
|
|
6
6
|
#
|
|
7
|
-
anyio==4.
|
|
7
|
+
anyio==4.12.0
|
|
8
8
|
# via
|
|
9
9
|
# httpx
|
|
10
10
|
# watchfiles
|
|
11
|
-
arrow==1.
|
|
11
|
+
arrow==1.4.0
|
|
12
12
|
# via cookiecutter
|
|
13
13
|
babel==2.17.0
|
|
14
14
|
# via mkdocs-material
|
|
15
|
-
backrefs==6.
|
|
15
|
+
backrefs==6.1
|
|
16
16
|
# via mkdocs-material
|
|
17
17
|
binaryornot==0.4.4
|
|
18
18
|
# via cookiecutter
|
|
19
|
-
certifi==2025.
|
|
19
|
+
certifi==2025.11.12
|
|
20
20
|
# via
|
|
21
21
|
# httpcore
|
|
22
22
|
# httpx
|
|
23
23
|
# requests
|
|
24
|
-
cfgv==3.
|
|
24
|
+
cfgv==3.5.0
|
|
25
25
|
# via pre-commit
|
|
26
26
|
chardet==5.2.0
|
|
27
27
|
# via binaryornot
|
|
28
|
-
charset-normalizer==3.4.
|
|
28
|
+
charset-normalizer==3.4.4
|
|
29
29
|
# via requests
|
|
30
|
-
click==8.
|
|
30
|
+
click==8.3.1
|
|
31
31
|
# via
|
|
32
32
|
# cookiecutter
|
|
33
33
|
# mkdocs
|
|
@@ -38,13 +38,13 @@ colorama==0.4.6
|
|
|
38
38
|
# mkdocs-material
|
|
39
39
|
cookiecutter==2.6.0
|
|
40
40
|
# via render_engine (pyproject.toml)
|
|
41
|
-
coverage[toml]==7.
|
|
41
|
+
coverage[toml]==7.13.1
|
|
42
42
|
# via pytest-cov
|
|
43
43
|
distlib==0.4.0
|
|
44
44
|
# via virtualenv
|
|
45
45
|
ephemeral-port-reserve==1.1.4
|
|
46
46
|
# via render_engine (pyproject.toml)
|
|
47
|
-
filelock==3.
|
|
47
|
+
filelock==3.20.1
|
|
48
48
|
# via virtualenv
|
|
49
49
|
ghp-import==2.1.0
|
|
50
50
|
# via mkdocs
|
|
@@ -52,7 +52,7 @@ gitdb==4.0.12
|
|
|
52
52
|
# via gitpython
|
|
53
53
|
gitpython==3.1.45
|
|
54
54
|
# via render_engine (pyproject.toml)
|
|
55
|
-
griffe==1.
|
|
55
|
+
griffe==1.15.0
|
|
56
56
|
# via mkdocstrings-python
|
|
57
57
|
h11==0.16.0
|
|
58
58
|
# via httpcore
|
|
@@ -60,14 +60,14 @@ httpcore==1.0.9
|
|
|
60
60
|
# via httpx
|
|
61
61
|
httpx==0.28.1
|
|
62
62
|
# via render_engine (pyproject.toml)
|
|
63
|
-
identify==2.6.
|
|
63
|
+
identify==2.6.15
|
|
64
64
|
# via pre-commit
|
|
65
|
-
idna==3.
|
|
65
|
+
idna==3.11
|
|
66
66
|
# via
|
|
67
67
|
# anyio
|
|
68
68
|
# httpx
|
|
69
69
|
# requests
|
|
70
|
-
iniconfig==2.
|
|
70
|
+
iniconfig==2.3.0
|
|
71
71
|
# via pytest
|
|
72
72
|
jinja2==3.1.6
|
|
73
73
|
# via
|
|
@@ -76,7 +76,7 @@ jinja2==3.1.6
|
|
|
76
76
|
# mkdocs-material
|
|
77
77
|
# mkdocstrings
|
|
78
78
|
# render_engine (pyproject.toml)
|
|
79
|
-
markdown==3.
|
|
79
|
+
markdown==3.10
|
|
80
80
|
# via
|
|
81
81
|
# mkdocs
|
|
82
82
|
# mkdocs-autorefs
|
|
@@ -89,7 +89,7 @@ markdown2==2.5.4
|
|
|
89
89
|
# via
|
|
90
90
|
# render-engine-markdown
|
|
91
91
|
# render_engine (pyproject.toml)
|
|
92
|
-
markupsafe==3.0.
|
|
92
|
+
markupsafe==3.0.3
|
|
93
93
|
# via
|
|
94
94
|
# jinja2
|
|
95
95
|
# mkdocs
|
|
@@ -113,23 +113,23 @@ mkdocs-autorefs==1.4.3
|
|
|
113
113
|
# mkdocstrings-python
|
|
114
114
|
mkdocs-get-deps==0.2.0
|
|
115
115
|
# via mkdocs
|
|
116
|
-
mkdocs-material==9.
|
|
116
|
+
mkdocs-material==9.7.1
|
|
117
117
|
# via render_engine (pyproject.toml)
|
|
118
118
|
mkdocs-material-extensions==1.3.1
|
|
119
119
|
# via mkdocs-material
|
|
120
|
-
mkdocstrings[python]==0.
|
|
120
|
+
mkdocstrings[python]==1.0.0
|
|
121
121
|
# via
|
|
122
122
|
# mkdocstrings-python
|
|
123
123
|
# render_engine (pyproject.toml)
|
|
124
|
-
mkdocstrings-python==
|
|
124
|
+
mkdocstrings-python==2.0.1
|
|
125
125
|
# via mkdocstrings
|
|
126
126
|
more-itertools==10.8.0
|
|
127
127
|
# via render_engine (pyproject.toml)
|
|
128
|
-
mypy==1.
|
|
128
|
+
mypy==1.19.1
|
|
129
129
|
# via render_engine (pyproject.toml)
|
|
130
130
|
mypy-extensions==1.1.0
|
|
131
131
|
# via mypy
|
|
132
|
-
nodeenv==1.
|
|
132
|
+
nodeenv==1.10.0
|
|
133
133
|
# via pre-commit
|
|
134
134
|
packaging==25.0
|
|
135
135
|
# via
|
|
@@ -139,7 +139,7 @@ paginate==0.5.7
|
|
|
139
139
|
# via mkdocs-material
|
|
140
140
|
pathspec==0.12.1
|
|
141
141
|
# via mkdocs
|
|
142
|
-
platformdirs==4.
|
|
142
|
+
platformdirs==4.5.1
|
|
143
143
|
# via
|
|
144
144
|
# mkdocs-get-deps
|
|
145
145
|
# virtualenv
|
|
@@ -147,25 +147,25 @@ pluggy==1.6.0
|
|
|
147
147
|
# via
|
|
148
148
|
# pytest
|
|
149
149
|
# render_engine (pyproject.toml)
|
|
150
|
-
pre-commit==4.
|
|
150
|
+
pre-commit==4.5.1
|
|
151
151
|
# via render_engine (pyproject.toml)
|
|
152
152
|
pygments==2.19.2
|
|
153
153
|
# via
|
|
154
154
|
# mkdocs-material
|
|
155
155
|
# rich
|
|
156
|
-
pymdown-extensions==10.
|
|
156
|
+
pymdown-extensions==10.20
|
|
157
157
|
# via
|
|
158
158
|
# mkdocs-material
|
|
159
159
|
# mkdocstrings
|
|
160
160
|
# render_engine (pyproject.toml)
|
|
161
|
-
pytest==
|
|
161
|
+
pytest==9.0.2
|
|
162
162
|
# via
|
|
163
163
|
# pytest-cov
|
|
164
164
|
# pytest-mock
|
|
165
165
|
# render_engine (pyproject.toml)
|
|
166
|
-
pytest-cov==
|
|
166
|
+
pytest-cov==7.0.0
|
|
167
167
|
# via render_engine (pyproject.toml)
|
|
168
|
-
pytest-mock==3.
|
|
168
|
+
pytest-mock==3.15.1
|
|
169
169
|
# via render_engine (pyproject.toml)
|
|
170
170
|
python-dateutil==2.9.0.post0
|
|
171
171
|
# via
|
|
@@ -180,7 +180,7 @@ python-slugify==8.0.4
|
|
|
180
180
|
# via
|
|
181
181
|
# cookiecutter
|
|
182
182
|
# render_engine (pyproject.toml)
|
|
183
|
-
pyyaml==6.0.
|
|
183
|
+
pyyaml==6.0.3
|
|
184
184
|
# via
|
|
185
185
|
# cookiecutter
|
|
186
186
|
# mkdocs
|
|
@@ -201,12 +201,12 @@ requests==2.32.5
|
|
|
201
201
|
# via
|
|
202
202
|
# cookiecutter
|
|
203
203
|
# mkdocs-material
|
|
204
|
-
rich==14.
|
|
204
|
+
rich==14.2.0
|
|
205
205
|
# via
|
|
206
206
|
# cookiecutter
|
|
207
207
|
# render_engine (pyproject.toml)
|
|
208
208
|
# typer
|
|
209
|
-
ruff==0.
|
|
209
|
+
ruff==0.14.10
|
|
210
210
|
# via render_engine (pyproject.toml)
|
|
211
211
|
shellingham==1.5.4
|
|
212
212
|
# via typer
|
|
@@ -219,22 +219,22 @@ sniffio==1.3.1
|
|
|
219
219
|
text-unidecode==1.3
|
|
220
220
|
# via python-slugify
|
|
221
221
|
toml==0.10.2
|
|
222
|
-
typer==0.
|
|
222
|
+
typer==0.21.0
|
|
223
223
|
# via render_engine (pyproject.toml)
|
|
224
|
-
types-python-dateutil==2.9.0.
|
|
224
|
+
types-python-dateutil==2.9.0.20251115
|
|
225
225
|
# via arrow
|
|
226
226
|
typing-extensions==4.15.0
|
|
227
227
|
# via
|
|
228
228
|
# anyio
|
|
229
229
|
# mypy
|
|
230
230
|
# typer
|
|
231
|
-
tzdata==2025.
|
|
231
|
+
tzdata==2025.3
|
|
232
232
|
# via render_engine (pyproject.toml)
|
|
233
|
-
urllib3==2.
|
|
233
|
+
urllib3==2.6.3
|
|
234
234
|
# via requests
|
|
235
|
-
virtualenv==20.
|
|
235
|
+
virtualenv==20.35.4
|
|
236
236
|
# via pre-commit
|
|
237
237
|
watchdog==6.0.0
|
|
238
238
|
# via mkdocs
|
|
239
|
-
watchfiles==1.1.
|
|
239
|
+
watchfiles==1.1.1
|
|
240
240
|
# via render_engine (pyproject.toml)
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '
|
|
32
|
-
__version_tuple__ = version_tuple = (
|
|
31
|
+
__version__ = version = '2026.1.1a2'
|
|
32
|
+
__version_tuple__ = version_tuple = (2026, 1, 1, 'a2')
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g6c6c29a4d'
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
import datetime
|
|
3
3
|
import logging
|
|
4
|
+
import os
|
|
4
5
|
from collections.abc import Callable, Generator
|
|
6
|
+
from multiprocessing.pool import ThreadPool
|
|
5
7
|
from pathlib import Path
|
|
6
8
|
from typing import Any
|
|
7
9
|
|
|
@@ -10,10 +12,9 @@ from more_itertools import batched
|
|
|
10
12
|
from render_engine_parser import BasePageParser
|
|
11
13
|
from slugify import slugify
|
|
12
14
|
|
|
13
|
-
from render_engine.content_managers import ContentManager, FileContentManager
|
|
14
|
-
|
|
15
15
|
from ._base_object import BaseObject
|
|
16
16
|
from .archive import Archive
|
|
17
|
+
from .content_managers import ContentManager, FileContentManager
|
|
17
18
|
from .feeds import RSSFeed
|
|
18
19
|
from .page import Page
|
|
19
20
|
from .plugins import PluginManager
|
|
@@ -245,6 +246,28 @@ class Collection(BaseObject):
|
|
|
245
246
|
def __iter__(self):
|
|
246
247
|
yield from self.content_manager
|
|
247
248
|
|
|
249
|
+
def __len__(self):
|
|
250
|
+
return len(self.content_manager)
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def all_content(self) -> Generator:
|
|
254
|
+
"""All of the content that is associated with the Collection including Pages, Feed, and Archives"""
|
|
255
|
+
yield from self
|
|
256
|
+
|
|
257
|
+
if getattr(self, "has_archive", False):
|
|
258
|
+
for archive in self.archives:
|
|
259
|
+
yield archive
|
|
260
|
+
if archive.is_index:
|
|
261
|
+
# In order to avoid collision with parallel processing we need to do a copy.
|
|
262
|
+
# A deepcopy is not necessary because we only care about not overwriting the
|
|
263
|
+
# slug on the original.
|
|
264
|
+
index = copy.copy(archive)
|
|
265
|
+
index.slug = "index"
|
|
266
|
+
yield index
|
|
267
|
+
|
|
268
|
+
if feed := getattr(self, "feed", None):
|
|
269
|
+
yield feed
|
|
270
|
+
|
|
248
271
|
def _run_collection_plugins(self, site, hook_type: str):
|
|
249
272
|
"""
|
|
250
273
|
Run plugins for a collection
|
|
@@ -261,32 +284,28 @@ class Collection(BaseObject):
|
|
|
261
284
|
return
|
|
262
285
|
method(collection=self, site=site, settings=self.plugin_manager.plugin_settings)
|
|
263
286
|
|
|
264
|
-
def
|
|
265
|
-
"""
|
|
287
|
+
def _render(self, entry):
|
|
288
|
+
"""
|
|
289
|
+
Renders 1 entry in the Collection
|
|
266
290
|
|
|
267
|
-
|
|
291
|
+
:param entry: The entry to process
|
|
292
|
+
"""
|
|
293
|
+
if not isinstance(entry, RSSFeed) and not isinstance(entry, Archive):
|
|
268
294
|
entry.plugin_manager = copy.deepcopy(self.plugin_manager)
|
|
269
295
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
entry.render(route, self.site.theme_manager)
|
|
296
|
+
entry.site = self.site
|
|
297
|
+
entry.render(self.site.theme_manager)
|
|
273
298
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
archive.render(self.routes[0], self.site.theme_manager)
|
|
285
|
-
feed: RSSFeed
|
|
286
|
-
if hasattr(self, "Feed"):
|
|
287
|
-
feed = self.feed
|
|
288
|
-
feed.site = self.site
|
|
289
|
-
feed.render(route="./", theme_manager=self.site.theme_manager)
|
|
299
|
+
def render(self) -> None:
|
|
300
|
+
"""Iterate through Pages and Check for Archives and Feeds"""
|
|
301
|
+
|
|
302
|
+
# Use a ThreadPool to process all of the entries in the collection in parallel.
|
|
303
|
+
# This is limited to the number of CPUs available. The easiest way to implement
|
|
304
|
+
# this parallelization for a single task is to use the imap_unordered method of
|
|
305
|
+
# the ThreadPool. This is, effectively, a generator so we need to loop over it
|
|
306
|
+
# for them to run. Since there is no actual return value to look at we just `pass`.
|
|
307
|
+
for entry in ThreadPool(processes=os.cpu_count()).imap_unordered(self._render, self.all_content):
|
|
308
|
+
pass
|
|
290
309
|
|
|
291
310
|
def create_entry(
|
|
292
311
|
self, filepath: Path = None, editor: str = None, content: str = None, metadata: dict = None
|
|
@@ -19,4 +19,21 @@ class ContentManager(ABC):
|
|
|
19
19
|
@abstractmethod
|
|
20
20
|
def create_entry(self, filepath: Path = None, editor: str = None, metadata: dict = None, content: str = None):
|
|
21
21
|
"""Create a new entry"""
|
|
22
|
-
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
def find_entry(self, **kwargs):
|
|
25
|
+
"""
|
|
26
|
+
Find an entry
|
|
27
|
+
|
|
28
|
+
:param kwargs: List of attributes to search by
|
|
29
|
+
:return: Page if it was found otherwise None
|
|
30
|
+
"""
|
|
31
|
+
for page in self:
|
|
32
|
+
if all(getattr(page, attr, None) == value for attr, value in kwargs.items()):
|
|
33
|
+
return page
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def update_entry(self, *, page, **kwargs):
|
|
38
|
+
"""Update an entry"""
|
|
39
|
+
pass
|
|
@@ -36,8 +36,16 @@ class FileContentManager(ContentManager):
|
|
|
36
36
|
def pages(self, value: Iterable):
|
|
37
37
|
self._pages = value
|
|
38
38
|
|
|
39
|
+
def __len__(self):
|
|
40
|
+
return len(list(self.pages))
|
|
41
|
+
|
|
39
42
|
def create_entry(
|
|
40
|
-
self,
|
|
43
|
+
self,
|
|
44
|
+
filepath: Path = None,
|
|
45
|
+
editor: str = None,
|
|
46
|
+
metadata: dict = None,
|
|
47
|
+
content: str = None,
|
|
48
|
+
update: bool = False,
|
|
41
49
|
) -> str:
|
|
42
50
|
"""
|
|
43
51
|
Create a new entry for the Collection
|
|
@@ -46,12 +54,33 @@ class FileContentManager(ContentManager):
|
|
|
46
54
|
:param editor: Editor to open to edit the entry.
|
|
47
55
|
:param content: The content for the entry
|
|
48
56
|
:param metadata: Metadata for the new entry
|
|
57
|
+
:param update: Allow overwriting the existing file
|
|
49
58
|
"""
|
|
50
59
|
if not filepath:
|
|
51
60
|
raise ValueError("filepath needs to be specified.")
|
|
52
61
|
|
|
62
|
+
if not update and filepath.exists():
|
|
63
|
+
raise RuntimeError(f"File at {filepath} exists and update is disabled.")
|
|
64
|
+
|
|
53
65
|
parsed_content = self.collection.Parser.create_entry(content=content, **metadata)
|
|
54
66
|
filepath.write_text(parsed_content)
|
|
55
67
|
if editor:
|
|
56
68
|
subprocess.run([editor, filepath])
|
|
57
69
|
return f"New entry created at {filepath} ."
|
|
70
|
+
|
|
71
|
+
def update_entry(self, page, *, content: str = None, **kwargs) -> str:
|
|
72
|
+
"""
|
|
73
|
+
Update an entry
|
|
74
|
+
|
|
75
|
+
:param page: Page object to update
|
|
76
|
+
:param content: Content for the updated page
|
|
77
|
+
:param kwargs: Attributes to be included in the updated page
|
|
78
|
+
:return: String indicating that the page was updated.
|
|
79
|
+
"""
|
|
80
|
+
self.create_entry(filepath=page.content_path, metadata=kwargs, content=content, update=True)
|
|
81
|
+
if self._pages:
|
|
82
|
+
self._pages = [
|
|
83
|
+
existing_page for existing_page in self._pages if page.content_path != existing_page.content_path
|
|
84
|
+
]
|
|
85
|
+
self._pages.append(self.collection.get_page(page.content_path))
|
|
86
|
+
return f"Entry at {page.content_path} updated."
|