Issue with ResourceOpenAttribute
and EmbedPropertiesAttribute
in Python
Hello everyone,
I am currently unable to use ResourceOpenAttribute
and EmbedPropertiesAttribute
in Python, even though I have the latest versions of the plugins installed.
Here is a sample of the code I am working with:
Broker = opentap.property(Broker, None).add_attribute(OpenTap.Display("MQTT Broker"))\
.add_attribute(OpenTap.ResourceOpenAttribute(OpenTap.ResourceOpenBehavior.Ignore))
Meas = opentap.property(Measurement, None).add_attribute(OpenTap.Display("Measurement"))\
.add_attribute(OpenTap.EmbedPropertiesAttribute())
The Measurement
class looks like this:
class Measurement:
UpperLimit = None
LowerLimit = None
Value = None
Verdict = None
Issue Log Excerpt:
2024-09-06 13:30:40.919831 ; Python ; Debug ; Loading: Demo.DemoStep
2024-09-06 13:30:40.970696 ; Python ; Error ; Caught exception loading Demo.DemoStep: Unable to cast object of type 'OpenTap.EmbedPropertiesAttribute' to type 'System.Type'.
2024-09-06 13:30:40.973255 ; Python ; Debug ; PythonException: Unable to cast object of type 'OpenTap.EmbedPropertiesAttribute' to type 'System.Type'.
2024-09-06 13:30:40.983317 ; Python ; Debug ; File "\Demo\DemoStep.py", line 16, in <module>
2024-09-06 13:30:40.983317 ; Python ; Debug ; class DemoStep(TestStep):
2024-09-06 13:30:40.983317 ; Python ; Debug ; at Python.Runtime.PythonException.ThrowLastAsClrException()
2024-09-06 13:30:40.983317 ; Python ; Debug ; at Python.Runtime.NewReferenceExtensions.BorrowOrThrow(NewReference& reference)
2024-09-06 13:30:40.983317 ; Python ; Debug ; at Python.Runtime.PyModule.Import(String name)
2024-09-06 13:30:40.983317 ; Python ; Debug ; at OpenTap.Python.PythonPluginProvider.Search()
2024-09-06 13:30:40.984329 ; Python ; Debug ; Exception caught at:
2024-09-06 13:30:40.987420 ; Python ; Debug ; at Void <StartAwaitable>b__0()
2024-09-06 13:30:40.987420 ; Python ; Debug ; at Void Process()
2024-09-06 13:30:40.987420 ; Python ; Debug ; at Void processQueue()
2024-09-06 13:30:40.987420 ; Python ; Debug ; at Void RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
Request for Help:
I am getting the following error:
- Unable to cast object of type âOpenTap.EmbedPropertiesAttributeâ to type âSystem.Typeâ
- Unable to cast object of type âOpenTap.ResourceOpenAttributeâ to type âSystem.Typeâ
Any help to resolve this issue would be highly appreciated!
Thanks!
Hi @yannick.njanjo,
Did you try writing OpenTap.ResourceOpen
instead of OpenTap.ResourceOpenAttribute
. This might seem a bit surprising. The problem is that OpenTap.ResourceOpenAttribute will create an instance of the attribute object, which is difficult to convert back into what we need to generate the .NET class.
I think this should work:
Broker = opentap.property(Broker, None).add_attribute(OpenTap.Display("MQTT Broker"))\
.add_attribute(OpenTap.ResourceOpen(OpenTap.ResourceOpenBehavior.Ignore))
Meas = opentap.property(Measurement, None).add_attribute(OpenTap.Display("Measurement"))\
.add_attribute(OpenTap.EmbedProperties())
Hi Rolf,
Thanks for your reply. I figured this out but forgot to update the post as I was busy with another project.
Unfortunately, the EmbedProperties
attribute still doesnât work. When I apply it to a class attribute, it disappears from the Settings area in the editor. Without it, the setting is displayed as a serialized string, e.g., {UpperLimit=0, LowerLimit=0,...}
.
Do you have any idea why this happens?
Can you show how you use it?
MV = opentap.property(Measurement, None).add_attribute(OpenTap.Display("Measurement"))\
.add_attribute(OpenTap.EmbedProperties())
Ok, thats a start
I also need to see the object you are embedding and when you create the instance of it.
Sorry it took me a few minutes. I had to rewrite it again from âscratchâ.
"""
An example of how to define a Python Test Step
"""
import time
import opentap
from opentap import *
import System
from System import Double, Int32, String
import OpenTap
from OpenTap import Display, Unit
VALID_DATA_TYPES = (int, float, str, bool)
class Measurement:
_UpperLimit = opentap.property(Double, None).add_attribute(OpenTap.Display("Upper Limit"))
_LowerLimit = opentap.property(Double, None).add_attribute(OpenTap.Display("Lower Limit"))
_SetPoint = opentap.property(Double, None).add_attribute(OpenTap.Display("Set Point"))
def __init__(self, data_type: type, unit: str):
if data_type not in VALID_DATA_TYPES:
raise ValueError(f"data_type must be one of the following types: {VALID_DATA_TYPES}")
self._data_type = data_type
self._unit = unit
self._value = None
self._timestamp = None
self._verdict = OpenTap.Verdict.NotSet
@property
def SetPoint(self):
return self._SetPoint
@SetPoint.setter
def SetPoint(self, value):
if not isinstance(value, self._data_type):
raise TypeError(f"SetPoint must be of type {self._data_type.__name__}")
if self._UpperLimit is not None and value > self._UpperLimit:
raise ValueError(f"SetPoint {value} cannot exceed UpperLimit {self._UpperLimit}")
if self._LowerLimit is not None and value < self._LowerLimit:
raise ValueError(f"SetPoint {value} cannot be less than LowerLimit {self._LowerLimit}")
self._SetPoint = value
@property
def Value(self):
return self._value
@Value.setter
def Value(self, new_value):
if not isinstance(new_value, self._data_type):
raise TypeError(f"Value must be of type {self._data_type.__name__}")
self._value = new_value
self._timestamp = time.time()
@property
def Verdict(self):
return self._verdict
@Verdict.setter
def Verdict(self, new_verdict: OpenTap.Verdict):
if not isinstance(new_verdict, OpenTap.Verdict):
raise ValueError(f"Invalid verdict. Verdict must be an instance of OpenTap.Verdict")
self._verdict = new_verdict
@property
def Timestamp(self):
return self._timestamp
@property
def DataType(self) -> str:
# return str(self._data_type).split("'")[1]
return self._data_type.__name__
@property
def Unit(self):
return self._unit
@attribute(Display(Name="ForumStep", Description="ForumStep Description", Group="Demo"))
class ForumStep(TestStep):
# Add your Test Step settings here
# Example:
# CellSize = property(Int32, 1)\
# .add_attribute(Display(Name="Cell Size", Description="Size of the cell", Group="Cell Settings", Order=1))
MV = opentap.property(Measurement, None).add_attribute(OpenTap.Display("Measurement"))\
.add_attribute(OpenTap.EmbedProperties())
def __init__(self):
super().__init__()
self.Name="ForumStep"
MV = Measurement(Int32, "°C")
def Run(self):
# Add code here to execute when test step runs
self.log.Info("Running ForumStep")
First thing to try: Can you try to make Measurement inherit from System.Object? It needs to be a .NET object for it to work with EmbedPropertiesAttribute.
1 Like
It worked thanks.
class Measurement(Object):
Hello there,
How did you get the setter to work . for some reason it doesnât work for me.
I am trying to create a simple test step that has 2 dropdowns. I want the 2nd dropdown values to be changed based on the selection of dropdown1. Any help would be greatly appreciated.
from opentap import *
import OpenTap
from System import String
@attribute(OpenTap.Display("Dynamic Dropdowns Test2", "Test step with dynamic dropdowns", "Settings"))
class TestStepWithDropdowns(TestStep):
# Define Dropdown1 as a class attribute with attributes
Dropdown1 = property(String, "")\
.add_attribute(OpenTap.Display("Select an Option for Dropdown1", "Choose an option for Dropdown1", "Settings"))\
.add_attribute(OpenTap.AvailableValues("AvailableOptions"))
AvailableOptions=property(List[String], None)
# Define Dropdown2 as a class attribute with attributes
Dropdown2 = property(String, "")\
.add_attribute(OpenTap.Display("Select an Option for Dropdown2", "Choose an option for Dropdown2", "Settings"))\
.add_attribute(OpenTap.AvailableValues("AvailableOptions2"))
AvailableOptions2=property(List[String], None)
def __init__(self):
super().__init__()
# Initialize available options for Dropdown1
#self.AvailableOptions = ["Option 1", "Option 2", "Option 3"]
self.AvailableOptions=List[String]()
self.AvailableOptions2=List[String]()
self.AvailableOptions.Add("Option 1")
self.AvailableOptions.Add("Option 2")
# Initialize Dropdown1 and Dropdown2 values
self.Dropdown1 = ""
self.Dropdown2 = ""
self.get_available_values_dropdown1()
self.get_available_values_dropdown2()
def get_available_values_dropdown1(self):
# Return the available options for Dropdown1
return self.AvailableOptions
def get_available_values_dropdown2(self):
# Return different options based on the selection in Dropdown1
if self.Dropdown1 == "Option 1":
return ["Option 1 - Choice A", "Option 1 - Choice B", "Option 1 - Choice C"]
elif self.Dropdown1 == "Option 2":
return ["Option 2 - Choice A", "Option 2 - Choice B", "Option 2 - Choice C"]
elif self.Dropdown1 == "Option 3":
return ["Option 3 - Choice A", "Option 3 - Choice B", "Option 3 - Choice C"]
else:
return [] # Return an empty list if Dropdown1 is not selected
def Dropdown1_changed(self):
# This method is called automatically when Dropdown1 changes
self.OnPropertyChanged("Dropdown2") # Notify that Dropdown2's available values may have changed
def Run(self):
super().Run()
self.log.Info(f"Dropdown1 selected: {self.Dropdown1}")
self.log.Info(f"Dropdown2 selected: {self.Dropdown2}")
self.UpgradeVerdict(OpenTap.Verdict.Pass)
Regards
Bharath
Hi @bharathkumar539,
I would recommend not doing things in the setter, but instead overriding the getter.
Here is an example with a boolean (From BasicFunctionality.py), but if you get it to return a List[Strng]
and call it AvailableOptions1/2 it should work.
@property(Boolean)
@attribute(Browsable(False)) # property not visible for the user.
def FrequencyIsDefault(self):
return abs(self.Frequency - 1e9) < 0.001
I hope it makes sense, otherwise I can write an example, but it will take some more time.
Hi Rolf,
Thank you for your fast response.
I will give it a shot and try your suggestion. If I cannot, I will reach out for your guidance.
I really appreciate your help.
On a side note, is there any documentation available for developing python plugins? I feel like the one on OpenTap website is kind of limited.
Regards
Bharath
The documentation lives here: Welcome to the OpenTAP Python Plugin | OpenTAP Python Integration
But I really recommend checking out the examples. You can download the PythonExamples plugin or check them out on github: OpenTap.Python/OpenTap.Python.Examples at dev · opentap/OpenTap.Python · GitHub
They are also automatically added as a dependency when you create a new project.
Thank you @rolf_madsen ,
I was using the examples as my basis all along. May be I should pay more attention to them as it has a lot more information than I initially thought.
Anyway , I tried your suggestion of overriding the getter, but I think I lack the understanding of how .Net objects are treated in python.
Here is where I need your help.
- How can I hide Available in the settings .
- How do I call AvailableValues1_2 after I make a selection on Dropdown1
My overall goal is to save the output of each test step into a dictionary and have this output available as an input to other test steps. Dropdown1 would be selecting the dictionary by each test step and dropdown2 would be selecting the key value pair from the selected dictionary on dropdown1.
Thank you for your help.
import sys
import opentap
import clr
clr.AddReference("System.Collections")
from System.Collections.Generic import List
from opentap import *
import OpenTap
from OpenTap import Log, AvailableValues, EnabledIfAttribute,Display
import System
from System import String # Import types to reference for generic methods
from System.ComponentModel import Browsable
@opentap.attribute(OpenTap.Display("Dynamic Dropdowns Test", "Test step with dynamic dropdowns", "Settings"))
class TestStepWithDropdowns(TestStep):
@property(List[String])
@attribute(Browsable(False)) # Property is not visible to the user
def AvailableValues1_2(self) :
# Create a .NET List[String] instead of Python's list
available_values = List[String]()
# Add options based on the selection in Dropdown1
if self.Dropdown1 == "Option 1":
available_values.Add("Option 1 - Choice A")
available_values.Add("Option 1 - Choice B")
available_values.Add("Option 1 - Choice C")
elif self.Dropdown1 == "Option 2":
available_values.Add("Option 2 - Choice A")
available_values.Add("Option 2 - Choice B")
available_values.Add("Option 2 - Choice C")
elif self.Dropdown1 == "Option 3":
available_values.Add("Option 3 - Choice A")
available_values.Add("Option 3 - Choice B")
available_values.Add("Option 3 - Choice C")
# Return the .NET List[String]
return available_values
Dropdown1= property(String,"")\
.add_attribute(AvailableValues("Available"))\
.add_attribute(Display("Select an Option", "Choose an option for dropdown1", "Settings"))
Available = property(List[String], None)
Dropdown2= property(String,"")\
.add_attribute(AvailableValues("Available2"))\
.add_attribute(Display("Select an Option", "Choose an option for dropdown1", "Settings"))
Available2 = property(List[String], None)
def __init__(self):
super().__init__()
self.Dropdown1 = '' # Store the selected value for dropdown1
self.Dropdown2 = '' # Store the selected value for dropdown2
self.Available = List[String]()
self.Available.Add("Option 1")
self.Available.Add("Option 2")
self.Available2 = List[String]()
self.Available2=self.AvailableValues1_2()
def Run(self):
super().Run()
self.log.Info("Running")
self.UpgradeVerdict(OpenTap.Verdict.Pass)
I managed to get this to work by changing this:
to
self.Available.Add("Option 2")
self.Available2 = List[String]()
self.Available2=self.AvailableValues1_2
That worked .
Thank you .
Any pointers on how we can have a generic output and input test steps that can work for all methods.
Do you think Json is a good idea to implement this?
Regards
Bharath