Coverage for yuio / __init__.py: 96%

57 statements  

« 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 

7 

8""" 

9Yuio library. See documentation at https://yuio.readthedocs.io/. 

10 

11""" 

12 

13from __future__ import annotations 

14 

15import enum as _enum 

16import logging as _logging 

17import os as _os 

18import sys as _sys 

19import warnings 

20 

21from typing import TYPE_CHECKING 

22 

23if TYPE_CHECKING: 

24 import typing_extensions as _t 

25else: 

26 from yuio import _typing as _t 

27 

28if TYPE_CHECKING: 

29 import yuio.string 

30 

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 ) 

38 

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] 

52 

53 

54class _Placeholders(_enum.Enum): 

55 DISABLED = "<disabled>" 

56 MISSING = "<missing>" 

57 POSITIONAL = "<positional>" 

58 GROUP = "<group>" 

59 

60 def __bool__(self) -> _t.Literal[False]: 

61 return False # pragma: no cover 

62 

63 def __repr__(self): 

64 return f"yuio.{self.name}" # pragma: no cover 

65 

66 def __str__(self) -> str: 

67 return self.value # pragma: no cover 

68 

69 

70Disabled: _t.TypeAlias = _t.Literal[_Placeholders.DISABLED] 

71""" 

72Type of the :data:`DISABLED` placeholder. 

73 

74""" 

75 

76DISABLED: Disabled = _Placeholders.DISABLED 

77""" 

78Indicates that some functionality is disabled. 

79 

80""" 

81 

82 

83Missing: _t.TypeAlias = _t.Literal[_Placeholders.MISSING] 

84""" 

85Type of the :data:`MISSING` placeholder. 

86 

87""" 

88 

89MISSING: Missing = _Placeholders.MISSING 

90""" 

91Indicates that some value is missing. 

92 

93""" 

94 

95 

96Positional: _t.TypeAlias = _t.Literal[_Placeholders.POSITIONAL] 

97""" 

98Type of the :data:`POSITIONAL` placeholder. 

99 

100""" 

101 

102POSITIONAL: Positional = _Placeholders.POSITIONAL 

103""" 

104Used with :func:`yuio.app.field` to enable positional arguments. 

105 

106""" 

107 

108 

109Group: _t.TypeAlias = _t.Literal[_Placeholders.GROUP] 

110""" 

111Type of the :data:`GROUP` placeholder. 

112 

113""" 

114 

115GROUP: Group = _Placeholders.GROUP 

116""" 

117Used with :func:`yuio.app.field` to omit arguments from CLI usage. 

118 

119""" 

120 

121 

122class YuioWarning(RuntimeWarning): 

123 """ 

124 Base class for all runtime warnings. 

125 

126 """ 

127 

128 

129class PrettyException(Exception): 

130 """PrettyException(msg: typing.LiteralString, /, *args: typing.Any) 

131 PrettyException(msg: str, /) 

132 

133 Base class for pretty-printable exceptions. 

134 

135 :param msg: 

136 message to format. Can be a literal string or any other colorable object. 

137 

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 

145 

146 class MyError(yuio.PrettyException): 

147 pass 

148 

149 

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) 

156 

157 """ 

158 

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 

165 

166 def __rich_repr__(self) -> yuio.string.RichReprResult: 

167 yield from ((None, arg) for arg in self.args) 

168 

169 def __str__(self) -> str: 

170 return str(self.to_colorable()) 

171 

172 def __colorized_str__( 

173 self, ctx: yuio.string.ReprContext 

174 ) -> yuio.string.ColorizedString: 

175 return ctx.str(self.to_colorable()) 

176 

177 def to_colorable(self) -> yuio.string.Colorable: 

178 """ 

179 Return a colorable object with this exception's message. 

180 

181 """ 

182 

183 if not self.args: 

184 return "" 

185 

186 import yuio.string 

187 

188 return yuio.string._to_colorable(self.args[0], self.args[1:]) 

189 

190 

191_logger = _logging.getLogger("yuio.internal") 

192_logger.setLevel(_logging.DEBUG) 

193_logger.propagate = False 

194 

195__stderr_handler = _logging.StreamHandler(_sys.__stderr__) 

196__stderr_handler.setLevel(_logging.CRITICAL) 

197_logger.addHandler(__stderr_handler) 

198 

199 

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. 

208 

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``. 

212 

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. 

223 

224 """ 

225 

226 warn_logger = _logging.getLogger("py.warnings") 

227 

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) 

240 

241 _logging.captureWarnings(True) 

242 warnings.simplefilter("default", category=YuioWarning) 

243 

244 if propagate is not None: 

245 warn_logger.propagate = propagate 

246 _logger.propagate = propagate 

247 

248 if add_handler: 

249 import yuio.io 

250 

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()) 

259 

260 

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)