# View Dataset from script console

**URL:** <https://forum.inductiveautomation.com/t/view-dataset-from-script-console/36704>\
**Category:** Ignition\
**Created:** [July 15, 2020, 12:31am UTC](https://forum.inductiveautomation.com/t/view-dataset-from-script-console/36704 "2020-07-15T00:31:01Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![damean.thomas](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/damean.thomas/32/11367_2.png) [@damean.thomas](https://forum.inductiveautomation.com/u/damean.thomas)\
**Post date:** [July 15, 2020, 12:31am UTC](https://forum.inductiveautomation.com/t/view-dataset-from-script-console/36704/1 "2020-07-15T00:31:01Z")

</div>

Is there an easy way to view a dataset from the script console while developing?

---

<div class="post-metadata">

**Author:** ![Kuskah](https://avatars.discourse-cdn.com/v4/letter/k/7993a0/32.png) [@Kuskah](https://forum.inductiveautomation.com/u/Kuskah)\
**Post date:** [July 15, 2020, 12:47am UTC](https://forum.inductiveautomation.com/t/view-dataset-from-script-console/36704/2 "2020-07-15T00:47:06Z")

</div>

The easiest way I know of would be to iterate through it and print it .

Check the [following page](https://docs.inductiveautomation.com/display/DOC80/Datasets) for an example.

---

<div class="post-metadata">

**Author:** ![damean.thomas](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/damean.thomas/32/11367_2.png) [@damean.thomas](https://forum.inductiveautomation.com/u/damean.thomas)\
**Post date:** [July 15, 2020, 1:52am UTC](https://forum.inductiveautomation.com/t/view-dataset-from-script-console/36704/3 "2020-07-15T01:52:59Z")

</div>

That’s what i ended up doing, thank you.

---

<div class="post-metadata">

**Author:** ![JordanCClark](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/jordancclark/32/83362_2.png) [@JordanCClark](https://forum.inductiveautomation.com/u/JordanCClark)\
**Post date:** [July 15, 2020, 11:37pm UTC](https://forum.inductiveautomation.com/t/view-dataset-from-script-console/36704/4 "2020-07-15T23:37:51Z")

</div>

Sorry, I'm on vacation, but family is napping, so I get to play. Yes, this is how I relax. Don't judge.  
~~EDIT: Added option for printing from Perspective.~~ Sorry, not quite ready for prime time. 🙄

```python
def printDataSet(datasetIn):
	from itertools import izip
	import ast
	if 'PropertyTreeScriptWrapper$ArrayWrapper' in str(type(datasetIn)):
		headers = datasetIn[0].keys()
		data = []
		for row in datasetIn:
			data.append([item.value for item in row.values()])
		
		datasetIn = system.dataset.toDataSet(headers, data)
		
	# Get Row and Column counts
	nrows = datasetIn.getRowCount()
	ncols = datasetIn.getColumnCount()
        		        			
	# Get max length of row labels
	rowLen = len(max(['row']+[unicode(i) for i in range(nrows)], key=len))
	# Get column names
	colNames = datasetIn.getColumnNames()
	# initialize lists to process columns
	headerList = []
	colData = []
	maxLen = []
	# Process columns
	for i, col in izip(range(ncols), colNames):
		# Get column as list, converting all elemenst to unicode strings
		colIn = ([unicode(element) for element in list(datasetIn.getColumnAsList(i))])
		# Get max lentgh of the column, including the column name
		maxLen = len(max([col]+colIn, key=len))
		# Append data as left justified strings.
		# ljust() will automatically pad to the max string length
		colData.append([element.ljust(maxLen) for element in colIn])
		# Append column name, also using the max string length
		headerList.append(col.ljust(maxLen))
	# Concatenate the header string and print it.
	headerString= 'row'.ljust(rowLen) + ' | ' + ' | '.join(headerList)
	
	print headerString
	# Print a bunch of dashes the same length as the header string
	print'-' * len(headerString)
	# Print the rows
	for row in enumerate(izip(*colData)):
		print unicode(row[0]).ljust(rowLen) + ' | ' + ' | '.join(row[1])

```

Sample output:

```plaintext
row | fgPartNum | Motor | Harness | Latch | DCM | PSAT | Antenna | Speaker | Chassis | PO | Description         
--------------------------------------------------------------------------------------------------------------------------------------------------
0 | 4CJ9902 | N/A | 68343451AA | 68367059AA | 68203031AA | 68370843AA | N/A | N/A | 68426067AA | 123456 | LH FRONT            
1 | 4CJ9904 | N/A | 68343389AA | 68367059AA | 68203031AA | 68370843AA | N/A | N/A | 68426067AA | 123456 | LH FRONT            
2 | 4CJ9906 | N/A | 68483665AA | 68367059AA | 68203031AA | 68370843AA | N/A | N/A | 68426067AA | 123456 | LH FRONT            
3 | 4CJ9908 | N/A | 68505567AA | 68367059AA | 68203031AA | 68370843AA | N/A | N/A | 68426067AA | 123456 | LH FRONT            
4 | 4CJ9910 | N/A | 68457400AA | 68367059AA | 68203031AA | 68370843AA | N/A | N/A | 68426067AA | 123456 | LH FRONT            
5 | 4CJ9914 | N/A | 68483666AA | 68367059AA | 68203031AA | 68370843AA | N/A | N/A | 68426067AA | 123456 | LH FRONT            
6 | 4CJ9916 | N/A | 68483667AA | 68366541AA | 68203031AA | 68370843AA | N/A | N/A | 68426067AA | 123456 | LH FRONT            
7 | 4CJ9918 | N/A | 68448401AA | 68366541AA | 68203031AA | 68370843AA | N/A | N/A | 68426067AA | 123456 | LH FRONT            
8 | 4CJ9920 | N/A | 68506501AA | 68367059AA | 68203031AA | 68370843AA | N/A | N/A | 68426067AA | 123456 | LH FRONT            
9 | 4CJ9922 | N/A | 68343451AA | 68367059AA | 68203033AA | 68370843AA | N/A | N/A | 68426067AA | 123456 | LH FRONT            
10 | 4CJ9924 | N/A | 68343389AA | 68367059AA | 68203033AA | 68370843AA | N/A | N/A | 68426067AA | 123456 | LH FRONT            
11 | 4CJ9926 | N/A | 68483665AA | 68367059AA | 68203033AA | 68370843AA | N/A | N/A | 68426067AA | 123456 | LH FRONT            
12 | 4CJ9930 | N/A | 68457400AA | 68367059AA | 68203033AA | 68370843AA | N/A | N/A | 68426067AA | 123456 | LH FRONT            
13 | 4CJ9932 | N/A | 68505567AA | 68367059AA | 68203033AA | 68370843AA | N/A | N/A | 68426067AA | 123456 | LH FRONT            
14 | 4CJ9934 | N/A | 68483666AA | 68367059AA | 68203033AA | 68370843AA | N/A | N/A | 68426067AA | 123456 | LH FRONT            
15 | 4CJ9936 | N/A | 68483667AA | 68366541AA | 68203033AA | 68370843AA | N/A | N/A | 68426067AA | 123456 | LH FRONT            
16 | 4CJ9938 | N/A | 68448401AA | 68366541AA | 68203033AA | 68370843AA | N/A | N/A | 68426067AA | 123456 | LH FRONT            
17 | 4CJ9940 | N/A | 68506501AA | 68367059AA | 68203033AA | 68370843AA | N/A | N/A | 68426067AA | 123456 | LH FRONT            
18 | 4CJ9952 | CM094380 | 68343453AA | 68366543AA | N/A | N/A | N/A | 68426487AA | 68426071AA | 123456 | LH REAR AUTO DOWN   
19 | 4CJ9954 | CM094380 | 68447658AA | 68366543AA | N/A | N/A | N/A | 68426487AA | 68426071AA | 123456 | LH REAR AUTO DOWN   
20 | 4CJ9956 | CM094380 | 68483657AA | 68366543AA | N/A | N/A | N/A | 68347029AA | 68426071AA | 123456 | LH REAR AUTO DOWN   
21 | 4CJ9958 | CM094360 | 68447659AA | 68366543AA | N/A | N/A | N/A | 68347029AA | 68426071AA | 123456 | LH REAR AUTO UP/DOWN
22 | 4CJ9960 | CM094360 | 68447660AA | 68366543AA | N/A | N/A | N/A | 68459976AA | 68426071AA | 123456 | LH REAR AUTO UP/DOWN
23 | 4CJ9962 | CM094380 | 68343449AA | 68366543AA | N/A | N/A | N/A | N/A | 68426071AA | 123456 | LH REAR AUTO DOWN   

```

---

<div class="post-metadata">

**Author:** ![bkarabinchak.psi](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bkarabinchak.psi/32/83567_2.png) [@bkarabinchak.psi](https://forum.inductiveautomation.com/u/bkarabinchak.psi)\
**Post date:** [July 16, 2020, 1:50pm UTC](https://forum.inductiveautomation.com/t/view-dataset-from-script-console/36704/5 "2020-07-16T13:50:51Z")

</div>

I get `NameError: global name 'tagIn' is not defined`, first line under your `for i, col in zip(range(ncols), colNames):`

---

<div class="post-metadata">

**Author:** ![code\_skin](https://avatars.discourse-cdn.com/v4/letter/c/43a26b/32.png) [@code\_skin](https://forum.inductiveautomation.com/u/code_skin)\
**Post date:** [July 16, 2020, 1:55pm UTC](https://forum.inductiveautomation.com/t/view-dataset-from-script-console/36704/6 "2020-07-16T13:55:23Z")

</div>

> [@bkarabinchak.psi](#):
>
> I get `NameError: global name 'tagIn' is not defined` , first line under your `for i, col in zip(range(ncols), colNames):`

Change **tagIn** to **datasetIn**

---

<div class="post-metadata">

**Author:** ![JordanCClark](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/jordancclark/32/83362_2.png) [@JordanCClark](https://forum.inductiveautomation.com/u/JordanCClark)\
**Post date:** [July 16, 2020, 3:43pm UTC](https://forum.inductiveautomation.com/t/view-dataset-from-script-console/36704/7 "2020-07-16T15:43:04Z")

</div>

Yep. Script updated.

---

<div class="post-metadata">

**Author:** ![zacharyw.larson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/zacharyw.larson/32/23749_2.png) [@zacharyw.larson](https://forum.inductiveautomation.com/u/zacharyw.larson)\
**Post date:** [September 28, 2022, 3:17pm UTC](https://forum.inductiveautomation.com/t/view-dataset-from-script-console/36704/8 "2022-09-28T15:17:55Z")

</div>

@JordanCClark  
Why don’t I see a return in the function?  
I thought functions had to have returns.  
I tried it, and it works, but I am confused.

* * *

```auto
util.printDataSet(datasetIn)

```

Is `util` a good name?

* * *

What else do you have stashed?

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [September 28, 2022, 3:21pm UTC](https://forum.inductiveautomation.com/t/view-dataset-from-script-console/36704/9 "2022-09-28T15:21:24Z")

</div>

> [@zacharyw.larson](#):
>
> Why don’t I see a return in the function?  
> I thought functions had to have returns.  
> I tried it, and it works, but I am confused.

In general, functions don't have to have a return. The return is only useful if you want to... return something.  
Now, in the case of python, it turns out they will always return something. If you don't specify what, they will return `None`.

> [@zacharyw.larson](#):
>
> Is `util` a good name?

If it's an utility function, then sure. I have a folder named `utils` with utility scripts in it.

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [September 28, 2022, 3:44pm UTC](https://forum.inductiveautomation.com/t/view-dataset-from-script-console/36704/10 "2022-09-28T15:44:20Z")

</div>

> [@zacharyw.larson](#):
>
> What else do you have stashed?

Most of what I have is a bit too specific to my needs, but those could be useful:

> **utils.dataset**
>
> ```python
> def to_dicts(ds):
> columns = system.dataset.getColumnHeaders(ds)
> return [dict(zip(columns, row)) for row in system.dataset.toPyDataSet(ds)]
> 
> def to_string(ds):
> headers = ds.getColumnNames()
> widths = [len(str(ds.getRowCount()))] + [
> max(
> max(len(str(value)) for value in ds.getColumnAsList(c)),
> len(headers[c])
> ) for c in range(ds.getColumnCount())
> ]
> headers = [""] + headers
> ds = system.dataset.toPyDataSet(ds)
> return (
> " | ".join(h.ljust(w) for h, w in zip(headers, widths)) + '\n'
> + "-" * (sum(widths) + 3*(len(widths)-1)) + '\n'
> + '\n'.join(" | ".join(str(v).ljust(w) for v, w in zip([i] + [r for r in row], widths)) for i, row in enumerate(ds))
> )
> 
> def print_ds(ds):
> print to_string(ds)
> 
> ```

> **utils.tags**
>
> ```python
> from com.inductiveautomation.ignition.common.model.values import BasicQualifiedValue, QualityCode
> 
> base_logger = system.util.getLogger("Utils").createSubLogger("Tags")
> 
> def force_quality(paths, quality):
> """
> Force a quality on all the tags in {paths}.
> 
> Values and timestamp are left unchanged.
> quality can either be a QualityCode, or a string that will be used to build a QualityCode.
> If quality is a string and not a valid QualityCode, QualityCode.Uncertain will be used instead.
> """
> 
> logger = base_logger.createSubLogger("ForceQuality")
> logger.info("forcing quality {} on the following tags: {}".format(quality, ", ".join(paths)))
> 
> if not isinstance(quality, QualityCode):
> quality = getattr(QualityCode, quality.title(), QualityCode.Uncertain)
> 
> tags = system.tag.readBlocking(paths)
> values = [BasicQualifiedValue(tag.value, quality) for tag in tags]
> utils.tag.write(paths, values, logger)
> 
> def write(paths, values, logger):
> write_return = system.tag.writeBlocking(paths, values)
> bad_writes = ["{} ({})".format(path, quality) for path, quality in zip(paths, write_return) if not quality.isGood()]
> if bad_writes:
> logger.warn("failed writes: {}".format(bad_writes))
> 
> def write_async(paths, values, logger):
> def log(write_return):
> bad_writes = ["{} ({})".format(path, quality) for path, quality in zip(paths, write_return) if not quality.isGood()]
> if bad_writes:
> logger.warn("failed writes: {}".format(bad_writes))
> system.tag.writeAsync(paths, values, log)
> 
> ```

> **utils.test**
>
> ```python
> import ast
> 
> """
> Testing framework
> 
> Import test in your file: from utils.test import test
> 
> Decorate your functions to be tested:
> @test
> def test_foo():
> foo = "foo"
> bar = get_bar()
> assert foo == bar, "expected {}, got {}".format(foo, bar)
> 
> Call the function individually, or use run_tests() to run all the marked functions in modules:
> run_tests(module_1, module_b)
> """
> 
> class TestFinder(ast.NodeVisitor):
> def visit_Module(self, node):
> self.test_funcs = []
> self.generic_visit(node)
> 
> def visit_FunctionDef(self, node):
> if "test" in (deco.id for deco in node.decorator_list):
> self.test_funcs.append(node.name)
> 	
> def get_testfuncs(self):
> return self.test_funcs
> 
> def test(func):
> """
> Decorator to mark functions to be tested.
> 	
> Simply import the decorator (from Tests.utils import test) and add @test before the function
> """
> 
> def wrapper():
> try:
> func()
> except AssertionError as e:
> message = "failed ({})".format(e)
> else:
> message = "passed"
> return message
> return wrapper
> 
> def run_tests(*modules):
> """
> Run all the functions marked as tests (with the test decorator) in every packages passed as parameter.
> """
> for m in modules:
> print("{}\ntesting functions in {}...".format("-"*20, m.name))
> 
> tree = ast.parse(m.code)
> f = TestFinder()
> f.visit(tree)
> funcs = f.get_testfuncs()
> 
> if not funcs:
> print("No functions marked for testing found in {}".format(m.name))
> for f in funcs:
> try:
> message = getattr(m, f)()
> print("{:.<40}{}".format(f, message))
> except BaseException as e:
> print(repr(e))
> 
> ```
> 
> I run this in the script console like this (basic example):
> 
> ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/4/b/4b76720f6e0d2284810c7127f8ac0a989a79e39b.png)

---

<div class="post-metadata">

**Author:** ![zacharyw.larson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/zacharyw.larson/32/23749_2.png) [@zacharyw.larson](https://forum.inductiveautomation.com/u/zacharyw.larson)\
**Post date:** [September 28, 2022, 4:00pm UTC](https://forum.inductiveautomation.com/t/view-dataset-from-script-console/36704/11 "2022-09-28T16:00:24Z")

</div>

I didn’t understand what those do Pascal.

* * *

```auto
# Get max lentgh of the column, including the column name
maxLen = len(max([col]+colIn, key=len))

```

I don’t understand what this is doing exactly.  
I tried looking up how key=len works.  
Tried testing it.

```auto
L1=['spider', 'moose', 'horse', 'ant', 'dog', 'cat']
L3= sorted(L1,key=len)
print (L3)

```

```auto
['ant', 'dog', 'cat', 'moose', 'horse', 'spider']

```

Still confused about it. I expected to see the long words first and in alphabetical order.  
Output was not alphabetized at all. Seemed like it is like setting the thing to sort by.  
However, the code uses key=len inside of the max(), so I am even more confused.

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [September 28, 2022, 4:25pm UTC](https://forum.inductiveautomation.com/t/view-dataset-from-script-console/36704/12 "2022-09-28T16:25:32Z")

</div>

That’s Jordan’s code. But I’ll try to crack this case !

`key` being an argument (or a key word argument) of those functions, it might have a different effect from one function to another. Just because it has the same name doesn’t mean it will have the same effect.

In the case of `sorted`, well… It tells the function what to sort by.  
So if you’re telling it to sort by length, there’s no reason it should be alphabetically.  
Also, it sorts by ascending order by default (try `sorted([5, 2, 3, 1, 4])`). If you want it to sort in descending order, you’ll need to pass it another argument: `sorted(seq, reverse=True)`

In the case of `max`, it’s actually pretty much the same thing. The `key` argument tells it what to use to compare elements. So if you give it `key=len`, it will use the return of the `len` function called on each element to evaluate what’s the biggest value.  
It’s very close to what `key` does with `sorted`, as you could get the max value using sorted:  
`max_value = sorted(seq, key=len)[-1]`

---

<div class="post-metadata">

**Author:** ![JordanCClark](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/jordancclark/32/83362_2.png) [@JordanCClark](https://forum.inductiveautomation.com/u/JordanCClark)\
**Post date:** [September 28, 2022, 5:08pm UTC](https://forum.inductiveautomation.com/t/view-dataset-from-script-console/36704/13 "2022-09-28T17:08:54Z")

</div>

As Pascal mentioned, _key_ tells what to use to perform the sort.

Here’s some extra examples. I changed up your list a bit to illustrate:

```python
L1=['spider', 'mooselings', 'ant', 'Zebra', 'dog', 'cat']

# Sort by alphanumeric value
print sorted(L1)
print max(L1)
print len(max(L1))
print '------------'

# Sort by length of the string.
print sorted(L1, key = len)
print max(L1, key = len)
print len(max(L1, key = len))

print '------------'

# Sort by alphabetical by using the lower case versions
print sorted(L1, key = str.lower)
print max(L1, key = str.lower)

```

```nohighlight
['Zebra', 'ant', 'cat', 'dog', 'mooselings', 'spider']
spider
6
------------
['ant', 'dog', 'cat', 'Zebra', 'spider', 'mooselings']
mooselings
10
------------
['ant', 'cat', 'dog', 'mooselings', 'spider', 'Zebra']
Zebra
```

---

<div class="post-metadata">

**Author:** ![zacharyw.larson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/zacharyw.larson/32/23749_2.png) [@zacharyw.larson](https://forum.inductiveautomation.com/u/zacharyw.larson)\
**Post date:** [September 28, 2022, 6:41pm UTC](https://forum.inductiveautomation.com/t/view-dataset-from-script-console/36704/14 "2022-09-28T18:41:39Z")

</div>

I did not realize that lowercase had a higher value than uppercase alphanumerically.

---

<div class="post-metadata">

**Author:** ![JordanCClark](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/jordancclark/32/83362_2.png) [@JordanCClark](https://forum.inductiveautomation.com/u/JordanCClark)\
**Post date:** [September 28, 2022, 6:46pm UTC](https://forum.inductiveautomation.com/t/view-dataset-from-script-console/36704/15 "2022-09-28T18:46:16Z")

</div>

If you look at the values in an ASCII table, you can get an idea of how characters compare.

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/2/2/22463ee88db7d670885824b8963c2db371484e35.png)
