render-engine 2025.11.1__py3-none-any.whl → 2026.1.1a1__py3-none-any.whl

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.
@@ -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 = '2025.11.1'
32
- __version_tuple__ = version_tuple = (2025, 11, 1)
31
+ __version__ = version = '2026.1.1a1'
32
+ __version_tuple__ = version_tuple = (2026, 1, 1, 'a1')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -27,6 +27,7 @@ class BaseObject:
27
27
  plugins: list[Callable] | None
28
28
  plugin_settings: dict = {"plugins": defaultdict(dict)}
29
29
  skip_site_map: bool = False
30
+ metadata: dict = dict()
30
31
 
31
32
  @property
32
33
  def _title(self) -> str:
@@ -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
 
@@ -245,6 +247,25 @@ class Collection(BaseObject):
245
247
  def __iter__(self):
246
248
  yield from self.content_manager
247
249
 
250
+ @property
251
+ def all_content(self) -> Generator:
252
+ """All of the content that is associated with the Collection including Pages, Feed, and Archives"""
253
+ yield from self
254
+
255
+ if getattr(self, "has_archive", False):
256
+ for archive in self.archives:
257
+ yield archive
258
+ if archive.is_index:
259
+ # In order to avoid collision with parallel processing we need to do a copy.
260
+ # A deepcopy is not necessary because we only care about not overwriting the
261
+ # slug on the original.
262
+ index = copy.copy(archive)
263
+ index.slug = "index"
264
+ yield index
265
+
266
+ if feed := getattr(self, "feed", None):
267
+ yield feed
268
+
248
269
  def _run_collection_plugins(self, site, hook_type: str):
249
270
  """
250
271
  Run plugins for a collection
@@ -261,32 +282,29 @@ class Collection(BaseObject):
261
282
  return
262
283
  method(collection=self, site=site, settings=self.plugin_manager.plugin_settings)
263
284
 
264
- def render(self) -> None:
265
- """Iterate through Pages and Check for Collections and Feeds"""
285
+ def _render(self, entry):
286
+ """
287
+ Renders 1 entry in the Collection
266
288
 
