curated_feed.render =================== .. py:module:: curated_feed.render .. autoapi-nested-parse:: 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 `` is the canonical article URL, and never changes; - an entry's `` is the original publication date, never the run time; - the feed's `` 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 ---------- .. autoapisummary:: curated_feed.render.ATOM_NAMESPACE curated_feed.render.PUBLISHED_LOG curated_feed.render.FEED_META curated_feed.render.FEED_FILENAME curated_feed.render.INDEX_FILENAME curated_feed.render.ROBOTS_FILENAME curated_feed.render.HEADERS_FILENAME curated_feed.render.FALLBACK_FEED_ID curated_feed.render.ROBOTS_TXT curated_feed.render.HEADERS Functions --------- .. autoapisummary:: curated_feed.render.to_entries curated_feed.render.unpublished curated_feed.render.window curated_feed.render.entry_updated curated_feed.render.summary_text curated_feed.render.feed_url curated_feed.render.build_feed curated_feed.render.render_atom curated_feed.render.content_hash curated_feed.render.render_index curated_feed.render.feed_directory curated_feed.render.write_build curated_feed.render.resolve_updated curated_feed.render.build_parser curated_feed.render.main Module Contents --------------- .. py:data:: ATOM_NAMESPACE :value: 'http://www.w3.org/2005/Atom' .. py:data:: PUBLISHED_LOG :value: 'published.jsonl' .. py:data:: FEED_META :value: 'feed.json' .. py:data:: FEED_FILENAME :value: 'feed.xml' .. py:data:: INDEX_FILENAME :value: 'index.html' .. py:data:: ROBOTS_FILENAME :value: 'robots.txt' .. py:data:: HEADERS_FILENAME :value: '_headers' .. py:data:: FALLBACK_FEED_ID :value: 'urn:curated-feed' .. py:data:: ROBOTS_TXT :value: Multiline-String .. raw:: html
Show Value .. code-block:: python """User-agent: * Disallow: / """ .. raw:: html
.. py:data:: HEADERS :value: Multiline-String .. raw:: html
Show Value .. code-block:: python """/* X-Robots-Tag: noindex, nofollow """ .. raw:: html
.. py:function:: to_entries(selection: curated_feed.models.SelectionResult, candidates: collections.abc.Sequence[curated_feed.models.Candidate]) -> list[curated_feed.models.PublishedEntry] The day's selection as feed entries, in rank order. .. py:function:: unpublished(entries: collections.abc.Sequence[curated_feed.models.PublishedEntry], published: collections.abc.Sequence[curated_feed.models.PublishedEntry]) -> list[curated_feed.models.PublishedEntry] 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 `` is the canonical URL, so that is what identity means here. .. py:function:: window(entries: collections.abc.Sequence[curated_feed.models.PublishedEntry], size: int) -> list[curated_feed.models.PublishedEntry] 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. .. py:function:: entry_updated(entry: curated_feed.models.PublishedEntry) -> str An entry's timestamp: its publication date, never the time of the run. .. py:function:: summary_text(description: str, rationale: str) -> str The text one entry publishes: the source's description, then the note. Both go in `` rather than one each in `` and ``, 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("", "") '' .. py:function:: feed_url(publish: curated_feed.config.PublishSettings) -> str 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' .. py:function:: build_feed(entries: collections.abc.Sequence[curated_feed.models.PublishedEntry], *, publish: curated_feed.config.PublishSettings, updated: datetime.datetime) -> xml.etree.ElementTree.Element 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. .. py:function:: render_atom(entries: collections.abc.Sequence[curated_feed.models.PublishedEntry], *, publish: curated_feed.config.PublishSettings, updated: datetime.datetime) -> str The feed as text. The same entries and timestamp give the same bytes. .. py:function:: content_hash(entries: collections.abc.Sequence[curated_feed.models.PublishedEntry], *, publish: curated_feed.config.PublishSettings) -> str 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 `` should follow. .. py:function:: render_index(entries: collections.abc.Sequence[curated_feed.models.PublishedEntry], *, publish: curated_feed.config.PublishSettings) -> str A plain landing page, so the URL is not a bare 404 to a curious visitor. .. py:function:: feed_directory(config: curated_feed.config.Config) -> pathlib.Path Where the feed is written: the build directory plus the secret segment. .. py:function:: write_build(entries: collections.abc.Sequence[curated_feed.models.PublishedEntry], *, config: curated_feed.config.Config, updated: datetime.datetime) -> pathlib.Path 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. .. py:function:: 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] The feed timestamp, moved only when the content has actually changed. .. py:function:: build_parser() -> argparse.ArgumentParser .. py:function:: main(argv: collections.abc.Sequence[str] | None = None) -> int