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
class NumericRange(tbapi.common.arguments.SerializableArgument):
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.

NumericRange(min_value: float = 0, max_value: float = 999999, step: float = 1)
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__()
min_value
max_value
step
attribute_name
@classmethod
def new( cls, min_value: float = 0, max_value: float = 999999, step: float = 1):
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)
class TbDescriptorBase(typing.Generic[~T], tbapi.common.arguments.SerializableArgument):
 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.

property_name: str
py_type: Optional[type]
def get_full_type_name(self) -> str:
 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

def to_json(unknown):

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")
class ScriptParameter(tbapi.common.descriptors.TbDescriptorBase[~T]):
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.

ScriptParameter( name: str, group_name: Optional[str] = None, description: Optional[str] = None, order: Optional[int] = None, show_in_signature: Optional[bool] = None, numeric_range: Optional[NumericRange] = None)
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
group_name
description
order
show_in_signature
numeric_range
attribute_name
parameter_name
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.

Plot(name: str)
170    def __init__(self, name: str) -> None:
171        super().__init__(name)
172        self.attribute_name  = "Plot"
173        self.plot_name = name
attribute_name
plot_name
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.

Source(name: str = 'Source')
180    def __init__(self, name: str = "Source") -> None:
181        super().__init__(name)
182        self.attribute_name  = "Source"
183        self.source_name = name
attribute_name
source_name