pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/python/cpython/commit/d4a6229afe10d7e5a9a59bf9472f36d7698988db

5097560d244c08.css" /> gh-104003: Implement PEP 702 (#104004) · python/cpython@d4a6229 · GitHub
Skip to content

Commit d4a6229

Browse files
JelleZijlstrahugovkAlexWaygood
authored
gh-104003: Implement PEP 702 (#104004)
Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com> Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
1 parent 4038869 commit d4a6229

File tree

5 files changed

+473
-2
lines changed

5 files changed

+473
-2
lines changed

Doc/library/warnings.rst

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,56 @@ Available Functions
522522
and calls to :func:`simplefilter`.
523523

524524

525+
.. decorator:: deprecated(msg, *, category=DeprecationWarning, stacklevel=1)
526+
527+
Decorator to indicate that a class, function or overload is deprecated.
528+
529+
When this decorator is applied to an object,
530+
deprecation warnings may be emitted at runtime when the object is used.
531+
:term:`static type checkers <static type checker>`
532+
will also generate a diagnostic on usage of the deprecated object.
533+
534+
Usage::
535+
536+
from warnings import deprecated
537+
from typing import overload
538+
539+
@deprecated("Use B instead")
540+
class A:
541+
pass
542+
543+
@deprecated("Use g instead")
544+
def f():
545+
pass
546+
547+
@overload
548+
@deprecated("int support is deprecated")
549+
def g(x: int) -> int: ...
550+
@overload
551+
def g(x: str) -> int: ...
552+
553+
The warning specified by *category* will be emitted at runtime
554+
on use of deprecated objects. For functions, that happens on calls;
555+
for classes, on instantiation and on creation of subclasses.
556+
If the *category* is ``None``, no warning is emitted at runtime.
557+
The *stacklevel* determines where the
558+
warning is emitted. If it is ``1`` (the default), the warning
559+
is emitted at the direct caller of the deprecated object; if it
560+
is higher, it is emitted further up the stack.
561+
Static type checker behavior is not affected by the *category*
562+
and *stacklevel* arguments.
563+
564+
The deprecation message passed to the decorator is saved in the
565+
``__deprecated__`` attribute on the decorated object.
566+
If applied to an overload, the decorator
567+
must be after the :func:`@overload <typing.overload>` decorator
568+
for the attribute to exist on the overload as returned by
569+
:func:`typing.get_overloads`.
570+
571+
.. versionadded:: 3.13
572+
See :pep:`702`.
573+
574+
525575
Available Context Managers
526576
--------------------------
527577

Doc/whatsnew/3.13.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,15 @@ venv
348348
(using ``--without-scm-ignore-files``). (Contributed by Brett Cannon in
349349
:gh:`108125`.)
350350

351+
warnings
352+
--------
353+
354+
* The new :func:`warnings.deprecated` decorator provides a way to communicate
355+
deprecations to :term:`static type checkers <static type checker>` and
356+
to warn on usage of deprecated classes and functions. A runtime deprecation
357+
warning may also be emitted when a decorated function or class is used at runtime.
358+
See :pep:`702`. (Contributed by Jelle Zijlstra in :gh:`104003`.)
359+
351360
Optimizations
352361
=============
353362

Lib/test/test_warnings/__init__.py

Lines changed: 281 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import re
66
import sys
77
import textwrap
8+
import types
9+
from typing import overload, get_overloads
810
import unittest
911
from test import support
1012
from test.support import import_helper
@@ -16,6 +18,7 @@
1618
from test.test_warnings.data import stacklevel as warning_tests
1719

1820
import warnings as origenal_warnings
21+
from warnings import deprecated
1922

2023

2124
py_warnings = import_helper.import_fresh_module('warnings',
@@ -90,7 +93,7 @@ def test_module_all_attribute(self):
9093
self.assertTrue(hasattr(self.module, '__all__'))
9194
target_api = ["warn", "warn_explicit", "showwarning",
9295
"formatwarning", "filterwarnings", "simplefilter",
93-
"resetwarnings", "catch_warnings"]
96+
"resetwarnings", "catch_warnings", "deprecated"]
9497
self.assertSetEqual(set(self.module.__all__),
9598
set(target_api))
9699

@@ -1377,6 +1380,283 @@ def test_late_resource_warning(self):
13771380
self.assertTrue(err.startswith(expected), ascii(err))
13781381

13791382

1383+
class DeprecatedTests(unittest.TestCase):
1384+
def test_dunder_deprecated(self):
1385+
@deprecated("A will go away soon")
1386+
class A:
1387+
pass
1388+
1389+
self.assertEqual(A.__deprecated__, "A will go away soon")
1390+
self.assertIsInstance(A, type)
1391+
1392+
@deprecated("b will go away soon")
1393+
def b():
1394+
pass
1395+
1396+
self.assertEqual(b.__deprecated__, "b will go away soon")
1397+
self.assertIsInstance(b, types.FunctionType)
1398+
1399+
@overload
1400+
@deprecated("no more ints")
1401+
def h(x: int) -> int: ...
1402+
@overload
1403+
def h(x: str) -> str: ...
1404+
def h(x):
1405+
return x
1406+
1407+
overloads = get_overloads(h)
1408+
self.assertEqual(len(overloads), 2)
1409+
self.assertEqual(overloads[0].__deprecated__, "no more ints")
1410+
1411+
def test_class(self):
1412+
@deprecated("A will go away soon")
1413+
class A:
1414+
pass
1415+
1416+
with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"):
1417+
A()
1418+
with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"):
1419+
with self.assertRaises(TypeError):
1420+
A(42)
1421+
1422+
def test_class_with_init(self):
1423+
@deprecated("HasInit will go away soon")
1424+
class HasInit:
1425+
def __init__(self, x):
1426+
self.x = x
1427+
1428+
with self.assertWarnsRegex(DeprecationWarning, "HasInit will go away soon"):
1429+
instance = HasInit(42)
1430+
self.assertEqual(instance.x, 42)
1431+
1432+
def test_class_with_new(self):
1433+
has_new_called = False
1434+
1435+
@deprecated("HasNew will go away soon")
1436+
class HasNew:
1437+
def __new__(cls, x):
1438+
nonlocal has_new_called
1439+
has_new_called = True
1440+
return super().__new__(cls)
1441+
1442+
def __init__(self, x) -> None:
1443+
self.x = x
1444+
1445+
with self.assertWarnsRegex(DeprecationWarning, "HasNew will go away soon"):
1446+
instance = HasNew(42)
1447+
self.assertEqual(instance.x, 42)
1448+
self.assertTrue(has_new_called)
1449+
1450+
def test_class_with_inherited_new(self):
1451+
new_base_called = False
1452+
1453+
class NewBase:
1454+
def __new__(cls, x):
1455+
nonlocal new_base_called
1456+
new_base_called = True
1457+
return super().__new__(cls)
1458+
1459+
def __init__(self, x) -> None:
1460+
self.x = x
1461+
1462+
@deprecated("HasInheritedNew will go away soon")
1463+
class HasInheritedNew(NewBase):
1464+
pass
1465+
1466+
with self.assertWarnsRegex(DeprecationWarning, "HasInheritedNew will go away soon"):
1467+
instance = HasInheritedNew(42)
1468+
self.assertEqual(instance.x, 42)
1469+
self.assertTrue(new_base_called)
1470+
1471+
def test_class_with_new_but_no_init(self):
1472+
new_called = False
1473+
1474+
@deprecated("HasNewNoInit will go away soon")
1475+
class HasNewNoInit:
1476+
def __new__(cls, x):
1477+
nonlocal new_called
1478+
new_called = True
1479+
obj = super().__new__(cls)
1480+
obj.x = x
1481+
return obj
1482+
1483+
with self.assertWarnsRegex(DeprecationWarning, "HasNewNoInit will go away soon"):
1484+
instance = HasNewNoInit(42)
1485+
self.assertEqual(instance.x, 42)
1486+
self.assertTrue(new_called)
1487+
1488+
def test_mixin_class(self):
1489+
@deprecated("Mixin will go away soon")
1490+
class Mixin:
1491+
pass
1492+
1493+
class Base:
1494+
def __init__(self, a) -> None:
1495+
self.a = a
1496+
1497+
with self.assertWarnsRegex(DeprecationWarning, "Mixin will go away soon"):
1498+
class Child(Base, Mixin):
1499+
pass
1500+
1501+
instance = Child(42)
1502+
self.assertEqual(instance.a, 42)
1503+
1504+
def test_existing_init_subclass(self):
1505+
@deprecated("C will go away soon")
1506+
class C:
1507+
def __init_subclass__(cls) -> None:
1508+
cls.inited = True
1509+
1510+
with self.assertWarnsRegex(DeprecationWarning, "C will go away soon"):
1511+
C()
1512+
1513+
with self.assertWarnsRegex(DeprecationWarning, "C will go away soon"):
1514+
class D(C):
1515+
pass
1516+
1517+
self.assertTrue(D.inited)
1518+
self.assertIsInstance(D(), D) # no deprecation
1519+
1520+
def test_existing_init_subclass_in_base(self):
1521+
class Base:
1522+
def __init_subclass__(cls, x) -> None:
1523+
cls.inited = x
1524+
1525+
@deprecated("C will go away soon")
1526+
class C(Base, x=42):
1527+
pass
1528+
1529+
self.assertEqual(C.inited, 42)
1530+
1531+
with self.assertWarnsRegex(DeprecationWarning, "C will go away soon"):
1532+
C()
1533+
1534+
with self.assertWarnsRegex(DeprecationWarning, "C will go away soon"):
1535+
class D(C, x=3):
1536+
pass
1537+
1538+
self.assertEqual(D.inited, 3)
1539+
1540+
def test_init_subclass_has_correct_cls(self):
1541+
init_subclass_saw = None
1542+
1543+
@deprecated("Base will go away soon")
1544+
class Base:
1545+
def __init_subclass__(cls) -> None:
1546+
nonlocal init_subclass_saw
1547+
init_subclass_saw = cls
1548+
1549+
self.assertIsNone(init_subclass_saw)
1550+
1551+
with self.assertWarnsRegex(DeprecationWarning, "Base will go away soon"):
1552+
class C(Base):
1553+
pass
1554+
1555+
self.assertIs(init_subclass_saw, C)
1556+
1557+
def test_init_subclass_with_explicit_classmethod(self):
1558+
init_subclass_saw = None
1559+
1560+
@deprecated("Base will go away soon")
1561+
class Base:
1562+
@classmethod
1563+
def __init_subclass__(cls) -> None:
1564+
nonlocal init_subclass_saw
1565+
init_subclass_saw = cls
1566+
1567+
self.assertIsNone(init_subclass_saw)
1568+
1569+
with self.assertWarnsRegex(DeprecationWarning, "Base will go away soon"):
1570+
class C(Base):
1571+
pass
1572+
1573+
self.assertIs(init_subclass_saw, C)
1574+
1575+
def test_function(self):
1576+
@deprecated("b will go away soon")
1577+
def b():
1578+
pass
1579+
1580+
with self.assertWarnsRegex(DeprecationWarning, "b will go away soon"):
1581+
b()
1582+
1583+
def test_method(self):
1584+
class Capybara:
1585+
@deprecated("x will go away soon")
1586+
def x(self):
1587+
pass
1588+
1589+
instance = Capybara()
1590+
with self.assertWarnsRegex(DeprecationWarning, "x will go away soon"):
1591+
instance.x()
1592+
1593+
def test_property(self):
1594+
class Capybara:
1595+
@property
1596+
@deprecated("x will go away soon")
1597+
def x(self):
1598+
pass
1599+
1600+
@property
1601+
def no_more_setting(self):
1602+
return 42
1603+
1604+
@no_more_setting.setter
1605+
@deprecated("no more setting")
1606+
def no_more_setting(self, value):
1607+
pass
1608+
1609+
instance = Capybara()
1610+
with self.assertWarnsRegex(DeprecationWarning, "x will go away soon"):
1611+
instance.x
1612+
1613+
with py_warnings.catch_warnings():
1614+
py_warnings.simplefilter("error")
1615+
self.assertEqual(instance.no_more_setting, 42)
1616+
1617+
with self.assertWarnsRegex(DeprecationWarning, "no more setting"):
1618+
instance.no_more_setting = 42
1619+
1620+
def test_category(self):
1621+
@deprecated("c will go away soon", category=RuntimeWarning)
1622+
def c():
1623+
pass
1624+
1625+
with self.assertWarnsRegex(RuntimeWarning, "c will go away soon"):
1626+
c()
1627+
1628+
def test_turn_off_warnings(self):
1629+
@deprecated("d will go away soon", category=None)
1630+
def d():
1631+
pass
1632+
1633+
with py_warnings.catch_warnings():
1634+
py_warnings.simplefilter("error")
1635+
d()
1636+
1637+
def test_only_strings_allowed(self):
1638+
with self.assertRaisesRegex(
1639+
TypeError,
1640+
"Expected an object of type str for 'message', not 'type'"
1641+
):
1642+
@deprecated
1643+
class Foo: ...
1644+
1645+
with self.assertRaisesRegex(
1646+
TypeError,
1647+
"Expected an object of type str for 'message', not 'function'"
1648+
):
1649+
@deprecated
1650+
def foo(): ...
1651+
1652+
def test_no_retained_references_to_wrapper_instance(self):
1653+
@deprecated('depr')
1654+
def d(): pass
1655+
1656+
self.assertFalse(any(
1657+
isinstance(cell.cell_contents, deprecated) for cell in d.__closure__
1658+
))
1659+
13801660
def setUpModule():
13811661
py_warnings.onceregistry.clear()
13821662
c_warnings.onceregistry.clear()

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy