Skip to content

Commit d20fd09

Browse files
coredumperrorhynek
andauthored
Cease all use of Colorama on non-Windows systems. (#345)
* Color codes are no longer defined as empty if colorama isn't installed. * Removed all use of colorama on non-Windows systems. * Updated the changelog. * Fixes suggested by hynek * Add pragma: no cover to if _IS_WINDOWS: check. Co-authored-by: Hynek Schlawack <[email protected]> Co-authored-by: Hynek Schlawack <[email protected]>
1 parent f18859b commit d20fd09

File tree

6 files changed

+59
-47
lines changed

6 files changed

+59
-47
lines changed

CHANGELOG.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ Changes:
3434
This only works if ``format_exc_info`` is **absent** in the processor chain.
3535
- ``structlog.threadlocal.get_threadlocal()`` and ``structlog.contextvars.get_threadlocal()`` can now be used to get a copy of the current thread-local/context-local context that has been bound using ``structlog.threadlocal.bind_threadlocal()`` and ``structlog.contextvars.bind_contextvars()``.
3636
- ``structlog.threadlocal.get_merged_threadlocal(bl)`` and ``structlog.contextvars.get_merged_contextvars(bl)`` do the same, but also merge the context from a bound logger *bl*.
37-
37+
- All use of ``colorama`` on non-Windows systems has been excised.
38+
Colors will now be enabled by default on non-Windows systems even if colorama is not installed, as it's not necessary on such systems.
3839

3940
----
4041

docs/development.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Development
33

44
To make development a more pleasurable experience, ``structlog`` comes with the `structlog.dev` module.
55

6-
The highlight is `structlog.dev.ConsoleRenderer` that offers nicely aligned and colorful (requires the `colorama package <https://pypi.org/project/colorama/>`_ installed) console output.
6+
The highlight is `structlog.dev.ConsoleRenderer` that offers nicely aligned and colorful (requires the `colorama package <https://pypi.org/project/colorama/>`_ if on Windows) console output.
77
If the `better-exceptions <https://github.com/Qix-/better-exceptions>`_ package is installed, it will also pretty-print exceptions with helpful contextual data.
88

99
.. figure:: _static/console_renderer.png

docs/getting-started.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ Here, ``structlog`` takes full advantage of its hopefully useful default setting
3434
- All keywords are formatted using `structlog.dev.ConsoleRenderer`.
3535
That in turn uses `repr` to serialize all values to strings.
3636
Thus, it's easy to add support for logging of your own objects\ [*]_.
37-
- If you have `colorama <https://pypi.org/project/colorama/>`_ installed, it's rendered in nice `colors <development>`.
37+
- On Windows, if you have `colorama <https://pypi.org/project/colorama/>`_ installed, it's rendered in nice `colors <development>`.
38+
Other OSes do not need colorama for nice colors.
3839
- If you have `better-exceptions <https://github.com/qix-/better-exceptions>`_ installed, exceptions will be rendered in colors and with additional helpful information.
3940

4041
It should be noted that even in most complex logging setups the example would still look just like that thanks to `configuration`.

src/structlog/_config.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
from ._log_levels import make_filtering_bound_logger
2525
from ._loggers import PrintLoggerFactory
26-
from .dev import ConsoleRenderer, _has_colorama, set_exc_info
26+
from .dev import ConsoleRenderer, _use_colors, set_exc_info
2727
from .processors import StackInfoRenderer, TimeStamper, add_log_level
2828
from .types import BindableLogger, Context, Processor, WrappedLogger
2929

@@ -39,7 +39,7 @@
3939
set_exc_info,
4040
TimeStamper(fmt="%Y-%m-%d %H:%M.%S", utc=False),
4141
ConsoleRenderer(
42-
colors=_has_colorama and sys.stdout is not None and sys.stdout.isatty()
42+
colors=_use_colors and sys.stdout is not None and sys.stdout.isatty()
4343
),
4444
]
4545
_BUILTIN_DEFAULT_CONTEXT_CLASS = cast(Type[Context], dict)

src/structlog/dev.py

+41-34
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ def _pad(s: str, length: int) -> str:
4747

4848

4949
if colorama is not None:
50-
_has_colorama = True
51-
5250
RESET_ALL = colorama.Style.RESET_ALL
5351
BRIGHT = colorama.Style.BRIGHT
5452
DIM = colorama.Style.DIM
@@ -60,11 +58,27 @@ def _pad(s: str, length: int) -> str:
6058
GREEN = colorama.Fore.GREEN
6159
RED_BACK = colorama.Back.RED
6260
else:
63-
_has_colorama = False
64-
65-
RESET_ALL = (
66-
BRIGHT
67-
) = DIM = RED = BLUE = CYAN = MAGENTA = YELLOW = GREEN = RED_BACK = ""
61+
# These are the same values as the colorama color codes. Redefining them
62+
# here allows users to specify that they want color without having to
63+
# install colorama, which is only supposed to be necessary in Windows.
64+
RESET_ALL = "\033[0m"
65+
BRIGHT = "\033[1m"
66+
DIM = "\033[2m"
67+
RED = "\033[31m"
68+
BLUE = "\033[34m"
69+
CYAN = "\033[36m"
70+
MAGENTA = "\033[35m"
71+
YELLOW = "\033[33m"
72+
GREEN = "\033[32m"
73+
RED_BACK = "\033[41m"
74+
75+
76+
if _IS_WINDOWS: # pragma: no cover
77+
# On Windows, use colors by default only if colorama is installed.
78+
_use_colors = colorama is not None
79+
else:
80+
# On other OSes, use colors by default.
81+
_use_colors = True
6882

6983

