Coverage for yuio / __init__.py: 97%

60 statements  

« 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 

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 "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] 

54 

55 

56class _Placeholders(_enum.Enum): 

57 DISABLED = "<disabled>" 

58 MISSING = "<missing>" 

59 POSITIONAL = "<positional>" 

60 COLLAPSE = "<group>" 

61 

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

63 return False # pragma: no cover 

64 

65 def __repr__(self): 

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

67 

68 def __str__(self) -> str: 

69 return self.value # pragma: no cover 

70 

71 

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

73""" 

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

75 

76""" 

77 

78DISABLED: Disabled = _Placeholders.DISABLED 

79""" 

80Indicates that some functionality is disabled. 

81 

82""" 

83 

84 

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

86""" 

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

88 

89""" 

90 

91MISSING: Missing = _Placeholders.MISSING 

92""" 

93Indicates that some value is missing. 

94 

95""" 

96 

97 

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

99""" 

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

101 

102""" 

103 

104POSITIONAL: Positional = _Placeholders.POSITIONAL 

105""" 

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

107 

108""" 

109 

110 

111Collapse: _t.TypeAlias = _t.Literal[_Placeholders.COLLAPSE] 

112""" 

113Type of the :data:`COLLAPSE` placeholder. 

114 

115""" 

116 

117COLLAPSE: Collapse = _Placeholders.COLLAPSE 

118""" 

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

120and to collapse argument groups. 

121 

122""" 

123 

124GROUP = COLLAPSE # Deprecated. 

125 

126 

127class YuioWarning(RuntimeWarning): 

128 """ 

129 Base class for all runtime warnings. 

130 

131 """ 

132 

133 

134class YuioDeprecationWarning(YuioWarning, DeprecationWarning): 

135 """ 

136 Base class for deprecation warnings. 

137 

138 """ 

139 

140 

141class YuioPendingDeprecationWarning(YuioWarning, PendingDeprecationWarning): 

142 """ 

143 Base class for pending deprecation warnings. 

144 

145 """ 

146 

147 

148class PrettyException(Exception): 

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

150 PrettyException(msg: ~string.templatelib.Template, /) 

151 PrettyException(msg: str, /) 

152 

153 Base class for pretty-printable exceptions. 

154 

155 :param msg: 

156 message to format. 

157 :param args: 

158 arguments for ``%``-formatting the message. 

159 :example: 

160 .. invisible-code-block: python 

161 

162 import yuio 

163 

164 .. code-block:: python 

165 

166 class MyError(yuio.PrettyException): 

167 pass 

168 

169 

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) 

176 

177 """ 

178 

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 

185 

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

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

188 

189 def __str__(self) -> str: 

190 return str(self.to_colorable()) 

191 

192 def __colorized_str__( 

193 self, ctx: yuio.string.ReprContext 

194 ) -> yuio.string.ColorizedString: 

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

196 

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

198 """ 

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

200 

201 """ 

202 

203 if not self.args: 

204 return "" 

205 

206 import yuio.string 

207 

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

209 

210 

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

212_logger.setLevel(_logging.DEBUG) 

213_logger.propagate = False 

214 

215__stderr_handler = _logging.StreamHandler(_sys.__stderr__) 

216__stderr_handler.setLevel(_logging.CRITICAL) 

217_logger.addHandler(__stderr_handler) 

218 

219 

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. 

228 

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

232 

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. 

243 

244 """ 

245 

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

247 

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) 

260 

261 _logging.captureWarnings(True) 

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

263 

264 if propagate is not None: 

265 warn_logger.propagate = propagate 

266 _logger.propagate = propagate 

267 

268 if add_handler: 

269 import yuio.io 

270 

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

279 

280 

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)