Coverage for yuio / __init__.py: 96%
56 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-04 10:05 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-04 10:05 +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 yuio import _typing as _t
23try:
24 from yuio._version import * # noqa: F403
25except ImportError:
26 raise ImportError(
27 "yuio._version not found. if you are developing locally, "
28 "run `pip install -e .` to generate it"
29 )
31if _t.TYPE_CHECKING:
32 import yuio.string
34__all__ = [
35 "DISABLED",
36 "GROUP",
37 "MISSING",
38 "POSITIONAL",
39 "Disabled",
40 "Group",
41 "Missing",
42 "Positional",
43 "PrettyException",
44 "YuioWarning",
45 "enable_internal_logging",
46]
49class _Placeholders(_enum.Enum):
50 DISABLED = "<disabled>"
51 MISSING = "<missing>"
52 POSITIONAL = "<positional>"
53 GROUP = "<group>"
55 def __bool__(self) -> _t.Literal[False]:
56 return False # pragma: no cover
58 def __repr__(self):
59 return f"yuio.{self.name}" # pragma: no cover
61 def __str__(self) -> str:
62 return self.value # pragma: no cover
65Disabled: _t.TypeAlias = _t.Literal[_Placeholders.DISABLED]
66"""
67Type of the :data:`DISABLED` placeholder.
69"""
71DISABLED: Disabled = _Placeholders.DISABLED
72"""
73Indicates that some functionality is disabled.
75"""
78Missing: _t.TypeAlias = _t.Literal[_Placeholders.MISSING]
79"""
80Type of the :data:`MISSING` placeholder.
82"""
84MISSING: Missing = _Placeholders.MISSING
85"""
86Indicates that some value is missing.
88"""
91Positional: _t.TypeAlias = _t.Literal[_Placeholders.POSITIONAL]
92"""
93Type of the :data:`POSITIONAL` placeholder.
95"""
97POSITIONAL: Positional = _Placeholders.POSITIONAL
98"""
99Used with :func:`yuio.app.field` to enable positional arguments.
101"""
104Group: _t.TypeAlias = _t.Literal[_Placeholders.GROUP]
105"""
106Type of the :data:`GROUP` placeholder.
108"""
110GROUP: Group = _Placeholders.GROUP
111"""
112Used with :func:`yuio.app.field` to omit arguments from CLI usage.
114"""
117class YuioWarning(RuntimeWarning):
118 """
119 Base class for all runtime warnings.
121 """
124class PrettyException(Exception):
125 """PrettyException(msg: typing.LiteralString, /, *args: typing.Any)
126 PrettyException(msg: str, /)
128 Base class for pretty-printable exceptions.
130 :param msg:
131 message to format. Can be a literal string or any other colorable object.
133 If it's given as a literal string, additional arguments for ``%``-formatting
134 may be given. Otherwise, giving additional arguments will cause
135 a :class:`TypeError`.
136 :param args:
137 arguments for ``%``-formatting the message.
138 :raises:
139 :class:`TypeError` if ``args`` are given with a non-string ``msg``.
140 :example:
141 .. code-block:: python
143 class MyError(yuio.PrettyException):
144 pass
147 try:
148 ...
149 raise MyError("A formatted <c b>error message</c>")
150 ...
151 except MyError as e:
152 yuio.io.error_with_tb(e)
154 """
156 @_t.overload
157 def __init__(self, msg: _t.LiteralString, /, *args): ...
158 @_t.overload
159 def __init__(self, msg: yuio.string.Colorable | None = None, /): ...
160 def __init__(self, *args):
161 self.args = args
163 def __rich_repr__(self) -> yuio.string.RichReprResult:
164 yield from ((None, arg) for arg in self.args)
166 def __str__(self) -> str:
167 return str(self.to_colorable())
169 def __colorized_str__(
170 self, ctx: yuio.string.ReprContext
171 ) -> yuio.string.ColorizedString:
172 return ctx.str(self.to_colorable())
174 def to_colorable(self) -> yuio.string.Colorable:
175 """
176 Return a colorable object with this exception's message.
178 """
180 if not self.args:
181 return ""
182 msg, *args = self.args
183 if isinstance(msg, str):
184 import yuio.string
186 return yuio.string.Format(_t.cast(_t.LiteralString, msg), *(args or ()))
187 else:
188 return msg
191_logger = _logging.getLogger("yuio.internal")
192_logger.propagate = False
194__stderr_handler = _logging.StreamHandler(_sys.__stderr__)
195__stderr_handler.setLevel("CRITICAL")
196_logger.addHandler(__stderr_handler)
199def enable_internal_logging(
200 path: str | None = None, level: str | int | None = None, propagate=None
201): # pragma: no cover
202 """
203 Enable Yuio's internal logging.
205 This function enables :func:`logging.captureWarnings`, and enables printing
206 of :class:`YuioWarning` messages, and sets up logging channels ``yuio.internal``
207 and ``py.warning``.
209 :param path:
210 if given, adds handlers that output internal log messages to the given file.
211 :param level:
212 configures logging level for file handler. Default is ``DEBUG``.
213 :param propagate:
214 if given, enables or disables log message propagation from ``yuio.internal``
215 and ``py.warning`` to the root logger.
217 """
219 if path:
220 if level is None:
221 level = _os.environ.get("YUIO_DEBUG", "").strip().upper() or "DEBUG"
222 if level in ["1", "Y", "YES", "TRUE"]:
223 level = "DEBUG"
224 file_handler = _logging.FileHandler(path, delay=True)
225 file_handler.setFormatter(
226 _logging.Formatter("%(filename)s:%(lineno)d: %(levelname)s: %(message)s")
227 )
228 file_handler.setLevel(level)
229 _logger.addHandler(file_handler)
230 _logging.getLogger("py.warnings").addHandler(file_handler)
232 _logging.captureWarnings(True)
233 warnings.simplefilter("default", category=YuioWarning)
235 if propagate is not None:
236 _logging.getLogger("py.warnings").propagate = propagate
237 _logger.propagate = propagate
240_debug = "YUIO_DEBUG" in _os.environ or "YUIO_DEBUG_FILE" in _os.environ
241if _debug: # pragma: no cover
242 enable_internal_logging(
243 path=_os.environ.get("YUIO_DEBUG_FILE") or "yuio.log", propagate=False
244 )
245else:
246 warnings.simplefilter("ignore", category=YuioWarning, append=True)