Every plugin declares who it is — and Postulo stops throwing that away #97

Closed
opened 2026-09-07 15:22:22 +00:00 by tiagoagueda · 0 comments
Owner

Observation

all plugins must hold:

  • a short name like "postulo-shortname"
  • a full name "Full name"
  • a author in the form of "First Last" first.last@mailserver.com
  • a version of any kind of tracking like a hash
  • a short description of what it does
  • its kind of plugin (internal....) / how it was installed / and a link to the repo /
    source code

The last line is #94's subject; this issue is the other five, plus the reason the last one
does not work today either.

What a plugin is actually required to declare

plugins/base.py, in full:

SourcePlugin ConnectedPlugin
Short name name name
Full name absent label
Version version version
Description absent absent
Author absent absent
Source link absent absent

A capture source is "anything with these four names" -- name, version, can_handle,
parse. So a source cannot say what it is called in words, what it does, who wrote it, or
where its code lives, and the built-in ones do not:

class SchemaOrgSource:
    name = "schema.org"
    version = "1.0"

Most of it is already read, and then thrown away

installing.PackageInfo pulls this out of a wheel before anything is installed:

summary: str = ""
licence: str = ""
author: str = ""
home_page: str = ""

and the confirmation screen shows all four. Then Installed(...) is built with name,
version, origin, source, sha256, installed_at, installed_by, entry_points,
disabled and dependencies -- and none of those four. They are read, displayed once,
and dropped. After the install completes there is nowhere to look up who wrote a plugin or
what it claims to do.

home_page=headers.get("Home-page", "") or "",

Home-page comes from setuptools' old url=. A project using [project.urls] -- which is
the current standard -- emits Project-URL instead, and Postulo reads none of it. Checked
against what is actually installed here rather than from memory:

annotated_types-0.8.0.dist-info/METADATA
  Project-URL: Homepage, https://github.com/annotated-types/annotated-types
  Project-URL: Source, https://github.com/annotated-types/annotated-types
  Author-email: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>
                                        (no Home-page line at all)

asgiref-3.12.1.dist-info/METADATA
  Home-page: https://github.com/django/asgiref/          (setuptools, older style)

This already bites Postulo's own reference plugin. postulo-helloworld is built with
hatchling and declares:

[project.urls]
Homepage = "https://source.tiagoagueda.com/postulo/postulo-helloworld"

which becomes Project-URL: Homepage, ... in the wheel. Postulo asks for Home-page, gets
nothing, and would show no source link for the plugin this project publishes as the example
to copy.

The author fallback has the same shape of problem:

author=headers.get("Author", "") or headers.get("Author-email", "") or "",

Author is a bare name; Author-email is First Last <address> -- exactly the form
asked for above
. Trying Author first prefers the less informative of the two whenever a
package sets both.

Which layer owns which field

This is the design decision, and getting it wrong means the same fact stored twice. One
distribution can ship several plugins
-- postulo-helloworld registers both a source and a
notifier from one wheel -- so some of these are per plugin and some are per package:

Field Declared by Why there
Short name the plugin One wheel, several plugins, one identifier each
Full name the plugin Likewise; ConnectedPlugin.label already is this
Description the plugin What this notifier does, not what the package is
Author the package Author-email, already standard, already the right format
Licence the package Ditto
Source link the package Project-URL, first of Source / Repository / Homepage
Version the package Already recorded
Checksum Postulo Installed.sha256, already recorded, over the exact bytes

So the protocol gains label for sources and description for both, and the packaging
metadata answers the rest. Nothing new is invented where a standard field exists.

Two things worth deciding

1. The postulo- prefix is already the convention, and it is a distribution name.
postulo-helloworld, postulo-apprise, postulo-paperless. The plugin identifiers inside
are short and unprefixed -- helloworld, schema.org, page-metadata -- which is right,
because they are already scoped by the entry-point group.

