Casting to DataSet

It appears that both a BasicDataset and a PyDataSet can be assigned to a custom property of type ‘Dataset’. Regardless of which type is assigned, reading this custom property always returns a BasicDataset.

Similarly, the toDataSet() expression function is able to cast a PyDataSet to a BasicDataset.

How is this conversion implemented? If I wanted to create my own dataset wrapper class, how can I make it so that my class can also be implicitly converted to a BasicDataset when required?

Thanks.

Well… you can’t.

Just so you’re clear:

There is Dataset. This is an interface. There are a variety of implementations, like BasicDataset, but honestly you shouldn’t really care about the implementations.

Then there’s PyDataset. It isn’t a Dataset. It just wraps up a dataset (any dataset) so that it is more python-friendly. We have coercion code in place that will convert from Dataset to PyDataset and vice-versa.

You’re free to implement your own implementation of the Dataset interface (using the module SDK, I would assume). This will work with everything mentioned above. You can’t write your own wrapper class that doesn’t implement Dataset and expect it to get coerced into the type Dataset.

Ah thanks. Looking at Dataset and DatasetUtilities in the module SDK has made it much clearer how this works.

The idea was to make something that looked like a PyDataset as far as the coercion code was concerned, in line with Python’s duck-typing philosophy. However, the coercion is done in Java so that won’t work due to the type checking.

Seems that the best approach is to extend PyDataset and go from there:

from com.inductiveautomation.ignition.common.script.builtin import DatasetUtilities
class CustomDataset(DatasetUtilities.PyDataset):
    pass

Thanks.

Yep, that should work. Note that the “Casting” from PyDataSet to Dataset is very simple - it just pulls the underlying dataset out of the PyDataset. (PyDataSet.data)

Update for anyone else who is interested:

Extending PyDataSet works and allows extra methods to be added, which is useful.

However, this cannot be used to make a mutable implementation of DataSet because PyDataSet.data is protected. Jython uses a proxy system so extending a class in Python doesn’t actually extend the class in Java, meaning that your new Python class doesn’t get access to the protected members.

Part of the contract of Dataset is that it is immutable anyhow, so building a mutable one would be a bad idea.

I've also been trying to get my head around a PyDataset. Am I right in thinking converting to a PyDataset is just a convenience and generally not necessary if a person is already comfortable with the Java functions available through the Dataset Interface?

Correct. I usually write my scripts with whichever format I get from the Ignition subsystem I'm using. If I need column names from a PyDataset, I'll access .underlyingDataset.columnNames. (-:

1 Like

Interesting … thanks! ( :

It turns out that implicit casting is possible thanks to Jython. Just implement __tojava__ like this:

def __tojava__(self, cls=None):
    return system.dataset.toDataSet(...)

We’ve been using this for a while and it works really well.

I have to say that PyDataset is one of the worst parts of Ignition’s scripting API. The fact that it doesn’t implement the methods on the BasicDataset is a total pain. If I’m working with arbitrary datasets I should be able to use the methods defined by the Dataset interface without worrying about PyDataset.

We don’t even use PyDataset anymore. We’ve implemented our own wrapper around the database access which always returns our custom Dataset implementation instead of PyDataset. Our custom Dataset implements both the PyDataset and the BasicDataset interfaces, and it implicitly casts to a BasicDataset with __tojava__. This is so much easier to work with.

I'm coming into this thread as an outsider without much context, but this statement seems reasonable to me...

After a quick look I can't see any reason why our PyDataSet doesn't implement the Dataset interface. Maybe we can change that...

1 Like

That would be fantastic!

This change should land in 7.9.5.

1 Like

Hi @Kevin.Herron. A small but great change! Can this change be back ported into Ignition 7.7 and Ignition 7.8?

1 Like

Probably not… 7.7 is only supposed to receive critical bug fixes. 7.8, not being an LTS version, is already end of life and won’t be receiving updates at all.

Hi dox,

I’m going through my client dashboards and trying to minimize excessive synchronous calls to the database. (getting ready to implement some modeling that will load up the database machine) The general idea for my dashboards 2.0 is to push data from transaction groups into a memory dataset tag in such a way that they feed each component according to the data structure the component expects to see, and according to when minimal data is updated from my data fetching routines.

What you’ve proposed above seems like a good candidate for a tag event script. (transaction group monitors Db for key changes and flips a bit in a boolean array tag when it’s done executing. Many memory dataset tags monitor this boolean array tag for a certain value to trigger an update of its dataset values.)

How would I integrate a dataset tag with a transaction group to do such a thing?

Is this something like you’ve implemented?

Best,
Dan