Skip to content

Lazily import pygments in TerminalWriter - #14948

Closed
yoshi-taka wants to merge 1 commit into
pytest-dev:mainfrom
yoshi-taka:lazy-pygments-import
Closed

Lazily import pygments in TerminalWriter#14948
yoshi-taka wants to merge 1 commit into
pytest-dev:mainfrom
yoshi-taka:lazy-pygments-import

Conversation

@yoshi-taka

Copy link
Copy Markdown

Summary

pygments is no longer imported at module load of _pytest/_io/terminalwriter.py.
It is imported lazily, only when source is actually syntax-highlighted (e.g. when
rendering a traceback with color output). This avoids importing the relatively heavy
pygments package on runs that don't need it — notably runs without markup, such as
most CI executions — slightly reducing pytest's startup time.

Risk analysis / considerations

  • The function return annotations (Lexer, TerminalFormatter) are only needed for
    type checkers; the module already uses from __future__ import annotations, so they
    are never evaluated at runtime. They are imported under TYPE_CHECKING.
  • Highlighting remains gated by hasmarkup / code_highlight, so behavior is
    unchanged: runs without color output (typical CI) never import pygments, while
    color runs import it once on first traceback, as before.
  • No public API is changed. All pygments imports were moved into the methods that
    use them. Because pygments is no longer imported at module level, the previously
    qualified references (pygments.util.ClassNotFound, pygments.util.OptionError,
    pygments.highlight) could only be reached via the pygments module object, which
    would mean importing the whole pygments package inside each of those methods — the
    very thing this change is trying to avoid. So the names are imported directly (e.g.
    from pygments.util import ClassNotFound, from pygments import highlight) and used
    unqualified. This is a naming-only change — the caught exceptions and the highlighted
    output are identical.
  • A regression test asserts pygments is not imported when the module is loaded.

Notes on Python versions

  • pytest's minimum supported Python is 3.10, so we cannot use the 3.15 lazy import
    keyword (PEP 810, Final, shipped in 3.15): it is a 3.15-only soft keyword and would
    be a SyntaxError on older interpreters.
  • PEP 810 also offers a __lazy_modules__ module-level opt-in that is a no-op before
    3.15, but moving the imports into the functions that use them is simpler, works
    uniformly across all supported versions, and matches the approach already used in this
    codebase (e.g. unittest in debugging).
  • This change is also robust under the global PYTHON_LAZY_IMPORTS=all mode (already
    exercised by testing/test_assertrewrite.py): a function-local import resolves at
    first use regardless of the global lazy-import setting.

Checklist

  • Include new tests or update existing tests when applicable
    (added test_terminalwriter_import_does_not_import_pygments)
  • Create a new changelog file (changelog/14948.improvement.rst)
  • Add yourself to AUTHORS in alphabetical order — skipped (minor/internal change)
  • Documentation for new features — N/A (behavior-preserving refactor, no new feature)

pygments is no longer imported at module load of
_pytest._io.terminalwriter. It is imported only when source is actually
syntax-highlighted, avoiding the import on runs without markup (e.g. most CI
executions) and reducing startup time.

Considerations / risk analysis:
- The function return annotations (Lexer, TerminalFormatter) are only needed
  for type checkers; the module uses `from __future__ import annotations`, so
  they are never evaluated at runtime. They are imported under TYPE_CHECKING.
- Highlighting remains gated by `hasmarkup` / `code_highlight`, so behavior
  is unchanged: runs without color output (typical CI) never import pygments,
  while color runs import it once on first traceback, as before.
- No public API is changed. All pygments imports were moved into the methods
  that use them. Because `pygments` is no longer imported at module level, the
  previously qualified references (`pygments.util.ClassNotFound`,
  `pygments.util.OptionError`, `pygments.highlight`) could only be reached
  via the `pygments` module object, which would mean importing the whole
  `pygments` package inside each of those methods — the very thing this change
  is trying to avoid. So the names are imported directly (e.g.
  `from pygments.util import ClassNotFound`, `from pygments import highlight`)
  and used unqualified. This is a naming-only change — the caught exceptions and
  the highlighted output are identical.
- A regression test asserts pygments is not imported when the module is loaded.
@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided (automation) changelog entry is part of PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants