Skip to content

refactor: workdir-centric chain API (env → config → workdir → version)#1378

Open
RonnyPfannschmidt wants to merge 15 commits into
pypa:developfrom
RonnyPfannschmidt:workdir-centric-api
Open

refactor: workdir-centric chain API (env → config → workdir → version)#1378
RonnyPfannschmidt wants to merge 15 commits into
pypa:developfrom
RonnyPfannschmidt:workdir-centric-api

Conversation

@RonnyPfannschmidt
Copy link
Copy Markdown
Contributor

Summary

  • Introduces VcsEnvironment as the explicit entry point for runtime settings (subprocess timeout, hg command, SOURCE_DATE_EPOCH, debug, env-var prefixes) and wires them through Configuration → workdir → ScmVersion instead of magic globals / ContextVar.
  • Replaces parse entry-point discovery with discover_workdir() and workdir objects (ScmWorkdir, fallbacks, egg-info metadata); setuptools-scm gains an egg_info mixin for workdir-based file finding and SCM metadata in sdist/egg-info.
  • Refactors GlobalOverrides to wrap VcsEnvironment (logging + EnvReader); setuptools hooks and get_version use ensure_context("SETUPTOOLS_SCM"). Backward-compatible shims remain for setuptools_scm.git / hg / scm_workdir.

Test plan

  • uv run pytest vcs-versioning/testing_vcs -n12
  • uv run pytest setuptools-scm/testing_scm -n12
  • uv run pytest -n12 (full suite)
  • pre-commit run --all-files

Made with Cursor

RonnyPfannschmidt and others added 12 commits March 30, 2026 12:26
Introduces the workdir-centric API as the primary version inference
path, replacing the parse-based entry point mechanism for built-in
SCM handling.

