4
4
5
5
import ast
6
6
from functools import cache
7
- from typing import Any , Mapping , Optional , Tuple , Type
7
+ from typing import Any , Mapping , Optional , Set , Tuple , Type
8
8
9
9
from jinja2 import meta
10
10
from jinja2 .environment import Template
@@ -30,6 +30,34 @@ def is_safe_attribute(self, obj: Any, attr: str, value: Any) -> bool:
30
30
return super ().is_safe_attribute (obj , attr , value ) # type: ignore # for some reason, mypy says 'Returning Any from function declared to return "bool"'
31
31
32
32
33
+ # These aliases are used to deprecate existing keywords without breaking all existing connectors.
34
+ _ALIASES = {
35
+ "stream_interval" : "stream_slice" , # Use stream_interval to access incremental_sync values
36
+ "stream_partition" : "stream_slice" , # Use stream_partition to access partition router's values
37
+ }
38
+
39
+ # These extensions are not installed so they're not currently a problem,
40
+ # but we're still explicitely removing them from the jinja context.
41
+ # At worst, this is documentation that we do NOT want to include these extensions because of the potential security risks
42
+ _RESTRICTED_EXTENSIONS = ["jinja2.ext.loopcontrols" ] # Adds support for break continue in loops
43
+
44
+ # By default, these Python builtin functions are available in the Jinja context.
45
+ # We explicitely remove them because of the potential security risk.
46
+ # Please add a unit test to test_jinja.py when adding a restriction.
47
+ _RESTRICTED_BUILTIN_FUNCTIONS = [
48
+ "range"
49
+ ] # The range function can cause very expensive computations
50
+
51
+ _ENVIRONMENT = StreamPartitionAccessEnvironment ()
52
+ _ENVIRONMENT .filters .update (** filters )
53
+ _ENVIRONMENT .globals .update (** macros )
54
+
55
+ for extension in _RESTRICTED_EXTENSIONS :
56
+ _ENVIRONMENT .extensions .pop (extension , None )
57
+ for builtin in _RESTRICTED_BUILTIN_FUNCTIONS :
58
+ _ENVIRONMENT .globals .pop (builtin , None )
59
+
60
+
33
61
class JinjaInterpolation (Interpolation ):
34
62
"""
35
63
Interpolation strategy using the Jinja2 template engine.
@@ -48,34 +76,6 @@ class JinjaInterpolation(Interpolation):
48
76
Additional information on jinja templating can be found at https://jinja.palletsprojects.com/en/3.1.x/templates/#
49
77
"""
50
78
51
- # These aliases are used to deprecate existing keywords without breaking all existing connectors.
52
- ALIASES = {
53
- "stream_interval" : "stream_slice" , # Use stream_interval to access incremental_sync values
54
- "stream_partition" : "stream_slice" , # Use stream_partition to access partition router's values
55
- }
56
-
57
- # These extensions are not installed so they're not currently a problem,
58
- # but we're still explicitely removing them from the jinja context.
59
- # At worst, this is documentation that we do NOT want to include these extensions because of the potential security risks
60
- RESTRICTED_EXTENSIONS = ["jinja2.ext.loopcontrols" ] # Adds support for break continue in loops
61
-
62
- # By default, these Python builtin functions are available in the Jinja context.
63
- # We explicitely remove them because of the potential security risk.
64
- # Please add a unit test to test_jinja.py when adding a restriction.
65
- RESTRICTED_BUILTIN_FUNCTIONS = [
66
- "range"
67
- ] # The range function can cause very expensive computations
68
-
69
- def __init__ (self ) -> None :
70
- self ._environment = StreamPartitionAccessEnvironment ()
71
- self ._environment .filters .update (** filters )
72
- self ._environment .globals .update (** macros )
73
-
74
- for extension in self .RESTRICTED_EXTENSIONS :
75
- self ._environment .extensions .pop (extension , None )
76
- for builtin in self .RESTRICTED_BUILTIN_FUNCTIONS :
77
- self ._environment .globals .pop (builtin , None )
78
-
79
79
def eval (
80
80
self ,
81
81
input_str : str ,
@@ -86,7 +86,7 @@ def eval(
86
86
) -> Any :
87
87
context = {"config" : config , ** additional_parameters }
88
88
89
- for alias , equivalent in self . ALIASES .items ():
89
+ for alias , equivalent in _ALIASES .items ():
90
90
if alias in context :
91
91
# This is unexpected. We could ignore or log a warning, but failing loudly should result in fewer surprises
92
92
raise ValueError (
@@ -105,6 +105,7 @@ def eval(
105
105
raise Exception (f"Expected a string, got { input_str } " )
106
106
except UndefinedError :
107
107
pass
108
+
108
109
# If result is empty or resulted in an undefined error, evaluate and return the default string
109
110
return self ._literal_eval (self ._eval (default , context ), valid_types )
110
111
@@ -132,16 +133,16 @@ def _eval(self, s: Optional[str], context: Mapping[str, Any]) -> Optional[str]:
132
133
return s
133
134
134
135
@cache
135
- def _find_undeclared_variables (self , s : Optional [str ]) -> Template :
136
+ def _find_undeclared_variables (self , s : Optional [str ]) -> Set [ str ] :
136
137
"""
137
138
Find undeclared variables and cache them
138
139
"""
139
- ast = self . _environment .parse (s ) # type: ignore # parse is able to handle None
140
+ ast = _ENVIRONMENT .parse (s ) # type: ignore # parse is able to handle None
140
141
return meta .find_undeclared_variables (ast )
141
142
142
143
@cache
143
- def _compile (self , s : Optional [ str ] ) -> Template :
144
+ def _compile (self , s : str ) -> Template :
144
145
"""
145
146
We must cache the Jinja Template ourselves because we're using `from_string` instead of a template loader
146
147
"""
147
- return self . _environment .from_string (s )
148
+ return _ENVIRONMENT .from_string (s )
0 commit comments