# Showing a .csv table from the server in Perspective

**URL:** <https://forum.inductiveautomation.com/t/showing-a-csv-table-from-the-server-in-perspective/112647>\
**Category:** Ignition\
**Tags:** perspective\
**Created:** [December 5, 2025, 2:01pm UTC](https://forum.inductiveautomation.com/t/showing-a-csv-table-from-the-server-in-perspective/112647 "2025-12-05T14:01:09Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![victoria.paya](https://avatars.discourse-cdn.com/v4/letter/v/7cd45c/32.png) [@victoria.paya](https://forum.inductiveautomation.com/u/victoria.paya)\
**Post date:** [December 5, 2025, 2:01pm UTC](https://forum.inductiveautomation.com/t/showing-a-csv-table-from-the-server-in-perspective/112647/1 "2025-12-05T14:01:09Z")

</div>

I have a power automate script that every hour refreshes a table in C:\SCADA\orders.csv with new orders. The csv file just have a simple table with the list of orders, with it’s date, number, description, qty, etc…  
I want to show this table in a perspective view, I been trying with chatgpt to get a script that reads the csv file and put the contents into a dataset tag, but how is normal with chatgpt, the script doesn’t work, and it doesn’t seem to be able to find a solution.

The biggest problem seems to be that ignition can’t just access C:\SCADA at all. The user that is running the service in the computer is a local admin, and the folder is shared with the ignition service, but still reports that there’s no file.

I’m trying to find information about this but I can’t. Is there anything special that needs to be done for the gateway to be able to read local files?

Thank you.

PS: This is the script:

`import csv`

`def getOrders():`  
` path = r"C:\SCADA\orders.csv"`  
` rows = []`  
` headers = []`  
` with open(path, 'r') as f:`  
` reader = csv.reader(f)`  
` for i, row in enumerate(reader):`  
` if i == 0:`  
` headers = row`  
` else:`  
` rows.append(row)`  
` return system.dataset.toDataSet(headers, rows)`

Also tried with path = r"C:\Program Files\Inductive Automation\Ignition\data\orders.csv" with no luck.  
This function is called by a timer script:

`def handleTimerEvent():

```
try:
    dataset = project.readorders.getOrders()
    system.tag.writeBlocking(["[default]orders/OrdersDataset"], [dataset])
except Exception as e:
    system.util.getLogger("getorderstimer").error("Timer error: " + str(e))`

```

---

<div class="post-metadata">

**Author:** ![MMaynard](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mmaynard/32/3366_2.png) [@MMaynard](https://forum.inductiveautomation.com/u/MMaynard)\
**Post date:** [December 5, 2025, 2:36pm UTC](https://forum.inductiveautomation.com/t/showing-a-csv-table-from-the-server-in-perspective/112647/2 "2025-12-05T14:36:38Z")

</div>

Are you calling it from a gateway timer event?

If not, move it there.

Also double the backslashes in the path, and then write the results to a dataset tag in the call itself.

> `path = r"C:\\SCADA\\orders.csv"`
> 
> blah blah
> 
> system.tag.writeBlocking("[default]orders/OrdersDataset", system.dataset.toDataSet(headers, rows))

---

<div class="post-metadata">

**Author:** ![victoria.paya](https://avatars.discourse-cdn.com/v4/letter/v/7cd45c/32.png) [@victoria.paya](https://forum.inductiveautomation.com/u/victoria.paya)\
**Post date:** [December 5, 2025, 3:06pm UTC](https://forum.inductiveautomation.com/t/showing-a-csv-table-from-the-server-in-perspective/112647/3 "2025-12-05T15:06:59Z")

</div>

![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/e/a/ea7e427bb0748c6b2b5989ab855549d4831a1032.png)

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/a/8/a86d8184a44453d7f84bd9525089a090160cbc56.png)

This is how it looks right now, as you can see the tag is still null. The double \\ didn’t seem to help. I don’t know if this may be a file access problem more than a code problem.

---

<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:** [December 5, 2025, 3:25pm UTC](https://forum.inductiveautomation.com/t/showing-a-csv-table-from-the-server-in-perspective/112647/4 "2025-12-05T15:25:37Z")

</div>

As a sanity check, see if you can write a file.

It should at least let you know if it’s a permissions issue.

---

<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:** [December 5, 2025, 3:45pm UTC](https://forum.inductiveautomation.com/t/showing-a-csv-table-from-the-server-in-perspective/112647/5 "2025-12-05T15:45:34Z")

</div>

tip: `csv.reader` is iterable, so you can use

```python
headers = next(reader)
rows = list(reader)

```

using `next` will consume the first line, so it won't appear in `rows`.  
I usually do something like

```python
headers = next(reader)
data = [dict(zip(headers, row)) for row in reader]

```

This builds a list of dicts that you can pass directly to a perspective table.

---

<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:** [December 5, 2025, 3:46pm UTC](https://forum.inductiveautomation.com/t/showing-a-csv-table-from-the-server-in-perspective/112647/6 "2025-12-05T15:46:51Z")

</div>

As another sanity check, is the project enabled?

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [December 7, 2025, 3:47am UTC](https://forum.inductiveautomation.com/t/showing-a-csv-table-from-the-server-in-perspective/112647/7 "2025-12-07T03:47:13Z")

</div>

> [@MMaynard](#):
>
> Also double the backslashes

The `r` indicates a raw string and you don’t need to double the slashes

---

<div class="post-metadata">

**Author:** ![victoria.paya](https://avatars.discourse-cdn.com/v4/letter/v/7cd45c/32.png) [@victoria.paya](https://forum.inductiveautomation.com/u/victoria.paya)\
**Post date:** [December 8, 2025, 9:57am UTC](https://forum.inductiveautomation.com/t/showing-a-csv-table-from-the-server-in-perspective/112647/8 "2025-12-08T09:57:57Z")

</div>

After some debugging, following the tips you all gave me, found some indentation problems from copy-pasting code in the timer. Now the function is properly called, and after checking if I can write (I can) I found the problem was something as stupid as replacing csv.reader(f) for csv.reader(f, delimiter=';')

After some cleanup i’ll leave the script that ChatGPT ended making in case someone finds this post in the future:

```python
# spaces-only indentation (no tabs)

import csv
import io

def getOrders():
logger = system.util.getLogger("readorders")
path = r"C:\SCADA\orders.csv"

encodings = ["utf-8-sig", "utf-8", "cp1252", "latin-1"]
used_encoding = None
headers = []
rows = []

# Try multiple encodings until one succeeds
for enc in encodings:
    try:
        with io.open(path, 'r', encoding=enc, newline='') as f:
            reader = csv.reader(
                f,
                delimiter='\t', # TAB-delimited
                quotechar='"',
                escapechar='\\'
            )
            headers = []
            rows = []
            for i, row in enumerate(reader):
                # Trim whitespace in cells
                row = [c.strip() for c in row]
                if i == 0:
                    headers = row
                else:
                    rows.append(row)
        used_encoding = enc
        break
    except UnicodeDecodeError:
        # Try next encoding
        continue

# Last-resort: decode with replacement to avoid crashes
if used_encoding is None:
    with io.open(path, 'r', encoding='latin-1', errors='replace', newline='') as f:
        reader = csv.reader(
            f,
            delimiter='\t',
            quotechar='"',
            escapechar='\\'
        )
        for i, row in enumerate(reader):
            row = [c.strip() for c in row]
            if i == 0:
                headers = row
            else:
                rows.append(row)
    used_encoding = "latin-1(replace)"

# Clean headers: keep content inside [...] when present; ensure uniqueness
cleaned = []
seen = set()
for h in headers:
    h = h.strip()
    if '[' in h and ']' in h:
        inside = h[h.find('[') + 1:h.rfind(']')].strip()
        if inside:
            h = inside
    base = h or "Column"
    name = base
    idx = 1
    while name in seen:
        idx += 1
        name = "%s_%d" % (base, idx)
    seen.add(name)
    cleaned.append(name)

# Normalize rows to header length
colcount = len(cleaned)
normalized = []
for r in rows:
    if len(r) < colcount:
        r = r + [''] * (colcount - len(r))
    elif len(r) > colcount:
        r = r[:colcount]
    normalized.append(r)

ds = system.dataset.toDataSet(cleaned, normalized)
logger.info(
    "getOrders: path=%s, rows=%d, cols=%d, encoding=%s" %
    (path, ds.getRowCount(), ds.getColumnCount(), used_encoding)
)
return ds`

```

I’m pretty sure this code is unnecessarily cluttered, but for now it just works for my .csv file
