Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/structlog/contextvars.py: 38%

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

65 statements  

1# SPDX-License-Identifier: MIT OR Apache-2.0 

2# This file is dual licensed under the terms of the Apache License, Version 

3# 2.0, and the MIT License. See the LICENSE file in the root of this 

4# repository for complete details. 

5 

6""" 

7Primitives to deal with a concurrency supporting context, as introduced in 

8Python 3.7 as :mod:`contextvars`. 

9 

10.. versionadded:: 20.1.0 

11.. versionchanged:: 21.1.0 

12 Reimplemented without using a single dict as context carrier for improved 

13 isolation. Every key-value pair is a separate `contextvars.ContextVar` now. 

14.. versionchanged:: 23.3.0 

15 Callsite parameters are now also collected under asyncio. 

16 

17See :doc:`contextvars`. 

18""" 

19 

20from __future__ import annotations 

21 

22import contextlib 

23import contextvars 

24 

25from types import FrameType 

26from typing import Any, Generator, Mapping 

27 

28import structlog 

29 

30from .typing import BindableLogger, EventDict, WrappedLogger 

31 

32 

33STRUCTLOG_KEY_PREFIX = "structlog_" 

34STRUCTLOG_KEY_PREFIX_LEN = len(STRUCTLOG_KEY_PREFIX) 

35 

36_ASYNC_CALLING_STACK: contextvars.ContextVar[FrameType] = ( 

37 contextvars.ContextVar("_ASYNC_CALLING_STACK") 

38) 

39 

40# For proper isolation, we have to use a dict of ContextVars instead of a 

41# single ContextVar with a dict. 

42# See https://github.com/hynek/structlog/pull/302 for details. 

43_CONTEXT_VARS: dict[str, contextvars.ContextVar[Any]] = {} 

44 

45 

46def get_contextvars() -> dict[str, Any]: 

47 """ 

48 Return a copy of the *structlog*-specific context-local context. 

49 

50 .. versionadded:: 21.2.0 

51 """ 

52 rv = {} 

53 ctx = contextvars.copy_context() 

54 

55 for k in ctx: 

56 if k.name.startswith(STRUCTLOG_KEY_PREFIX) and ctx[k] is not Ellipsis: 

57 rv[k.name[STRUCTLOG_KEY_PREFIX_LEN:]] = ctx[k] 

58 

59 return rv 

60 

61 

62def get_merged_contextvars(bound_logger: BindableLogger) -> dict[str, Any]: 

63 """ 

64 Return a copy of the current context-local context merged with the context 

65 from *bound_logger*. 

66 

67 .. versionadded:: 21.2.0 

68 """ 

69 ctx = get_contextvars() 

70 ctx.update(structlog.get_context(bound_logger)) 

71 

72 return ctx 

73 

74 

75def merge_contextvars( 

76 logger: WrappedLogger, method_name: str, event_dict: EventDict 

77) -> EventDict: 

78 """ 

79 A processor that merges in a global (context-local) context. 

80 

81 Use this as your first processor in :func:`structlog.configure` to ensure 

82 context-local context is included in all log calls. 

83 

84 .. versionadded:: 20.1.0 

85 .. versionchanged:: 21.1.0 See toplevel note. 

86 """ 

87 ctx = contextvars.copy_context() 

88 

89 for k in ctx: 

90 if k.name.startswith(STRUCTLOG_KEY_PREFIX) and ctx[k] is not Ellipsis: 

91 event_dict.setdefault(k.name[STRUCTLOG_KEY_PREFIX_LEN:], ctx[k]) 

92 

93 return event_dict 

94 

95 

96def clear_contextvars() -> None: 

97 """ 

98 Clear the context-local context. 

99 

100 The typical use-case for this function is to invoke it early in request- 

101 handling code. 

102 

103 .. versionadded:: 20.1.0 

104 .. versionchanged:: 21.1.0 See toplevel note. 

105 """ 

106 ctx = contextvars.copy_context() 

107 for k in ctx: 

108 if k.name.startswith(STRUCTLOG_KEY_PREFIX): 

109 k.set(Ellipsis) 

110 

111 

112def bind_contextvars(**kw: Any) -> Mapping[str, contextvars.Token[Any]]: 

113 r""" 

114 Put keys and values into the context-local context. 

115 

116 Use this instead of :func:`~structlog.BoundLogger.bind` when you want some 

117 context to be global (context-local). 

118 

119 Return the mapping of `contextvars.Token`\s resulting 

120 from setting the backing :class:`~contextvars.ContextVar`\s. 

121 Suitable for passing to :func:`reset_contextvars`. 

122 

123 .. versionadded:: 20.1.0 

124 .. versionchanged:: 21.1.0 Return the `contextvars.Token` mapping 

125 rather than None. See also the toplevel note. 

126 """ 

127 rv = {} 

128 for k, v in kw.items(): 

129 structlog_k = f"{STRUCTLOG_KEY_PREFIX}{k}" 

130 try: 

131 var = _CONTEXT_VARS[structlog_k] 

132 except KeyError: 

133 var = contextvars.ContextVar(structlog_k, default=Ellipsis) 

134 _CONTEXT_VARS[structlog_k] = var 

135 

136 rv[k] = var.set(v) 

137 

138 return rv 

139 

140 

141def reset_contextvars(**kw: contextvars.Token[Any]) -> None: 

142 r""" 

143 Reset contextvars corresponding to the given Tokens. 

144 

145 .. versionadded:: 21.1.0 

146 """ 

147 for k, v in kw.items(): 

148 structlog_k = f"{STRUCTLOG_KEY_PREFIX}{k}" 

149 var = _CONTEXT_VARS[structlog_k] 

150 var.reset(v) 

151 

152 

153def unbind_contextvars(*keys: str) -> None: 

154 """ 

155 Remove *keys* from the context-local context if they are present. 

156 

157 Use this instead of :func:`~structlog.BoundLogger.unbind` when you want to 

158 remove keys from a global (context-local) context. 

159 

160 .. versionadded:: 20.1.0 

161 .. versionchanged:: 21.1.0 See toplevel note. 

162 """ 

163 for k in keys: 

164 structlog_k = f"{STRUCTLOG_KEY_PREFIX}{k}" 

165 if structlog_k in _CONTEXT_VARS: 

166 _CONTEXT_VARS[structlog_k].set(Ellipsis) 

167 

168 

169@contextlib.contextmanager 

170def bound_contextvars(**kw: Any) -> Generator[None, None, None]: 

171 """ 

172 Bind *kw* to the current context-local context. Unbind or restore *kw* 

173 afterwards. Do **not** affect other keys. 

174 

175 Can be used as a context manager or decorator. 

176 

177 .. versionadded:: 21.4.0 

178 """ 

179 context = get_contextvars() 

180 saved = {k: context[k] for k in context.keys() & kw.keys()} 

181 

182 bind_contextvars(**kw) 

183 try: 

184 yield 

185 finally: 

186 unbind_contextvars(*kw.keys()) 

187 bind_contextvars(**saved)