Skip to content

feat: entry-point provider discovery with inline-table local form#74

Merged
henryiii merged 6 commits into
mainfrom
feat/entry-point-providers
Jul 1, 2026
Merged

feat: entry-point provider discovery with inline-table local form#74
henryiii merged 6 commits into
mainfrom
feat/entry-point-providers

Conversation

@henryiii

@henryiii henryiii commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

🤖 AI text below 🤖

Adds an entry-point discovery mechanism for dynamic-metadata providers, replacing the import-path + provider-path scheme with a two-shape provider config. Follows up on the "could we provide an entry point system?" design discussion.

What changed

provider now takes one of two shapes:

# installed plugin — a name registered in the `dynamic_metadata.provider` group
provider = "dynamic_metadata.regex"

# local plugin inside the project — inline table (replaces `provider-path`)
provider = { path = "scripts", module = "my_plugin" }   # module may be "mod:Class"
  • Installed plugins register a name in the dynamic_metadata.provider entry-point group. Bundled plugins register under dynamic_metadata.-prefixed names; third-party plugins are encouraged to prefix with their own package (the group is shared, and a name registered by two distributions is a hard error).
  • Local plugins use the inline { path, module } table. The raw module-path form is only accepted this way — there is no bare-import fallback.
  • Unknown names raise with a did you mean …? suggestion.

For each audience

  • Plugin authors: hooks unchanged. To get a name, add one [project.entry-points."dynamic_metadata.provider"] table to your own pyproject.toml. Loose in-project scripts still need no packaging (inline table). No runtime dependency on this package either way.
  • Backends: no new required API — resolution is internal to the loader; process_dynamic_metadata / load_dynamic_metadata signatures are unchanged. New optional list_providers() for diagnostics.
  • Schema: provider is now a oneOf (string, or {path, module} object).

Notable bits

  • New _compat.metadata.entry_points shim handles the 3.8/3.9-vs-3.10+ API split without tripping filterwarnings = error.
  • list_providers() + dynamic-metadata providers CLI subcommand.
  • RESERVED_KEYS shrank to just {provider}.

Testing

uv run pytest (80 passed), mypy --strict, and prek -a all green. New tests cover entry-point resolution (module/class/singleton/import-failure/duplicate), the inline-table form and its validation, rejection of the raw-import form without a path, and the discovery helper + CLI.

Open questions

  • Design is still WiP (per the repo). Naming (module key, dynamic_metadata.-prefixed provider names) and the drop of provider-path were chosen in discussion but are easy to revisit.

henryiii added 2 commits July 1, 2026 09:35
Add a `dynamic_metadata.provider` entry-point group so providers are
referenced by a registered name rather than an import path. The bundled
plugins register there under `dynamic_metadata.`-prefixed names
(`dynamic_metadata.regex`, ...); third-party plugins register their own,
conventionally prefixed with their package to avoid collisions in the
shared group.

`provider` now takes one of two shapes:

- a string: a registered entry-point name (installed plugins);
- an inline table `{ path, module }`: a local plugin imported from a
  directory, replacing the old top-level `provider-path` key.

The raw module-path form is only accepted via the inline table; without
it there is no bare-import fallback. Duplicate names across distributions
are a hard error, and an unknown name suggests close matches.

Also adds `list_providers()` and a `dynamic-metadata providers` CLI
subcommand, plus a dependency-free `_compat.metadata.entry_points` shim
for the 3.8/3.9-vs-3.10+ API split.

Assisted-by: ClaudeCode:claude-opus-4.8
Assisted-by: ClaudeCode:claude-opus-4.8
@henryiii
henryiii marked this pull request as ready for review July 1, 2026 14:18
henryiii added 3 commits July 1, 2026 10:26
Drop the DMProtocols union in favor of the base DynamicMetadataProtocol
(callers isinstance-narrow to the optional-hook protocols anyway), inline
the single-element RESERVED_KEYS check, and fix a stale docstring in
_load_entry_point that referred to a removed module-import fallback.

Assisted-by: ClaudeCode:claude-opus-4.8
Move list_providers() into a new discovery.py so loader.py holds only what a
backend needs to resolve dynamic metadata; the unknown-provider error now
enumerates entry-point names inline instead of via list_providers. Update the
CLI, tests, and docs to the new location.

Assisted-by: ClaudeCode:claude-opus-4.8
The provider_path=None branch was a vestige of the removed bare-import
fallback; the sole caller only invokes it with a real directory. Make
provider_path required and validate it up front.

Assisted-by: ClaudeCode:claude-opus-4.8
Collapse the provider loading pipeline: load_provider now accepts the
raw provider value (entry-point name string or {path, module} table) and
dispatches internally, instead of a (provider, provider_path) pair with a
separate _provider_location parser. The unknown-provider hint moves into
_load_entry_point (resolve-or-raise, no None handshake) and _instantiate
is inlined.

Type the parameter as object so the isinstance validation is required,
and reword _import_provider around the {path, module} vocabulary.

Assisted-by: ClaudeCode:claude-opus-4-8
@henryiii
henryiii merged commit bf14cea into main Jul 1, 2026
11 checks passed
@henryiii
henryiii deleted the feat/entry-point-providers branch July 1, 2026 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant