Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/matplotlib/_api/__init__.py: 61%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

122 statements  

1""" 

2Helper functions for managing the Matplotlib API. 

3 

4This documentation is only relevant for Matplotlib developers, not for users. 

5 

6.. warning:: 

7 

8 This module and its submodules are for internal use only. Do not use them 

9 in your own code. We may change the API at any time with no warning. 

10 

11""" 

12 

13import functools 

14import itertools 

15import re 

16import sys 

17import warnings 

18 

19from .deprecation import ( # noqa: F401 

20 deprecated, warn_deprecated, 

21 rename_parameter, delete_parameter, make_keyword_only, 

22 deprecate_method_override, deprecate_privatize_attribute, 

23 suppress_matplotlib_deprecation_warning, 

24 MatplotlibDeprecationWarning) 

25 

26 

27class classproperty: 

28 """ 

29 Like `property`, but also triggers on access via the class, and it is the 

30 *class* that's passed as argument. 

31 

32 Examples 

33 -------- 

34 :: 

35 

36 class C: 

37 @classproperty 

38 def foo(cls): 

39 return cls.__name__ 

40 

41 assert C.foo == "C" 

42 """ 

43 

44 def __init__(self, fget, fset=None, fdel=None, doc=None): 

45 self._fget = fget 

46 if fset is not None or fdel is not None: 

47 raise ValueError('classproperty only implements fget.') 

48 self.fset = fset 

49 self.fdel = fdel 

50 # docs are ignored for now 

51 self._doc = doc 

52 

53 def __get__(self, instance, owner): 

54 return self._fget(owner) 

55 

56 @property 

57 def fget(self): 

58 return self._fget 

59 

60 

61# In the following check_foo() functions, the first parameter is positional-only to make 

62# e.g. `_api.check_isinstance([...], types=foo)` work. 

63 

64def check_isinstance(types, /, **kwargs): 

65 """ 

66 For each *key, value* pair in *kwargs*, check that *value* is an instance 

67 of one of *types*; if not, raise an appropriate TypeError. 

68 

69 As a special case, a ``None`` entry in *types* is treated as NoneType. 

70 

71 Examples 

72 -------- 

73 >>> _api.check_isinstance((SomeClass, None), arg=arg) 

74 """ 

75 none_type = type(None) 

76 types = ((types,) if isinstance(types, type) else 

77 (none_type,) if types is None else 

78 tuple(none_type if tp is None else tp for tp in types)) 

79 

80 def type_name(tp): 

81 return ("None" if tp is none_type 

82 else tp.__qualname__ if tp.__module__ == "builtins" 

83 else f"{tp.__module__}.{tp.__qualname__}") 

84 

85 for k, v in kwargs.items(): 

86 if not isinstance(v, types): 

87 names = [*map(type_name, types)] 

88 if "None" in names: # Move it to the end for better wording. 

89 names.remove("None") 

90 names.append("None") 

91 raise TypeError( 

92 "{!r} must be an instance of {}, not a {}".format( 

93 k, 

94 ", ".join(names[:-1]) + " or " + names[-1] 

95 if len(names) > 1 else names[0], 

96 type_name(type(v)))) 

97 

98 

99def check_in_list(values, /, *, _print_supported_values=True, **kwargs): 

100 """ 

101 For each *key, value* pair in *kwargs*, check that *value* is in *values*; 

102 if not, raise an appropriate ValueError. 

103 

104 Parameters 

105 ---------- 

106 values : iterable 

107 Sequence of values to check on. 

108 _print_supported_values : bool, default: True 

109 Whether to print *values* when raising ValueError. 

110 **kwargs : dict 

111 *key, value* pairs as keyword arguments to find in *values*. 

112 

113 Raises 

114 ------ 

115 ValueError 

116 If any *value* in *kwargs* is not found in *values*. 

117 

118 Examples 

119 -------- 

120 >>> _api.check_in_list(["foo", "bar"], arg=arg, other_arg=other_arg) 

121 """ 

122 if not kwargs: 

123 raise TypeError("No argument to check!") 

124 for key, val in kwargs.items(): 

125 if val not in values: 

126 msg = f"{val!r} is not a valid value for {key}" 

127 if _print_supported_values: 

128 msg += f"; supported values are {', '.join(map(repr, values))}" 

129 raise ValueError(msg) 

130 

131 

132def check_shape(shape, /, **kwargs): 

133 """ 

134 For each *key, value* pair in *kwargs*, check that *value* has the shape *shape*; 

135 if not, raise an appropriate ValueError. 

136 

137 *None* in the shape is treated as a "free" size that can have any length. 

138 e.g. (None, 2) -> (N, 2) 

139 

140 The values checked must be numpy arrays. 

141 

142 Examples 

143 -------- 

144 To check for (N, 2) shaped arrays 

145 

146 >>> _api.check_shape((None, 2), arg=arg, other_arg=other_arg) 

147 """ 

