|
15 | 15 |
|
16 | 16 | deprecated
|
17 | 17 | deprecation_warning
|
| 18 | + relocated_module |
18 | 19 | relocated_module_attribute
|
19 | 20 | RenamedClass
|
20 | 21 | """
|
@@ -322,6 +323,71 @@ def __getattr__(self, name):
|
322 | 323 | raise AttributeError("module '%s' has no attribute '%s'"
|
323 | 324 | % (self.__name__, name))
|
324 | 325 |
|
| 326 | +def relocated_module(new_name, msg=None, logger=None, |
| 327 | + version=None, remove_in=None): |
| 328 | + """Provide a deprecation path for moved / renamed modules |
| 329 | +
|
| 330 | + Upon import, the old module (that called `relocated_module()`) will |
| 331 | + be replaced in `sys.modules` by an alias that points directly to the |
| 332 | + new module. As a result, the old module should have only two lines |
| 333 | + of executable Python code (the import of `relocated_module` and the |
| 334 | + call to it). |
| 335 | +
|
| 336 | + Parameters |
| 337 | + ---------- |
| 338 | + new_name: str |
| 339 | + The new (fully-qualified) module name |
| 340 | +
|
| 341 | + msg: str |
| 342 | + A custom deprecation message. |
| 343 | +
|
| 344 | + logger: str |
| 345 | + The logger to use for emitting the warning (default: the calling |
| 346 | + pyomo package, or "pyomo") |
| 347 | +
|
| 348 | + version: str [required] |
| 349 | + The version in which the module was renamed or moved. |
| 350 | + General practice is to set version to '' or 'TBD' during |
| 351 | + development and update it to the actual release as part of the |
| 352 | + release process. |
| 353 | +
|
| 354 | + remove_in: str |
| 355 | + The version in which the module will be removed from the code. |
| 356 | +
|
| 357 | + Example |
| 358 | + ------- |
| 359 | + >>> from pyomo.common.deprecation import relocated_module |
| 360 | + >>> relocated_module('pyomo.common.deprecation', version='1.2.3') |
| 361 | + WARNING: DEPRECATED: The '...' module has been moved to |
| 362 | + 'pyomo.common.deprecation'. Please update your import. |
| 363 | + (deprecated in 1.2.3) ... |
| 364 | +
|
| 365 | + """ |
| 366 | + from importlib import import_module |
| 367 | + new_module = import_module(new_name) |
| 368 | + |
| 369 | + # The relevant module (the one being deprecated) is the one that |
| 370 | + # holds the function/method that called deprecated_module(). The |
| 371 | + # relevant calling frame for the deprecation warning is the first |
| 372 | + # frame in the stack that doesn't look like the importer (i.e., the |
| 373 | + # thing that imported the deprecated module). |
| 374 | + cf = _find_calling_frame(1) |
| 375 | + old_name = cf.f_globals.get('__name__', '<stdin>') |
| 376 | + cf = cf.f_back |
| 377 | + if cf is not None: |
| 378 | + importer = cf.f_back.f_globals['__name__'].split('.')[0] |
| 379 | + while cf is not None and \ |
| 380 | + cf.f_globals['__name__'].split('.')[0] == importer: |
| 381 | + cf = cf.f_back |
| 382 | + if cf is None: |
| 383 | + cf = _find_calling_frame(1) |
| 384 | + |
| 385 | + sys.modules[old_name] = new_module |
| 386 | + if msg is None: |
| 387 | + msg = f"The '{old_name}' module has been moved to '{new_name}'. " \ |
| 388 | + 'Please update your import.' |
| 389 | + deprecation_warning(msg, logger, version, remove_in, cf) |
| 390 | + |
325 | 391 | def relocated_module_attribute(local, target, version, remove_in=None):
|
326 | 392 | """Provide a deprecation path for moved / renamed module attributes
|
327 | 393 |
|
@@ -386,40 +452,40 @@ class RenamedClass(type):
|
386 | 452 | This metaclass provides a mechanism for renaming old classes while
|
387 | 453 | still preserving isinstance / issubclass relationships.
|
388 | 454 |
|
389 |
| - Example |
390 |
| - ------- |
391 |
| - >>> from pyomo.common.deprecation import RenamedClass |
392 |
| - >>> class NewClass(object): |
393 |
| - ... pass |
394 |
| - >>> class OldClass(metaclass=RenamedClass): |
395 |
| - ... __renamed__new_class__ = NewClass |
396 |
| - ... __renamed__version__ = '6.0' |
397 |
| -
|
398 |
| - Deriving from the old class generates a warning: |
399 |
| -
|
400 |
| - >>> class DerivedOldClass(OldClass): |
401 |
| - ... pass |
402 |
| - WARNING: DEPRECATED: Declaring class 'DerivedOldClass' derived from |
403 |
| - 'OldClass'. The class 'OldClass' has been renamed to 'NewClass'. |
404 |
| - (deprecated in 6.0) ... |
405 |
| -
|
406 |
| - As does instantiating the old class: |
407 |
| -
|
408 |
| - >>> old = OldClass() |
409 |
| - WARNING: DEPRECATED: Instantiating class 'OldClass'. The class |
410 |
| - 'OldClass' has been renamed to 'NewClass'. (deprecated in 6.0) ... |
411 |
| -
|
412 |
| - Finally, isinstance and issubclass still work, for example: |
413 |
| -
|
414 |
| - >>> isinstance(old, NewClass) |
415 |
| - True |
416 |
| - >>> class NewSubclass(NewClass): |
417 |
| - ... pass |
418 |
| - >>> new = NewSubclass() |
419 |
| - >>> isinstance(new, OldClass) |
420 |
| - WARNING: DEPRECATED: Checking type relative to 'OldClass'. The class |
421 |
| - 'OldClass' has been renamed to 'NewClass'. (deprecated in 6.0) ... |
422 |
| - True |
| 455 | + Examples |
| 456 | + -------- |
| 457 | + >>> from pyomo.common.deprecation import RenamedClass |
| 458 | + >>> class NewClass(object): |
| 459 | + ... pass |
| 460 | + >>> class OldClass(metaclass=RenamedClass): |
| 461 | + ... __renamed__new_class__ = NewClass |
| 462 | + ... __renamed__version__ = '6.0' |
| 463 | +
|
| 464 | + Deriving from the old class generates a warning: |
| 465 | +
|
| 466 | + >>> class DerivedOldClass(OldClass): |
| 467 | + ... pass |
| 468 | + WARNING: DEPRECATED: Declaring class 'DerivedOldClass' derived from |
| 469 | + 'OldClass'. The class 'OldClass' has been renamed to 'NewClass'. |
| 470 | + (deprecated in 6.0) ... |
| 471 | +
|
| 472 | + As does instantiating the old class: |
| 473 | +
|
| 474 | + >>> old = OldClass() |
| 475 | + WARNING: DEPRECATED: Instantiating class 'OldClass'. The class |
| 476 | + 'OldClass' has been renamed to 'NewClass'. (deprecated in 6.0) ... |
| 477 | +
|
| 478 | + Finally, `isinstance` and `issubclass` still work, for example: |
| 479 | +
|
| 480 | + >>> isinstance(old, NewClass) |
| 481 | + True |
| 482 | + >>> class NewSubclass(NewClass): |
| 483 | + ... pass |
| 484 | + >>> new = NewSubclass() |
| 485 | + >>> isinstance(new, OldClass) |
| 486 | + WARNING: DEPRECATED: Checking type relative to 'OldClass'. The class |
| 487 | + 'OldClass' has been renamed to 'NewClass'. (deprecated in 6.0) ... |
| 488 | + True |
423 | 489 |
|
424 | 490 | """
|
425 | 491 | def __new__(cls, name, bases, classdict, *args, **kwargs):
|
|
0 commit comments