2

I am trying to use Tableau calculated field to use my python script, which gets JSON data. My ultimate goal is to get this data into tableau in a tabular format.

I have read JSON is easier to get into tableau opposed to dataframe object.

I currently use it in Spyder program. and execute this to get my data.

print (get1D ("2019-02-02", "2019-02-05", "Tableau", "Limits"))

In my calculated field I get the error: "Error Parsing Number" on

.format(status_code))

error msg:

enter image description here

Any help would be appreciated on getting this data into tableau. Here is my full script.

SCRIPT_INT(  

import time  
import requests  
import json  
import pandas as pd  
import re  
import urllib3  
import math  

from io import StringIO  

from datetime import datetime, date,timedelta  
from pandas.tseries.offsets import BDay  
from urllib.parse import urlencode  
from flask import json  


def call_api(url, request_dict, post):     


    if post:  
        header = {'content-type':'application/json'}  

        resp = requests.post(url, data=json.dumps(request_dict), auth=('user', 'pass'), headers = header, verify=False)  
    else:  
        url = url + urlencode(request_dict)  


        resp = requests.get(url, auth=('user', 'pass'), verify=False)  




    status_code = resp.status_code  

    if status_code == 401:  
        raise ValueError("There is an error with the connection.\nLogin failed. \nNot authorized. Please check your credentials and try again.\nStatus code {}".format(status_code))  
    elif status_code == 404:  
        raise ValueError("There is an error with the connection.\nCould not connect to the server.\nStatus code {}".format(status_code))  
    elif status_code == 200:  
        pass  
    else:  
        raise ValueError("There is an error with the connection.\nStatus code {}".format(status_code))  

    return resp  


def getData (startDate, endDate, nodeName, Type, Id):  



    request_dict = [{  
        "hierarchy": "Tableau",  
        "nodeName": nodeName,  
        "FilterId": Type,  
        "Id": Id ,  
    }]  


    url = "https://sampleurl/startDate={0}&endDate={1}"   



    startDate = datetime.strptime(startDate, '%Y-%m-%d')  
    startDate = startDate.strftime ('%Y%m%d')  

    endDate = datetime.strptime(endDate, '%Y-%m-%d')  
    endDate = endDate.strftime ('%Y%m%d')  


    url = url.format(startDate, endDate)  


    resp = call_api(url, request_dict, True)   

    return resp.json ()  

def get1D(startDate, endDate, nodeName, Type):  
    return getData (startDate, endDate, nodeName, Type, 1)  
)  
piet.t
  • 11,718
  • 21
  • 43
  • 52
JonnyBoy
  • 385
  • 1
  • 2
  • 10

2 Answers2

1

Take a look at Tableau's guide to Authoring Python Calculations in Tableau.

In general, the format needs to be as such:

