Coverage for yuio / __init__.py: 96%

56 statements  

« 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 

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 yuio import _typing as _t 

22 

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 ) 

30 

31if _t.TYPE_CHECKING: 

32 import yuio.string 

33 

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] 

47 

48 

49class _Placeholders(_enum.Enum): 

50 DISABLED = "<disabled>" 

51 MISSING = "<missing>" 

52 POSITIONAL = "<positional>" 

53 GROUP = "<group>" 

54 

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

56 return False # pragma: no cover 

57 

58 def __repr__(self): 

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

60 

61 def __str__(self) -> str: 

62 return self.value # pragma: no cover 

63 

64 

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

66""" 

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

68 

69""" 

70 

71DISABLED: Disabled = _Placeholders.DISABLED 

72""" 

73Indicates that some functionality is disabled. 

74 

75""" 

76 

77 

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

79""" 

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

81 

82""" 

83 

84MISSING: Missing = _Placeholders.MISSING 

85""" 

86Indicates that some value is missing. 

87 

88""" 

89 

90 

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

92""" 

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

94 

95""" 

96 

97POSITIONAL: Positional = _Placeholders.POSITIONAL 

98""" 

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

100 

101""" 

102 

103 

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

105""" 

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

107 

108""" 

109 

110GROUP: Group = _Placeholders.GROUP 

111""" 

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

113 

114""" 

115 

116 

117class YuioWarning(RuntimeWarning): 

118 """ 

119 Base class for all runtime warnings. 

120 

121 """ 

122 

123 

124class PrettyException(Exception): 

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

126 PrettyException(msg: str, /) 

127 

128 Base class for pretty-printable exceptions. 

129 

130 :param msg: 

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

132 

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 

142 

143 class MyError(yuio.PrettyException): 

144 pass 

145 

146 

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) 

153 

154 """ 

155 

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 

162 

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

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

165 

166 def __str__(self) -> str: 

167 return str(self.to_colorable()) 

168 

169 def __colorized_str__( 

170 self, ctx: yuio.string.ReprContext 

171 ) -> yuio.string.ColorizedString: 

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

173 

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

175 """ 

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

177 

178 """ 

179 

180 if not self.args: 

181 return "" 

182 msg, *args = self.args 

183 if isinstance(msg, str): 

184 import yuio.string 

185 

186 return yuio.string.Format(_t.cast(_t.LiteralString, msg), *(args or ())) 

187 else: 

188 return msg 

189 

190 

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

192_logger.propagate = False 

193 

194__stderr_handler = _logging.StreamHandler(_sys.__stderr__) 

195__stderr_handler.setLevel("CRITICAL") 

196_logger.addHandler(__stderr_handler) 

197 

198 

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. 

204 

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

208 

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. 

216 

217 """ 

218 

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) 

231 

232 _logging.captureWarnings(True) 

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

234 

235 if propagate is not None: 

236 _logging.getLogger("py.warnings").propagate = propagate 

237 _logger.propagate = propagate 

238 

239 

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)