14
14
15
15
import logging
16
16
import typing
17
+ from functools import wraps
17
18
from os import environ
18
19
from sys import version_info
19
20
25
26
_RUNTIME_CONTEXT = None # type: typing.Optional[RuntimeContext]
26
27
27
28
29
+ _F = typing .TypeVar ("_F" , bound = typing .Callable [..., typing .Any ])
30
+
31
+
32
+ def _load_runtime_context (func : _F ) -> _F :
33
+ """A decorator used to initialize the global RuntimeContext
34
+
35
+ Returns:
36
+ A wrapper of the decorated method.
37
+ """
38
+
39
+ @wraps (func ) # type: ignore
40
+ def wrapper (
41
+ * args : typing .Tuple [typing .Any , typing .Any ],
42
+ ** kwargs : typing .Dict [typing .Any , typing .Any ]
43
+ ) -> typing .Optional [typing .Any ]:
44
+ global _RUNTIME_CONTEXT # pylint: disable=global-statement
45
+ if _RUNTIME_CONTEXT is None :
46
+ # FIXME use a better implementation of a configuration manager to avoid having
47
+ # to get configuration values straight from environment variables
48
+ if version_info < (3 , 5 ):
49
+ # contextvars are not supported in 3.4, use thread-local storage
50
+ default_context = "threadlocal_context"
51
+ else :
52
+ default_context = "contextvars_context"
53
+
54
+ configured_context = environ .get (
55
+ "OPENTELEMETRY_CONTEXT" , default_context
56
+ ) # type: str
57
+ try :
58
+ _RUNTIME_CONTEXT = next (
59
+ iter_entry_points (
60
+ "opentelemetry_context" , configured_context
61
+ )
62
+ ).load ()()
63
+ except Exception : # pylint: disable=broad-except
64
+ logger .error ("Failed to load context: %s" , configured_context )
65
+ return func (* args , ** kwargs ) # type: ignore
66
+
67
+ return wrapper # type:ignore
68
+
69
+
28
70
def get_value (key : str , context : typing .Optional [Context ] = None ) -> "object" :
29
71
"""To access the local state of a concern, the RuntimeContext API
30
72
provides a function which takes a context and a key as input,
@@ -33,6 +75,9 @@ def get_value(key: str, context: typing.Optional[Context] = None) -> "object":
33
75
Args:
34
76
key: The key of the value to retrieve.
35
77
context: The context from which to retrieve the value, if None, the current context is used.
78
+
79
+ Returns:
80
+ The value associated with the key.
36
81
"""
37
82
return context .get (key ) if context is not None else get_current ().get (key )
38
83
@@ -46,91 +91,55 @@ def set_value(
46
91
which contains the new value.
47
92
48
93
Args:
49
- key: The key of the entry to set
50
- value: The value of the entry to set
51
- context: The context to copy, if None, the current context is used
52
- """
53
- if context is None :
54
- context = get_current ()
55
- new_values = context .copy ()
56
- new_values [key ] = value
57
- return Context (new_values )
94
+ key: The key of the entry to set.
95
+ value: The value of the entry to set.
96
+ context: The context to copy, if None, the current context is used.
58
97
59
-
60
- def remove_value (
61
- key : str , context : typing .Optional [Context ] = None
62
- ) -> Context :
63
- """To remove a value, this method returns a new context with the key
64
- cleared. Note that the removed value still remains present in the old
65
- context.
66
-
67
- Args:
68
- key: The key of the entry to remove
69
- context: The context to copy, if None, the current context is used
98
+ Returns:
99
+ A new `Context` containing the value set.
70
100
"""
71
101
if context is None :
72
102
context = get_current ()
73
103
new_values = context .copy ()
74
- new_values . pop ( key , None )
104
+ new_values [ key ] = value
75
105
return Context (new_values )
76
106
77
107
108
+ @_load_runtime_context # type: ignore
78
109
def get_current () -> Context :
79
110
"""To access the context associated with program execution,
80
- the RuntimeContext API provides a function which takes no arguments
81
- and returns a RuntimeContext.
82
- """
83
-
84
- global _RUNTIME_CONTEXT # pylint: disable=global-statement
85
- if _RUNTIME_CONTEXT is None :
86
- # FIXME use a better implementation of a configuration manager to avoid having
87
- # to get configuration values straight from environment variables
88
- if version_info < (3 , 5 ):
89
- # contextvars are not supported in 3.4, use thread-local storage
90
- default_context = "threadlocal_context"
91
- else :
92
- default_context = "contextvars_context"
93
-
94
- configured_context = environ .get (
95
- "OPENTELEMETRY_CONTEXT" , default_context
96
- ) # type: str
97
- try :
98
- _RUNTIME_CONTEXT = next (
99
- iter_entry_points ("opentelemetry_context" , configured_context )
100
- ).load ()()
101
- except Exception : # pylint: disable=broad-except
102
- logger .error ("Failed to load context: %s" , configured_context )
111
+ the Context API provides a function which takes no arguments
112
+ and returns a Context.
103
113
114
+ Returns:
115
+ The current `Context` object.
116
+ """
104
117
return _RUNTIME_CONTEXT .get_current () # type:ignore
105
118
106
119
107
- def set_current (context : Context ) -> Context :
108
- """To associate a context with program execution, the Context
109
- API provides a function which takes a Context.
120
+ @_load_runtime_context # type: ignore
121
+ def attach (context : Context ) -> object :
122
+ """Associates a Context with the caller's current execution unit. Returns
123
+ a token that can be used to restore the previous Context.
110
124
111
125
Args:
112
- context: The context to use as current.
113
- """
114
- old_context = get_current ()
115
- _RUNTIME_CONTEXT .set_current (context ) # type:ignore
116
- return old_context
117
-
126
+ context: The Context to set as current.
118
127
119
- def with_current_context (
120
- func : typing . Callable [..., "object" ]
121
- ) -> typing . Callable [..., "object" ]:
122
- """Capture the current context and apply it to the provided func."""
128
+ Returns:
129
+ A token that can be used with `detach` to reset the context.
130
+ """
131
+ return _RUNTIME_CONTEXT . attach ( context ) # type:ignore
123
132
124
- caller_context = get_current ()
125
133
126
- def call_with_current_context (
127
- * args : "object" , ** kwargs : "object"
128
- ) -> "object" :
129
- try :
130
- backup = get_current ()
131
- set_current (caller_context )
132
- return func (* args , ** kwargs )
133
- finally :
134
- set_current (backup )
134
+ @_load_runtime_context # type: ignore
135
+ def detach (token : object ) -> None :
136
+ """Resets the Context associated with the caller's current execution unit
137
+ to the value it had before attaching a specified Context.
135
138
136
- return call_with_current_context
139
+ Args:
140
+ token: The Token that was returned by a previous call to attach a Context.
141
+ """
142
+ try :
143
+ _RUNTIME_CONTEXT .detach (token ) # type: ignore
144
+ except Exception : # pylint: disable=broad-except
145
+ logger .error ("Failed to detach context" )
0 commit comments