148 for k, v in kwargs.items(): 

149 data_shape = v.shape 

150 

151 if (len(data_shape) != len(shape) 

152 or any(s != t and t is not None for s, t in zip(data_shape, shape))): 

153 dim_labels = iter(itertools.chain( 

154 'NMLKJIH', 

155 (f"D{i}" for i in itertools.count()))) 

156 text_shape = ", ".join([str(n) if n is not None else next(dim_labels) 

157 for n in shape[::-1]][::-1]) 

158 if len(shape) == 1: 

159 text_shape += "," 

160 

161 raise ValueError( 

162 f"{k!r} must be {len(shape)}D with shape ({text_shape}), " 

163 f"but your input has shape {v.shape}" 

164 ) 

165 

166 

167def check_getitem(mapping, /, **kwargs): 

168 """ 

169 *kwargs* must consist of a single *key, value* pair. If *key* is in 

170 *mapping*, return ``mapping[value]``; else, raise an appropriate 

171 ValueError. 

172 

173 Examples 

174 -------- 

175 >>> _api.check_getitem({"foo": "bar"}, arg=arg) 

176 """ 

177 if len(kwargs) != 1: 

178 raise ValueError("check_getitem takes a single keyword argument") 

179 (k, v), = kwargs.items() 

180 try: 

181 return mapping[v] 

182 except KeyError: 

183 raise ValueError( 

184 f"{v!r} is not a valid value for {k}; supported values are " 

185 f"{', '.join(map(repr, mapping))}") from None 

186 

187 

188def caching_module_getattr(cls): 

189 """ 

190 Helper decorator for implementing module-level ``__getattr__`` as a class. 

191 

192 This decorator must be used at the module toplevel as follows:: 

193 

194 @caching_module_getattr 

195 class __getattr__: # The class *must* be named ``__getattr__``. 

196 @property # Only properties are taken into account. 

197 def name(self): ... 

198 

199 The ``__getattr__`` class will be replaced by a ``__getattr__`` 

200 function such that trying to access ``name`` on the module will 

201 resolve the corresponding property (which may be decorated e.g. with 

202 ``_api.deprecated`` for deprecating module globals). The properties are 

203 all implicitly cached. Moreover, a suitable AttributeError is generated 

204 and raised if no property with the given name exists. 

205 """ 

206 

207 assert cls.__name__ == "__getattr__" 

208 # Don't accidentally export cls dunders. 

209 props = {name: prop for name, prop in vars(cls).items() 

210 if isinstance(prop, property)} 

211 instance = cls() 

212 

213 @functools.cache 

214 def __getattr__(name): 

215 if name in props: 

216 return props[name].__get__(instance) 

217 raise AttributeError( 

218 f"module {cls.__module__!r} has no attribute {name!r}") 

219 

220 return __getattr__ 

221 

222 

223def define_aliases(alias_d, cls=None): 

224 """ 

225 Class decorator for defining property aliases. 

226 

227 Use as :: 

228 

229 @_api.define_aliases({"property": ["alias", ...], ...}) 

230 class C: ... 

231 

232 For each property, if the corresponding ``get_property`` is defined in the 

233 class so far, an alias named ``get_alias`` will be defined; the same will 

234 be done for setters. If neither the getter nor the setter exists, an 

235 exception will be raised. 

236 

237 The alias map is stored as the ``_alias_map`` attribute on the class and 

238 can be used by `.normalize_kwargs` (which assumes that higher priority 

239 aliases come last). 

240 """ 

241 if cls is None: # Return the actual class decorator. 

242 return functools.partial(define_aliases, alias_d) 

243 

244 def make_alias(name): # Enforce a closure over *name*. 

245 @functools.wraps(getattr(cls, name)) 

246 def method(self, *args, **kwargs): 

247 return getattr(self, name)(*args, **kwargs) 

248 return method 

249 

250 for prop, aliases in alias_d.items(): 

251 exists = False 

252 for prefix in ["get_", "set_"]: 

253 if prefix + prop in vars(cls): 

254 exists = True 

255 for alias in aliases: 

256 method = make_alias(prefix + prop) 

257 method.__name__ = prefix + alias 

258 method.__doc__ = f"Alias for `{prefix + prop}`." 

259 setattr(cls, prefix + alias, method) 

260 if not exists: 

261 raise ValueError( 

262 f"Neither getter nor setter exists for {prop!r}") 

263 

264 def get_aliased_and_aliases(d): 

265 return {*d, *(alias for aliases in d.values() for alias in aliases)} 

266 

