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

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

176 statements  

1""" 

2Helper functions for deprecating parts of the Matplotlib API. 

3 

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

5 

6.. warning:: 

7 

8 This module is for internal use only. Do not use it in your own code. 

9 We may change the API at any time with no warning. 

10 

11""" 

12 

13import contextlib 

14import functools 

15import inspect 

16import math 

17import warnings 

18 

19 

20class MatplotlibDeprecationWarning(DeprecationWarning): 

21 """A class for issuing deprecation warnings for Matplotlib users.""" 

22 

23 

24def _generate_deprecation_warning( 

25 since, message='', name='', alternative='', pending=False, obj_type='', 

26 addendum='', *, removal=''): 

27 if pending: 

28 if removal: 

29 raise ValueError( 

30 "A pending deprecation cannot have a scheduled removal") 

31 else: 

32 if not removal: 

33 macro, meso, *_ = since.split('.') 

34 removal = f'{macro}.{int(meso) + 2}' 

35 removal = f"in {removal}" 

36 if not message: 

37 message = ( 

38 ("The %(name)s %(obj_type)s" if obj_type else "%(name)s") 

39 + (" will be deprecated in a future version" 

40 if pending else 

41 " was deprecated in Matplotlib %(since)s and will be removed %(removal)s" 

42 ) 

43 + "." 

44 + (" Use %(alternative)s instead." if alternative else "") 

45 + (" %(addendum)s" if addendum else "")) 

46 warning_cls = (PendingDeprecationWarning if pending 

47 else MatplotlibDeprecationWarning) 

48 return warning_cls(message % dict( 

49 func=name, name=name, obj_type=obj_type, since=since, removal=removal, 

50 alternative=alternative, addendum=addendum)) 

51 

52 

53def warn_deprecated( 

54 since, *, message='', name='', alternative='', pending=False, 

55 obj_type='', addendum='', removal=''): 

56 """ 

57 Display a standardized deprecation. 

58 

59 Parameters 

60 ---------- 

61 since : str 

62 The release at which this API became deprecated. 

63 message : str, optional 

64 Override the default deprecation message. The ``%(since)s``, 

65 ``%(name)s``, ``%(alternative)s``, ``%(obj_type)s``, ``%(addendum)s``, 

66 and ``%(removal)s`` format specifiers will be replaced by the values 

67 of the respective arguments passed to this function. 

68 name : str, optional 

69 The name of the deprecated object. 

70 alternative : str, optional 

71 An alternative API that the user may use in place of the deprecated 

72 API. The deprecation warning will tell the user about this alternative 

73 if provided. 

74 pending : bool, optional 

75 If True, uses a PendingDeprecationWarning instead of a 

76 DeprecationWarning. Cannot be used together with *removal*. 

77 obj_type : str, optional 

78 The object type being deprecated. 

79 addendum : str, optional 

80 Additional text appended directly to the final message. 

81 removal : str, optional 

82 The expected removal version. With the default (an empty string), a 

83 removal version is automatically computed from *since*. Set to other 

84 Falsy values to not schedule a removal date. Cannot be used together 

85 with *pending*. 

86 

87 Examples 

88 -------- 

89 :: 

90 

91 # To warn of the deprecation of "matplotlib.name_of_module" 

92 warn_deprecated('1.4.0', name='matplotlib.name_of_module', 

93 obj_type='module') 

94 """ 

95 warning = _generate_deprecation_warning( 

96 since, message, name, alternative, pending, obj_type, addendum, 

97 removal=removal) 

98 from . import warn_external 

99 warn_external(warning, category=MatplotlibDeprecationWarning) 

100 

101 

102def deprecated(since, *, message='', name='', alternative='', pending=False, 

103 obj_type=None, addendum='', removal=''): 

