<ObjectWrapper> issue while writing the data

Description:

I am encountering an issue with the format of the data rows in an Ignition script. Specifically, the script is receiving rows in a format that is not a standard dictionary but an ObjectWrapper. This is causing errors when trying to process and save the data.
Error Message:
Unexpected row format (not a dictionary): <type 'com.inductiveautomation.perspective.gateway.script.PropertyTreeScriptWrapper$ObjectWrapper'>: <ObjectWrapper>: {u'BrassTag_Desc': u'far', u'BrassTag': u'8900'}

Script Details:

The script aims to process data from a Table component and save each row to a database. Here's a snippet of the relevant code:

def runAction(self, event):
    # Get the logger instance
    logger = system.util.getLogger("BrassTag_Save")

    try:
        # Get the data from the Table component
        tbl = self.getSibling("Table").props.data

        # Ensure tbl is not None
        if tbl is None:
            logger.error("Table data is None. Aborting save operation.")
            return

        # Convert ObjectWrapper to list of dictionaries if needed
        if hasattr(tbl, 'value'):
            tbl = tbl.value

        # Handle the case where there might not be enough rows
        if len(tbl) < 2:
            lastBrassTag = None
            logger.info("Not enough rows to determine lastBrassTag.")
        else:
            lastBrassTag = tbl[-2].get("BrassTag", None)
            logger.info("Last BrassTag: {}".format(lastBrassTag))

        # Run the Named Query to save each row in tbl
        for row in tbl:
            # Convert ObjectWrapper to dictionary if needed
            if hasattr(row, 'value'):
                row = row.value

            # Check if the row is a dictionary
            if isinstance(row, dict):
                brass_tag = row.get("BrassTag")
                brass_tag_desc = row.get("BrassTag_Desc")
                
                if brass_tag:
                    system.db.runNamedQuery("Query_BrassTag_Insert",
                                            {"BrassTag": brass_tag,
                                             "BrassTag_Desc": brass_tag_desc,
                                             "user": self.session.props.auth.user.userName})
                    logger.info("Saved BrassTag: {}, BrassTag_Desc: {}".format(brass_tag, brass_tag_desc))
                else:
                    logger.error("Row missing 'BrassTag' key or 'BrassTag' is empty: {}".format(row))
            else:
                # Log the type and content of row for debugging
                logger.error("Unexpected row format (not a dictionary): {}: {}".format(type(row), row))

        # Clear the Table data after saving
        self.getSibling("Table").props.data = []
        logger.info("Cleared Table data after saving.")

    except Exception as e:
        logger.error("Error during BrassTag save operation: {}".format(e))

Issue Details:

  • Writing some data from a "Table" to database
  • Expected Format: Dictionary
  • Actual Format: ObjectWrapper with dictionary-like content
  • Example Row: {u'BrassTag_Desc': u'far', u'BrassTag': u'8900'}

Steps Taken:

  • Checked if tbl and row are ObjectWrapper instances and attempted conversion using row.value.
  • Verified that tbl and row should be dictionaries.

Request for Help:

  • How can I handle ObjectWrapper instances correctly to ensure they are processed as dictionaries?
  • Is there a different method to convert or extract data from ObjectWrapper in Ignition?

Thank you for your assistance.

What was the actual problem you were encountering before you attempted to coerce the types?

The bandaid solution is basically just to do what you're doing, but recursively, to flatten Perspective's faux objects into native Python objects:

But ideally, you wouldn't have to do this at all; the objects you get are dictionaries and lists, according to duck typing, so you should ideally be able to pass them through other mechanisms without correction.

Basically I simply just want to save some data in the database

def runAction(self, event):
	tbl = self.getSibling("Table").props.data
	lastBrassTag = tbl[len(tbl) - 2]["BrassTag"]

	for row in tbl:
		if row["BrassTag"] != "":
			system.db.runNamedQuery("Query_BrassTag_Insert", 
			{"BrassTag":row['BrassTag'], "BrassTag_Desc":row['BrassTag_Desc'], "user":self.session.props.auth.user.userName})