Renaming the built-in sources would be a data migration, not a rename. base.py says
name is "recorded against every capture this source produced", and JobPostingData keeps
a source field, so every capture anybody has ever made carries the string schema.org or
page-metadata. Changing them orphans that history unless the old values are migrated. If
the prefix matters more than the history, say so and the migration comes with it -- but it
should be a decision rather than a side effect of tidying names.

2. An author's email address is a personal address, on a page. Author-email is public
in the wheel and on PyPI, so showing it to an administrator is no disclosure. #96 puts a
plugin list in front of every person on the instance, and an address rendered there is an
address harvested there. Suggest: the name for everyone, the address for administrators.

Scope

  • SourcePlugin gains label; both protocols gain description. Both optional at first,
    with a sensible fallback, so no existing plugin stops loading -- a hard requirement would
    break every plugin already written against the current contract.
  • Installed keeps the summary, licence, author and source link it already reads.
  • Metadata read correctly: Project-URL (Source, then Repository, then Homepage) before
    Home-page; Author-email before Author.
  • Built-ins carry the same identity, with Postulo as the author, AGPL-3.0-or-later as the
    licence, and this repository as the source.
  • A migration for the record on the volume, so an instance that already has plugins fills in
    what it can from the installed dist-info rather than showing blanks for ever.
  • docs/PLUGINS.md and the reference plugin updated -- postulo-helloworld declares
    authors = [{ name = "Tiago Agueda" }] with no address, so it would not itself satisfy the
    format this issue is about.
  • Tests: a wheel with only Project-URL yields a source link; one with both prefers
    Author-email; a plugin missing the new optional fields still loads.

Classification

Enhancement, interface, documentation. Sits under #94, which uses this to say where a plugin
came from, and feeds #96, which shows it to the person.