104 """ 

105 Decorator to mark a function, a class, or a property as deprecated. 

106 

107 When deprecating a classmethod, a staticmethod, or a property, the 

108 ``@deprecated`` decorator should go *under* ``@classmethod`` and 

109 ``@staticmethod`` (i.e., `deprecated` should directly decorate the 

110 underlying callable), but *over* ``@property``. 

111 

112 When deprecating a class ``C`` intended to be used as a base class in a 

113 multiple inheritance hierarchy, ``C`` *must* define an ``__init__`` method 

114 (if ``C`` instead inherited its ``__init__`` from its own base class, then 

115 ``@deprecated`` would mess up ``__init__`` inheritance when installing its 

116 own (deprecation-emitting) ``C.__init__``). 

117 

118 Parameters are the same as for `warn_deprecated`, except that *obj_type* 

119 defaults to 'class' if decorating a class, 'attribute' if decorating a 

120 property, and 'function' otherwise. 

121 

122 Examples 

123 -------- 

124 :: 

125 

126 @deprecated('1.4.0') 

127 def the_function_to_deprecate(): 

128 pass 

129 """ 

130 

131 def deprecate(obj, message=message, name=name, alternative=alternative, 

132 pending=pending, obj_type=obj_type, addendum=addendum): 

133 from matplotlib._api import classproperty 

134 

135 if isinstance(obj, type): 

136 if obj_type is None: 

137 obj_type = "class" 

138 func = obj.__init__ 

139 name = name or obj.__name__ 

140 old_doc = obj.__doc__ 

141 

142 def finalize(wrapper, new_doc): 

143 try: 

144 obj.__doc__ = new_doc 

145 except AttributeError: # Can't set on some extension objects. 

146 pass 

147 obj.__init__ = functools.wraps(obj.__init__)(wrapper) 

148 return obj 

149 

150 elif isinstance(obj, (property, classproperty)): 

151 if obj_type is None: 

152 obj_type = "attribute" 

153 func = None 

154 name = name or obj.fget.__name__ 

155 old_doc = obj.__doc__ 

156 

157 class _deprecated_property(type(obj)): 

158 def __get__(self, instance, owner=None): 

159 if instance is not None or owner is not None \ 

160 and isinstance(self, classproperty): 

161 emit_warning() 

162 return super().__get__(instance, owner) 

163 

164 def __set__(self, instance, value): 

165 if instance is not None: 

166 emit_warning() 

167 return super().__set__(instance, value) 

168 

169 def __delete__(self, instance): 

170 if instance is not None: 

171 emit_warning() 

172 return super().__delete__(instance) 

173 

174 def __set_name__(self, owner, set_name): 

175 nonlocal name 

176 if name == "<lambda>": 

177 name = set_name 

178 

179 def finalize(_, new_doc): 

180 return _deprecated_property( 

181 fget=obj.fget, fset=obj.fset, fdel=obj.fdel, doc=new_doc) 

182 

183 else: 

184 if obj_type is None: 

185 obj_type = "function" 

186 func = obj 

187 name = name or obj.__name__ 

188 old_doc = func.__doc__ 

189 

190 def finalize(wrapper, new_doc): 

191 wrapper = functools.wraps(func)(wrapper) 

192 wrapper.__doc__ = new_doc 

193 return wrapper 

194 

195 def emit_warning(): 

196 warn_deprecated( 

197 since, message=message, name=name, alternative=alternative, 

198 pending=pending, obj_type=obj_type, addendum=addendum, 

199 removal=removal) 

200 

201 def wrapper(*args, **kwargs): 

202 emit_warning() 

203 return func(*args, **kwargs) 

204 

205 old_doc = inspect.cleandoc(old_doc or '').strip('\n') 

206 

207 notes_header = '\nNotes\n-----' 

208 second_arg = ' '.join([t.strip() for t in 

209 (message, f"Use {alternative} instead." 

210 if alternative else "", addendum) if t]) 

211 new_doc = (f"[*Deprecated*] {old_doc}\n" 

212 f"{notes_header if notes_header not in old_doc else ''}\n" 

213 f".. deprecated:: {since}\n" 

214 f" {second_arg}") 

215 

216 if not old_doc: 

217 # This is to prevent a spurious 'unexpected unindent' warning from 

218 # docutils when the original docstring was blank. 

219 new_doc += r'\ ' 