Initially I get this error

Table data retrieved: <ArrayWrapper>: [{u'BrassTag_Desc': u'fr', u'BrassTag': u'5200'}]

Fixing this takes me to another then another.

I don't understand why it can't be just simple reading form Table and Writing it into the database...

I assume this is related to your other post concerning popups ?

Let's put everything together and take it from the top. So, quick recap, to make things clear.

  • You have a view with a table.
  • People can add rows to that table
  • There's a "save" button, which opens a confirmation popup
  • When confirmed, the table is saved to the database

Now some questions:

  • Which rows should be inserted ? It appears some rows don't have a value for the column BrassTag, in which case you don't want to insert them. Anything else ?
  • Are ALL rows new ? Or are you loading data from somewhere, and in this case what happens to the data that was already there ? Can it be modified ?
  • What's the point of lastBrassTag ? You're not using it for anything except logging. And why is it at the index "-2" ? Is the last row a special one ?

For the simplest case, I'll assume that all data is new, and that you're following the method I suggested in the other thread.
It will look something like this:

  • There's a custom property on the view called data
  • The table's prop.data property is bound to that custom prop
  • You have a mechanism in place to add rows to view.custom.data
  • This mechanism makes sure the data is properly formatted for a table: All rows have keys for all columns
  • The "save" buttons opens a popup as discussed in the other thread. Let's call the message handler "insert_brass_tags" for future references
  • Put a message handler called "insert_brass_tags" on the view's root - And I mean the base view, not the popup
  • The script in this message handler looks like this:
if payload['confirm'] and self.view.custom.data:
	data = [
		{
			'brass_tag': row['brassTag'],
			'brass_tag_Desc': row['BrassTag_Desc'],
			'user': self.session.props.auth.user.username
		} for row in self.view.custom.data if row['BrassTag']
	]
	q, v = build_insert_string('brass_tag_table', data)
	system.db.runPrepUpdate(q, v)
	self.view.custom.data = []

That's it.
I'm leaving out error handling for now, let's get the logic right first.
Also, note that the keys in the data dictionnary should match the column names of your database table.


Side notes:
Drop the pointless comments.
ie:

 # Get the logger instance
logger = system.util.getLogger("BrassTag_Save")

The function is called getLogger. You don't need to comment that you're getting a logger.

# Get the data from the Table component
tbl = self.getSibling("Table").props.data

It's a simple assignation. This doesn't need to be explained

# Ensure tbl is not None
if tbl is None:

The comment basically translates code into a full english sentence... Which really isn't needed.

Use comments to explain what you do, not describe it.
For exemple:

# Convert ObjectWrapper to list of dictionaries if needed
if hasattr(tbl, 'value'):
    tbl = tbl.value

How does that convert anything to a list of dictionaries ? The comment doesn't help me understand what's going on, it even raises more questions than it answers.

Exactly, It is related to the same issue.
There's one point missing from the recap.
When We click on the Confirm Button and a Confirmation Popup ConfirmSave opens, there's another Table on this popup. I want to get the Data from this Table and write it into the Database.

This Table is Binded with the Table Tbl_Insert_New_BrassTag on the View using Data Property.

The Data is being transferred from View Table to the Popup Table in this format {'BrassTag_Desc':Desc 1234, 'BrassTag': 1234}

So on the Popup Table the First Column is "BrassTag_Desc" and Second is "BrassTag" but I am switching the Columns using Column Property on the Popup Table.

In this Video, I have Described the Issue and Wrokflow in Detail.

Do NOT do this. That's what all my previous messages were about.
You can pass data to your confirmation popup for display purposes, but DO NOT write to the db from there.

Follow the steps in my post above, you'll see the data is written to the database from the initial view. This makes the popup completely irrelevant to the database operations. It's there for display and confirmation only. You can display anything you want there without affecting the operations in the calling view.

If there's no special processing of the data, those steps all you need. You may want to go back to the other thread and the confirmation popup I described to understand how it works.

Ok, Let me Try What you've suggested. I am very new to Ignition, that's why I thought using a different approach maybe took more time.