267 preexisting_aliases = getattr(cls, "_alias_map", {}) 

268 conflicting = (get_aliased_and_aliases(preexisting_aliases) 

269 & get_aliased_and_aliases(alias_d)) 

270 if conflicting: 

271 # Need to decide on conflict resolution policy. 

272 raise NotImplementedError( 

273 f"Parent class already defines conflicting aliases: {conflicting}") 

274 cls._alias_map = {**preexisting_aliases, **alias_d} 

275 return cls 

276 

277 

278def select_matching_signature(funcs, *args, **kwargs): 

279 """ 

280 Select and call the function that accepts ``*args, **kwargs``. 

281 

282 *funcs* is a list of functions which should not raise any exception (other 

283 than `TypeError` if the arguments passed do not match their signature). 

284 

285 `select_matching_signature` tries to call each of the functions in *funcs* 

286 with ``*args, **kwargs`` (in the order in which they are given). Calls 

287 that fail with a `TypeError` are silently skipped. As soon as a call 

288 succeeds, `select_matching_signature` returns its return value. If no 

289 function accepts ``*args, **kwargs``, then the `TypeError` raised by the 

290 last failing call is re-raised. 

291 

292 Callers should normally make sure that any ``*args, **kwargs`` can only 

293 bind a single *func* (to avoid any ambiguity), although this is not checked 

294 by `select_matching_signature`. 

295 

296 Notes 

297 ----- 

298 `select_matching_signature` is intended to help implementing 

299 signature-overloaded functions. In general, such functions should be 

300 avoided, except for back-compatibility concerns. A typical use pattern is 

301 :: 

302 

303 def my_func(*args, **kwargs): 

304 params = select_matching_signature( 

305 [lambda old1, old2: locals(), lambda new: locals()], 

306 *args, **kwargs) 

307 if "old1" in params: 

308 warn_deprecated(...) 

309 old1, old2 = params.values() # note that locals() is ordered. 

310 else: 

311 new, = params.values() 

312 # do things with params 

313 

314 which allows *my_func* to be called either with two parameters (*old1* and 

315 *old2*) or a single one (*new*). Note that the new signature is given 

316 last, so that callers get a `TypeError` corresponding to the new signature 

317 if the arguments they passed in do not match any signature. 

318 """ 

319 # Rather than relying on locals() ordering, one could have just used func's 

320 # signature (``bound = inspect.signature(func).bind(*args, **kwargs); 

321 # bound.apply_defaults(); return bound``) but that is significantly slower. 

322 for i, func in enumerate(funcs): 

323 try: 

324 return func(*args, **kwargs) 

325 except TypeError: 

326 if i == len(funcs) - 1: 

327 raise 

328 

329 

330def nargs_error(name, takes, given): 

331 """Generate a TypeError to be raised by function calls with wrong arity.""" 

332 return TypeError(f"{name}() takes {takes} positional arguments but " 

333 f"{given} were given") 

334 

335 

336def kwarg_error(name, kw): 

337 """ 

338 Generate a TypeError to be raised by function calls with wrong kwarg. 

339 

340 Parameters 

341 ---------- 

342 name : str 

343 The name of the calling function. 

344 kw : str or Iterable[str] 

345 Either the invalid keyword argument name, or an iterable yielding 

346 invalid keyword arguments (e.g., a ``kwargs`` dict). 

347 """ 

348 if not isinstance(kw, str): 

349 kw = next(iter(kw)) 

350 return TypeError(f"{name}() got an unexpected keyword argument '{kw}'") 

351 

352 

353def recursive_subclasses(cls): 

354 """Yield *cls* and direct and indirect subclasses of *cls*.""" 

355 yield cls 

356 for subcls in cls.__subclasses__(): 

357 yield from recursive_subclasses(subcls) 

358 

359 

360def warn_external(message, category=None): 

361 """ 

362 `warnings.warn` wrapper that sets *stacklevel* to "outside Matplotlib". 

363 

364 The original emitter of the warning can be obtained by patching this 

365 function back to `warnings.warn`, i.e. ``_api.warn_external = 

366 warnings.warn`` (or ``functools.partial(warnings.warn, stacklevel=2)``, 

367 etc.). 

368 """ 

369 frame = sys._getframe() 

370 for stacklevel in itertools.count(1): 

371 if frame is None: 

372 # when called in embedded context may hit frame is None 

373 break 

374 if not re.match(r"\A(matplotlib|mpl_toolkits)(\Z|\.(?!tests\.))", 

375 # Work around sphinx-gallery not setting __name__. 

376 frame.f_globals.get("__name__", "")): 

377 break 

378 frame = frame.f_back 

379 # preemptively break reference cycle between locals and the frame 

380 del frame 

381 warnings.warn(message, category, stacklevel)