Unable to use EmbedPropertiesAttribute and ResourceOpenAttribute and others in Python

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 :slight_smile:

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.

  1. How can I hide Available in the settings .
  2. 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