7084
class _Styles(Protocol):
@@ -174,34 +188,39 @@ class ConsoleRenderer:
174188
`structlog.processors.format_exc_info` processor together with
175189
`ConsoleRenderer` anymore! It will keep working, but you can't have
176190
pretty exceptions and a warning will be raised if you ask for them.
191+
.. versionchanged:: 21.3 The colors keyword now defaults to True on
192+
non-Windows systems, and either True or False in Windows depending on
193+
whether colorama is installed.
177194
"""
178195

179196
def __init__(
180197
self,
181198
pad_event: int = _EVENT_WIDTH,
182-
colors: bool = _has_colorama,
199+
colors: bool = _use_colors,
183200
force_colors: bool = False,
184201
repr_native_str: bool = False,
185202
level_styles: Optional[Styles] = None,
186203
pretty_exceptions: bool = (better_exceptions is not None),
187204
):
188-
self._force_colors = self._init_colorama = False
189205
styles: Styles
190-
if colors is True:
191-
if colorama is None:
192-
raise SystemError(
193-
_MISSING.format(
194-
who=self.__class__.__name__ + " with `colors=True`",
195-
package="colorama",
196-
)
197-
)
198-
206+
if colors:
199207
if _IS_WINDOWS: # pragma: no cover
200-
_init_colorama(self._force_colors)
201-
else:
202-
self._init_colorama = True
208+
# On Windows, we can't do colorful output without colorama.
209+
if colorama is None:
210+
classname = self.__class__.__name__
211+
raise SystemError(
212+
_MISSING.format(
213+
who=classname + " with `colors=True`",
214+
package="colorama",
215+
)
216+
)
217+
# Colorama must be init'd on Windows, but must NOT be
218+
# init'd on other OSes, because it can break colors.
203219
if force_colors:
204-
self._force_colors = True
220+
colorama.deinit()
221+
colorama.init(strip=False)
222+
else:
223+
colorama.init()
205224

206225
styles = _ColorfulStyles
207226
else:
@@ -241,10 +260,6 @@ def __call__(
241260
self, logger: WrappedLogger, name: str, event_dict: EventDict
242261
) -> str:
243262

244-
# Initialize lazily to prevent import side-effects.
245-
if self._init_colorama:
246-
_init_colorama(self._force_colors)
247-
self._init_colorama = False
248263
sio = StringIO()
249264

250265
ts = event_dict.pop("timestamp", None)
@@ -366,14 +381,6 @@ def get_default_level_styles(colors: bool = True) -> Any:
366381
}
367382

368383

369-
def _init_colorama(force: bool) -> None:
370-
if force:
371-
colorama.deinit()
372-
colorama.init(strip=False)
373-
else:
374-
colorama.init()
375-
376-
377384
_SENTINEL = object()
378385

379386

tests/test_dev.py

+11-8
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def test_negative(self):
2626

2727
@pytest.fixture(name="cr")
2828
def _cr():
29-
return dev.ConsoleRenderer(colors=dev._has_colorama)
29+
return dev.ConsoleRenderer(colors=dev._use_colors)
3030

3131

3232
@pytest.fixture(name="styles")
@@ -47,11 +47,14 @@ def _unpadded(styles):
4747

4848

4949
class TestConsoleRenderer:
50-
@pytest.mark.skipif(dev._has_colorama, reason="Colorama must be missing.")
50+
@pytest.mark.skipif(dev.colorama, reason="Colorama must be missing.")
51+
@pytest.mark.skipif(
52+
not dev._IS_WINDOWS, reason="Must be running on Windows."
53+
)
5154
def test_missing_colorama(self):
5255
"""
5356
ConsoleRenderer(colors=True) raises SystemError on initialization if
54-
colorama is missing.
57+
colorama is missing and _IS_WINDOWS is True.
5558
"""
5659
with pytest.raises(SystemError) as e:
5760
dev.ConsoleRenderer(colors=True)
@@ -117,11 +120,11 @@ def test_init_accepts_overriding_levels(self, styles, padded):
117120
Stdlib levels are rendered aligned, in brackets, and color coded.
118121
"""
119122
my_styles = dev.ConsoleRenderer.get_default_level_styles(
120-
colors=dev._has_colorama
123+
colors=dev._use_colors
121124
)
122125
my_styles["MY_OH_MY"] = my_styles["critical"]
123126
cr = dev.ConsoleRenderer(
124-
colors=dev._has_colorama, level_styles=my_styles
127+
colors=dev._use_colors, level_styles=my_styles
125128
)
126129

127130
# this would blow up if the level_styles override failed
@@ -234,7 +237,7 @@ def test_pad_event_param(self, styles):
234237
"""
235238
`pad_event` parameter works.
236239
"""
237-
rv = dev.ConsoleRenderer(42, dev._has_colorama)(
240+
rv = dev.ConsoleRenderer(42, dev._use_colors)(
238241
None, None, {"event": "test", "foo": "bar"}
239242
)
240243

@@ -350,7 +353,7 @@ def test_colorama_force_colors(self, styles, padded):
350353
If force_colors is True, use colors even if the destination is non-tty.
351354
"""
352355
cr = dev.ConsoleRenderer(
353-
colors=dev._has_colorama, force_colors=dev._has_colorama
356+
colors=dev._use_colors, force_colors=dev._use_colors
354357
)
355358

356359
rv = cr(
@@ -374,7 +377,7 @@ def test_colorama_force_colors(self, styles, padded):
374377
+ styles.reset
375378
) == rv
376379

377-
assert not dev._has_colorama or dev._ColorfulStyles is cr._styles
380+
assert not dev._use_colors or dev._ColorfulStyles is cr._styles
378381

379382
@pytest.mark.parametrize("rns", [True, False])
380383
def test_repr_native_str(self, rns):

0 commit comments

Comments
 (0)