Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

# -*- encoding: utf-8 -*- 

  

"""Utilities for interfacing with the standard library's atexit module.""" 

  

#***************************************************************************** 

# Copyright (C) 2017 Erik M. Bray <erik.bray@lri.fr> 

# 

# This program is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 2 of the License, or 

# (at your option) any later version. 

# http://www.gnu.org/licenses/ 

#***************************************************************************** 

  

from __future__ import absolute_import 

  

  

import atexit 

  

  

__all__ = ['restore_atexit'] 

  

  

cdef class restore_atexit: 

r""" 

Context manager that restores the state of the atexit module to its 

previous state when exiting the context. 

  

INPUT: 

  

- ``run`` (bool, default: False) -- if True, when exiting the 

context (but before restoring the old exit functions), run all 

atexit functions which were added inside the context. 

  

- ``clear`` (bool, default: equal to ``run``) -- if True, clear 

already registered atexit handlers upon entering the context. 

  

.. WARNING:: 

  

The combination ``run=True`` and ``clear=False`` will cause 

already-registered exit functions to be run twice: once when 

exiting the context and again when exiting Python. 

  

EXAMPLES: 

  

For this example we will wrap the entire example with 

``restore_atexit(clear=True)`` so as to start with a fresh atexit 

module state for the sake of the example. 

  

Note that the function ``atexit._run_exitfuncs()`` runs all registered 

handlers, and then clears the list of handlers, so we can use it to test 

manipulation of the ``atexit`` state:: 

  

sage: import atexit 

sage: from sage.cpython.atexit import restore_atexit 

sage: def handler(*args, **kwargs): 

....: print((args, kwargs)) 

sage: atexit.register(handler, 1, 2, c=3) 

<function handler at 0x...> 

sage: atexit.register(handler, 4, 5, d=6) 

<function handler at 0x...> 

sage: with restore_atexit(clear=True): 

....: atexit._run_exitfuncs() # Should be none registered 

....: atexit.register(handler, 1, 2, c=3) 

....: with restore_atexit(): 

....: atexit._run_exitfuncs() # Run just registered handler 

....: atexit._run_exitfuncs() # Handler should be run again 

<function handler at 0x...> 

((1, 2), {'c': 3}) 

((1, 2), {'c': 3}) 

  

We test the ``run`` option:: 

  

sage: with restore_atexit(run=True): 

....: # this handler is run when exiting the context 

....: _ = atexit.register(handler, 7, 8, e=9) 

((7, 8), {'e': 9}) 

sage: with restore_atexit(clear=False, run=True): 

....: # original handlers are run when exiting the context 

....: pass 

((4, 5), {'d': 6}) 

((1, 2), {'c': 3}) 

  

The original handlers are still in place:: 

  

sage: atexit._run_exitfuncs() 

((4, 5), {'d': 6}) 

((1, 2), {'c': 3}) 

  

TESTS:: 

  

sage: from sage.cpython.atexit import (_get_exithandlers, 

....: _clear_exithandlers) 

sage: atexit.register(handler, 1, 2, c=3) 

<function handler at 0x...> 

sage: atexit.register(handler, 4, 5, d=6) 

<function handler at 0x...> 

sage: print("Initial exit handlers:\n{}".format(_get_exithandlers())) 

Initial exit handlers: 

[(<function handler at 0x...>, (1, 2), {'c': 3}), 

(<function handler at 0x...>, (4, 5), {'d': 6})] 

  

sage: with restore_atexit(): 

....: pass 

sage: print("After restore_atexit:\n{}".format(_get_exithandlers())) 

After restore_atexit: 

[(<function handler at 0x...>, (1, 2), {'c': 3}), 

(<function handler at 0x...>, (4, 5), {'d': 6})] 

  

sage: with restore_atexit(clear=True): 

....: print("Exit handlers in context manager: {}".format( 

....: _get_exithandlers())) 

Exit handlers in context manager: [] 

  

sage: print("After restore_atexit with clear=True:\n{}".format( 

....: _get_exithandlers())) 

After restore_atexit with clear=True: 

[(<function handler at 0x...>, (1, 2), {'c': 3}), 

(<function handler at 0x...>, (4, 5), {'d': 6})] 

sage: _clear_exithandlers() 

sage: _get_exithandlers() 

[] 

""" 

  

cdef list _exithandlers 

cdef bint _clear, _run 

  

def __init__(self, *, run=False, clear=None): 

self._clear = self._run = run 

if clear is not None: 

self._clear = clear 

self._exithandlers = None 

  

def __enter__(self): 

self._exithandlers = _get_exithandlers() 

if self._clear: 

_clear_exithandlers() 

  

return self 

  

def __exit__(self, *exc): 

if self._run: 

atexit._run_exitfuncs() 

_set_exithandlers(self._exithandlers) 

  

  

# These are helper functions for implementing restore_atexit. The only reason 

# they're broken out from the class itself is because these require very 

# different implementations on Python 2 and 3 

IF PY_MAJOR_VERSION == 2: 

def _get_exithandlers(): 

"""Return list of exit handlers registered with the atexit module.""" 

return atexit._exithandlers[:] 

  

def _set_exithandlers(exithandlers): 

""" 

Replace the list of exit handlers registered with the atexit module 

with a new list. 

""" 

atexit._exithandlers[:] = exithandlers 

  

def _clear_exithandlers(): 

"""Clear the atexit module of all registered exit handlers.""" 

# It generally shouldn't matter, but this keeps the same list 

# object in place rather than replacing it with a new one in 

# case any other code is accessing this list directly. 

del atexit._exithandlers[:] 

ELSE: 

from cpython.ref cimport PyObject 

  

# Internal structures defined in the CPython source in 

# Modules/atexitmodule.c and subject to (but unlikely to) change. Watch 

# https://bugs.python.org/issue32082 for a request to (eventually) 

# re-expose more of the atexit module's internals to Python 

ctypedef struct atexit_callback: 

PyObject* func 

PyObject* args 

PyObject* kwargs 

  

  

ctypedef struct atexitmodule_state: 

atexit_callback** atexit_callbacks 

int ncallbacks 

int callback_len 

  

  

cdef extern from "Python.h": 

void* PyModule_GetState(object module) 

  

  

def _get_exithandlers(): 

"""Return list of exit handlers registered with the atexit module.""" 

cdef atexitmodule_state* state 

cdef atexit_callback callback 

cdef list exithandlers 

cdef int idx 

cdef object kwargs 

  

state = <atexitmodule_state*>PyModule_GetState(atexit) 

  

if not state: 

raise RuntimeError("atexit module state missing or corrupt") 

  

exithandlers = [] 

  

for idx in range(state.ncallbacks): 

callback = state.atexit_callbacks[idx][0] 

if callback.kwargs: 

kwargs = <object>callback.kwargs 

else: 

kwargs = {} 

exithandlers.append((<object>callback.func, 

<object>callback.args, 

kwargs)) 

return exithandlers 

  

def _set_exithandlers(exithandlers): 

""" 

Replace the list of exit handlers registered with the atexit module 

with a new list. 

""" 

  

# Clear the existing list 

atexit._clear() 

  

# We could do this more efficiently by directly rebuilding the array 

# of atexit_callbacks, but this is much simpler 

for callback in exithandlers: 

atexit.register(callback[0], *callback[1], **callback[2]) 

  

  

def _clear_exithandlers(): 

"""Clear the atexit module of all registered exit handlers.""" 

atexit._clear()