220 

221 return finalize(wrapper, new_doc) 

222 

223 return deprecate 

224 

225 

226class deprecate_privatize_attribute: 

227 """ 

228 Helper to deprecate public access to an attribute (or method). 

229 

230 This helper should only be used at class scope, as follows:: 

231 

232 class Foo: 

233 attr = _deprecate_privatize_attribute(*args, **kwargs) 

234 

235 where *all* parameters are forwarded to `deprecated`. This form makes 

236 ``attr`` a property which forwards read and write access to ``self._attr`` 

237 (same name but with a leading underscore), with a deprecation warning. 

238 Note that the attribute name is derived from *the name this helper is 

239 assigned to*. This helper also works for deprecating methods. 

240 """ 

241 

242 def __init__(self, *args, **kwargs): 

243 self.deprecator = deprecated(*args, **kwargs) 

244 

245 def __set_name__(self, owner, name): 

246 setattr(owner, name, self.deprecator( 

247 property(lambda self: getattr(self, f"_{name}"), 

248 lambda self, value: setattr(self, f"_{name}", value)), 

249 name=name)) 

250 

251 

252# Used by _copy_docstring_and_deprecators to redecorate pyplot wrappers and 

253# boilerplate.py to retrieve original signatures. It may seem natural to store 

254# this information as an attribute on the wrapper, but if the wrapper gets 

255# itself functools.wraps()ed, then such attributes are silently propagated to 

256# the outer wrapper, which is not desired. 

257DECORATORS = {} 

258 

259 

260def rename_parameter(since, old, new, func=None): 

261 """ 

262 Decorator indicating that parameter *old* of *func* is renamed to *new*. 

263 

264 The actual implementation of *func* should use *new*, not *old*. If *old* 

265 is passed to *func*, a DeprecationWarning is emitted, and its value is 

266 used, even if *new* is also passed by keyword (this is to simplify pyplot 

267 wrapper functions, which always pass *new* explicitly to the Axes method). 

268 If *new* is also passed but positionally, a TypeError will be raised by the 

269 underlying function during argument binding. 

270 

271 Examples 

272 -------- 

273 :: 

274 

275 @_api.rename_parameter("3.1", "bad_name", "good_name") 

276 def func(good_name): ... 

277 """ 

278 

279 decorator = functools.partial(rename_parameter, since, old, new) 

280 

281 if func is None: 

282 return decorator 

283 

284 signature = inspect.signature(func) 

285 assert old not in signature.parameters, ( 

286 f"Matplotlib internal error: {old!r} cannot be a parameter for " 

287 f"{func.__name__}()") 

288 assert new in signature.parameters, ( 

289 f"Matplotlib internal error: {new!r} must be a parameter for " 

290 f"{func.__name__}()") 

291 

292 @functools.wraps(func) 

293 def wrapper(*args, **kwargs): 

294 if old in kwargs: 

295 warn_deprecated( 

296 since, message=f"The {old!r} parameter of {func.__name__}() " 

297 f"has been renamed {new!r} since Matplotlib {since}; support " 

298 f"for the old name will be dropped %(removal)s.") 

299 kwargs[new] = kwargs.pop(old) 

300 return func(*args, **kwargs) 

301 

302 # wrapper() must keep the same documented signature as func(): if we 

303 # instead made both *old* and *new* appear in wrapper()'s signature, they 

304 # would both show up in the pyplot function for an Axes method as well and 

305 # pyplot would explicitly pass both arguments to the Axes method. 

306 

307 DECORATORS[wrapper] = decorator 

308 return wrapper 

309 

310 

311class _deprecated_parameter_class: 

312 def __repr__(self): 

313 return "<deprecated parameter>" 

314 

315 

316_deprecated_parameter = _deprecated_parameter_class() 

317 

318 

319def delete_parameter(since, name, func=None, **kwargs): 

