mafw.devtools.pyversions.sync

Structure-aware pyproject.toml synchronization service.

This module implements PyprojectSyncService, the component that bridges the pure Python version policy core and the on-disk pyproject.toml. It uses tomlkit so that comments, whitespace, array formatting and key ordering are preserved, mutates only the managed derived locations in place (never by whole-file string replacement), writes atomically so a failure leaves the file byte-for-byte unchanged, and offers a shared --dry-run driver that renders a unified diff without touching the file.

The managed derived locations and their canonical sources are:

TOML location

Canonical source

project.classifiers

supported-python

tool.hatch.envs.hatch-test.matrix (standard)

supported-python

tool.hatch.envs.hatch-test.matrix (free-thr)

free-threaded-python

tool.hatch.envs.dev.matrix

supported-python

tool.hatch.envs.types.matrix

supported-python

tool.hatch.envs.nogil.python

free-threaded-python

tool.hatch.envs.gil.python

base of free-threaded-python

tool.hatch.envs.experimental.matrix

experimental-python

tool.hatch.envs.default.pre-push (default)

default-python

tool.hatch.envs.default.pre-push (free-thr)

free-threaded-python

Added in version 2.4.

author:

Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu)

Classes

DerivedChange(location, previous, new)

A single managed derived-location modification, used for reporting.

PyprojectSyncService(path)

Load, transform, synchronize and persist pyproject.toml.

SyncOutcome(report, diff, written)

The result of running a full operation through the service.

SyncReport(changes, skipped, notices)

The outcome of a synchronization pass.

class mafw.devtools.pyversions.sync.DerivedChange(location: str, previous: str, new: str)[source]

Bases: object

A single managed derived-location modification, used for reporting.

Variables:
  • location – Human-readable TOML key path that changed.

  • previous – The previous value, rendered as a string.

  • new – The new value, rendered as a string.

class mafw.devtools.pyversions.sync.PyprojectSyncService(path: Path)[source]

Bases: object

Load, transform, synchronize and persist pyproject.toml.

Parameters:

path (Path) – Path to the pyproject.toml file to manage.

static _assign_array(container: Any, key: str, values: list[str]) → None[source]

Replace the string items of a TOML array while preserving its style.

Reusing the existing tomlkit.items.Array (rather than assigning a plain Python list) keeps the array’s multiline-or-single-line layout and surrounding whitespace intact, which is what keeps the diff minimal.

static _is_free_threaded_row(versions: list[str]) → bool[source]

Whether a matrix row is the dedicated free-threaded row (X.Yt only).

static _is_version_classifier(classifier: str) → bool[source]

Whether classifier is a per-minor-version Python classifier.

static _matrix_rows(doc: TOMLDocument, env: str) → Any | None[source]

Return the matrix array-of-tables for an environment, or None.

static _record(report: SyncReport, location: str, previous: Any, new: Any) → None[source]

Record a change only when the rendered value actually differs.

static _sync_env_python(doc: TOMLDocument, report: SyncReport, *, env: str, desired: str) → None[source]

Synchronize a scalar python key of an environment (nogil / gil).

static _write_canonical(doc: TOMLDocument, cfg: PythonConfig) → None[source]

Write the canonical [tool.mafw] keys from cfg into doc.

static render(doc: TOMLDocument) → str[source]

Serialize a tomlkit document back to text, preserving formatting.

_rebuild_classifiers(existing: list[str], non_version: list[str], desired_versions: list[str]) → list[str][source]

Rebuild the classifier list with managed version entries resynchronised.

_sync_classifiers(doc: TOMLDocument, cfg: PythonConfig, report: SyncReport) → None[source]

Rewrite the per-version Python classifiers, keeping all other entries.

_sync_experimental_matrix(doc: TOMLDocument, cfg: PythonConfig, report: SyncReport) → None[source]

Create or update the dedicated experimental environment matrix.

The environment is created on demand so that experimental versions have a home that never expands the mandatory matrices.

_sync_hatch_test_matrix(doc: TOMLDocument, cfg: PythonConfig, report: SyncReport) → None[source]