## Observation > all plugins must hold: > - a short name like "postulo-shortname" > - a full name "Full name" > - a author in the form of "First Last" <first.last@mailserver.com> > - a version of any kind of tracking like a hash > - a short description of what it does > - its kind of plugin (internal....) / how it was installed / and a link to the repo / > source code The last line is #94's subject; this issue is the other five, plus the reason the last one does not work today either. ## What a plugin is actually required to declare `plugins/base.py`, in full: | | `SourcePlugin` | `ConnectedPlugin` | | --- | --- | --- | | Short name | `name` | `name` | | Full name | **absent** | `label` | | Version | `version` | `version` | | Description | **absent** | **absent** | | Author | **absent** | **absent** | | Source link | **absent** | **absent** | A capture source is *"anything with these four names"* -- `name`, `version`, `can_handle`, `parse`. So a source cannot say what it is called in words, what it does, who wrote it, or where its code lives, and the built-in ones do not: ```python class SchemaOrgSource: name = "schema.org" version = "1.0" ``` ## Most of it is already read, and then thrown away `installing.PackageInfo` pulls this out of a wheel before anything is installed: ```python summary: str = "" licence: str = "" author: str = "" home_page: str = "" ``` and the confirmation screen shows all four. Then `Installed(...)` is built with `name`, `version`, `origin`, `source`, `sha256`, `installed_at`, `installed_by`, `entry_points`, `disabled` and `dependencies` -- **and none of those four**. They are read, displayed once, and dropped. After the install completes there is nowhere to look up who wrote a plugin or what it claims to do. ## And the source link is read from a header modern packaging no longer emits ```python home_page=headers.get("Home-page", "") or "", ``` `Home-page` comes from setuptools' old `url=`. A project using `[project.urls]` -- which is the current standard -- emits `Project-URL` instead, and Postulo reads none of it. Checked against what is actually installed here rather than from memory: ``` annotated_types-0.8.0.dist-info/METADATA Project-URL: Homepage, https://github.com/annotated-types/annotated-types Project-URL: Source, https://github.com/annotated-types/annotated-types Author-email: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> (no Home-page line at all) asgiref-3.12.1.dist-info/METADATA Home-page: https://github.com/django/asgiref/ (setuptools, older style) ``` **This already bites Postulo's own reference plugin.** `postulo-helloworld` is built with hatchling and declares: ```toml [project.urls] Homepage = "https://source.tiagoagueda.com/postulo/postulo-helloworld" ``` which becomes `Project-URL: Homepage, ...` in the wheel. Postulo asks for `Home-page`, gets nothing, and would show no source link for the plugin this project publishes as the example to copy. The author fallback has the same shape of problem: ```python author=headers.get("Author", "") or headers.get("Author-email", "") or "", ``` `Author` is a bare name; `Author-email` is `First Last <address>` -- **exactly the form asked for above**. Trying `Author` first prefers the less informative of the two whenever a package sets both. ## Which layer owns which field This is the design decision, and getting it wrong means the same fact stored twice. **One distribution can ship several plugins** -- `postulo-helloworld` registers both a source and a notifier from one wheel -- so some of these are per plugin and some are per package: | Field | Declared by | Why there | | --- | --- | --- | | Short name | the plugin | One wheel, several plugins, one identifier each | | Full name | the plugin | Likewise; `ConnectedPlugin.label` already is this | | Description | the plugin | What *this* notifier does, not what the package is | | Author | the package | `Author-email`, already standard, already the right format | | Licence | the package | Ditto | | Source link | the package | `Project-URL`, first of Source / Repository / Homepage | | Version | the package | Already recorded | | Checksum | Postulo | `Installed.sha256`, already recorded, over the exact bytes | So the protocol gains `label` for sources and `description` for both, and the packaging metadata answers the rest. Nothing new is invented where a standard field exists. ## Two things worth deciding **1. The `postulo-` prefix is already the convention, and it is a *distribution* name.** `postulo-helloworld`, `postulo-apprise`, `postulo-paperless`. The plugin identifiers inside are short and unprefixed -- `helloworld`, `schema.org`, `page-metadata` -- which is right, because they are already scoped by the entry-point group. **Renaming the built-in sources would be a data migration, not a rename.** `base.py` says `name` is *"recorded against every capture this source produced"*, and `JobPostingData` keeps a `source` field, so every capture anybody has ever made carries the string `schema.org` or `page-metadata`. Changing them orphans that history unless the old values are migrated. If the prefix matters more than the history, say so and the migration comes with it -- but it should be a decision rather than a side effect of tidying names. **2. An author's email address is a personal address, on a page.** `Author-email` is public in the wheel and on PyPI, so showing it to an administrator is no disclosure. #96 puts a plugin list in front of *every* person on the instance, and an address rendered there is an address harvested there. Suggest: the name for everyone, the address for administrators. ## Scope - `SourcePlugin` gains `label`; both protocols gain `description`. Both optional at first, with a sensible fallback, so no existing plugin stops loading -- a hard requirement would break every plugin already written against the current contract. - `Installed` keeps the summary, licence, author and source link it already reads. - Metadata read correctly: `Project-URL` (Source, then Repository, then Homepage) before `Home-page`; `Author-email` before `Author`. - Built-ins carry the same identity, with Postulo as the author, AGPL-3.0-or-later as the licence, and this repository as the source. - A migration for the record on the volume, so an instance that already has plugins fills in what it can from the installed `dist-info` rather than showing blanks for ever. - `docs/PLUGINS.md` and the reference plugin updated -- `postulo-helloworld` declares `authors = [{ name = "Tiago Agueda" }]` with no address, so it would not itself satisfy the format this issue is about. - Tests: a wheel with only `Project-URL` yields a source link; one with both prefers `Author-email`; a plugin missing the new optional fields still loads. ## Classification Enhancement, interface, documentation. Sits under #94, which uses this to say where a plugin came from, and feeds #96, which shows it to the person.
tiagoagueda added this to the 0.3.0 milestone 2026-09-07 15:22:22 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
Postulo/postulo#97
No description provided.