320 """ 

321 Decorator indicating that parameter *name* of *func* is being deprecated. 

322 

323 The actual implementation of *func* should keep the *name* parameter in its 

324 signature, or accept a ``**kwargs`` argument (through which *name* would be 

325 passed). 

326 

327 Parameters that come after the deprecated parameter effectively become 

328 keyword-only (as they cannot be passed positionally without triggering the 

329 DeprecationWarning on the deprecated parameter), and should be marked as 

330 such after the deprecation period has passed and the deprecated parameter 

331 is removed. 

332 

333 Parameters other than *since*, *name*, and *func* are keyword-only and 

334 forwarded to `.warn_deprecated`. 

335 

336 Examples 

337 -------- 

338 :: 

339 

340 @_api.delete_parameter("3.1", "unused") 

341 def func(used_arg, other_arg, unused, more_args): ... 

342 """ 

343 

344 decorator = functools.partial(delete_parameter, since, name, **kwargs) 

345 

346 if func is None: 

347 return decorator 

348 

349 signature = inspect.signature(func) 

350 # Name of `**kwargs` parameter of the decorated function, typically 

351 # "kwargs" if such a parameter exists, or None if the decorated function 

352 # doesn't accept `**kwargs`. 

353 kwargs_name = next((param.name for param in signature.parameters.values() 

354 if param.kind == inspect.Parameter.VAR_KEYWORD), None) 

355 if name in signature.parameters: 

356 kind = signature.parameters[name].kind 

357 is_varargs = kind is inspect.Parameter.VAR_POSITIONAL 

358 is_varkwargs = kind is inspect.Parameter.VAR_KEYWORD 

359 if not is_varargs and not is_varkwargs: 

360 name_idx = ( 

361 # Deprecated parameter can't be passed positionally. 

362 math.inf if kind is inspect.Parameter.KEYWORD_ONLY 

363 # If call site has no more than this number of parameters, the 

364 # deprecated parameter can't have been passed positionally. 

365 else [*signature.parameters].index(name)) 

366 func.__signature__ = signature = signature.replace(parameters=[ 

367 param.replace(default=_deprecated_parameter) 

368 if param.name == name else param 

369 for param in signature.parameters.values()]) 

370 else: 

371 name_idx = -1 # Deprecated parameter can always have been passed. 

372 else: 

373 is_varargs = is_varkwargs = False 

374 # Deprecated parameter can't be passed positionally. 

375 name_idx = math.inf 

376 assert kwargs_name, ( 

377 f"Matplotlib internal error: {name!r} must be a parameter for " 

378 f"{func.__name__}()") 

379 

380 addendum = kwargs.pop('addendum', None) 

381 

382 @functools.wraps(func) 

383 def wrapper(*inner_args, **inner_kwargs): 

384 if len(inner_args) <= name_idx and name not in inner_kwargs: 

385 # Early return in the simple, non-deprecated case (much faster than 

386 # calling bind()). 

387 return func(*inner_args, **inner_kwargs) 

388 arguments = signature.bind(*inner_args, **inner_kwargs).arguments 

389 if is_varargs and arguments.get(name): 

390 warn_deprecated( 

391 since, message=f"Additional positional arguments to " 

392 f"{func.__name__}() are deprecated since %(since)s and " 

393 f"support for them will be removed %(removal)s.") 

394 elif is_varkwargs and arguments.get(name): 

395 warn_deprecated( 

396 since, message=f"Additional keyword arguments to " 

397 f"{func.__name__}() are deprecated since %(since)s and " 

398 f"support for them will be removed %(removal)s.") 

399 # We cannot just check `name not in arguments` because the pyplot 

400 # wrappers always pass all arguments explicitly. 

401 elif any(name in d and d[name] != _deprecated_parameter 

402 for d in [arguments, arguments.get(kwargs_name, {})]): 

403 deprecation_addendum = ( 

404 f"If any parameter follows {name!r}, they should be passed as " 

405 f"keyword, not positionally.") 

406 warn_deprecated( 

407 since, 

408 name=repr(name), 

409 obj_type=f"parameter of {func.__name__}()", 

410 addendum=(addendum + " " + deprecation_addendum) if addendum 

411 else deprecation_addendum, 

412 **kwargs) 

