Coverage for yuio / __init__.py: 97%
60 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-03 15:42 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-03 15:42 +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 "COLLAPSE",
41 "DISABLED",
42 "MISSING",
43 "POSITIONAL",
44 "Collapse",
45 "Disabled",
46 "Missing",
47 "Positional",
48 "PrettyException",
49 "YuioDeprecationWarning",
50 "YuioPendingDeprecationWarning",
51 "YuioWarning",
52 "enable_internal_logging",
53]
56class _Placeholders(_enum.Enum):
57 DISABLED = "<disabled>"
58 MISSING = "<missing>"
59 POSITIONAL = "<positional>"
60 COLLAPSE = "<group>"
62 def __bool__(self) -> _t.Literal[False]:
63 return False # pragma: no cover
65 def __repr__(self):
66 return f"yuio.{self.name}" # pragma: no cover
68 def __str__(self) -> str:
69 return self.value # pragma: no cover
72Disabled: _t.TypeAlias = _t.Literal[_Placeholders.DISABLED]
73"""
74Type of the :data:`DISABLED` placeholder.
76"""
78DISABLED: Disabled = _Placeholders.DISABLED
79"""
80Indicates that some functionality is disabled.
82"""
85Missing: _t.TypeAlias = _t.Literal[_Placeholders.MISSING]
86"""
87Type of the :data:`MISSING` placeholder.
89"""
91MISSING: Missing = _Placeholders.MISSING
92"""
93Indicates that some value is missing.
95"""
98Positional: _t.TypeAlias = _t.Literal[_Placeholders.POSITIONAL]
99"""
100Type of the :data:`POSITIONAL` placeholder.
102"""
104POSITIONAL: Positional = _Placeholders.POSITIONAL
105"""
106Used with :func:`yuio.app.field` to enable positional arguments.
108"""
111Collapse: _t.TypeAlias = _t.Literal[_Placeholders.COLLAPSE]
112"""
113Type of the :data:`COLLAPSE` placeholder.
115"""
117COLLAPSE: Collapse = _Placeholders.COLLAPSE
118"""
119Used with :func:`yuio.app.field` to omit arguments from CLI usage
120and to collapse argument groups.
122"""
124GROUP = COLLAPSE # Deprecated.
127class YuioWarning(RuntimeWarning):
128 """
129 Base class for all runtime warnings.
131 """
134class YuioDeprecationWarning(YuioWarning, DeprecationWarning):
135 """
136 Base class for deprecation warnings.
138 """
141class YuioPendingDeprecationWarning(YuioWarning, PendingDeprecationWarning):
142 """
143 Base class for pending deprecation warnings.
145 """
148class PrettyException(Exception):
149 """PrettyException(msg: typing.LiteralString, /, *args: typing.Any)
150 PrettyException(msg: ~string.templatelib.Template, /)
151 PrettyException(msg: str, /)
153 Base class for pretty-printable exceptions.
155 :param msg:
156 message to format.
157 :param args:
158 arguments for ``%``-formatting the message.
159 :example:
160 .. invisible-code-block: python
162 import yuio
164 .. code-block:: python
166 class MyError(yuio.PrettyException):
167 pass
170 try:
171 ...
172 raise MyError("A formatted <c b>error message</c>")
173 ...
174 except MyError as e:
175 yuio.io.error_with_tb(e)
177 """
179 @_t.overload
180 def __init__(self, msg: _t.LiteralString, /, *args): ...
181 @_t.overload
182 def __init__(self, msg: yuio.string.ToColorable | None = None, /): ...
183 def __init__(self, *args):
184 self.args = args
186 def __rich_repr__(self) -> yuio.string.RichReprResult:
187 yield from ((None, arg) for arg in self.args)
189 def __str__(self) -> str:
190 return str(self.to_colorable())
192 def __colorized_str__(
193 self, ctx: yuio.string.ReprContext
194 ) -> yuio.string.ColorizedString:
195 return ctx.str(self.to_colorable())
197 def to_colorable(self) -> yuio.string.Colorable:
198 """
199 Return a colorable object with this exception's message.
201 """
203 if not self.args:
204 return ""
206 import yuio.string
208 return yuio.string._to_colorable(self.args[0], self.args[1:])
211_logger = _logging.getLogger("yuio.internal")
212_logger.setLevel(_logging.DEBUG)
213_logger.propagate = False
215__stderr_handler = _logging.StreamHandler(_sys.__stderr__)
216__stderr_handler.setLevel(_logging.CRITICAL)
217_logger.addHandler(__stderr_handler)
220def enable_internal_logging(
221 path: str | None = None,
222 level: str | int | None = None,
223 propagate=None,
224 add_handler: bool = False,
225): # pragma: no cover
226 """
227 Enable Yuio's internal logging.
229 This function enables :func:`logging.captureWarnings`, and enables printing
230 of :class:`YuioWarning` messages, and sets up logging channels ``yuio.internal``
231 and ``py.warning``.
233 :param path:
234 if given, adds handlers that output internal log messages to the given file.
235 :param level:
236 configures logging level for file handler. Default is ``DEBUG``.
237 :param propagate:
238 if given, enables or disables log message propagation from ``yuio.internal``
239 and ``py.warning`` to the root logger.
240 :param add_handler:
241 if :data:`True`, adds yuio handler to the logging. This is useful if you wish
242 to see yuio log before main setup.
244 """
246 warn_logger = _logging.getLogger("py.warnings")
248 if path:
249 if level is None:
250 level = _os.environ.get("YUIO_DEBUG", "").strip().upper() or "DEBUG"
251 if level in ["1", "Y", "YES", "TRUE"]:
252 level = "DEBUG"
253 file_handler = _logging.FileHandler(path, delay=True)
254 file_handler.setFormatter(
255 _logging.Formatter("%(filename)s:%(lineno)d: %(levelname)s: %(message)s")
256 )
257 file_handler.setLevel(level)
258 _logger.addHandler(file_handler)
259 warn_logger.addHandler(file_handler)
261 _logging.captureWarnings(True)
262 warnings.simplefilter("default", category=YuioWarning)
264 if propagate is not None:
265 warn_logger.propagate = propagate
266 _logger.propagate = propagate
268 if add_handler:
269 import yuio.io
271 if not any(
272 isinstance(handler, yuio.io.Handler) for handler in _logger.handlers
273 ):
274 _logger.addHandler(yuio.io.Handler())
275 if not any(
276 isinstance(handler, yuio.io.Handler) for handler in warn_logger.handlers
277 ):
278 warn_logger.addHandler(yuio.io.Handler())
281_debug = "YUIO_DEBUG" in _os.environ or "YUIO_DEBUG_FILE" in _os.environ
282if _debug: # pragma: no cover
283 enable_internal_logging(path=_os.environ.get("YUIO_DEBUG_FILE"), add_handler=True)
284elif hasattr(_sys, "ps1"):
285 enable_internal_logging(add_handler=True)
286else:
287 warnings.simplefilter("ignore", category=YuioWarning, append=True)