curated_feed.render¶
Rendering the published window as an Atom feed.
A pure function of state/published.jsonl: the rolling log of everything ever published, denormalised so that rendering cannot be changed by a later edit to the corpus or to a selection.
Four things here are not stylistic choices, because getting any of them wrong makes a reader resurface items that were already read:
an entry’s <id> is the canonical article URL, and never changes;
an entry’s <updated> is the original publication date, never the run time;
the feed’s <updated> moves only when a content hash moves;
the same state renders to the same bytes, every time.
The feed itself is public, so its title and author say nothing about the reader.
Attributes¶
Functions¶
|
The day's selection as feed entries, in rank order. |
|
The entries not already in the log, keyed by the id the feed uses. |
|
The newest size entries, newest first. |
|
An entry's timestamp: its publication date, never the time of the run. |
|
The text one entry publishes: the source's description, then the note. |
|
Where the feed will be served from, or a stable urn if that is unset. |
|
The Atom document, as an element tree. |
|
The feed as text. The same entries and timestamp give the same bytes. |
|
A hash of everything except the feed timestamp. |
|
A plain landing page, so the URL is not a bare 404 to a curious visitor. |
|
Where the feed is written: the build directory plus the secret segment. |
|
Write the feed and its companions, and return the feed's path. |
|
The feed timestamp, moved only when the content has actually changed. |
|
|
|
Module Contents¶
- curated_feed.render.ATOM_NAMESPACE = 'http://www.w3.org/2005/Atom'¶
- curated_feed.render.PUBLISHED_LOG = 'published.jsonl'¶
- curated_feed.render.FEED_META = 'feed.json'¶
- curated_feed.render.FEED_FILENAME = 'feed.xml'¶
- curated_feed.render.INDEX_FILENAME = 'index.html'¶
- curated_feed.render.ROBOTS_FILENAME = 'robots.txt'¶
- curated_feed.render.HEADERS_FILENAME = '_headers'¶
- curated_feed.render.FALLBACK_FEED_ID = 'urn:curated-feed'¶
- curated_feed.render.ROBOTS_TXT = Multiline-String¶
Show Value
"""User-agent: * Disallow: / """
- curated_feed.render.HEADERS = Multiline-String¶
Show Value
"""/* X-Robots-Tag: noindex, nofollow """
- curated_feed.render.to_entries(selection: curated_feed.models.SelectionResult, candidates: collections.abc.Sequence[curated_feed.models.Candidate]) list[curated_feed.models.PublishedEntry][source]¶
The day’s selection as feed entries, in rank order.
- curated_feed.render.unpublished(entries: collections.abc.Sequence[curated_feed.models.PublishedEntry], published: collections.abc.Sequence[curated_feed.models.PublishedEntry]) list[curated_feed.models.PublishedEntry][source]¶
The entries not already in the log, keyed by the id the feed uses.
Re-running a day must not publish it twice, and the entry <id> is the canonical URL, so that is what identity means here.
- curated_feed.render.window(entries: collections.abc.Sequence[curated_feed.models.PublishedEntry], size: int) list[curated_feed.models.PublishedEntry][source]¶
The newest size entries, newest first.
A reader polls on its own schedule and can miss items in a file that churns too fast, so the feed carries far more than one day.
- curated_feed.render.entry_updated(entry: curated_feed.models.PublishedEntry) str[source]¶
An entry’s timestamp: its publication date, never the time of the run.
- curated_feed.render.summary_text(description: str, rationale: str) str[source]¶
The text one entry publishes: the source’s description, then the note.
Both go in <summary> rather than one each in <summary> and <content>, because that is the element every reader is certain to show. Splitting them would put the description — the only text worth skimming — behind whether a particular reader renders content at all.
The description comes first because it is what the entry is about; the note is a sentence of provenance and reads as a footer. Either can be empty: an article whose feed gave no description, and a selection whose rationale mode is none, are both ordinary.
>>> summary_text("A panel rejected the argument.", "Reported by 3 outlets.") 'A panel rejected the argument.\n\nReported by 3 outlets.' >>> summary_text("A panel rejected the argument.", "") 'A panel rejected the argument.' >>> summary_text("", "") ''
- curated_feed.render.feed_url(publish: curated_feed.config.PublishSettings) str[source]¶
Where the feed will be served from, or a stable urn if that is unset.
>>> feed_url(PublishSettings(site_url="https://example.com", path_prefix="abc")) 'https://example.com/abc/feed.xml'
- curated_feed.render.build_feed(entries: collections.abc.Sequence[curated_feed.models.PublishedEntry], *, publish: curated_feed.config.PublishSettings, updated: datetime.datetime) xml.etree.ElementTree.Element[source]¶
The Atom document, as an element tree.
Entries are ordered here rather than trusted to arrive ordered, so that the byte-identical guarantee holds for the function and not merely for one caller.
- curated_feed.render.render_atom(entries: collections.abc.Sequence[curated_feed.models.PublishedEntry], *, publish: curated_feed.config.PublishSettings, updated: datetime.datetime) str[source]¶
The feed as text. The same entries and timestamp give the same bytes.
- curated_feed.render.content_hash(entries: collections.abc.Sequence[curated_feed.models.PublishedEntry], *, publish: curated_feed.config.PublishSettings) str[source]¶
A hash of everything except the feed timestamp.
Rendering with a fixed timestamp and hashing the result is what makes the hash cover exactly what a change of <updated> should follow.
- curated_feed.render.render_index(entries: collections.abc.Sequence[curated_feed.models.PublishedEntry], *, publish: curated_feed.config.PublishSettings) str[source]¶
A plain landing page, so the URL is not a bare 404 to a curious visitor.
- curated_feed.render.feed_directory(config: curated_feed.config.Config) pathlib.Path[source]¶
Where the feed is written: the build directory plus the secret segment.
- curated_feed.render.write_build(entries: collections.abc.Sequence[curated_feed.models.PublishedEntry], *, config: curated_feed.config.Config, updated: datetime.datetime) pathlib.Path[source]¶
Write the feed and its companions, and return the feed’s path.
The feed and its landing page sit under the unguessable segment; the two files that instruct crawlers sit at the root, which is where a host looks for them.
- curated_feed.render.resolve_updated(entries: collections.abc.Sequence[curated_feed.models.PublishedEntry], *, config: curated_feed.config.Config, meta_path: pathlib.Path, now: datetime.datetime) tuple[datetime.datetime, bool][source]¶
The feed timestamp, moved only when the content has actually changed.
- curated_feed.render.build_parser() argparse.ArgumentParser[source]¶