repomatic.metadata.project module

Python-project reading of Metadata.

Everything derived from pyproject.toml and the Sphinx configuration: project identity, entry points, version state, and tool parameters.

repomatic.metadata.project.MANPAGES_ASSET_SUFFIX: Final[str] = '-manpages'

What the package name takes to name the man-page tarball, by default.

repomatic.metadata.project.MYPY_VERSION_MIN: Final[tuple[int, int]] = (3, 10)

Earliest Python dialect the pinned mypy accepts for --python-version.

Floors the value ProjectMetadata.mypy_params derives from the project’s requires-python, which the mypy entry of TOOL_REGISTRY passes through computed_params. A project declaring an older floor would otherwise hand mypy a version it refuses with exit code 2. Mypy’s own PYTHON3_VERSION_MIN holds the value, in mypy/defaults.py.

class repomatic.metadata.project.BumpBaseline(current, released)[source]

Bases: NamedTuple

The two versions a version-bump gate compares.

Create new instance of BumpBaseline(current, released)

current: Version

The version the bump-my-version configuration declares now.

released: Version

The latest release, read from its tag or from its release commit.

classmethod resolve()[source]

Read both versions from the working directory and its git history.

The latest release comes from the tags first. When none is found, as on a shallow clone or while the release workflow has not pushed its tag yet, it comes from the most recent release commit message.

Return type:

BumpBaseline | None

Returns:

The baseline, or None when either version is unknown: a gate with nothing to compare against allows the bump.

allows(part)[source]

Whether a bump of part would not increment it twice in one cycle.

  • Last release 5.0.1, current 5.0.2: a minor bump is allowed.

  • Last release 5.0.1, current 5.1.0: the minor was already bumped.

  • Last release 5.0.1, current 6.0.0: the major was already bumped, which blocks both parts.

Parameters:

part (Literal['minor', 'major']) – The version part to bump.

Return type:

bool

Returns:

True if the bump should proceed.

repomatic.metadata.project.is_version_bump_allowed(part)[source]

Check if a version bump of the specified part is allowed.

This prevents double version increments within a development cycle. A bump is blocked if the version has already been bumped (but not released) since the last tagged release. BumpBaseline.allows() holds the rule.

Parameters:

part (Literal['minor', 'major']) – The version part to check (minor or major).

Return type:

bool

Returns:

True if the bump should proceed, False if it should be skipped.

Raises:

ValueError – When part is not a bumpable part.

class repomatic.metadata.project.ProjectMetadata[source]

Bases: object

What pyproject.toml and the Sphinx configuration declare.

A concern mixin of Metadata: never instantiated on its own.

pyproject_path = PosixPath('pyproject.toml')

The project file every property here reads.

sphinx_conf_path = PosixPath('docs/conf.py')

The Sphinx configuration file.

property is_python_project: bool[source]

Returns True if repository is a Python project.

Presence of a pyproject.toml file that respects the standards is enough to consider the project as a Python one. Delegates to repomatic.pyproject.is_python_project() so the detection rule has a single source of truth.

property is_python_package: bool[source]

Returns True if the repository builds a distributable package.

Strictly narrower than is_python_project: a uv virtual project declares a [project] table to carry its dependencies, then opts out of being built with [tool.uv] package = false. Delegates to repomatic.pyproject.is_python_package(), the same predicate PACKAGE_ONLY resolves against, so the release lane and the checks that police it agree on who publishes.

Prefer this over the truthiness of package_name when gating anything about publishing. package_name only reports what [project] name says, which a virtual project still declares.

property changelog_has_entries: bool[source]

Whether the changelog section of the current version holds an entry.

The prepare-release job of changelog.yaml is gated on it: a release cut from an empty section publishes empty release notes, so the release pull request waits until a bullet names what moved, even on a mechanical cycle. False with no changelog at changelog.location, no current version, or no section for it.

property pyproject_toml: dict[str, Any][source]

Returns the parsed content of pyproject.toml.

The cached document read_pyproject_toml() shares with every other reader of the file, so treat it as read-only. Empty when the file does not exist or does not parse.