413 return func(*inner_args, **inner_kwargs) 

414 

415 DECORATORS[wrapper] = decorator 

416 return wrapper 

417 

418 

419def make_keyword_only(since, name, func=None): 

420 """ 

421 Decorator indicating that passing parameter *name* (or any of the following 

422 ones) positionally to *func* is being deprecated. 

423 

424 When used on a method that has a pyplot wrapper, this should be the 

425 outermost decorator, so that :file:`boilerplate.py` can access the original 

426 signature. 

427 """ 

428 

429 decorator = functools.partial(make_keyword_only, since, name) 

430 

431 if func is None: 

432 return decorator 

433 

434 signature = inspect.signature(func) 

435 POK = inspect.Parameter.POSITIONAL_OR_KEYWORD 

436 KWO = inspect.Parameter.KEYWORD_ONLY 

437 assert (name in signature.parameters 

438 and signature.parameters[name].kind == POK), ( 

439 f"Matplotlib internal error: {name!r} must be a positional-or-keyword " 

440 f"parameter for {func.__name__}()") 

441 names = [*signature.parameters] 

442 name_idx = names.index(name) 

443 kwonly = [name for name in names[name_idx:] 

444 if signature.parameters[name].kind == POK] 

445 

446 @functools.wraps(func) 

447 def wrapper(*args, **kwargs): 

448 # Don't use signature.bind here, as it would fail when stacked with 

449 # rename_parameter and an "old" argument name is passed in 

450 # (signature.bind would fail, but the actual call would succeed). 

451 if len(args) > name_idx: 

452 warn_deprecated( 

453 since, message="Passing the %(name)s %(obj_type)s " 

454 "positionally is deprecated since Matplotlib %(since)s; the " 

455 "parameter will become keyword-only %(removal)s.", 

456 name=name, obj_type=f"parameter of {func.__name__}()") 

457 return func(*args, **kwargs) 

458 

459 # Don't modify *func*'s signature, as boilerplate.py needs it. 

460 wrapper.__signature__ = signature.replace(parameters=[ 

461 param.replace(kind=KWO) if param.name in kwonly else param 

462 for param in signature.parameters.values()]) 

463 DECORATORS[wrapper] = decorator 

464 return wrapper 

465 

466 

467def deprecate_method_override(method, obj, *, allow_empty=False, **kwargs): 

468 """ 

469 Return ``obj.method`` with a deprecation if it was overridden, else None. 

470 

471 Parameters 

472 ---------- 

473 method 

474 An unbound method, i.e. an expression of the form 

475 ``Class.method_name``. Remember that within the body of a method, one 

476 can always use ``__class__`` to refer to the class that is currently 

477 being defined. 

478 obj 

479 Either an object of the class where *method* is defined, or a subclass 

480 of that class. 

481 allow_empty : bool, default: False 

482 Whether to allow overrides by "empty" methods without emitting a 

483 warning. 

484 **kwargs 

485 Additional parameters passed to `warn_deprecated` to generate the 

486 deprecation warning; must at least include the "since" key. 

487 """ 

488 

489 def empty(): pass 

490 def empty_with_docstring(): """doc""" 

491 

492 name = method.__name__ 

493 bound_child = getattr(obj, name) 

494 bound_base = ( 

495 method # If obj is a class, then we need to use unbound methods. 

496 if isinstance(bound_child, type(empty)) and isinstance(obj, type) 

497 else method.__get__(obj)) 

498 if (bound_child != bound_base 

499 and (not allow_empty 

500 or (getattr(getattr(bound_child, "__code__", None), 

501 "co_code", None) 

502 not in [empty.__code__.co_code, 

503 empty_with_docstring.__code__.co_code]))): 

504 warn_deprecated(**{"name": name, "obj_type": "method", **kwargs}) 

505 return bound_child 

506 return None 

507 

508 

509@contextlib.contextmanager 

510def suppress_matplotlib_deprecation_warning(): 

511 with warnings.catch_warnings(): 

512 warnings.simplefilter("ignore", MatplotlibDeprecationWarning) 

513 yield