Skip to content

Commit 469c768

Browse files
RonnyPfannschmidtCursor AIclaude
committed
refactor: move additional_loggers and logging setup to VcsEnvironment
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>
1 parent 084c59e commit 469c768

3 files changed

Lines changed: 60 additions & 55 deletions

File tree

vcs-versioning/src/vcs_versioning/_environment.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@
1515
import dataclasses
1616
import logging
1717
import os
18-
from collections.abc import Mapping
18+
from collections.abc import Mapping, MutableMapping
19+
from datetime import datetime
1920
from typing import TYPE_CHECKING, Any, Literal
2021

2122
if TYPE_CHECKING:
23+
from pytest import MonkeyPatch
24+
2225
from . import _config, overrides
2326

2427
log = logging.getLogger(__name__)
@@ -61,13 +64,23 @@ class VcsEnvironment:
6164
_env: Mapping[str, str] = dataclasses.field(
6265
default_factory=lambda: os.environ, repr=False, compare=False
6366
)
67+
additional_loggers: tuple[logging.Logger, ...] = ()
6468

6569
def log_level(self) -> int:
6670
"""Logging level derived from the debug setting."""
6771
if self.debug is False:
6872
return logging.WARNING
6973
return self.debug
7074

75+
def configure_logging(self) -> None:
76+
"""Configure all loggers for this environment's debug level."""
77+
from ._log import _configure_loggers
78+
79+
_configure_loggers(
80+
log_level=self.log_level(),
81+
additional_loggers=list(self.additional_loggers),
82+
)
83+
7184
def make_reader(self, dist_name: str | None = None) -> overrides.EnvReader:
7285
"""Create an :class:`EnvReader` configured with this env's tool names."""
7386
from .overrides import EnvReader
@@ -76,6 +89,36 @@ def make_reader(self, dist_name: str | None = None) -> overrides.EnvReader:
7689
tools_names=self.tool_names, env=self._env, dist_name=dist_name
7790
)
7891

