Perspective Scripting: Data Type Issue

In an effort to minimize bloat, I'm going to try to just describe the problem and the suspected culprits:

From a Message Handler on a Tree component, I am receiving a TypeError due to a function in my script returning None. I am feeding the Tree's items property to the function as x:

x = self.props.items

def find_first(obj_to_inspect, val_to_find, listpath=()):
	    if isinstance(obj_to_inspect, dict):
	        # Omitted for brevity...
	    if isinstance(obj_to_inspect, list):
	        # Omitted for brevity...
	    if isinstance(obj_to_inspect, str):
	        # Omitted for brevity...

y = find_first(x, payload)

When I copy and paste the Tree's items into the script console as x and check the type with type(x), I get the expected result that x is a <type 'list'>. My function find_first() therefore works as expected in the script console, and returns a proper data type - a tuple, for what it's worth.

However, when I check the type of x in the message handler by sending type(x) to a sibling Label, I get:

class com.inductiveautomation.perspective.gateway.script.PropertyTreeScriptWrapper$ArrayWrapper

This explains why my function is returning None; it's looking for a list, dict, or str, but getting something different.

I've tried coercing x into a usable list via x = list(self.props.items), but the type is just changed to class org.python.core.PyList, and the find_first() function still returns None.

So my problem really has two potential solutions:

  1. How do I convert the Tree's items into a list so I can use it in the message handler's find_first() function?
  2. If Option 1 can't be acheived, how do I alter the if isinstance() statements within the find_first() function so that they work with the Tree's self.props.items data type?

BQuacken, you gotta embrace duck typing :smiley:

8 Likes

That makes a lot of sense! (And I'm embarrassed that I had no idea what that was, in spite of my name :sweat_smile:)

Unfortunately - even though utilizing Justin's function you linked to is certainly better than just converting the top level list - the result is the same; I receive a TypeError due to find_first() returning None, and testing for type(x) - after running it through sanitize_tree() - still yields class org.python.core.PyList rather than <type 'list'> or something similar.

I feel like I'm missing something obvious...

1 Like

Don't check the type. Neither with type() nor with isinstance(). Duck typing means to use the methods you expect for a list (iteration, whatever), and it'll walk like a duck (list) if it is duck-like (list-like).

Hmm... Perhaps then it is time to expand on what I'm doing a little bit. The full find_first() function looks like this:

def find_first(obj_to_inspect, val_to_find, listpath=()):
	if isinstance(obj_to_inspect, dict):
	    for key, value in obj_to_inspect.items():
	        item = find_first(value, val_to_find, listpath)
	        if item is not None:
	            return item
	if isinstance(obj_to_inspect, list):
	    for ind, value in enumerate(obj_to_inspect):
	        item = find_first(value, val_to_find, listpath + (ind,))
	        if item is not None:
	            return item
	if isinstance(obj_to_inspect, str):
	    if obj_to_inspect == val_to_find:
	        return listpath

I want to use this to find the page path - supplied to the message handler as the payload - within the Tree's items; it was added to the appropriate item's data previously. The list of indices to that item is what I really need, and I don't know how to arrive at that without using isinstance() as above.

Obviusly, you should probably insure that you don't send a 'NoneType' object to the function, but if for some reason you can't do that, then just add a check:

def find_first(obj_to_inspect, val_to_find, listpath=()):
    if obj_to_inspect and not isinstance(obj_to_inspect,str):
        if hasAttr(obj_to_inspect, '__iter__'):
            if hasAttr(obj_to_inspect, 'keys'):
                #do dictionary work
            else:
                #do list work
    else:
        #do string work
1 Like

Oh!
....
:man_facepalming: Yeah, that's probably it. Let me try that.

I check (with hasattr()) for the items method for duck-typed dictionaries. (Or just try to iterate over items() and catch the exception.)

Then check for __iter__ or just attempt to iterate.

If both of those fail, I would then do the string search.

1 Like

The key insight is that strict subtyping is just too narrow for Ignition's Jython environment.
Jython models Java and Python as (basically) two completely distinct type hierarchies. Any Java code you interact with (literally everything written by IA) can return either a "true" Python object or a Java object that Jython will automatically adapt. If it is close enough to a list, Jython will adapt it and it will behave as a list from your Jython code (e.g. Java arrays, PyList, ArrayWrapper, etc) -- but it will not be a subtype/instance of Python's list class.

You have no choice but to be more defensive in your code.

4 Likes