Coverage for yuio / __init__.py: 96%
57 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-05 11:41 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-05 11:41 +0000
1# Yuio project, MIT license.
2#
3# https://github.com/taminomara/yuio/
4#
5# You're free to copy this file to your project and edit it for your needs,
6# just keep this copyright line please :3
8"""
9Yuio library. See documentation at https://yuio.readthedocs.io/.
11"""
13from __future__ import annotations
15import enum as _enum
16import logging as _logging
17import os as _os
18import sys as _sys
19import warnings
21from typing import TYPE_CHECKING
23if TYPE_CHECKING:
24 import typing_extensions as _t
25else:
26 from yuio import _typing as _t
28if TYPE_CHECKING:
29 import yuio.string
31try:
32 from yuio._version import * # noqa: F403
33except ImportError:
34 raise ImportError(
35 "yuio._version not found. if you are developing locally, "
36 "run `pip install -e .` to generate it"
37 )
39__all__ = [
40 "DISABLED",
41 "GROUP",
42 "MISSING",
43 "POSITIONAL",
44 "Disabled",
45 "Group",
46 "Missing",
47 "Positional",
48 "PrettyException",
49 "YuioWarning",
50 "enable_internal_logging",
51]
54class _Placeholders(_enum.Enum):
55 DISABLED = "<disabled>"
56 MISSING = "<missing>"
57 POSITIONAL = "<positional>"
58 GROUP = "<group>"
60 def __bool__(self) -> _t.Literal[False]:
61 return False # pragma: no cover
63 def __repr__(self):
64 return f"yuio.{self.name}" # pragma: no cover
66 def __str__(self) -> str:
67 return self.value # pragma: no cover
70Disabled: _t.TypeAlias = _t.Literal[_Placeholders.DISABLED]
71"""
72Type of the :data:`DISABLED` placeholder.
74"""
76DISABLED: Disabled = _Placeholders.DISABLED
77"""
78Indicates that some functionality is disabled.
80"""
83Missing: _t.TypeAlias = _t.Literal[_Placeholders.MISSING]
84"""
85Type of the :data:`MISSING` placeholder.
87"""
89MISSING: Missing = _Placeholders.MISSING
90"""
91Indicates that some value is missing.
93"""
96Positional: _t.TypeAlias = _t.Literal[_Placeholders.POSITIONAL]
97"""
98Type of the :data:`POSITIONAL` placeholder.
100"""
102POSITIONAL: Positional = _Placeholders.POSITIONAL
103"""
104Used with :func:`yuio.app.field` to enable positional arguments.
106"""
109Group: _t.TypeAlias = _t.Literal[_Placeholders.GROUP]
110"""
111Type of the :data:`GROUP` placeholder.
113"""
115GROUP: Group = _Placeholders.GROUP
116"""
117Used with :func:`yuio.app.field` to omit arguments from CLI usage.
119"""
122class YuioWarning(RuntimeWarning):
123 """
124 Base class for all runtime warnings.
126 """
129class PrettyException(Exception):
130 """PrettyException(msg: typing.LiteralString, /, *args: typing.Any)
131 PrettyException(msg: str, /)
133 Base class for pretty-printable exceptions.
135 :param msg:
136 message to format. Can be a literal string or any other colorable object.
138 If it's given as a literal string, additional arguments for ``%``-formatting
139 may be given. Otherwise, giving additional arguments will cause
140 a :class:`TypeError`.
141 :param args:
142 arguments for ``%``-formatting the message.
143 :example:
144 .. code-block:: python
146 class MyError(yuio.PrettyException):
147 pass
150 try:
151 ...
152 raise MyError("A formatted <c b>error message</c>")
153 ...
154 except MyError as e:
155 yuio.io.error_with_tb(e)
157 """
159 @_t.overload
160 def __init__(self, msg: _t.LiteralString, /, *args): ...
161 @_t.overload
162 def __init__(self, msg: yuio.string.ToColorable | None = None, /): ...
163 def __init__(self, *args):
164 self.args = args
166 def __rich_repr__(self) -> yuio.string.RichReprResult:
167 yield from ((None, arg) for arg in self.args)
169 def __str__(self) -> str:
170 return str(self.to_colorable())
172 def __colorized_str__(
173 self, ctx: yuio.string.ReprContext
174 ) -> yuio.string.ColorizedString:
175 return ctx.str(self.to_colorable())
177 def to_colorable(self) -> yuio.string.Colorable:
178 """
179 Return a colorable object with this exception's message.
181 """
183 if not self.args:
184 return ""
186 import yuio.string
188 return yuio.string._to_colorable(self.args[0], self.args[1:])
191_logger = _logging.getLogger("yuio.internal")
192_logger.setLevel(_logging.DEBUG)
193_logger.propagate = False
195__stderr_handler = _logging.StreamHandler(_sys.__stderr__)
196__stderr_handler.setLevel(_logging.CRITICAL)
197_logger.addHandler(__stderr_handler)
200def enable_internal_logging(
201 path: str | None = None,
202 level: str | int | None = None,
203 propagate=None,
204 add_handler: bool = False,
205): # pragma: no cover
206 """
207 Enable Yuio's internal logging.
209 This function enables :func:`logging.captureWarnings`, and enables printing
210 of :class:`YuioWarning` messages, and sets up logging channels ``yuio.internal``
211 and ``py.warning``.
213 :param path:
214 if given, adds handlers that output internal log messages to the given file.
215 :param level:
216 configures logging level for file handler. Default is ``DEBUG``.
217 :param propagate:
218 if given, enables or disables log message propagation from ``yuio.internal``
219 and ``py.warning`` to the root logger.
220 :param add_handler:
221 if :data:`True`, adds yuio handler to the logging. This is useful if you wish
222 to see yuio log before main setup.
224 """
226 warn_logger = _logging.getLogger("py.warnings")
228 if path:
229 if level is None:
230 level = _os.environ.get("YUIO_DEBUG", "").strip().upper() or "DEBUG"
231 if level in ["1", "Y", "YES", "TRUE"]:
232 level = "DEBUG"
233 file_handler = _logging.FileHandler(path, delay=True)
234 file_handler.setFormatter(
235 _logging.Formatter("%(filename)s:%(lineno)d: %(levelname)s: %(message)s")
236 )
237 file_handler.setLevel(level)
238 _logger.addHandler(file_handler)
239 warn_logger.addHandler(file_handler)
241 _logging.captureWarnings(True)
242 warnings.simplefilter("default", category=YuioWarning)
244 if propagate is not None:
245 warn_logger.propagate = propagate
246 _logger.propagate = propagate
248 if add_handler:
249 import yuio.io
251 if not any(
252 isinstance(handler, yuio.io.Handler) for handler in _logger.handlers
253 ):
254 _logger.addHandler(yuio.io.Handler())
255 if not any(
256 isinstance(handler, yuio.io.Handler) for handler in warn_logger.handlers
257 ):
258 warn_logger.addHandler(yuio.io.Handler())
261_debug = "YUIO_DEBUG" in _os.environ or "YUIO_DEBUG_FILE" in _os.environ
262if _debug: # pragma: no cover
263 enable_internal_logging(path=_os.environ.get("YUIO_DEBUG_FILE"), add_handler=True)
264elif hasattr(_sys, "ps1"):
265 enable_internal_logging(add_handler=True)
266else:
267 warnings.simplefilter("ignore", category=YuioWarning, append=True)