ansilogs 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ansilogs/__init__.py ADDED
@@ -0,0 +1,173 @@
1
+ """
2
+ Colored, single-line log output for command-line tools.
3
+
4
+ A small, dependency-free replacement for ``coloredlogs.install()``::
5
+
6
+ import logging
7
+ import ansilogs
8
+
9
+ ansilogs.install(logging.DEBUG)
10
+
11
+ Each record is one line, ``<time> <host> <name>[<pid>] <LEVEL> <message>``, colored with ANSI
12
+ escape codes when stderr is a terminal (honoring ``NO_COLOR`` and ``FORCE_COLOR``, and switching
13
+ Windows consoles to ANSI processing). The handler writes to whatever ``sys.stderr`` is at the
14
+ time of each record, so redirections (test capture, live progress displays) are respected.
15
+ """
16
+
17
+ import logging
18
+ import os
19
+ import socket
20
+ import sys
21
+ from typing import IO, Optional, Union
22
+
23
+ __all__ = ["DATE_FORMAT", "ColoredFormatter", "StderrHandler", "install", "set_level", "supports_color"]
24
+
25
+ DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
26
+
27
+ _RESET = "\x1b[0m"
28
+ _TIME = "32" # green
29
+ _HOST = "35" # magenta
30
+ _NAME = "34" # blue
31
+ _LEVEL_NAME = "1;30" # bold black
32
+ _MESSAGE_BY_LEVEL = {
33
+ logging.DEBUG: "32", # green
34
+ logging.WARNING: "33", # yellow
35
+ logging.ERROR: "31", # red
36
+ logging.CRITICAL: "1;31", # bold red
37
+ }
38
+
39
+ # SetConsoleMode flag that makes a Windows console interpret ANSI escape sequences.
40
+ _ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
41
+ _STD_ERROR_HANDLE = -12
42
+
43
+
44
+ def _sgr(code: str, text: str) -> str:
45
+ return f"\x1b[{code}m{text}{_RESET}"
46
+
47
+
48
+ class ColoredFormatter(logging.Formatter):
49
+ """
50
+ ``<time> <host> <name>[<pid>] <LEVEL> <message>``, with ANSI colors when ``colors`` is set.
51
+
52
+ The colors are baked into the format string once; per record only the message is wrapped in
53
+ its level's color. Tracebacks and stack info are appended uncolored.
54
+ """
55
+
56
+ def __init__(self, colors: bool = True, hostname: Optional[str] = None) -> None:
57
+ host = (hostname or socket.gethostname()).replace("%", "%%")
58
+ fields = [
59
+ ("%(asctime)s", _TIME),
60
+ (host, _HOST),
61
+ ("%(name)s[%(process)d]", _NAME),
62
+ ("%(levelname)s", _LEVEL_NAME),
63
+ ]
64
+ prefix = " ".join(_sgr(code, text) if colors else text for text, code in fields)
65
+ super().__init__(f"{prefix} %(message)s", datefmt=DATE_FORMAT)
66
+ self.colors = colors
67
+
68
+ def formatMessage(self, record: logging.LogRecord) -> str:
69
+ code = _MESSAGE_BY_LEVEL.get(record.levelno) if self.colors else None
70
+ if code is None:
71
+ return super().formatMessage(record)
72
+ message = record.message
73
+ record.message = _sgr(code, message)
74
+ try:
75
+ return super().formatMessage(record)
76
+ finally:
77
+ record.message = message
78
+
79
+
80
+ class StderrHandler(logging.Handler):
81
+ """Writes each record to the current ``sys.stderr`` (looked up per record, never cached)."""
82
+
83
+ terminator = "\n"
84
+
85
+ def emit(self, record: logging.LogRecord) -> None:
86
+ try:
87
+ line = self.format(record)
88
+ stream = sys.stderr
89
+ if stream is None:
90
+ return
91
+ stream.write(line + self.terminator)
92
+ stream.flush()
93
+ except RecursionError:
94
+ raise
95
+ except Exception:
96
+ self.handleError(record)
97
+
98
+
99
+ def _enable_windows_ansi(stream: IO[str]) -> bool:
100
+ """Switch the Windows console behind ``stream`` to ANSI processing; False if it isn't a console."""
101
+ if sys.platform != "win32":
102
+ return True
103
+ import ctypes
104
+ from ctypes import wintypes
105
+
106
+ if stream is not sys.__stderr__:
107
+ # Only the process' own stderr maps to the standard error console handle.
108
+ return False
109
+ kernel32 = ctypes.windll.kernel32
110
+ handle = kernel32.GetStdHandle(_STD_ERROR_HANDLE)
111
+ mode = wintypes.DWORD()
112
+ if not kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
113
+ return False
114
+ if mode.value & _ENABLE_VIRTUAL_TERMINAL_PROCESSING:
115
+ return True
116
+ return bool(kernel32.SetConsoleMode(handle, mode.value | _ENABLE_VIRTUAL_TERMINAL_PROCESSING))
117
+
118
+
119
+ def supports_color(stream: Optional[IO[str]] = None) -> bool:
120
+ """
121
+ Whether to color output written to ``stream`` (default: ``sys.stderr``).
122
+
123
+ ``NO_COLOR`` (non-empty) disables colors and ``FORCE_COLOR`` (non-empty) forces them;
124
+ otherwise the stream must be a terminal, and on Windows a console that accepts ANSI codes.
125
+ """
126
+ if os.environ.get("NO_COLOR"):
127
+ return False
128
+ if os.environ.get("FORCE_COLOR"):
129
+ return True
130
+ stream = sys.stderr if stream is None else stream
131
+ if stream is None:
132
+ return False
133
+ isatty = getattr(stream, "isatty", None)
134
+ try:
135
+ if isatty is None or not isatty():
136
+ return False
137
+ except ValueError: # closed stream
138
+ return False
139
+ return _enable_windows_ansi(stream)
140
+
141
+
142
+ def _installed_handler() -> Optional[StderrHandler]:
143
+ return next((h for h in logging.getLogger().handlers if isinstance(h, StderrHandler)), None)
144
+
145
+
146
+ def install(level: Union[int, str] = logging.INFO, colors: Optional[bool] = None) -> None:
147
+ """
148
+ Attach a colored stderr handler to the root logger and show records from ``level`` up.
149
+
150
+ Calling it again reconfigures the existing handler instead of adding another. ``colors``
151
+ defaults to :func:`supports_color` for the current ``sys.stderr``.
152
+ """
153
+ handler = _installed_handler()
154
+ if handler is None:
155
+ handler = StderrHandler()
156
+ logging.getLogger().addHandler(handler)
157
+ handler.setFormatter(ColoredFormatter(colors=supports_color() if colors is None else colors))
158
+ set_level(level)
159
+
160
+
161
+ def set_level(level: Union[int, str]) -> None:
162
+ """
163
+ Show records from ``level`` up: sets the handler's level (installing it if needed) and relaxes
164
+ the root logger's level if it would filter those records out.
165
+ """
166
+ handler = _installed_handler()
167
+ if handler is None:
168
+ install(level)
169
+ return
170
+ handler.setLevel(level)
171
+ root = logging.getLogger()
172
+ if root.getEffectiveLevel() > handler.level:
173
+ root.setLevel(handler.level)
ansilogs/_version.py ADDED
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '1.0.0'
22
+ __version_tuple__ = version_tuple = (1, 0, 0)
23
+
24
+ __commit_id__ = commit_id = 'gaa8e027c2'
ansilogs/py.typed ADDED
File without changes
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: ansilogs
3
+ Version: 1.0.0
4
+ Summary: Colored, single-line log output for command-line tools: a dependency-free coloredlogs.install() replacement
5
+ Author-email: doronz88 <doron88@gmail.com>
6
+ Maintainer-email: doronz88 <doron88@gmail.com>
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026 doronz88
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ Project-URL: Homepage, https://github.com/doronz88/ansilogs
30
+ Project-URL: Bug Reports, https://github.com/doronz88/ansilogs/issues
31
+ Keywords: logging,color,ansi,terminal,cli
32
+ Classifier: Development Status :: 5 - Production/Stable
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Programming Language :: Python :: 3.9
36
+ Classifier: Programming Language :: Python :: 3.10
37
+ Classifier: Programming Language :: Python :: 3.11
38
+ Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: Programming Language :: Python :: 3.13
40
+ Classifier: Programming Language :: Python :: 3.14
41
+ Classifier: Programming Language :: Python :: 3.15
42
+ Classifier: Programming Language :: Python :: 3 :: Only
43
+ Classifier: Topic :: System :: Logging
44
+ Classifier: Typing :: Typed
45
+ Requires-Python: >=3.9
46
+ Description-Content-Type: text/markdown
47
+ License-File: LICENSE
48
+ Provides-Extra: test
49
+ Requires-Dist: pytest; extra == "test"
50
+ Dynamic: license-file
51
+
52
+ # ansilogs
53
+
54
+ Colored, single-line log output for command-line tools. A small, dependency-free replacement
55
+ for `coloredlogs.install()` with the same default look.
56
+
57
+ ```python
58
+ import logging
59
+
60
+ import ansilogs
61
+
62
+ ansilogs.install(logging.DEBUG)
63
+ logging.getLogger(__name__).warning("careful")
64
+ ```
65
+
66
+ ```
67
+ 2026-10-19 09:35:12 my-host my_tool.cli[4242] WARNING careful
68
+ ```
69
+
70
+ - One line per record: `<time> <host> <name>[<pid>] <LEVEL> <message>`; tracebacks follow
71
+ uncolored.
72
+ - Colors only when stderr is a terminal. `NO_COLOR` (non-empty) disables them, `FORCE_COLOR`
73
+ (non-empty) forces them. On Windows the console is switched to ANSI processing; if that
74
+ fails, output is plain.
75
+ - Records go to whatever `sys.stderr` is at the time (test capture and live progress displays
76
+ that redirect stderr keep working).
77
+
78
+ ## API
79
+
80
+ - `install(level=logging.INFO, colors=None)`: attach the handler to the root logger (once) and
81
+ show records from `level` up. `colors=None` means `supports_color()`.
82
+ - `set_level(level)`: change the shown level (installs the handler if needed).
83
+ - `supports_color(stream=None)`: the color decision used by `install()`.
84
+ - `ColoredFormatter(colors=True, hostname=None)` and `StderrHandler` for custom setups.
85
+
86
+ ## Migrating from coloredlogs
87
+
88
+ ```diff
89
+ -import coloredlogs
90
+ -coloredlogs.install(level=logging.DEBUG)
91
+ +import ansilogs
92
+ +ansilogs.install(logging.DEBUG)
93
+ ```
94
+
95
+ `coloredlogs.set_level(level)` becomes `ansilogs.set_level(level)`. `COLOREDLOGS_*` environment
96
+ variables and custom field/level styles are not supported.
@@ -0,0 +1,8 @@
1
+ ansilogs/__init__.py,sha256=nBjnREOzvlPmODpcCawH07WIFg_YB3LytcPaQSSiKQc,5959
2
+ ansilogs/_version.py,sha256=AJPm44ajQjO1SQUg8c6rfc3xjhbaff-iRq7PTfDEQd8,528
3
+ ansilogs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ ansilogs-1.0.0.dist-info/licenses/LICENSE,sha256=yLCk8rOuuGsdobWBq_7cZBbX5LgGqK7cwF081kL9AzU,1065
5
+ ansilogs-1.0.0.dist-info/METADATA,sha256=D92rIu5-iRhYyDcza_oUz3zfJeEPme839TMXpR6k60g,4008
6
+ ansilogs-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
7
+ ansilogs-1.0.0.dist-info/top_level.txt,sha256=9HngRK4yiRWsJDSHFiaOx-h61aDiR6Mm1PfDBQ11MIo,9
8
+ ansilogs-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 doronz88
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ ansilogs