procfunc 0.30.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. procfunc/__init__.py +87 -0
  2. procfunc/color.py +57 -0
  3. procfunc/compute_graph/__init__.py +28 -0
  4. procfunc/compute_graph/compute_graph.py +115 -0
  5. procfunc/compute_graph/node.py +200 -0
  6. procfunc/compute_graph/operators_info.py +92 -0
  7. procfunc/compute_graph/proxy.py +173 -0
  8. procfunc/compute_graph/util.py +282 -0
  9. procfunc/context.py +115 -0
  10. procfunc/control.py +174 -0
  11. procfunc/nodes/__init__.py +66 -0
  12. procfunc/nodes/bindings_util.py +196 -0
  13. procfunc/nodes/bpy_node_info.py +280 -0
  14. procfunc/nodes/compositor.py +2242 -0
  15. procfunc/nodes/execute/construct_nodes.py +571 -0
  16. procfunc/nodes/execute/construct_special_cases.py +246 -0
  17. procfunc/nodes/execute/execute.py +548 -0
  18. procfunc/nodes/execute/infer_runtime_data_type.py +195 -0
  19. procfunc/nodes/execute/util.py +247 -0
  20. procfunc/nodes/func.py +1417 -0
  21. procfunc/nodes/geo.py +4240 -0
  22. procfunc/nodes/manifest.json +8769 -0
  23. procfunc/nodes/math.py +644 -0
  24. procfunc/nodes/node_function.py +160 -0
  25. procfunc/nodes/shader.py +2359 -0
  26. procfunc/nodes/types.py +347 -0
  27. procfunc/ops/__init__.py +35 -0
  28. procfunc/ops/_util.py +275 -0
  29. procfunc/ops/addons.py +59 -0
  30. procfunc/ops/attr.py +426 -0
  31. procfunc/ops/collection.py +90 -0
  32. procfunc/ops/curve.py +18 -0
  33. procfunc/ops/file.py +126 -0
  34. procfunc/ops/manifest.json +39149 -0
  35. procfunc/ops/mesh.py +1510 -0
  36. procfunc/ops/modifier.py +603 -0
  37. procfunc/ops/object.py +258 -0
  38. procfunc/ops/primitives/__init__.py +31 -0
  39. procfunc/ops/primitives/camera.py +45 -0
  40. procfunc/ops/primitives/curve.py +71 -0
  41. procfunc/ops/primitives/light.py +114 -0
  42. procfunc/ops/primitives/mesh.py +358 -0
  43. procfunc/ops/uv.py +271 -0
  44. procfunc/random.py +247 -0
  45. procfunc/tracer/__init__.py +43 -0
  46. procfunc/tracer/decorator.py +121 -0
  47. procfunc/tracer/patch.py +494 -0
  48. procfunc/tracer/proxy.py +127 -0
  49. procfunc/tracer/trace.py +222 -0
  50. procfunc/transforms/__init__.py +49 -0
  51. procfunc/transforms/cleanup.py +214 -0
  52. procfunc/transforms/convert.py +20 -0
  53. procfunc/transforms/distribution.py +191 -0
  54. procfunc/transforms/extract_materials.py +116 -0
  55. procfunc/transforms/infer_distribution.py +326 -0
  56. procfunc/transforms/parameters.py +15 -0
  57. procfunc/transforms/util.py +35 -0
  58. procfunc/transpiler/__init__.py +24 -0
  59. procfunc/transpiler/bpy_to_computegraph.py +1348 -0
  60. procfunc/transpiler/codegen.py +919 -0
  61. procfunc/transpiler/identifiers.py +595 -0
  62. procfunc/transpiler/main.py +299 -0
  63. procfunc/types.py +380 -0
  64. procfunc/util/__init__.py +0 -0
  65. procfunc/util/bpy_info.py +145 -0
  66. procfunc/util/camera.py +0 -0
  67. procfunc/util/keyframe.py +70 -0
  68. procfunc/util/log.py +96 -0
  69. procfunc/util/manifest.py +121 -0
  70. procfunc/util/pytree.py +343 -0
  71. procfunc/util/teardown.py +37 -0
  72. procfunc-0.30.0.dist-info/METADATA +120 -0
  73. procfunc-0.30.0.dist-info/RECORD +76 -0
  74. procfunc-0.30.0.dist-info/WHEEL +5 -0
  75. procfunc-0.30.0.dist-info/licenses/LICENSE.md +11 -0
  76. procfunc-0.30.0.dist-info/top_level.txt +1 -0
