Dynamic-metadata (the package)

I’ve released an alpha of dynamic-metadata (somewhat arbitrarily versioned 0.3), and I’d like to see if I can get some feedback before I lock in. While this is a package, it’s primarily a contract between build-backends and generic dynamic metadata plugins. This would allow any build backend that wants to support it access to any dynamic metadata plugin that adds a simple hook or two.

It’s very similar to the system that’s been shipping inside scikit-build-core for several years, but I made a key change that I think will make it easier to implement.

Using it looks like this:

[build-system]
requires = ["...", "dynamic-metadata"]
build-backend = "..."

[project]
dynamic = ["description", "version"]

[[tool.dynamic-metadata]]
provider = "dynamic_metadata.plugins.regex"
field = "version"
input = "src/my_package/__init__.py"

[[tool.dynamic-metadata]]
provider = "dynamic_metadata.plugins.template"
field = "description"
result = "This is {project[name]}, version {project[version]}"

This only requires dynamic-metadata because we are loading the two built-in plugins (regex and template); there’s no requirement that either the backend or the plugin install dynamic-metadata (though it does have a reference implementation you can use).

Ideas for plugins:

  • vcs-versioning (setuptools_scm)
  • hatch-fancy-pypi-readme (we might be able to add a “concat” plugin that would do a lot of this generally)
  • An SBOM one to help with a new SBOM field (now that PEP 808 is accepted, it’s reasonable to pursue)
  • One to pin build requirements based on what was present in the build

There’s a way to specify plugins locally (with provider-path) which has been quite popular in scikit-build-core, even though it’s gated behind an experimental flag. This allows projects to control their own metadata from Python even though they have otherwise static config.

There are several advanced features, too, with extra hooks: you can specify METADATA 2.2+ dynamic fields, you can detect the build state, and you can request build dependencies.

Anyway, here are the points I’ve got questions on:

  • Currently, you can specify provider = “pkg” or provider = “pkg:Class”, the class form runs from an instance, which makes the extra hooks nicer (you can store the results and then use them in other hooks). But if a plugin wants to “upgrade”, users have to put something new in. Should we have a required name like DynamicMetadata, and maybe always require the class form? (The current form is really easy to implement by backends, but it only adds a line)
  • Do we need the build dependencies hook? They were mostly used to implement the original wrappers, but they are really powerful - users can use them to add things like cmake conditionally, which is really important - but also not quite dynamic metadata. This is also a separate part of the implementation. I’m torn. We could consider it optional, and indicate which backends don’t support it if we don’t get much uptake on this?
  • Currently, I require each plugin to only add to lists/dicts - the hook only returns the new items, not the original too. While this is correct for PEP808, it’s overly restrictive if two hooks modify the same metadata. You can modify a previous hook’s values, you just can’t modify the static ones. I think this is fine, though - you could make a wrapper around the plugins you need to do this to if you really had do that. The current form is much easier for backends to validate.
  • The hook args are positional, just like the build-system ones. But sometimes you don’t need all of the, and keyword-based is nicer, though slightly more to implement on the backend side, so I left it as positional - I think that’s best?

I can make draft PRs to backends or existing plugins if you’d like to see something concrete (and if you do, let me know if you don’t want me to use AI, it’s pretty trivial by hand, but even more trivial with AI, especially in a foreign codebase. I can do either.).

I’m hoping to finish this up by SIMPLE-Py, which is alongside SciPy’s tutorials. Scikit-build-core 1.0 is targeting that date, and this is one of the last major things that needs to go in for that.

Thoughts? Interest from plugin authors or backend authors?

10 Likes

Are there plans to standardize parts of its machinery for use at runtime?

I’ve been trying to get people to stop doing __version__ and they usually reply that it’s the only way to get reliably fresh metadata at runtime for editable packages. I’d like to change that so nobody has to use anything other than importlib.metadata anymore

1 Like

__version__ won’t ever fully go away. It’s simply not a correct option to rely on importlib.metadata for many intended uses. Versioning a file that may end up vendored is different from versioning a package, and it’s almost always a mistake to assume the package version is appropriate for any runtime use if you are even aware of vendoring of your code.

The other way around is safe. Generating package version from __version__ at the time the package is built doesn’t have that consistency potential issue.

5 Likes

Are there plans to standardize parts of its machinery for use at runtime?

This won’t affect the decision to provide __version__. But yes, if you look in scikit-build-core, you might see python -m scikit_build_core build project-table which produces the fully rendered project table including dynamic metadata from plugins; that could be added to the dynamic-metadata package trivially, I believe. (One oddity is that only produces JSON output, since there’s not a TOML writer in the stdlib).

What might affect it more is having a “generate” feature, which scikit-build-core also has, and it works really well. You can write out any metadata you want to file(s). But that’s up to the backend, I’m not trying to standardize that - it’s too tightly tied to the process of making the package. I’d recommend backends copy [[tool.scikit-build.generate]], though. :slight_smile:

Off-topic, but I don’t think any handling (__version__ or importlib.metadata) will help with it unless the code for extracting the version dynamically is bundled in. Then there’s the issue of the build dependencies that would have to be moved to runtime ones.

dynamic-metadata can technically help with the get_requires_for_dynamic_metadata, but that would probably be a separate issue to formalize.

1 Like

i consider it something to put into editables as plugin - self-updating metadata