System.tag.browse returns different number of keys

WHAT?!. Is the below even normal?
system.tag.browse returns different number of keys:

My code:

	path = value['path']
	fltr={}
	fltr['recursive'] = value['isRecursive']
	results = system.tag.browse(path, fltr)
	
	return results

I would really expect it, to return same number of keys, to present it using table..

If this is the case, table component returns error.
How can I fix this?

The top tag is a UDT instance. That's why they aren't the same it's not an actual atomic tag.
It looks like this code snippet is from a binding where you are passing in a dictionary with the property isRecursive into it.

You could simplify the code a little:

fltr={
    'recursive' : value['isRecursive']
}

If you don't want the UDT defintion in the returned list, but do want the child tags then add tagType to the filter.

	fltr={
		'recursive':True,
		'tagType':'AtomicTag'
		}
1 Like

If you still have issues with the keys returned then you could filter the return keys with a requried list.

def transform(self, value, quality, timestamp):
	fltr={
		'recursive':True,
		'tagType':'AtomicTag'
		}
	path = '[default]myTags'
	fullTags = system.tag.browse(path,fltr)
	requiredKeys = ['fullPath','value','name','fakeProp']
	return [{k:tag.setdefault(k,None) for k in requiredKeys} for tag in fullTags]

My intention is to dispay tag properties of all child tags of different tag types from base parent path.

i guess you are right tag.browse usually use to return one type of tag. I could get all the unique key of list of dict and use python get method to return vale when key does not exist.

	path = value['path']
	fltr={}
	fltr['recursive'] = value['isRecursive']
	lod = system.tag.browse(path, fltr)
	
	keys = list(set(key for d in lod for key in d.keys()))
	
	newLod = []
	for d in lod:
		d0={}
		for key in keys:
			if key=="value":
				d0[key] = d[key].getValue()
			else:
				d0[key] = str(d.get(key, ""))
		newLod.append(d0)
	
	return newLod

I have a few suggestions:

  • You don't have to convert the set to a list. In fact, sets are more efficient for membership checks
  • I'd keep the None and the actual datatype instead of converting to strings. You can manage how nulls appear in a table with the nullFormat prop:
    image
  • maybe exclude folders ? lod = filter(lambda tag: tag['tagType'] is not tag['tagType'].UdtInstance, lod)

All in all, I'd probably write it like this:

	lod = system.tag.browse(
		value['path'],
		filter={'recursive': value['isRecursive']}
	)
	lod = filter(lambda tag: tag['tagType'] is not tag['tagType'].Folder, lod)

	keys = set(key for d in lod for key in d.keys())
	data = [{k: d.get(k) for k in keys} for d in lod]
	for d in data:
		d['value'] = d['value'].value
	return data

last thing: Pick a better name than lod. What does it mean ? The only idea I have is "List Of Dicts".

I built this tool for education purpose, where I have tag browse tree on the left.
To understand more on about tags.

You are right no need to convert to list. During debuging, I return the 'set', but custom property returned a string. So I wrapped it with list. (forgot to remove them).

This is very true. I was avoiding more debug, so decide to set to harmless string.

Yes i came up with lod means "list of dictionary". ds for dataset.
For list i set to plural.
Very helpful, when my mind read lod, immediately i know it is literally a list of dict.

I appreciate you looking at my code.

This is nice, list of dict comprehension.

newLod = [{k: d.get(k) for k in keys} for d in lod]

But I see the expanded version is more flexible/reusable code in this case.
Why would you loop thru each key-value pair, if you won't do some processing.

You may end up having more debug to do by converting to strings, as it may hide errors that would otherwise be obvious.

1 Like

To prepare for the processing.

In this case you can actually eliminate the loop after as well.

newLod = [{k: d.get(k).value if k == 'value' else d.get(k) for k in keys} for d in lod]

So the script as a whole could be:

lod =system.tag.browse(
    value['path'],
    filter={'recursive': value['isRecursive']}
)
lod = filter(lambda tag: tag['tagType'] in not tag['tagType'].Folder, log)

keys = set(key for d in log for key in d.keys())
return [{k: d.get(K).value if k == 'value' else d.get(K) for k in keys} for d in lod]

I missed this question when I last answered...
I find it more readable.
We build a list of dicts, because that's what we need.
Then we reprocess what needs to be reprocessed.

Yes, there are 2 loops when it could be done in one.
But I find it makes things clearer, and unless you're processing a lot of rows, I doubt you'll see any difference.
I usually write for clarity of intent first, then rewrite it if needed.
Note that I specifically say "of intent", which is not always the same a general readability.
It's a distinction I recently started to make, because some people will find iterative loops more generally readable than comprehensions, but I don't think anyone can argue that comprehensions WITH EXPLICIT NAMES don't convey the intent better.
This is also why I prefer using filter() than filtering in a comprehension: clarity of intent.
And it's also why I make helper functions with just one line.