property pyproject: StandardMetadata | None[source]

Returns metadata stored in the pyproject.toml file.

Returns None if the pyproject.toml does not exists or does not respects the PEP standards.

Warning

Some third-party apps have their configuration saved into pyproject.toml file, but that does not means the project is a Python one. For that, the pyproject.toml needs to respect the PEPs.

property config: Config[source]

Returns the [tool.repomatic] section from pyproject.toml.

Merges user configuration with defaults from Config, through load_repomatic_config(): for the project in the working directory, the configuration the running command published wins over the file.

property package_name: str | None[source]

Returns package name as published on PyPI.

property manpages_asset_name: str | None[source]

Filename stem of the man-page tarball attached to a release.

[tool.repomatic] manpages.asset-name when set, <package-name>-manpages otherwise. None for a project with neither.

property project_description: str | None[source]

Returns project description from pyproject.toml.

property script_entries: list[tuple[str, str, str]][source]

The (name, module, callable) tuple of each console script.

Results are derived from the script entries of pyproject.toml. So that:

[project.scripts]
mdedup = "mail_deduplicate.cli:mdedup"
mpm = "meta_package_manager.__main__:main"

Will yields the following list:

(
    ("mdedup", "mail_deduplicate.cli", "mdedup"),
    ("mpm", "meta_package_manager.__main__", "main"),
    ...,
)

Each entry is validated against PEP 621 and PyPI conventions:

  • The script name (the dict key) must be non-empty, contain at least one non-dot character, and match [A-Za-z0-9._-]+. This mirrors the rule PyPI enforces on uploaded wheels and the check uv-build performs; rejecting names like ../escape, nested/script or . here keeps them from flowing into the binary file path template {{cli_id}}-{{current_version}}-{{target}}.{{extension}} and from there into shell-quoted artifact names, chmod, and attestation commands in the release workflow.

  • The script value must split on : into exactly two non-empty parts (module:object). Malformed values raise a descriptive ValueError instead of crashing with an unpacking error.

property main_cli_script: str | None[source]

The first [project.scripts] entry, the CLI the self-tests drive.

Returns:

The script name, or None when the project declares none.

property import_name: str | None[source]

The top-level package that python -m runs.

Read from the module of main_cli_script, since the CLI lives in that package: mail_deduplicate.cli gives mail_deduplicate. A project with no script falls back to its package name, with each hyphen turned into an underscore.

Returns:

The import name, or None when the project has no name.

property requires_python_floor: tuple[int, int] | None[source]

The project’s requires-python lower bound, as (major, minor).

The one reduction of the specifier to a floor, shared by mypy_params and the lint-repo Python-consistency check so the two cannot disagree on which operators count as a bound.

Returns:

The first >=/> bound’s release pair, or None when the project declares no requires-python or no lower bound.

property mypy_params: list[str] | None[source]

Generates mypy parameters.

Mypy needs to be fed with this parameter: --python-version 3.x.

Extracts the minimum Python version from the project’s requires-python specifier. Only takes major.minor into account.

property is_sphinx: bool[source]

Returns True if the Sphinx config file is present.

property builds_sphinx: bool[source]

Whether the Docs workflow builds this repository’s Sphinx site.

The build installs the project to run Sphinx, so it needs a Python project as well as a Sphinx configuration.

sphinx_conf_strings(name)[source]

The strings the Sphinx configuration assigns to the variable name.

Reads every top-level assignment to the bare name: a plain or annotated one sets the list, an augmented one (+=) extends it. An assignment to anything other than the bare name, like a tuple target or a subscript, is passed over, and so is a value that is not a list or tuple literal.

Parameters:

name (str) – The configuration variable, like extensions.

Return type:

frozenset[str]

Returns:

The string items, empty when there is no Sphinx configuration or it does not parse.

property sphinx_extensions: frozenset[str][source]

The extensions the Sphinx configuration lists, read once.

sphinx_conf_strings() says which assignments it reads.

property active_autodoc: bool[source]

Returns True if Sphinx autodoc is active.

property uses_myst: bool[source]

Returns True if MyST-Parser is active in Sphinx.