92+
def source_epoch_or_utc_now(self) -> datetime:
93+
"""Get datetime from SOURCE_DATE_EPOCH or current UTC time."""
94+
from datetime import timezone
95+
96+
if self.source_date_epoch is not None:
97+
return datetime.fromtimestamp(self.source_date_epoch, timezone.utc)
98+
return datetime.now(timezone.utc)
99+
100+
def export(self, target: MutableMapping[str, str] | MonkeyPatch) -> None:
101+
"""Export settings to environment variables using ``tool_names[0]`` as prefix."""
102+
103+
def set_var(key: str, value: str) -> None:
104+
if isinstance(target, MutableMapping):
105+
target[key] = value
106+
else:
107+
target.setenv(key, value)
108+
109+
if self.source_date_epoch is not None:
110+
set_var("SOURCE_DATE_EPOCH", str(self.source_date_epoch))
111+
112+
prefix = self.tool_names[0]
113+
114+
if self.debug is False:
115+
set_var(f"{prefix}_DEBUG", "0")
116+
else:
117+
set_var(f"{prefix}_DEBUG", str(self.debug))
118+
119+
set_var(f"{prefix}_SUBPROCESS_TIMEOUT", str(self.subprocess_timeout))
120+
set_var(f"{prefix}_HG_COMMAND", self.hg_command)
121+
79122
@classmethod
80123
def from_env(
81124
cls,

vcs-versioning/src/vcs_versioning/_log.py

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,13 @@ def _get_all_scm_loggers(
2828
) -> list[logging.Logger]:
2929
"""Get all SCM-related loggers that need configuration.
3030
31-
Always configures vcs_versioning logger.
32-
If additional_loggers is provided, also configures those loggers.
33-
If not provided, tries to get them from active GlobalOverrides context.
31+
Always includes the ``vcs_versioning`` logger.
32+
If *additional_loggers* is provided, those are appended.
3433
"""
3534
loggers = [logging.getLogger("vcs_versioning")]
3635

3736
if additional_loggers is not None:
3837
loggers.extend(additional_loggers)
39-
else:
40-
# Try to get additional loggers from active overrides context
41-
try:
42-
from .overrides import _active_overrides
43-
44-
overrides = _active_overrides.get()
45-
if overrides is not None:
46-
loggers.extend(overrides.additional_loggers)
47-
except ImportError:
48-
# During early initialization, overrides module might not be available yet
49-
pass
5038

5139
return loggers
5240

vcs-versioning/src/vcs_versioning/overrides.py

Lines changed: 14 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -262,19 +262,17 @@ class GlobalOverrides:
262262
version = get_version(...)
263263
"""
264264

265-
__slots__ = ("vcs_env", "tool", "dist_name", "additional_loggers", "_token")
265+
__slots__ = ("vcs_env", "tool", "dist_name", "_token")
266266

267267
def __init__(
268268
self,
269269
vcs_env: _environment.VcsEnvironment,
270270
tool: str,
271271
dist_name: str | None = None,
272-
additional_loggers: tuple[logging.Logger, ...] = (),
273272
) -> None:
274273
self.vcs_env = vcs_env
275274
self.tool = tool
276275
self.dist_name = dist_name
277-
self.additional_loggers = additional_loggers
278276
self._token: contextvars.Token[GlobalOverrides | None] | None = None
279277

280278
# ------------------------------------------------------------------
@@ -301,6 +299,10 @@ def source_date_epoch(self) -> int | None:
301299
def ignore_vcs_roots(self) -> list[str]:
302300
return list(self.vcs_env.ignore_vcs_roots)
303301

302+
@property
303+
def additional_loggers(self) -> tuple[logging.Logger, ...]:
304+
return self.vcs_env.additional_loggers
305+
304306
@property
305307
def env_reader(self) -> EnvReader:
306308
return self.vcs_env.make_reader(dist_name=self.dist_name)
@@ -332,6 +334,8 @@ def from_env(
332334
Returns:
333335
GlobalOverrides instance ready to use as context manager
334336
"""
337+
import dataclasses as dc
338+
335339
from ._environment import VcsEnvironment
336340

337341
vcs_env = VcsEnvironment.from_env(tool, env=env)
@@ -344,11 +348,13 @@ def from_env(
344348
else:
345349
logger_tuple = ()
346350

351+
if logger_tuple:
352+
vcs_env = dc.replace(vcs_env, additional_loggers=logger_tuple)
353+
347354
return cls(
348355
vcs_env=vcs_env,
349356
tool=tool,
350357
dist_name=dist_name,
351-
additional_loggers=logger_tuple,
352358
)
353359

354360
# ------------------------------------------------------------------
@@ -358,14 +364,7 @@ def from_env(
358364
def __enter__(self) -> GlobalOverrides:
359365
"""Enter context: set this as the active override and configure logging."""
360366
self._token = _active_overrides.set(self)
361-
362-
from ._log import _configure_loggers
363-
364-
_configure_loggers(
365-
log_level=self.vcs_env.log_level(),
366-
additional_loggers=list(self.additional_loggers),
367-
)
368-
367+
self.vcs_env.configure_logging()
369368
return self
370369

371370
def __exit__(self, *exc_info: Any) -> None:
@@ -384,12 +383,7 @@ def log_level(self) -> int:
384383

385384
def source_epoch_or_utc_now(self) -> datetime:
386385
"""Get datetime from SOURCE_DATE_EPOCH or current UTC time."""
387-
from datetime import datetime, timezone
388-
389-
if self.source_date_epoch is not None:
390-
return datetime.fromtimestamp(self.source_date_epoch, timezone.utc)
391-
else:
392-
return datetime.now(timezone.utc)
386+
return self.vcs_env.source_epoch_or_utc_now()
393387

394388
@classmethod
395389
def from_active(cls, **changes: Any) -> GlobalOverrides:
@@ -418,14 +412,13 @@ def from_active(cls, **changes: Any) -> GlobalOverrides:
418412

419413
new_tool = changes.pop("tool", active.tool)
420414
new_dist_name = changes.pop("dist_name", active.dist_name)
421-
new_loggers = changes.pop("additional_loggers", active.additional_loggers)
422415

423416
if new_tool != active.tool:
424417
vcs_env = VcsEnvironment.from_env(new_tool, env=active.vcs_env._env)
425418
else:
426419
vcs_env = active.vcs_env
427420

428-
# Remaining changes are VcsEnvironment field overrides
421+
# Remaining changes are VcsEnvironment field overrides (includes additional_loggers)
429422
vcs_env_fields = {f.name for f in dc.fields(VcsEnvironment)}
430423
env_changes = {k: v for k, v in changes.items() if k in vcs_env_fields}
431424
if env_changes:
@@ -435,7 +428,6 @@ def from_active(cls, **changes: Any) -> GlobalOverrides:
435428
vcs_env=vcs_env,
436429
tool=new_tool,
437430
dist_name=new_dist_name,
438-
additional_loggers=new_loggers,
439431
)
440432

441433
def export(self, target: MutableMapping[str, str] | MonkeyPatch) -> None:
@@ -444,25 +436,7 @@ def export(self, target: MutableMapping[str, str] | MonkeyPatch) -> None:
444436
Can export to either a dict-like environment or a pytest monkeypatch fixture.
445437
This is useful for tests that need to propagate overrides to subprocesses.
446438
"""
447-
448-
def set_var(key: str, value: str) -> None:
449-
if isinstance(target, MutableMapping):
450-
target[key] = value
451-
else:
452-
target.setenv(key, value)
453-
454-
if self.source_date_epoch is not None:
455-
set_var("SOURCE_DATE_EPOCH", str(self.source_date_epoch))
456-
457-
prefix = self.tool
458-
459-
if self.debug is False:
460-
set_var(f"{prefix}_DEBUG", "0")
461-
else:
462-
set_var(f"{prefix}_DEBUG", str(self.debug))
463-
464-
set_var(f"{prefix}_SUBPROCESS_TIMEOUT", str(self.subprocess_timeout))
465-
set_var(f"{prefix}_HG_COMMAND", self.hg_command)
439+
self.vcs_env.export(target)
466440

467441

468442
# Thread-local storage for active global overrides

0 commit comments

Comments
 (0)