procfunc/__init__.py ADDED
@@ -0,0 +1,87 @@
1
+ # ruff: noqa: I001, F401
2
+ # ensure this gets imported first so that mathutils etc is available even if later modules dont import bpy
3
+ import bpy
4
+
5
+ __version__ = "0.30.0"
6
+
7
+ from numpy.random import Generator as RNG
8
+
9
+ # ensure these are always imported first and in the right order
10
+ import bpy as _bpy
11
+ import mathutils as _mu
12
+
13
+ from . import (
14
+ compute_graph,
15
+ control,
16
+ nodes,
17
+ ops,
18
+ random,
19
+ tracer,
20
+ util,
21
+ context,
22
+ color,
23
+ transforms,
24
+ )
25
+ from .types import (
26
+ Vector,
27
+ Color,
28
+ Euler,
29
+ Quaternion,
30
+ Matrix,
31
+ BVHTree,
32
+ Object,
33
+ CameraObject,
34
+ MeshObject,
35
+ CurveObject,
36
+ VolumeObject,
37
+ EmptyObject,
38
+ ArmatureObject,
39
+ HairObject,
40
+ LatticeObject,
41
+ LightObject,
42
+ LightProbeObject,
43
+ MetaObject,
44
+ Material,
45
+ Texture,
46
+ Collection,
47
+ Image,
48
+ ViewLayer,
49
+ Asset,
50
+ World,
51
+ )
52
+ from .nodes import ProcNode, Shader, NodeDataType
53
+ from .tracer import trace, autowrap_module, add_search_scope
54
+ from .util.manifest import module_path
55
+
56
+ autowrap_module(random)
57
+ autowrap_module(color)
58
+ add_search_scope(nodes)
59
+ add_search_scope(ops)
60
+
61
+ __all__ = [
62
+ # Subpackages
63
+ "compute_graph",
64
+ "control",
65
+ "context",
66
+ "color",
67
+ "nodes",
68
+ "ops",
69
+ "random",
70
+ "tracer",
71
+ "transforms",
72
+ "util",
73
+ # Node graph primitives
74
+ "ProcNode",
75
+ "Shader",
76
+ "NodeDataType",
77
+ # Tracing entrypoints
78
+ "trace",
79
+ # Utilities
80
+ "RNG",
81
+ "module_path",
82
+ # NOTE: Blender wrapper types (Object, MeshObject, Material, Texture, ...)
83
+ # are re-exported above for convenience but live in procfunc.types — see
84
+ # that page for documentation. Blender/mathutils re-exports (Vector, Color,
85
+ # Euler, Quaternion, Matrix, BVHTree, NodeGroup, Scene, ViewLayer) are
86
+ # likewise kept importable but documented upstream by Blender.
87
+ ]
procfunc/color.py ADDED
@@ -0,0 +1,57 @@
1
+ import numpy as np
2
+
3
+ from procfunc import types as t
4
+
5
+
6
+ def hsv_color(
7
+ hsv: np.ndarray | tuple | None = None,
8
+ *,
9
+ hue: float | None = None,
10
+ saturation: float | None = None,
11
+ value: float | None = None,
12
+ ) -> t.Color:
13
+ color = t.Color()
14
+ if hsv is not None:
15
+ color.hsv = hsv
16
+ else:
17
+ color.hsv = (hue, saturation, value)
18
+ return color
19
+
20
+
21
+ hsv_to_rgba = hsv_color
22
+
23
+
24
+ def rgb_color(
25
+ r: float | None = None,
26
+ g: float | None = None,
27
+ b: float | None = None,
28
+ rgb: np.ndarray | None = None,
29
+ ) -> t.Color:
30
+ color = t.Color()
31
+ if rgb is not None:
32
+ color.r, color.g, color.b = rgb
33
+ else:
34
+ color.r, color.g, color.b = r, g, b
35
+ return color
36
+
37
+
38
+ def _srgb_to_linearrgb(c):
39
+ if c < 0:
40
+ return 0
41
+ elif c < 0.04045:
42
+ return c / 12.92
43
+ else:
44
+ return ((c + 0.055) / 1.055) ** 2.4
45
+
46
+
47
+ def _hex_to_rgb(h: int, alpha: float = 1):
48
+ r = (h & 0xFF0000) >> 16
49
+ g = (h & 0x00FF00) >> 8
50
+ b = h & 0x0000FF
51
+ return tuple([_srgb_to_linearrgb(c / 0xFF) for c in (r, g, b)])
52
+
53
+
54
+ def hex_color(h: int, alpha: float = 1):
55
+ c = t.Color()
56
+ c.r, c.g, c.b = _hex_to_rgb(h, alpha)
57
+ return c
@@ -0,0 +1,28 @@
1
+ from .compute_graph import (
2
+ ComputeGraph,
3
+ )
4
+ from .node import (
5
+ ConstantNode,
6
+ FunctionCallNode,
7
+ GetAttributeNode,
8
+ InputPlaceholderNode,
9
+ MethodCallNode,
10
+ MutatedArgumentNode,
11
+ Node,
12
+ ProceduralNode,
13
+ SubgraphCallNode,
14
+ normalize_args_to_kwargs,
15
+ )
16
+ from .operators_info import OperatorType
17
+ from .proxy import AttributeProxy, Proxy
18
+ from .util import (
19
+ LiteralConstant,
20
+ graph_nodes_equal,
21
+ transform_compute_graph,
22
+ transform_nodetree,
23
+ traverse_breadth_first,
24
+ traverse_depth_first,
25
+ traverse_depth_first_node,
26
+ traverse_nested_graphs,
27
+ usages_per_node,
28
+ )
@@ -0,0 +1,115 @@
1
+ import itertools
2
+ import logging
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ from procfunc.util.pytree import PyTree
7
+
8
+ from .node import Node
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def _evaluate_node(node: Node) -> Any:
14
+ if node.result is not None:
15
+ return node.result
16
+
17
+ for arg in itertools.chain(node.args, node.kwargs.values()):
18
+ if isinstance(arg, Node):
19
+ arg.result = _evaluate_node(arg)
20
+
21
+ arg_eval, kwarg_eval = node.inputs.map(
22
+ lambda x: x.result if isinstance(x, Node) else x
23
+ ).obj()
24
+
25
+ # match node:
26
+ # case cg.FunctionCallNode:
27
+ # return node.func(*arg_vals, **kwarg_vals)
28
+ # case cg.PlaceholderNode:
29
+ # raise NotImplementedError(
30
+ # f"Placeholder {node!r} or '<unnamed>'} should not be evaluated - "
31
+ # "its .result should be populated in advance"
32
+ # )
33
+ # case cg.MutatedArgumentNode:
34
+ # # Evaluate the mutation call first (for side effects), then return the original object
35
+ # mutation_call_node = node.args[1]
36
+ # _evaluate_node(mutation_call_node)
37
+ # return (
38
+ # node.args[0].result if isinstance(node.args[0], Node) else node.args[0]
39
+ # )
40
+ # case _:
41
+ # raise NotImplementedError(f"Unsupported node operation: {node.kind}")
42
+
43
+
44
+ def _clear_node_results(node: Node):
45
+ node.result = None
46
+ for arg in itertools.chain(node.args, node.kwargs.values()):
47
+ if isinstance(arg, Node) and arg.result is not None:
48
+ _clear_node_results(arg)
49
+
50
+
51
+ @dataclass
52
+ class ComputeGraph:
53
+ inputs: PyTree[Any, Node]
54
+ outputs: PyTree[Any, Node]
55
+ name: str
56
+ metadata: dict[str, Any]
57
+
58
+ def __post_init__(self):
59
+ # input_names = set(self.inputs.names())
60
+ # if len(input_names) != len(set(input_names)):
61
+ # raise ValueError(f"Input names had duplicates: {input_names}")
62
+
63
+ # output_names = set(self.outputs.names())
64
+ # if len(output_names) != len(set(output_names)):
65
+ # raise ValueError(f"Output names had duplicates: {output_names}")
66
+ pass
67
+
68
+ def __repr__(self):
69
+ return f"{self.__class__.__name__}({self.name!r})"
70
+
71
+ def clear_values(self):
72
+ for node in self.outputs.values():
73
+ _clear_node_results(node)
74
+
75
+ def __call__(
76
+ self,
77
+ *args,
78
+ allow_clear: bool = True,
79
+ **kwargs,
80
+ ):
81
+ """
82
+ Execute the compute graph. If this graph came from a tracer,
83
+ this should be exactly equivelant to executing the original python function.
84
+ """
85
+
86
+ raise NotImplementedError("Not implemented")
87
+
88
+ if len(args) != len(self.inputs):
89
+ raise ValueError(f"Expected {len(self.inputs)} arguments, got {len(args)}")
90
+
91
+ if allow_clear:
92
+ self.clear_values()
93
+
94
+ extra_kwargs = self.kwarg_nodes.keys() - kwargs.keys()
95
+ if extra_kwargs:
96
+ raise ValueError(
97
+ f"{self.__class__.__name__} {self.name!r} got unexpected keyword arguments: {extra_kwargs}"
98
+ )
99
+
100
+ missing_kwargs = kwargs.keys() - self.kwarg_nodes.keys()
101
+ if missing_kwargs:
102
+ raise ValueError(
103
+ f"{self.__class__.__name__} {self.name!r} had missing keyword arguments: {missing_kwargs}"
104
+ )
105
+
106
+ for arg, arg_node in zip(args, self.arg_nodes):
107
+ arg_node.result = arg
108
+
109
+ for k, v in kwargs.items():
110
+ self.kwarg_nodes[k].result = v
111
+
112
+ for node in self.outputs.values():
113
+ _evaluate_node(node)
114
+
115
+ return {name: node.result for name, node in self.outputs.items()}
@@ -0,0 +1,200 @@
1
+ import inspect
2
+ import logging
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Any, Callable, TypeVar
5
+
6
+ if TYPE_CHECKING:
7
+ from procfunc.compute_graph.compute_graph import ComputeGraph
8
+
9
+ from procfunc.util.pytree import PyTree
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class Node:
15
+ def __init__(self, args: tuple, kwargs: dict, metadata: dict[str, Any] = None):
16
+ assert isinstance(args, tuple), args
17
+ assert isinstance(kwargs, dict), kwargs
18
+ self.args = args
19
+ self.kwargs = kwargs
20
+ if metadata is None:
21
+ metadata = {}
22
+ self.metadata = metadata
23
+
24
+ def inputs_pytree(self) -> PyTree:
25
+ return PyTree((self.args, self.kwargs))
26
+
27
+
28
+ class SubgraphCallNode(Node):
29
+ def __init__(
30
+ self,
31
+ subgraph: "ComputeGraph",
32
+ args: tuple,
33
+ kwargs: dict,
34
+ metadata: dict[str, Any] = None,
35
+ ):
36
+ super().__init__(args, kwargs, metadata)
37
+ self.subgraph = subgraph
38
+
39
+ def __repr__(self):
40
+ return f"{self.__class__.__name__}({self.subgraph.name}, ...)"
41
+
42
+
43
+ class FunctionCallNode(Node):
44
+ def __init__(
45
+ self,
46
+ func: Callable[..., Any],
47
+ args: tuple,
48
+ kwargs: dict,
49
+ metadata: dict[str, Any] = None,
50
+ ):
51
+ super().__init__(args=args, kwargs=kwargs, metadata=metadata)
52
+ self.func = func
53
+
54
+ def __repr__(self):
55
+ return f"{self.__class__.__name__}({self.func.__name__}, ...)"
56
+
57
+
58
+ class MethodCallNode(Node):
59
+ """
60
+ represents an {args[0]}.{method_name}(*args[1:], **kwargs) call
61
+
62
+ - the node to be used as `self` is the first arg, since it is a dynamic value
63
+ - the method name is assumed to be const
64
+ """
65
+
66
+ def __init__(
67
+ self,
68
+ callee: Node,
69
+ method_name: str,
70
+ args: tuple,
71
+ kwargs: dict,
72
+ metadata: dict[str, Any] = None,
73
+ ):
74
+ super().__init__(args=(callee, *args), kwargs=kwargs, metadata=metadata)
75
+ self.method_name = method_name
76
+
77
+ def __repr__(self):
78
+ return f"{self.__class__.__name__}({self.method_name}, ...)"
79
+
80
+
81
+ @dataclass
82
+ class GetAttributeNode(Node):
83
+ def __init__(
84
+ self, source: Node, attribute_name: str, metadata: dict[str, Any] = None
85
+ ):
86
+ # store source as args since it is a Node and may need to be recursively constructed
87
+ super().__init__(args=(source,), kwargs={}, metadata=metadata)
88
+ self.attribute_name = attribute_name
89
+
90
+ def __repr__(self):
91
+ return f"{self.__class__.__name__}({self.attribute_name})"
92
+
93
+
94
+ @dataclass
95
+ class ProceduralNode(Node):
96
+ def __init__(
97
+ self,
98
+ node_type: str,
99
+ attrs: dict[str, Any],
100
+ kwargs: dict,
101
+ metadata: dict[str, Any] = None,
102
+ ):
103
+ super().__init__(args=(), kwargs=kwargs, metadata=metadata)
104
+ self.node_type = node_type
105
+
106
+ for k, v in attrs.items():
107
+ if isinstance(v, Node):
108
+ raise ValueError(
109
+ f"{self.__class__.__name__}({node_type=}) recieved attrs with non-constant value {k}={v}. "
110
+ "(Node values are not allowed as attrs)"
111
+ )
112
+ self.attrs = attrs
113
+
114
+ def __repr__(self):
115
+ return f"{self.__class__.__name__}({self.node_type}, ...)"
116
+
117
+
118
+ class MutatedArgumentNode(Node):
119
+ def __init__(
120
+ self,
121
+ original_node: "Node",
122
+ mutator_call_node: "FunctionCallNode | MethodCallNode",
123
+ metadata: dict[str, Any] = None,
124
+ ):
125
+ # store orig/mutator as args() since they are Node and may need to be recursively constructed
126
+ super().__init__(
127
+ args=(original_node, mutator_call_node), kwargs={}, metadata=metadata
128
+ )
129
+
130
+ def __repr__(self):
131
+ return f"{self.__class__.__name__}(...)"
132
+
133
+
134
+ class ConstantNode(Node):
135
+ def __init__(self, value: Any, metadata: dict[str, Any] = None):
136
+ super().__init__(args=(), kwargs={}, metadata=metadata)
137
+ self.value = value
138
+
139
+ def __repr__(self):
140
+ return f"{self.__class__.__name__}({self.value})"
141
+
142
+
143
+ class InputPlaceholderNode(Node):
144
+ def __init__(self, name: str, default_value: Any, metadata: dict[str, Any] = None):
145
+ super().__init__(args=(), kwargs={}, metadata=metadata)
146
+ self.input_name = name
147
+ self.default_value = default_value
148
+
149
+ def __repr__(self):
150
+ return f"{self.__class__.__name__}({self.default_value})"
151
+
152
+
153
+ T = TypeVar("T")
154
+
155
+
156
+ def normalize_args_to_kwargs(
157
+ func: Callable,
158
+ args: tuple,
159
+ kwargs: dict,
160
+ ) -> tuple[tuple, dict]:
161
+ """
162
+ Try to fully populate kwargs, by moving over positional args & filling in defaults
163
+
164
+ Some args may not be able to be converted to kwargs, e.g. *args have no names that work
165
+
166
+ Args:
167
+ func: The function whose signature we should respect
168
+ args: The original positional arguments to the function
169
+ kwargs: The keyword arguments to the function
170
+
171
+ Returns:
172
+ A tuple of (args, kwargs) where args is a tuple of positional arguments and kwargs is a dictionary of keyword arguments.
173
+
174
+ GUARANTEE: func(*returned_args, **returned_kwargs) == func(*args, **kwargs) and does not crash
175
+ """
176
+
177
+ sig = inspect.signature(func)
178
+ bound = sig.bind(*args, **kwargs)
179
+ bound.apply_defaults() # optional: fills in default values
180
+
181
+ # Extract any *args parameter back to args tuple
182
+ remaining_args = ()
183
+ updated_kwargs = {}
184
+
185
+ for param_name, value in bound.arguments.items():
186
+ param = sig.parameters[param_name]
187
+ if param.kind == inspect.Parameter.VAR_POSITIONAL: # *args
188
+ remaining_args = value
189
+ elif param.kind == inspect.Parameter.VAR_KEYWORD: # **kwargs
190
+ # Unpack **kwargs back to individual kwargs instead of wrapping in dict
191
+ updated_kwargs.update(value)
192
+ else:
193
+ updated_kwargs[param_name] = value
194
+
195
+ if False and logger.isEnabledFor(logging.DEBUG):
196
+ logger.debug(
197
+ f"normalized {func.__name__} with {args=} {kwargs=} to {remaining_args=} {updated_kwargs=}"
198
+ )
199
+
200
+ return remaining_args, updated_kwargs
@@ -0,0 +1,92 @@
1
+ import operator
2
+ from enum import Enum
3
+
4
+
5
+ class OperatorType(Enum):
6
+ ADD = "add"
7
+ SUB = "sub"
8
+ MUL = "mul"
9
+ DIV = "div"
10
+ TRUEDIV = "truediv"
11
+ POW = "pow"
12
+ MOD = "mod"
13
+ LESS_THAN = "lt"
14
+ LESS_THAN_EQUAL = "le"
15
+ GREATER_THAN = "gt"
16
+ GREATER_THAN_EQUAL = "ge"
17
+ EQUAL = "eq"
18
+ NOT_EQUAL = "ne"
19
+ AND = "and"
20
+ OR = "or"
21
+ NOT = "invert"
22
+ LSHIFT = "lshift"
23
+ RSHIFT = "rshift"
24
+ BIT_AND = "and"
25
+ BIT_OR = "or"
26
+ BIT_XOR = "xor"
27
+
28
+ GETITEM = "getitem"
29
+
30
+ VECTOR_PACK = "VECTOR_PACK"
31
+ NOOP = "NOOP"
32
+
33
+
34
+ OPERATOR_TEMPLATES = {
35
+ OperatorType.ADD: "{} + {}",
36
+ OperatorType.SUB: "{} - {}",
37
+ OperatorType.MUL: "{} * {}",
38
+ OperatorType.DIV: "{} / {}",
39
+ OperatorType.POW: "{} ** {}",
40
+ OperatorType.MOD: "{} % {}",
41
+ OperatorType.LESS_THAN: "{} < {}",
42
+ OperatorType.LESS_THAN_EQUAL: "{} <= {}",
43
+ OperatorType.GREATER_THAN: "{} > {}",
44
+ OperatorType.GREATER_THAN_EQUAL: "{} >= {}",
45
+ OperatorType.EQUAL: "{} == {}",
46
+ OperatorType.NOT_EQUAL: "{} != {}",
47
+ OperatorType.VECTOR_PACK: "({}, {}, {})",
48
+ OperatorType.NOOP: "{}",
49
+ OperatorType.LSHIFT: "{} << {}",
50
+ OperatorType.RSHIFT: "{} >> {}",
51
+ OperatorType.BIT_AND: "{} & {}",
52
+ OperatorType.BIT_OR: "{} | {}",
53
+ OperatorType.BIT_XOR: "{} ^ {}",
54
+ OperatorType.GETITEM: "{}[{}]",
55
+ }
56
+
57
+ OPERATORS_TO_FUNCTIONS = {
58
+ OperatorType.ADD: operator.add,
59
+ OperatorType.SUB: operator.sub,
60
+ OperatorType.MUL: operator.mul,
61
+ OperatorType.DIV: operator.truediv,
62
+ OperatorType.MOD: operator.mod,
63
+ OperatorType.POW: operator.pow,
64
+ OperatorType.LESS_THAN: operator.lt,
65
+ OperatorType.LESS_THAN_EQUAL: operator.le,
66
+ OperatorType.GREATER_THAN: operator.gt,
67
+ OperatorType.GREATER_THAN_EQUAL: operator.ge,
68
+ OperatorType.EQUAL: operator.eq,
69
+ OperatorType.NOT_EQUAL: operator.ne,
70
+ OperatorType.LSHIFT: operator.lshift,
71
+ OperatorType.RSHIFT: operator.rshift,
72
+ OperatorType.BIT_AND: operator.and_,
73
+ OperatorType.BIT_OR: operator.or_,
74
+ OperatorType.BIT_XOR: operator.xor,
75
+ OperatorType.GETITEM: operator.getitem,
76
+ }
77
+
78
+ FUNCTIONS_TO_OPERATORS = {v: k for k, v in OPERATORS_TO_FUNCTIONS.items()}
79
+
80
+ REFLECTABLE_OPERATORS = {
81
+ OperatorType.ADD,
82
+ OperatorType.SUB,
83
+ OperatorType.MUL,
84
+ OperatorType.DIV,
85
+ OperatorType.MOD,
86
+ OperatorType.POW,
87
+ OperatorType.LSHIFT,
88
+ OperatorType.RSHIFT,
89
+ OperatorType.BIT_AND,
90
+ OperatorType.BIT_OR,
91
+ OperatorType.BIT_XOR,
92
+ }