Custom Menu using named query

I'm attempting to break through this barrier of errors in the Script Console. jStr=system.db.runNamedQuery('test_menu_nq')

I set up a label, bound to the same query: here is the text, which is what the Perspective.Print gives me:

[{"MenulabelText":"Dashboards","MenuVisible":true,"MenuTarget":"dashboards"}]

If I JSON encode it, I get this:

{"columns":[{"name":"MenulabelText","type":"java.lang.String"},{"name":"MenuVisible","type":"java.lang.Boolean"},{"name":"MenuTarget","type":"java.lang.String"}],"rows":[["Dashboards",true,"dashboards"]]}

Well, I found this to work in the Script Console:

jStr = system.db.runNamedQuery('test_menu_nq')
itemDefs = system.dataset.toPyDataSet(jStr)
defaultItem = {}
items = []
for iDef in itemDefs:
	if iDef['MenuVisible']:
		thisItem = dict(**defaultItem)
		thisItem['target'] = iDef['MenuTarget']
		thisItem['path'] = iDef['MenuLabelText']
		items.append(thisItem)
print(items[0])

This prints out:

{'target': u'dashboards', 'path': u'Dashboards'}

BUT(!!) it can't be coerced into a Dataset like:

itemDefs = system.dataset.toPyDataSet(value)

in Perspective script transform.

This works for some things in perspective, but not all. The Script Console is really Vision Client Scope, and perspective runs in Gateway Scope.

I believe you will get to the bottom of your issues quicker if you troubleshoot in perspective itself.

This would work with your code.

This would not work with your code as written. This looks like the JSON encoding of a Dataset Object. I suspect that you've been caught unawares by the fact that the script function to call a named query will only return a dataset, where a binding can return a JSON string.

@lrose My apologies! There is one major thing I had somehow missed when I copied your script :man_facepalming: and the is the structure of the dictionary for defaultItems!

However, the jsonDecode() did require a str() function to fix the coercing into string issue.

Many thanks :clap: and apologies, you've been most helpful and patient. Give yourself a raise :dollar:, too!

FWIW, I find comprehensions make stuff like this a lot cleaner:

itemDefs = system.dataset.toPyDataSet(jStr)
items = [
	{
		'target': item['MenuTarget'],
		'path': item['MenuLabelText'],
	}
	for item in itemDefs
	if item['MenuVisible']
]

No need for repeated references to the same item from the outer scope, no need to append items to a list manually, etc.

There is no loop here, so how would you cope with more than one row? When I put this concept into the scripting console, I can print item[0-2] and return the values of the first row, but any integer larger than the number of columns, with zero based index, is an out of range error.

Please excuse my lack of python, JSON, Java understanding as I attempt to understand how this works. Much appreciated!

This is an example of a comprehension, specifically a list comprehension. It's a standard bit of Python syntax; there is a loop, it's just embedded inside the list construction.

I haven't quite solved it, yet. It seems that one row's value overwrites every other value returned for a particular dictionary value: ['label']['text']

Which suggests that the original dict structure might be off.

In this case, I've used:
Dashboards
Inventory
Trucks
Cars

And 'Trucks' overwrites all the other labels.

That usually means you are placing the same dictionary in multiple rows, after which following changes affect prior work. Double check how you are instantiating each row. Or share the latest version of your code.

I was just going over the structure again, and it seems fine to me.

def transform(self, value, quality, timestamp):
	itemDefs = system.util.jsonDecode(str(value))
	defaultItem = {'target':'','items':[],'navIcon': {'path':''},'label': {'text':'','icon': {'path':''}},'showHeader': True}
	items = []
	for iDef in itemDefs:
	    if iDef['MenuVisible']:
	        thisItem = dict(**defaultItem)
	        thisItem['target'] = iDef['MenuTarget']
	        thisItem['label']['text'] = iDef['MenuLabel']
	        items.append(thisItem)

I thought that there might be a missing (or extra) set of curly braces that would cause the label:text to be overwritten, but I don't see that.

FWIW, here is the returned data:

[{"showHeader":true,"navIcon":{"path":""},"label":{"icon":{"path":""},"text":"4"},"items":[],"target":"1"},
{"showHeader":true,"navIcon":{"path":""},"label":{"icon":{"path":""},"text":"4"},"items":[],"target":"2"},
{"showHeader":true,"navIcon":{"path":""},"label":{"icon":{"path":""},"text":"4"},"items":[],"target":"3"},
{"showHeader":true,"navIcon":{"path":""},"label":{"icon":{"path":""},"text":"4"},"items":[],"target":"4"}]

Where the text should be numbered the same as target.

this is doing a shallow copy. I would import copy and do thisItem = copy.deepcopy(**defaultItem) in it's place.

copy — Shallow and deep copy operations — Python 3.12.1 documentation.

I understood the ** to unpack a python dictionary. Rose linked an article above

I've only ever used the ** operator to unpack a dictionary when passing it to a function. I would assume dict(**defaultItem) isn't accomplishing anything and would be the same as dict(defaultItem) and just copy the outermost dictionary and not any nested within.

Given:

defaultItem = {'target':'','items':[],'navIcon': {'path':''},'label': {'text':'','icon': {'path':''}},'showHeader': True}

dict(**defaultItem) is almost equivalent to

dict(target='', items=[], navIcon={'path': ''}, label={'text': '', 'icon': {'path': ''}}, showHeader=True)

However, the dict values (for navIcon and label) are actually pointers to the originals

Correct. The default item has nested lists and dictionaries. Those will be shared by all items unless completely replaced.

1 Like

No, the second form is supplying newly constructed lists and nested dictionaries.

Edit: caught you just before you added "almost". :grin:

I added "almost" in an edit, with extra info below.

Haha. I have a bad habit of "post first, read after"

2 Likes

I still maintain a well formatted comprehension is cleaner and easier to reason about. Plus no faffing about with deepcopies or mutable references.

def transform(self, value, quality, timestamp):
	itemDefs = system.util.jsonDecode(str(value))

	return [
		{
			'target': item['MenuTarget'],
			'items': [],
			'navIcon': {
				'path': ''
			},
			'label': {
				'text': item['MenuLabel'],
				'icon': {
					'path': ''
				}
			},
			'showHeader': True
		}
		for item in itemDefs
		if item['MenuVisible']
	]
4 Likes

@PGriffith’s example is a much better implementation @mdemaris.

In my defense, I threw that example together with little to no testing and not a whole lot of thought towards actual implementation. It was mostly for illustration purposes.

Yes, but...

Many would disagree that comprehensions are comprehensible in the first place. I like them, but I learned long ago not to project this sort of thing onto others.

I tend to keep list comprehensions to one-liners, for others to maintain.

1 Like