Changes:
- Evolve Workdir into ScmWorkdir with project_root/project_path
- Add FallbackWorkdir hierarchy (Metadata, Archived, PkgInfo, Static)
- Implement discover_workdir() with smart probe factories
- Add vcs_versioning.discover_workdir EP group
- Remove legacy parse_scm/parse_scm_fallback EPs from vcs-versioning
- Move PKG-INFO discovery to setuptools-scm (setuptools artifact)
- Add project_path to Configuration (pypa#872) with root bridge
- Add per-project overrides via .config/python-vcs-versioning.toml
- Write scm_version.json + scm_file_list.json to egg-info for sdists

Still to address:
- Consolidate old parse() functions into workdir get_scm_version()
  (currently the workdir methods wrap the old parse internals)
- Backward compat testing with mock third-party parse plugins
- API surface assessment: document public vs internal boundaries
  and migration path for third-party integrators
- File finder integration: active workdir only used as fallback
  after legacy file finder EPs; needs full validation
- Deprecation warnings for root → project_path migration not yet
  enabled (bridge silently computes)

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude <claude@anthropic.com>
…xtVar

Restructure _get_version_impl.py to reflect the clean workdir-centric
pipeline (Config -> Workdir -> ScmVersion -> formatted version):

- Extract legacy EP dispatch to _legacy_parse.py (parse_scm_version,
  parse_fallback_version, has_legacy_parse_eps, resolved_fallback_root)
- Delete parse_version() orchestrator; replace with _resolve_version()
  that inlines the pipeline directly
- Add _finalize() helper to avoid repeating overrides+format+write
- Inline the pipeline in setuptools-scm's infer_version_with_config,
  giving it direct access to the discovered workdir
- Add workdir field to VersionInferenceData so downstream consumers
  (egg_info mixin, file finders) can access it via Distribution
- Remove ContextVar (_active_workdir), get_active_workdir, and
  set_active_workdir entirely -- no more global mutable state
- Remove ContextVar fallback from find_files; file finding now relies
  solely on registered entry points (workdir-based finding will come
  via setuptools egg_info mixin)
- Update create_release_proposal.py to use discover_workdir directly
- Update test monkeypatch target from parse_version to _resolve_version

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude <claude@anthropic.com>
Replace the fragile _write_scm_metadata_to_egg_info (which guessed
the egg-info path and silently skipped on clean builds) with a proper
egg_info command mixin that:

- Overrides find_sources() to supply tracked files from the discovered
  workdir directly to manifest_maker, bypassing the walk_revctrl() ->
  setuptools.file_finders EP chain that cannot pass context.
- Writes scm_version.json and scm_file_list.json into the egg-info
  directory in run(), where the directory is guaranteed to exist.

Follows the same registration pattern as ScmVersionFileMixin/build_py.

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude <claude@anthropic.com>
Add integration test that a git-tracked file excluded via MANIFEST.in
is absent from the sdist, even when the egg_info mixin supplies tracked
files directly (bypassing walk_revctrl).

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude <claude@anthropic.com>
Extend the MANIFEST.in integration test with two phases:
1. Empty MANIFEST.in → git-tracked secret.txt IS in the sdist
2. MANIFEST.in with "exclude secret.txt" → file is absent

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude <claude@anthropic.com>
Store Configuration on workdirs via _config field so get_scm_version()
and run_describe() no longer require an explicit config parameter.
Introduce VcsEnvironment to capture runtime settings (timeout, hg
command, SOURCE_DATE_EPOCH) without ContextVar.  Add instance
run_git/run_hg methods that thread env settings.  Backward-compat
shims in setuptools-scm re-export modules accept optional config arg.

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude Opus 4 <claude@anthropic.com>
Remove get_active_overrides(), get_debug_level(), get_subprocess_timeout(),
get_hg_command(), get_source_date_epoch(), and source_epoch_or_utc_now()
module-level accessor functions from vcs_versioning.overrides.

Each call site now reads configuration explicitly:
- _version_missing() accepts a `tool` parameter instead of querying globals
- is_toplevel_acceptable() accepts `ignore_vcs_roots` or reads os.environ
- _get_timeout() reads SUBPROCESS_TIMEOUT from env with prefix fallback
- _get_hg_command() reads HG_COMMAND from env with prefix fallback
- _source_epoch_or_utc_now() reads SOURCE_DATE_EPOCH from os.environ
- _read_pretended_*() and read_toml_overrides() accept explicit `env`

GlobalOverrides, GlobalOverrides.from_env(), and ensure_context() are
retained as the public API for entering override contexts.

External consumer research (grep.app + GitHub code search):
- scikit-build/scikit-build-core: uses GlobalOverrides.from_env() only (safe)
- nipreps/version-schemes: uses GlobalOverrides.from_env() only (safe)
- kulgan/setuptools-scmx: uses GlobalOverrides.from_env() only (safe)
- No external project imports or calls any of the removed accessor functions

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude Opus 4 <claude@anthropic.com>
Configuration.env is now always non-None — __post_init__ falls back to
VcsEnvironment.from_env() when no explicit environment was provided.
This eliminates all None-checks downstream and ensures runtime settings
(timeout, hg_command, source_date_epoch) flow through the explicit chain:
VcsEnvironment -> Configuration -> workdir -> version.

Key changes:
- Configuration.__post_init__ creates VcsEnvironment.from_env() fallback
- Configuration.env property provides typed access (always non-None)
- ScmWorkdir._subprocess_timeout/._hg_command use config.env directly
- _scm_version.meta() unconditionally reads config.env.source_date_epoch
- _should_write_to_source() receives config instead of standalone EnvReader
- LegacyParseWorkdir wraps config.parse in discover_workdir uniformly
- _compat_helpers simplified (env always present, no manual fallback)
- docs/integrators.md documents as breaking change with migration path

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude Opus 4 <claude@anthropic.com>
Override helpers (_read_pretended_version_for, _read_pretended_metadata_for)
now derive tool names from config.env.tool_names instead of hardcoding
("SETUPTOOLS_SCM", "VCS_VERSIONING"). This ensures other tools using
vcs-versioning get their own prefixes respected.

To support this, the setuptools-scm layer passes VcsEnvironment with
SETUPTOOLS_SCM prefix through the chain:
- setuptools_scm.get_version() creates VcsEnvironment.from_env("SETUPTOOLS_SCM")
- get_version() accepts _env parameter, forwarded to Configuration
- WorkDir._env allows test fixtures to inject the right env
- setuptools-scm conftest sets wd._env with SETUPTOOLS_SCM prefix

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude Opus 4 <claude@anthropic.com>
VcsEnvironment is now the single source of truth for all env-var
settings (timeout, hg command, epoch, ignore roots, debug). GlobalOverrides
becomes a thin wrapper that delegates to vcs_env for runtime values and
adds context-manager semantics for logging configuration.

- Add debug field, _env mapping, log_level(), and make_reader() to VcsEnvironment
- Convert GlobalOverrides from frozen dataclass to plain class with __slots__
- from_env() delegates entirely to VcsEnvironment.from_env() -- zero duplicated parsing
- from_active() supports overriding VcsEnvironment fields via dataclasses.replace()
- Backward-compatible properties (debug, subprocess_timeout, etc.) delegate to vcs_env

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude Opus 4 <claude@anthropic.com>
…resolution

Replace aliased class imports (e.g. VcsEnvironment as _VcsEnvironment)
under TYPE_CHECKING with module imports, then use module.Class in
annotations. Combined with from __future__ import annotations, this
gives proper type checking without Any escape hatches or runtime imports.

Also fix missing assert statements in test_nested_from_active_contexts.

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude Opus 4 <claude@anthropic.com>
VcsEnvironment now stores additional_loggers and provides
configure_logging() which sets up all loggers at the correct level.
GlobalOverrides.__enter__ delegates to vcs_env.configure_logging().

- Add additional_loggers field to VcsEnvironment
- Add configure_logging() method to VcsEnvironment
- Remove additional_loggers from GlobalOverrides __slots__/__init__
- Add backward-compat property delegating to vcs_env
- from_env() attaches loggers via dataclasses.replace
- Simplify _log._get_all_scm_loggers: no longer reads _active_overrides

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude Opus 4 <claude@anthropic.com>
Copilot AI review requested due to automatic review settings May 24, 2026 18:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors version inference to a workdir-centric, explicitly-threaded pipeline (VcsEnvironment -> Configuration -> workdir -> ScmVersion -> formatted string), replacing prior reliance on implicit/global state. It also introduces workdir discovery entry points and setuptools integration that can persist SCM metadata into egg-info for sdist fallbacks.

Changes:

  • Add VcsEnvironment and thread runtime settings through config/workdir/version instead of magic globals/ContextVars.
  • Replace parse-entry-point discovery with discover_workdir() + workdir objects (SCM + fallback workdirs), including JSON metadata read/write helpers.
  • Add setuptools-scm egg_info mixin + discovery factories to source tracked files from the discovered workdir and to write/read SCM metadata in egg-info.

Reviewed changes

Copilot reviewed 52 out of 52 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
docs/integrators.md Documents chained API + migration guidance for integrators.
docs/overrides.md Updates override priority and resolution pipeline documentation.
setuptools-scm/changelog.d/compat-shims.feature.md Notes compatibility shims for legacy workdir APIs.
setuptools-scm/pyproject.toml Registers vcs_versioning.discover_workdir EPs for setuptools-scm fallbacks.
setuptools-scm/src/setuptools_scm/_compat_helpers.py Adds internal helper for temporarily binding config to workdirs.
setuptools-scm/src/setuptools_scm/_get_version.py Creates VcsEnvironment with SETUPTOOLS_SCM prefix and forwards into core get_version.
setuptools-scm/src/setuptools_scm/_integration/_discover.py Adds setuptools-scm discovery factories (PKG-INFO, egg-info metadata).
setuptools-scm/src/setuptools_scm/_integration/build_py.py Extends inference data to carry the discovered workdir.
setuptools-scm/src/setuptools_scm/_integration/egg_info.py Adds egg_info mixin to use workdir-tracked files and write SCM metadata into egg-info.
setuptools-scm/src/setuptools_scm/_integration/setuptools.py Registers the new egg_info mixin command in setuptools integration.
setuptools-scm/src/setuptools_scm/_integration/version_inference.py Inlines the version pipeline and stores workdir in inference data.
setuptools-scm/src/setuptools_scm/git.py Adds backward-compatible shim GitWorkdir accepting optional config.
setuptools-scm/src/setuptools_scm/hg.py Adds backward-compatible shim HgWorkdir accepting optional config.
setuptools-scm/src/setuptools_scm/hg_git.py Adds backward-compatible shim GitWorkdirHgClient accepting optional config.
setuptools-scm/src/setuptools_scm/scm_workdir.py Adds backward-compatible shim ScmWorkdir accepting optional config.
setuptools-scm/testing_scm/conftest.py Ensures tests attach a SETUPTOOLS_SCM-prefixed VcsEnvironment to WorkDir.
setuptools-scm/testing_scm/test_basic_api.py Updates monkeypatch target to _resolve_version.
setuptools-scm/testing_scm/test_integration.py Adds MANIFEST.in exclusion integration test for sdist with egg_info mixin.
setuptools-scm/testing_scm/test_sdist_metadata.py Adds tests for egg-info metadata discovery and JSON round-tripping.
src/vcs_versioning_workspace/create_release_proposal.py Switches release proposal script to discover_workdir() workflow.
vcs-versioning/changelog.d/workdir-config-error.bugfix.md Notes improved error for unbound workdir config access.
vcs-versioning/pyproject.toml Replaces parse EPs with vcs_versioning.discover_workdir EPs.
vcs-versioning/src/vcs_versioning/init.py Exposes VcsEnvironment in public API.
vcs-versioning/src/vcs_versioning/_backends/_discover_vcs.py Adds smart-probe factory for git/hg/hg-git discovery.
vcs-versioning/src/vcs_versioning/_backends/_git.py Adds instance methods + workdir API methods (version, file listing, tracking).
vcs-versioning/src/vcs_versioning/_backends/_hg.py Adds instance methods + workdir API methods (version, file listing, tracking).
vcs-versioning/src/vcs_versioning/_backends/_hg_git.py Adds workdir API methods for hg-git hybrid.
vcs-versioning/src/vcs_versioning/_backends/_scm_workdir.py Refactors base SCM workdir type to hold project_root + config backref.
vcs-versioning/src/vcs_versioning/_cli/init.py Threads VcsEnvironment through CLI config creation.
vcs-versioning/src/vcs_versioning/_config.py Adds project_path, stores _env, and provides config.discover_workdir().
vcs-versioning/src/vcs_versioning/_environment.py Introduces VcsEnvironment for runtime settings and config construction.
vcs-versioning/src/vcs_versioning/_fallback_workdir.py Adds fallback workdir hierarchy (archival, PKG-INFO, static, metadata).
vcs-versioning/src/vcs_versioning/_file_finders/init.py Adjusts ignore-roots handling and keeps EP-based file finder dispatch.
vcs-versioning/src/vcs_versioning/_get_version_impl.py Refactors to _resolve_version() + _finalize() and uses workdir discovery.
vcs-versioning/src/vcs_versioning/_legacy_parse.py Extracts legacy parse EP dispatch and wraps config.parse as a workdir.
vcs-versioning/src/vcs_versioning/_log.py Removes dependency on active overrides for logger discovery.
vcs-versioning/src/vcs_versioning/_overrides.py Reads pretend overrides using config.env.tool_names and allows env injection.
vcs-versioning/src/vcs_versioning/_project_overrides.py Adds (currently unused) per-project override reader.
vcs-versioning/src/vcs_versioning/_run_cmd.py Reads timeout env vars directly for standalone callers.
vcs-versioning/src/vcs_versioning/_scm_metadata.py Adds JSON read/write helpers for SCM version + tracked file lists.
vcs-versioning/src/vcs_versioning/_scm_version.py Adds ScmVersion.format() and updates time derivation from SOURCE_DATE_EPOCH.
vcs-versioning/src/vcs_versioning/_test_utils.py Allows tests to inject an explicit environment into get_version().
vcs-versioning/src/vcs_versioning/_worktree_discovery.py Adds discover_workdir() entry-point based discovery pipeline.
vcs-versioning/testing_vcs/test_chain_api.py Adds tests for the chained env->config->workdir->version API.
vcs-versioning/testing_vcs/test_git.py Updates SOURCE_DATE_EPOCH handling in dirty-tag test.
vcs-versioning/testing_vcs/test_overrides_api.py Updates tests to use _active_overrides ContextVar directly.
vcs-versioning/testing_vcs/test_project_overrides.py Adds tests for per-project overrides file reader.
vcs-versioning/testing_vcs/test_project_path.py Adds tests for project_path and root->project_path bridging.
vcs-versioning/testing_vcs/test_scm_metadata.py Adds tests for SCM JSON metadata round-trip.
vcs-versioning/testing_vcs/test_workdir_api.py Adds tests for workdir method APIs and fallbacks.
vcs-versioning/testing_vcs/test_workdir_discovery.py Adds tests for discovery ordering, project_path verification, and factories.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/integrators.md
Comment thread vcs-versioning/src/vcs_versioning/_worktree_discovery.py Outdated
Comment thread vcs-versioning/src/vcs_versioning/_overrides.py
Comment thread vcs-versioning/src/vcs_versioning/_scm_metadata.py
Comment thread vcs-versioning/src/vcs_versioning/_fallback_workdir.py
Comment thread vcs-versioning/src/vcs_versioning/_backends/_hg.py
Comment thread vcs-versioning/src/vcs_versioning/_backends/_hg_git.py Outdated
Comment thread setuptools-scm/src/setuptools_scm/_integration/egg_info.py
RonnyPfannschmidt and others added 3 commits May 24, 2026 21:49
Implement lazy config.env with DeprecationWarning, resolve_runtime_env
for GlobalOverrides bridges, thread hg settings through workdirs, harden
metadata parsing, normalize sdist file lists, and thread tool_names into
TOML override reads.

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude Sonnet 4.5 <claude@anthropic.com>
…sion

Use forward-slash project_path on all platforms for stable comparisons,
and attach resolve_runtime_env() in get_version() without leaking imports
into Configuration kwargs (fixes Windows CI and mercurial warning test).

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude Sonnet 4.5 <claude@anthropic.com>
xmlsec excludes PyPy in its cibuildwheel config; the test passes on CPython.

Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Composer <composer@cursor.com>
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.

2 participants