SCRIPT_INT("import xyz foo=_arg1+_arg2 return foo", SUM([A number]), SUM([Another Number])

From what I can see, you'll need to add quotation marks to your calculated field, replace any field variables that need to be passed with _argX, add commas to the calculated field, then a list of fields to pass as those args.

Take note that anytime you see the error, "The calculation contains errors," in the Tableau Calculated field window that the problem is within the Tableau Calculated field (Formatting/compiling) and not necessarily with the underlying Python. (The error you're seeing is a red herring. The Tableau Calculated Field interpreter is seeing the "." as a decimal and expecting to receive a number behind it.) In the calculated field window, Tableau will have no way of checking the underlying Python - it only passes it to TabPy as a string. The opposite is true as well - seeing "This calculation is valid" in the Tableau Calculated Field window doesn't necessarily mean that the Python script will return properly.

Hope this helps.

Edit in response to comment:

Below is an example using the code you provided in your question.

  • Replaced double quotations within Python script with single quotations. In this way, Tableau will be able to tell its own double quotations apart. (Tableau is the same as Python in that it treats double and single quotes the same.)
  • Replaced the input parameters for getData() with the _arg1 through _arg4.
  • Passed [Start Date], [End Date], [Node Name], and [Type] as args after the Python script is passed as a string. (These get implanted into the string as _arg1 through _arg4. (ATTR() might not be the right aggregation method here - you'll have to experiment.)
  • The Calculated field will now at least compile, however, I cannot guarantee that it will execute on the Python side or that it will accomplish what you're trying to do.
  • I'm not sure how get1D() is going to react here. You may have to put your _arg1 through _arg4 as parameters there too. There is some work that will need to be done - and maybe even a reformat of the code to accept Tableau args.

Please do read the TabPy documentation for more clarification on its use than I'm able to provide here. Also, here is a good blog post. When used properly, its immensely powerful.

Good luck!

SCRIPT_INT(  

"import time  
import requests  
import json  
import pandas as pd  
import re  
import urllib3  
import math  

from io import StringIO  

from datetime import datetime, date,timedelta  
from pandas.tseries.offsets import BDay  
from urllib.parse import urlencode  
from flask import json  


def call_api(url, request_dict, post):     


    if post:  
        header = {'content-type':'application/json'}  

        resp = requests.post(url, data=json.dumps(request_dict), auth=('user', 'pass'), headers = header, verify=False)  
    else:  
        url = url + urlencode(request_dict)  


        resp = requests.get(url, auth=('user', 'pass'), verify=False)  




    status_code = resp.status_code  

    if status_code == 401:  
        raise ValueError('There is an error with the connection.\nLogin 
        failed. \nNot authorized. Please check your credentials and try 
    again.\nStatus code {}'.format(status_code))  
    elif status_code == 404:  
        raise ValueError('There is an error with the connection.\nCould not 
        connect to the server.\nStatus code {}'.format(status_code))  
    elif status_code == 200:  
        pass  
    else:  
        raise ValueError('There is an error with the connection.\nStatus 
    code {}'.format(status_code))  

    return resp  


def getData (startDate, endDate, nodeName, Type, Id):  



    request_dict = [{  
        'hierarchy': 'Tableau',  
        'nodeName': nodeName,  
        'FilterId': Type,  
        'Id': Id ,  
    }]  


    url = 'https://sampleurl/startDate={0}&endDate={1}'  



    startDate = datetime.strptime(startDate, '%Y-%m-%d')  
    startDate = startDate.strftime ('%Y%m%d')  

    endDate = datetime.strptime(endDate, '%Y-%m-%d')  
    endDate = endDate.strftime ('%Y%m%d')  


    url = url.format(startDate, endDate)  


    resp = call_api(url, request_dict, True)   

    return resp.json ()  

def get1D(startDate, endDate, nodeName, Type):  
    return getData (_arg1, _arg2, _arg3, _arg4, 1)"
,
ATTR([Start Date]),ATTR([End Date]),ATTR([Node Name], ATTR([Type]
)  
MonteCarloSims
  • 1,751
  • 1
  • 7
  • 19
  • Thanks. This is pretty difficult.. I have quotes already within my python to store my url. anychance you could alter my code so I can understand better on how to implement? – JonnyBoy Mar 07 '19 at 14:41
  • Edit to original answer added to show example using your code. It's a tough one. The more I think about it, the more I think you're going to have to rewrite your code a little. Please read the suggested documentation and blog post so that you may fully understand how best to author the underlying Python code. Good luck! – MonteCarloSims Mar 07 '19 at 15:22
  • thanks daniel! I am getting errors on `ATTR([Start Date]` "reference to undefined field". Are these fields needing to be created first? Also noticed I think your skipped some `]` at the end of your code. – JonnyBoy Mar 07 '19 at 17:58
  • Ah, I've assumed the Start Date, End Date, Node Name, and Type are what you are trying to pass to Tableau. Are those not in your dataset? Keep in mind that TabPy is only designed to operate on fields present in Tableau and not designed to be a datasource. If you were intending this to pull JSON as a datasource, then Tableau has a JSON connector which would be able to operate on the output of the Script. – MonteCarloSims Mar 07 '19 at 18:05
  • Right, these fields are not in tableau, im working from a blank sheet. I want json as a datasource (not json file). How do I find out more about this? – JonnyBoy Mar 07 '19 at 18:16
  • https://onlinehelp.tableau.com/current/pro/desktop/en-us/examples_json.htm You'll need to have the python script put the JSON in some intermediate location so that it can be read by Tableau. Sorry I didn't catch that's what you were trying to do with the calculated field until now. – MonteCarloSims Mar 07 '19 at 18:17
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/189614/discussion-between-jonnyboy-and-daniel-sims). – JonnyBoy Mar 07 '19 at 18:44
1

Adding this answer for posterity and to simplify my previous answer and resulting thread.

The Python script presented in the question is intending to load data directly into Tableau. TabPy is meant to operate on data already existing within Tableau, as opposed to being a source if ingestion.

A necessity here is to place the output of the Python script into an intermediary location where it can then be connected to by Tableau.

MonteCarloSims
  • 1,751
  • 1
  • 7
  • 19