Synchronize the standard and free-threaded hatch-test matrix rows.

_sync_pre_push(doc: TOMLDocument, cfg: PythonConfig, report: SyncReport, *, previous_default: str) → None[source]

Rewrite managed pre-push commands, leaving pins and unknowns untouched.

_sync_simple_matrix(doc: TOMLDocument, cfg: PythonConfig, report: SyncReport, *, env: str, desired: list[str]) → None[source]

Synchronize the single python matrix row of a simple environment.

check() → SyncReport[source]

Compute the inconsistencies between canonical config and derived state.

This is the read-only dual of synchronize(): it loads the document, validates the canonical configuration, then computes what a synchronization would change. Every would-be change is an existing inconsistency. The document on disk is never modified.

Returns:

A report whose changes list enumerates the inconsistencies (empty when the configuration is fully consistent).

Raises:

DevtoolsError – If the file is missing, unparsable, or the canonical configuration is invalid.

diff(before: str, after: str) → str[source]

Return a unified diff between two renderings of the document.

load() → tuple[TOMLDocument, PythonConfig][source]

Parse the document and read the canonical configuration.

Returns:

The parsed tomlkit document and the (unvalidated) configuration.

Return type:

tuple[tomlkit.TOMLDocument, PythonConfig]

Raises:

DevtoolsError – If the file is missing or cannot be parsed.

run_operation(transform: Callable[[PythonConfig], PythonConfig], *, dry_run: bool) → SyncOutcome[source]

Load, transform, synchronize and (optionally) persist the document.

This is the shared driver behind every modifying command. Because dry-run and real runs share the same load → transform → validate → synchronize pipeline, a dry-run reports exactly the same validation errors and the same diff a real run would produce — it simply does not write.

Parameters:
  • transform – Maps the current canonical configuration to the desired one. For sync this is the identity; for the other commands it is a VersionManager operation.

  • dry_run – If True, compute and return the diff without writing.

Returns:

The operation outcome.

Raises:

DevtoolsError – On parse errors, invalid canonical configuration, rejected transforms, or write failures.

synchronize(doc: TOMLDocument, cfg: PythonConfig, *, previous: PythonConfig | None = None) → SyncReport[source]

Update every managed derived location in doc to match cfg.

Mutates the document in place using structured tomlkit assignment so that unrelated content, comments and formatting survive. Returns a report of the per-location previous → new changes and of any pre-push command that could not be classified (and was therefore left untouched).

Parameters:
  • doc – The tomlkit document to mutate.

  • cfg – The validated canonical configuration to project.

  • previous – The configuration before the transform, used to detect which stable selector managed pre-push commands currently carry. When omitted, cfg is used (appropriate for a pure sync).

Returns:

The change report.

write_atomic(text: str) → None[source]

Write text to pyproject.toml atomically.

The content is first written to a temporary file in the same directory and then moved into place with os.replace, which is atomic on the same filesystem. Any failure before the replacement leaves the original file byte-for-byte unchanged.

Parameters:

text – The full new file content.

Raises:

DevtoolsError – If writing or replacing the file fails.

class mafw.devtools.pyversions.sync.SyncOutcome(report: SyncReport, diff: str, written: bool)[source]

Bases: object

The result of running a full operation through the service.

Variables:
  • report – The per-location change report.

  • diff – The unified diff of pyproject.toml (empty if nothing changed).

  • written – Whether the file was actually written (False for a dry-run or a no-op).

class mafw.devtools.pyversions.sync.SyncReport(changes: list[~mafw.devtools.pyversions.sync.DerivedChange] = <factory>, skipped: list[str] = <factory>, notices: list[str] = <factory>)[source]

Bases: object

The outcome of a synchronization pass.

Variables:
  • changes – The managed locations that were modified.

  • skipped – Locations that could not be classified and were left untouched.

  • notices – Informational messages for the operator about structural changes that are not simple value edits (for example the removal of an environment table).

property modified: bool

Whether any managed location was modified.

mafw.devtools.pyversions.sync._CLASSIFIER_PREFIX = 'Programming Language :: Python :: '

Prefix of the per-version Python classifiers.