Terminal detection¶
A ProgressBar answers two separate questions about its output stream,
using different logic for each: is this a terminal at all (is_terminal,
controlling whether it overwrites one line or prints a new line per update),
and how many colors can it show (enable_colors). Both can be
overridden explicitly by constructor argument. Left alone, both fall back
to environment inspection in progressbar.env and
progressbar.bar. This page traces exactly what each check does
and in what order. The order is usually the answer to “why isn’t my bar
colored?”.
is_terminal: is this a terminal?¶
progressbar.env.is_terminal() resolves in this order, stopping at
the first step that produces a non-None result:
The
is_terminalconstructor argument, if you passed one explicitly.progressbar.env.is_ansi_terminal(), which itself checks, in order: whether this looks like a Jupyter kernel (JUPYTER_COLUMNS,JUPYTER_LINESorJPY_PARENT_PIDset) or a modern-enough PyCharm terminal (PYCHARM_HOSTED=1and not under pytest); then, if neither matched, whether the stream’s ownfd.isatty()is true andTERMmatches a known ANSI-terminal pattern, orANSICONis set, or (on Windows) the console mode reports processed output.The
PROGRESSBAR_IS_TERMINALenvironment variable (y/n,1/0,true/false, etc. – seeprogressbar.env.env_flag()), as an explicit override for situations auto-detection can’t cover.A bare
fd.isatty()call, falling back toFalseif the stream doesn’t support it (closed, detached, or not a real file object).
The default for line_breaks is derived from this: it defaults to
not is_terminal (via the PROGRESSBAR_LINE_BREAKS environment
variable, which itself defaults to that), so a bar piped to a file or tee
switches to one line per update automatically, without needing
--numeric/line_breaks=True set explicitly. Print one line per update instead of overwriting
shows the override in action.
Color depth: two separate layers¶
Color support is resolved by two different pieces of logic that run at different times, and they scan the same two environment variables in opposite order.
Layer 1 – the module-level ceiling¶
When progressbar.env is first imported, it computes
COLOR_SUPPORT, a ColorSupport value, once,
via ColorSupport.from_env().
This is the ceiling: the best color depth this environment could support,
independent of any particular bar.
If this looks like a Jupyter kernel, the answer is immediately
XTERM_TRUECOLOR.Otherwise, on Windows, the console mode is probed instead of the environment variables below.
Otherwise, four variables are scanned in this order:
FORCE_COLOR,PROGRESSBAR_ENABLE_COLORS,COLORTERM,TERM. For each one that is set, a literaltruecolor/24bitvalue or a generic truthy flag (1,yes,true, …) wins immediately with truecolor and stops the scan. A value naming a terminal that is always truecolor-capable (xterm-kitty,xterm-ghostty) raises the running maximum to truecolor, a value containing256raises it to 256-color, and a value matching a known ANSI terminal pattern raises it to 16-color. Any other value is ignored and the scan continues. The result is the highest depth implied by any of the four:COLORTERM=truecoloroverridesTERM=xterm-256coloreven thoughCOLORTERMandTERMare just two entries scanned in order, not a simple first-match-wins.
Layer 2 – the per-bar on/off decision¶
Separately, when a ProgressBar is constructed, enable_colors=None
(the default) triggers DefaultFdMixin._determine_enable_colors(), which decides
whether this bar uses color at all, and does so by scanning three
signals in this order: PROGRESSBAR_ENABLE_COLORS,
FORCE_COLOR, then is_ansi_terminal (see above). The first of the
three that is not None wins: if truthy, the bar uses the Layer 1
ceiling (COLOR_SUPPORT). If falsy, it uses no color at all
(ColorSupport.NONE). If none of the three produced an answer, the
default is also no color.
Note the reversal: Layer 1 checks FORCE_COLOR before
PROGRESSBAR_ENABLE_COLORS, Layer 2 the other way around. The order
only changes the outcome when both variables are set to conflicting
plain-boolean values.
Passing enable_colors explicitly (True, False, or a specific
ColorSupport member) to the ProgressBar
constructor skips both layers for that bar.
COLUMNS: a different mechanism entirely¶
COLUMNS is not part of either color check above – it plays no role in
progressbar.env at all. It affects the terminal width
(term_width), through a completely separate path: when a bar is created
without an explicit term_width, or on a resize signal,
ResizableMixin._handle_resize() calls
progressbar.utils.get_terminal_size() (re-exported from
python-utils). That function
tries, in order, an IPython-specific check, then Python’s
shutil.get_terminal_size(), then its own explicit COLUMNS/
LINES read, then a few further platform-specific fallbacks, ending in a
hard-coded 79, 24.
The practical effect: since shutil.get_terminal_size() itself
checks the COLUMNS/LINES environment variables before querying the
real terminal, a valid positive COLUMNS value takes priority over the
actual terminal width whenever it’s set – which is exactly the mechanism
scripts use to pin a bar’s width in a non-interactive or resized-oddly
environment.