Skip to content

Commit e09fb71

Browse files
committed
fix python#21076: turn signal module constants into enums
1 parent bcc1746 commit e09fb71

File tree

7 files changed

+138
-8
lines changed

7 files changed

+138
-8
lines changed

Doc/library/signal.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ Besides, only the main thread is allowed to set a new signal handler.
6565
Module contents
6666
---------------
6767

68+
.. versionchanged:: 3.5
69+
signal (SIG*), handler (:const:`SIG_DFL`, :const:`SIG_IGN`) and sigmask
70+
(:const:`SIG_BLOCK`, :const:`SIG_UNBLOCK`, :const:`SIG_SETMASK`)
71+
related constants listed below were turned into
72+
:class:`enums <enum.IntEnum>`.
73+
:func:`getsignal`, :func:`pthread_sigmask`, :func:`sigpending` and
74+
:func:`sigwait` functions return human-readable
75+
:class:`enums <enum.IntEnum>`.
76+
77+
6878
The variables defined in the :mod:`signal` module are:
6979

7080

Doc/whatsnew/3.5.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ New Modules
134134
Improved Modules
135135
================
136136

137+
* Different constants of :mod:`signal` module are now enumeration values using
138+
the :mod:`enum` module. This allows meaningful names to be printed during
139+
debugging, instead of integer “magic numbers”. (contribute by Giampaolo
140+
Rodola' in :issue:`21076`)
141+
137142
* :class:`xmlrpc.client.ServerProxy` is now a :term:`context manager`
138143
(contributed by Claudiu Popa in :issue:`20627`).
139144

Lib/signal.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import _signal
2+
from _signal import *
3+
from functools import wraps as _wraps
4+
from enum import IntEnum as _IntEnum
5+
6+
_globals = globals()
7+
8+
Signals = _IntEnum(
9+
'Signals',
10+
{name: value for name, value in _globals.items()
11+
if name.isupper()
12+
and (name.startswith('SIG') and not name.startswith('SIG_'))
13+
or name.startswith('CTRL_')})
14+
15+
class Handlers(_IntEnum):
16+
SIG_DFL = _signal.SIG_DFL
17+
SIG_IGN = _signal.SIG_IGN
18+
19+
_globals.update(Signals.__members__)
20+
_globals.update(Handlers.__members__)
21+
22+
if 'pthread_sigmask' in _globals:
23+
class Sigmasks(_IntEnum):
24+
SIG_BLOCK = _signal.SIG_BLOCK
25+
SIG_UNBLOCK = _signal.SIG_UNBLOCK
26+
SIG_SETMASK = _signal.SIG_SETMASK
27+
28+
_globals.update(Sigmasks.__members__)
29+
30+
31+
def _int_to_enum(value, enum_klass):
32+
"""Convert a numeric value to an IntEnum member.
33+
If it's not a known member, return the numeric value itself.
34+
"""
35+
try:
36+
return enum_klass(value)
37+
except ValueError:
38+
return value
39+
40+
41+
def _enum_to_int(value):
42+
"""Convert an IntEnum member to a numeric value.
43+
If it's not a IntEnum member return the value itself.
44+
"""
45+
try:
46+
return int(value)
47+
except (ValueError, TypeError):
48+
return value
49+
50+
51+
@_wraps(_signal.signal)
52+
def signal(signalnum, handler):
53+
handler = _signal.signal(_enum_to_int(signalnum), _enum_to_int(handler))
54+
return _int_to_enum(handler, Handlers)
55+
56+
57+
@_wraps(_signal.getsignal)
58+
def getsignal(signalnum):
59+
handler = _signal.getsignal(signalnum)
60+
return _int_to_enum(handler, Handlers)
61+
62+
63+
if 'pthread_sigmask' in _globals:
64+
@_wraps(_signal.pthread_sigmask)
65+
def pthread_sigmask(how, mask):
66+
sigs_set = _signal.pthread_sigmask(how, mask)
67+
return set(_int_to_enum(x, Signals) for x in sigs_set)
68+
pthread_sigmask.__doc__ = _signal.pthread_sigmask.__doc__
69+
70+
71+
@_wraps(_signal.sigpending)
72+
def sigpending():
73+
sigs = _signal.sigpending()
74+
return set(_int_to_enum(x, Signals) for x in sigs)
75+
76+
77+
if 'sigwait' in _globals:
78+
@_wraps(_signal.sigwait)
79+
def sigwait(sigset):
80+
retsig = _signal.sigwait(sigset)
81+
return _int_to_enum(retsig, Signals)
82+
sigwait.__doc__ = _signal.sigwait
83+
84+
del _globals, _wraps

Lib/test/test_doctest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2897,7 +2897,7 @@ def test_CLI(): r"""
28972897

28982898
def test_main():
28992899
# Check the doctest cases in doctest itself:
2900-
support.run_doctest(doctest, verbosity=True)
2900+
ret = support.run_doctest(doctest, verbosity=True)
29012901
# Check the doctest cases defined here:
29022902
from test import test_doctest
29032903
support.run_doctest(test_doctest, verbosity=True)

Lib/test/test_signal.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import unittest
22
from test import support
33
from contextlib import closing
4+
import enum
45
import gc
56
import pickle
67
import select
@@ -39,6 +40,22 @@ def ignoring_eintr(__func, *args, **kwargs):
3940
return None
4041

4142

43+
class GenericTests(unittest.TestCase):
44+
45+
def test_enums(self):
46+
for name in dir(signal):
47+
sig = getattr(signal, name)
48+
if name in {'SIG_DFL', 'SIG_IGN'}:
49+
self.assertIsInstance(sig, signal.Handlers)
50+
elif name in {'SIG_BLOCK', 'SIG_UNBLOCK', 'SIG_SETMASK'}:
51+
self.assertIsInstance(sig, signal.Sigmasks)
52+
elif name.startswith('SIG') and not name.startswith('SIG_'):
53+
self.assertIsInstance(sig, signal.Signals)
54+
elif name.startswith('CTRL_'):
55+
self.assertIsInstance(sig, signal.Signals)
56+
self.assertEqual(sys.platform, "win32")
57+
58+
4259
@unittest.skipIf(sys.platform == "win32", "Not valid on Windows")
4360
class InterProcessSignalTests(unittest.TestCase):
4461
MAX_DURATION = 20 # Entire test should last at most 20 sec.
@@ -195,6 +212,7 @@ def test_setting_signal_handler_to_none_raises_error(self):
195212

196213
def test_getsignal(self):
197214
hup = signal.signal(signal.SIGHUP, self.trivial_signal_handler)
215+
self.assertIsInstance(hup, signal.Handlers)
198216
self.assertEqual(signal.getsignal(signal.SIGHUP),
199217
self.trivial_signal_handler)
200218
signal.signal(signal.SIGHUP, hup)
@@ -271,7 +289,7 @@ def check_signum(signals):
271289
272290
os.close(read)
273291
os.close(write)
274-
""".format(signals, ordered, test_body)
292+
""".format(tuple(map(int, signals)), ordered, test_body)
275293

276294
assert_python_ok('-c', code)
277295

@@ -604,6 +622,8 @@ def handler(signum, frame):
604622
signal.pthread_sigmask(signal.SIG_BLOCK, [signum])
605623
os.kill(os.getpid(), signum)
606624
pending = signal.sigpending()
625+
for sig in pending:
626+
assert isinstance(sig, signal.Signals), repr(pending)
607627
if pending != {signum}:
608628
raise Exception('%s != {%s}' % (pending, signum))
609629
try:
@@ -660,6 +680,7 @@ def wait_helper(self, blocked, test):
660680
code = '''if 1:
661681
import signal
662682
import sys
683+
from signal import Signals
663684
664685
def handler(signum, frame):
665686
1/0
@@ -702,6 +723,7 @@ def test_sigwait(self):
702723
def test(signum):
703724
signal.alarm(1)
704725
received = signal.sigwait([signum])
726+
assert isinstance(received, signal.Signals), received
705727
if received != signum:
706728
raise Exception('received %s, not %s' % (received, signum))
707729
''')
@@ -842,8 +864,14 @@ def handler(signum, frame):
842864
def kill(signum):
843865
os.kill(os.getpid(), signum)
844866
867+
def check_mask(mask):
868+
for sig in mask:
869+
assert isinstance(sig, signal.Signals), repr(sig)
870+
845871
def read_sigmask():
846-
return signal.pthread_sigmask(signal.SIG_BLOCK, [])
872+
sigmask = signal.pthread_sigmask(signal.SIG_BLOCK, [])
873+
check_mask(sigmask)
874+
return sigmask
847875
848876
signum = signal.SIGUSR1
849877
@@ -852,6 +880,7 @@ def read_sigmask():
852880
853881
# Unblock SIGUSR1 (and copy the old mask) to test our signal handler
854882
old_mask = signal.pthread_sigmask(signal.SIG_UNBLOCK, [signum])
883+
check_mask(old_mask)
855884
try:
856885
kill(signum)
857886
except ZeroDivisionError:
@@ -861,11 +890,13 @@ def read_sigmask():
861890
862891
# Block and then raise SIGUSR1. The signal is blocked: the signal
863892
# handler is not called, and the signal is now pending
864-
signal.pthread_sigmask(signal.SIG_BLOCK, [signum])
893+
mask = signal.pthread_sigmask(signal.SIG_BLOCK, [signum])
894+
check_mask(mask)
865895
kill(signum)
866896
867897
# Check the new mask
868898
blocked = read_sigmask()
899+
check_mask(blocked)
869900
if signum not in blocked:
870901
raise Exception("%s not in %s" % (signum, blocked))
871902
if old_mask ^ blocked != {signum}:
@@ -928,7 +959,7 @@ def handler(signum, frame):
928959

929960
def test_main():
930961
try:
931-
support.run_unittest(PosixTests, InterProcessSignalTests,
962+
support.run_unittest(GenericTests, PosixTests, InterProcessSignalTests,
932963
WakeupFDTests, WakeupSignalTests,
933964
SiginterruptTest, ItimerTest, WindowsSignalTests,
934965
PendingSignalsTests)

Modules/signalmodule.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -967,7 +967,7 @@ static struct PyModuleDef signalmodule = {
967967
};
968968

969969
PyMODINIT_FUNC
970-
PyInit_signal(void)
970+
PyInit__signal(void)
971971
{
972972
PyObject *m, *d, *x;
973973
int i;
@@ -1380,7 +1380,7 @@ PyErr_SetInterrupt(void)
13801380
void
13811381
PyOS_InitInterrupts(void)
13821382
{
1383-
PyObject *m = PyImport_ImportModule("signal");
1383+
PyObject *m = PyImport_ImportModule("_signal");
13841384
if (m) {
13851385
Py_DECREF(m);
13861386
}

PC/config.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ extern PyObject* PyInit_math(void);
1919
extern PyObject* PyInit__md5(void);
2020
extern PyObject* PyInit_nt(void);
2121
extern PyObject* PyInit__operator(void);
22-
extern PyObject* PyInit_signal(void);
22+
extern PyObject* PyInit__signal(void);
2323
extern PyObject* PyInit__sha1(void);
2424
extern PyObject* PyInit__sha256(void);
2525
extern PyObject* PyInit__sha512(void);

0 commit comments

Comments
 (0)