tbapi.common.descriptors
1from typing import TypeVar, Generic, Optional, Callable, Any, Dict, Final 2from dataclasses import dataclass 3from enum import Enum, IntFlag 4from tbapi.common.converters import * 5from tbapi.common.arguments import SerializableArgument 6from typing import get_args, get_origin 7import clr 8import json 9 10 11class NumericRange(SerializableArgument): 12 """ 13 Specifies the numeric range constraints for a parameter. 14 15 Attributes: 16 min_value: The minimum value of the numeric range. Default is 0. 17 max_value: The maximum value of the numeric range. Default is inf. 18 step: The step value for incrementing within the range. Default is 1. 19 """ 20 21 def __init__(self, min_value: float = 0, max_value: float = 999999, step: float = 1): 22 self.min_value = min_value 23 self.max_value = max_value 24 self.step = step 25 self.attribute_name = "NumericRange" 26 super().__init__() 27 28 @classmethod 29 def new(cls, min_value: float = 0, max_value: float = 999999, step: float = 1): 30 return cls(min_value, max_value, step) 31 32 33T = TypeVar("T") 34 35 36class TbDescriptorBase(Generic[T], SerializableArgument): 37 """Base descriptor for internal use only.""" 38 39 def __init__(self, name: str) -> None: 40 self.property_name: str = name 41 self._clr_name: Optional[str] = None 42 self.py_type: Optional[type] = None 43 super().__init__() 44 45 def __set_name__(self, owner: type, name: str) -> None: 46 self._clr_name = "".join(part.capitalize() for part in name.split("_")) 47 annots = getattr(owner, "__annotations__", {}) 48 49 if name in annots: 50 self.py_type = annots[name] 51 52 if self.py_type is None: 53 if hasattr(self, "__orig_class__"): 54 args = get_args(self.__orig_class__) 55 if args: 56 self.py_type = args[0] 57 58 self.property_type = self.get_full_type_name() 59 60 def __get__(self, obj: Any, objtype: Optional[type] = None) -> Optional[T]: 61 if obj is None: 62 return self 63 64 value_obj = getattr(obj, "_value", None) 65 if value_obj is None: 66 return None 67 68 raw_value = getattr(value_obj, self._clr_name) 69 70 if issubclass(self.py_type, Enum) or issubclass(self.py_type, IntFlag): 71 return self.py_type(int(raw_value)) 72 73 if self.py_type is not None and hasattr(self.py_type, "_value") and hasattr(self.py_type, "__tb_decorated__"): 74 return self.py_type(_existing=raw_value) 75 76 return raw_value 77 78 def __set__(self, obj: Any, val: T) -> None: 79 value_obj: Any = getattr(obj, "_value", None) 80 if value_obj is None: 81 raise AttributeError("_value not initialized on the wrapper instance") 82 83 if self.py_type is not None and hasattr(val, "_value") and hasattr(val, "__tb_decorated__"): 84 setattr(value_obj, self._clr_name, val._value) 85 else: 86 if isinstance(val, (Enum, IntFlag)): 87 setattr(value_obj, self._clr_name, get_origin_enum(val, val)) 88 else: 89 setattr(value_obj, self._clr_name, val) 90 91 def get_full_type_name(self) -> str: 92 """ 93 Returns the full type name of the parameter (module + class). 94 For internal use only 95 """ 96 if hasattr(self, "py_type") and self.py_type is not None: 97 py_type = self.py_type 98 module = getattr(py_type, "__module__", "UnknownModule") 99 name = getattr(py_type, "__name__", "UnknownType") 100 if module == "builtins": 101 return name 102 return f"{module}.{name}" 103 104 if hasattr(self, "__orig_class__") and self.__orig_class__ is not None: 105 s = str(self.__orig_class__) 106 start = s.find("[") 107 end = s.find("]") 108 if start >= 0 and end > start: 109 return s[start + 1:end] 110 111 return "Unknown" 112 113 @clr.clrmethod(None, [str]) 114 def to_json(self) -> str: 115 return json.dumps( 116 self.encode() if isinstance(self, SerializableArgument) else str(self), 117 indent=4, 118 default=lambda o: ( 119 o.encode() if isinstance(o, SerializableArgument) else 120 (o.__name__ if isinstance(o, type) else str(o)) 121 ) 122 ) 123 124 125class ScriptParameter(TbDescriptorBase[T]): 126 """ 127 Specifies metadata for a parameter property. 128 129 Attributes: 130 name: The display name of the parameter. 131 group_name: Group name of the parameter (optional). 132 description: Description of the parameter (optional). 133 order: Order of the parameter (optional). 134 show_in_signature: Indicates whether the parameter is shown in the script signature. 135 numeric_range: The numeric range constraints for a parameter. 136 """ 137 138 def __init__( 139 self, 140 name: str, 141 group_name: Optional[str] = None, 142 description: Optional[str] = None, 143 order: Optional[int] = None, 144 show_in_signature: Optional[bool] = None, 145 numeric_range : Optional[NumericRange] = None, 146 ) -> None: 147 super().__init__(name) 148 self.group_name = group_name 149 self.description = description 150 self.order = order 151 self.show_in_signature = show_in_signature 152 self.numeric_range = numeric_range 153 self.attribute_name = "ScriptParameter" 154 self.parameter_name = name 155 156 157class Plot(TbDescriptorBase[T]): 158 """ 159 Specifies metadata for a Plot property. 160 161 Attributes: 162 name: The display name of the plot. 163 """ 164 165 def __init__(self, name: str) -> None: 166 super().__init__(name) 167 self.attribute_name = "Plot" 168 self.plot_name = name 169 170class Source(TbDescriptorBase[T]): 171 """ 172 Specifies metadata for a Source property. 173 """ 174 175 def __init__(self, name: str = "Source") -> None: 176 super().__init__(name) 177 self.attribute_name = "Source" 178 self.source_name = name
16class NumericRange(SerializableArgument): 17 """ 18 Specifies the numeric range constraints for a parameter. 19 20 Attributes: 21 min_value: The minimum value of the numeric range. Default is 0. 22 max_value: The maximum value of the numeric range. Default is inf. 23 step: The step value for incrementing within the range. Default is 1. 24 """ 25 26 def __init__(self, min_value: float = 0, max_value: float = 999999, step: float = 1): 27 self.min_value = min_value 28 self.max_value = max_value 29 self.step = step 30 self.attribute_name = "NumericRange" 31 super().__init__() 32 33 @classmethod 34 def new(cls, min_value: float = 0, max_value: float = 999999, step: float = 1): 35 return cls(min_value, max_value, step)
Specifies the numeric range constraints for a parameter.
Attributes: min_value: The minimum value of the numeric range. Default is 0. max_value: The maximum value of the numeric range. Default is inf. step: The step value for incrementing within the range. Default is 1.
41class TbDescriptorBase(Generic[T], SerializableArgument): 42 """Base descriptor for internal use only.""" 43 44 def __init__(self, name: str) -> None: 45 self.property_name: str = name 46 self._clr_name: Optional[str] = None 47 self.py_type: Optional[type] = None 48 super().__init__() 49 50 def __set_name__(self, owner: type, name: str) -> None: 51 self._clr_name = "".join(part.capitalize() for part in name.split("_")) 52 annots = getattr(owner, "__annotations__", {}) 53 54 if name in annots: 55 self.py_type = annots[name] 56 57 if self.py_type is None: 58 if hasattr(self, "__orig_class__"): 59 args = get_args(self.__orig_class__) 60 if args: 61 self.py_type = args[0] 62 63 self.property_type = self.get_full_type_name() 64 65 def __get__(self, obj: Any, objtype: Optional[type] = None) -> Optional[T]: 66 if obj is None: 67 return self 68 69 value_obj = getattr(obj, "_value", None) 70 if value_obj is None: 71 return None 72 73 raw_value = getattr(value_obj, self._clr_name) 74 75 if issubclass(self.py_type, Enum) or issubclass(self.py_type, IntFlag): 76 return self.py_type(int(raw_value)) 77 78 if self.py_type is not None and hasattr(self.py_type, "_value") and hasattr(self.py_type, "__tb_decorated__"): 79 return self.py_type(_existing=raw_value) 80 81 return raw_value 82 83 def __set__(self, obj: Any, val: T) -> None: 84 value_obj: Any = getattr(obj, "_value", None) 85 if value_obj is None: 86 raise AttributeError("_value not initialized on the wrapper instance") 87 88 if self.py_type is not None and hasattr(val, "_value") and hasattr(val, "__tb_decorated__"): 89 setattr(value_obj, self._clr_name, val._value) 90 else: 91 if isinstance(val, (Enum, IntFlag)): 92 setattr(value_obj, self._clr_name, get_origin_enum(val, val)) 93 else: 94 setattr(value_obj, self._clr_name, val) 95 96 def get_full_type_name(self) -> str: 97 """ 98 Returns the full type name of the parameter (module + class). 99 For internal use only 100 """ 101 if hasattr(self, "py_type") and self.py_type is not None: 102 py_type = self.py_type 103 module = getattr(py_type, "__module__", "UnknownModule") 104 name = getattr(py_type, "__name__", "UnknownType") 105 if module == "builtins": 106 return name 107 return f"{module}.{name}" 108 109 if hasattr(self, "__orig_class__") and self.__orig_class__ is not None: 110 s = str(self.__orig_class__) 111 start = s.find("[") 112 end = s.find("]") 113 if start >= 0 and end > start: 114 return s[start + 1:end] 115 116 return "Unknown" 117 118 @clr.clrmethod(None, [str]) 119 def to_json(self) -> str: 120 return json.dumps( 121 self.encode() if isinstance(self, SerializableArgument) else str(self), 122 indent=4, 123 default=lambda o: ( 124 o.encode() if isinstance(o, SerializableArgument) else 125 (o.__name__ if isinstance(o, type) else str(o)) 126 ) 127 )
Base descriptor for internal use only.
96 def get_full_type_name(self) -> str: 97 """ 98 Returns the full type name of the parameter (module + class). 99 For internal use only 100 """ 101 if hasattr(self, "py_type") and self.py_type is not None: 102 py_type = self.py_type 103 module = getattr(py_type, "__module__", "UnknownModule") 104 name = getattr(py_type, "__name__", "UnknownType") 105 if module == "builtins": 106 return name 107 return f"{module}.{name}" 108 109 if hasattr(self, "__orig_class__") and self.__orig_class__ is not None: 110 s = str(self.__orig_class__) 111 start = s.find("[") 112 end = s.find("]") 113 if start >= 0 and end > start: 114 return s[start + 1:end] 115 116 return "Unknown"
Returns the full type name of the parameter (module + class). For internal use only
Method decorator for exposing python methods to .NET. The argument and return types must be specified as arguments to clrmethod.
e.g.::
class X(object):
@clrmethod(int, [str])
def test(self, x):
return len(x)
Methods decorated this way can be called from .NET, e.g.::
dynamic x = getX(); // get an instance of X declared in Python
int z = x.test("hello"); // calls into python and returns len("hello")
130class ScriptParameter(TbDescriptorBase[T]): 131 """ 132 Specifies metadata for a parameter property. 133 134 Attributes: 135 name: The display name of the parameter. 136 group_name: Group name of the parameter (optional). 137 description: Description of the parameter (optional). 138 order: Order of the parameter (optional). 139 show_in_signature: Indicates whether the parameter is shown in the script signature. 140 numeric_range: The numeric range constraints for a parameter. 141 """ 142 143 def __init__( 144 self, 145 name: str, 146 group_name: Optional[str] = None, 147 description: Optional[str] = None, 148 order: Optional[int] = None, 149 show_in_signature: Optional[bool] = None, 150 numeric_range : Optional[NumericRange] = None, 151 ) -> None: 152 super().__init__(name) 153 self.group_name = group_name 154 self.description = description 155 self.order = order 156 self.show_in_signature = show_in_signature 157 self.numeric_range = numeric_range 158 self.attribute_name = "ScriptParameter" 159 self.parameter_name = name
Specifies metadata for a parameter property.
Attributes: name: The display name of the parameter. group_name: Group name of the parameter (optional). description: Description of the parameter (optional). order: Order of the parameter (optional). show_in_signature: Indicates whether the parameter is shown in the script signature. numeric_range: The numeric range constraints for a parameter.
143 def __init__( 144 self, 145 name: str, 146 group_name: Optional[str] = None, 147 description: Optional[str] = None, 148 order: Optional[int] = None, 149 show_in_signature: Optional[bool] = None, 150 numeric_range : Optional[NumericRange] = None, 151 ) -> None: 152 super().__init__(name) 153 self.group_name = group_name 154 self.description = description 155 self.order = order 156 self.show_in_signature = show_in_signature 157 self.numeric_range = numeric_range 158 self.attribute_name = "ScriptParameter" 159 self.parameter_name = name
Inherited Members
162class Plot(TbDescriptorBase[T]): 163 """ 164 Specifies metadata for a Plot property. 165 166 Attributes: 167 name: The display name of the plot. 168 """ 169 170 def __init__(self, name: str) -> None: 171 super().__init__(name) 172 self.attribute_name = "Plot" 173 self.plot_name = name
Specifies metadata for a Plot property.
Attributes: name: The display name of the plot.
Inherited Members
175class Source(TbDescriptorBase[T]): 176 """ 177 Specifies metadata for a Source property. 178 """ 179 180 def __init__(self, name: str = "Source") -> None: 181 super().__init__(name) 182 self.attribute_name = "Source" 183 self.source_name = name
Specifies metadata for a Source property.