267
- for entry in self:
289
+ :param entry: The entry to process
290
+ """
291
+ if not isinstance(entry, RSSFeed) and not isinstance(entry, Archive):
268
292
  entry.plugin_manager = copy.deepcopy(self.plugin_manager)
269
293
 
270
- for route in entry.routes:
271
- entry.site = self.site
272
- entry.render(route, self.site.theme_manager)
273
-
274
- if getattr(self, "has_archive", False):
275
- for archive in self.archives:
276
- archive.site = self.site
277
- logging.debug("Adding Archive: %s", archive.__class__.__name__)
278
-
279
- for _ in self.routes:
280
- archive.render(self.routes[0], self.site.theme_manager)
294
+ entry.site = self.site
295
+ for route in entry.routes:
296
+ entry.render(route, self.site.theme_manager)
281
297
 
282
- if archive.is_index:
283
- archive.slug = "index"
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)
298
+ def render(self) -> None:
299
+ """Iterate through Pages and Check for Archives and Feeds"""
300
+
301
+ # Use a ThreadPool to process all of the entries in the collection in parallel.
302
+ # This is limited to the number of CPUs available. The easiest way to implement
303
+ # this parallelization for a single task is to use the imap_unordered method of
304
+ # the ThreadPool. This is, effectively, a generator so we need to loop over it
305
+ # for them to run. Since there is no actual return value to look at we just `pass`.
306
+ for entry in ThreadPool(processes=os.cpu_count()).imap_unordered(self._render, self.all_content):
307
+ pass
290
308
 
291
309
  def create_entry(
292
310
  self, filepath: Path = None, editor: str = None, content: str = None, metadata: dict = None
render_engine/page.py CHANGED
@@ -204,10 +204,10 @@ class Page(BasePage):
204
204
 
205
205
  content: Any
206
206
  content_path: Path | str | None
207
- Parser: type[BasePageParser] = BasePageParser
208
207
  inherit_plugins: bool
209
208
  parser_extras: dict[str, Any] | None
210
209
  title: str
210
+ Parser: type[BasePageParser] = BasePageParser
211
211
 
212
212
  def __init__(
213
213
  self,
@@ -229,17 +229,17 @@ class Page(BasePage):
229
229
 
230
230
  # Parse Content from the Content Path or the Content
231
231
  if content_path := (content_path or getattr(self, "content_path", None)):
232
- attrs, self.content = self.Parser.parse_content_path(content_path)
232
+ self.metadata, self.content = self.Parser.parse_content_path(content_path)
233
233
 
234
234
  elif content := (content or getattr(self, "content", None)):
235
- attrs, self.content = self.Parser.parse_content(content)
235
+ self.metadata, self.content = self.Parser.parse_content(content)
236
236
 
237
237
  else:
238
- attrs = {}
238
+ self.metadata = {}
239
239
  self.content = None
240
240
 
241
241
  # Set the attributes
242
- for key, val in attrs.items():
242
+ for key, val in self.metadata.items():
243
243
  setattr(self, key.lower(), val)
244
244
 
245
245
  @property
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: render_engine
3
- Version: 2025.11.1
3
+ Version: 2026.1.1a1
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/
@@ -1,16 +1,16 @@
1
1
  render_engine/.gitignore,sha256=74oa8YR8gNxKxB6lCtoqmgtB2xpZnWM539Qsl4wI_lg,12
2
2
  render_engine/__init__.py,sha256=3fgua4ZA9o1pvQ5unhY1gRARLXFqAu019NEYqZTjP20,154
3
3
  render_engine/__main__.py,sha256=uI7aBBZz0qSDwwwD11nS5oltWsuLw9hStfYo8O1aNws,144
4
- render_engine/__version__.py,sha256=s8M9-UvQuY0Oq3E-DpMy-7tuQGi8g1QtU5RnILzjZ7c,712
5
- render_engine/_base_object.py,sha256=DIyLdQ6gS4a0DP46zwGYkYIyewh8uRFznJmaUch7d8M,3546
4
+ render_engine/__version__.py,sha256=hfZmZGRgu1zYwAQDJZKkcPJnWnfiuAJLIE-fSAXX7c8,718
5
+ render_engine/_base_object.py,sha256=B05rDWJUe5GM5FF94NeYY71KweBHY-IYel1lSBwLOrU,3574
6
6
  render_engine/archive.py,sha256=S3-kCmDNVKkEfKDKxcEk-sXkBD0vS0RDnFfPunYkU8g,2072
7
7
  render_engine/blog.py,sha256=f9GqFUFsta0KZnFhCiajobpfQyALqvgI5sbLm6zt1zw,1571
8
- render_engine/collection.py,sha256=eqeVxZ_ASrOra7kayhAkPSHocpM1mbxwhsH6ZtK-oMk,11294
8
+ render_engine/collection.py,sha256=9x2ixfxRPc2es3vVuCeJbvncLxz3-BFvg2Kj3RCFHk0,12184
9
9
  render_engine/engine.py,sha256=GOtUiq4ny5GHaLSCeH5u1Zk1JnWJVh63vK7etJiwS20,2843
10
10
  render_engine/feeds.py,sha256=i-VHsb6pRplMzaenBn6oeqh9yI_N4WVUAExPox6iJgw,921
11
11
  render_engine/hookspecs.py,sha256=GhOpw0zTQjfwWOFYYbJ4P7Cvq-oy1MmTPHmd90dr0kg,2292
12
12
  render_engine/links.py,sha256=pKmQMTz8-yGX8IecHcrlF3Dkejk7cptaO3qCkQiHB9I,2560
13
- render_engine/page.py,sha256=l6sKWNJ4gBtC_ONEc0u479q3znL-8Q7U_phNqXqmh6w,8988
13
+ render_engine/page.py,sha256=FqyjgVdeNwYx3uoWSS-YqxgN_0KFRQkN46mgSKDwJdQ,9020
14
14
  render_engine/plugins.py,sha256=NXM8QTbbRV-DwgpQRoIhILijJBN4SyYg2Rkk1LUAuZM,4703
15
15
  render_engine/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  render_engine/site.py,sha256=aarHkveSVDOy7hRPzrx_ga3DmmirZQcLvcJdyw-b8lY,13329
@@ -36,7 +36,7 @@ render_engine/render_engine_templates/base_templates/_page.html,sha256=jjrY2BAwl
36
36
  render_engine/render_engine_templates/components/footer.html,sha256=HkPGGhfN0HcYm7t8zgXWCQ3bsCbT8FxT4_n2-9e1zUE,74
37
37
  render_engine/render_engine_templates/components/page_title.html,sha256=l8aE1TY94UPHXHqAyy6jv4IoN2Hv9cbrTPh7ILkMyxg,137
38
38
  render_engine/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
- render_engine-2025.11.1.dist-info/METADATA,sha256=RQFVPrcI7wh8jq6DaDPSS5Rlv-s8r1zqeSeRhlJU7Zk,11893
40
- render_engine-2025.11.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
41
- render_engine-2025.11.1.dist-info/top_level.txt,sha256=aNGALDMsFyrusho04AvUjSivsgEE9tQp_LP_jGr312Q,14
42
- render_engine-2025.11.1.dist-info/RECORD,,
39
+ render_engine-2026.1.1a1.dist-info/METADATA,sha256=ElQwTlE7OJj5tzl3SxQqemrblppdtOQs1PXrVXCw-4M,11894
40
+ render_engine-2026.1.1a1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
41
+ render_engine-2026.1.1a1.dist-info/top_level.txt,sha256=aNGALDMsFyrusho04AvUjSivsgEE9tQp_LP_jGr312Q,14
42
+ render_engine-2026.1.1a1.dist-info/RECORD,,