Tableau Desktop 2020.1: Advanced Analytics Improvements

With Tableau Desktop 2020.1 released (https://www.tableau.com/support/releases/desktop/2020.1) there are plenty of improvements and new features. But for the purpose of this blog let’s take a look at External Services the most significant change.

The change is for how secure connection is created. When configuring a connection with main menu Help, Settings and Performance, Manage External Service Connection… here’s how new configuration dialog looks like:

What is different there is no link to specify a certificate for secure connection – there’s just Require SSL option.

To compare this is how the same dialog looks in Tableau Desktop 2019.4:

How certificate validation works now is instead of comparing server (TabPy, Rserve, etc.) certificate with user-provided certificate Tableau validates the server certificate and trusts it only if it is installed as a trusted cert on the client OS or if it is signed with a trusted cert.

The server certificate can be signed with an intermediate certificate that can be signed with another certificate and so on until there’s a root certificate Tableau can trust.

For self-signed certificates (those which are not signed by any other certificate) they should be installed as trusted as well.

For how to install a certificate and make it trusted read documentation for your OS (the steps are very different for each OS).

NOTE: Rserve only sends leaf certificate and not the whole chain (there’s an issue opened for that – https://github.com/s-u/Rserve/issues/140) which makes it impossible to validate the certificate on the Tableau side unless the whole chain is installed as trusted.

Documentation for this new UI and behavior is at https://help.tableau.com/current/pro/desktop/en-us/r_connection_manage.htm page.

Share the post if you liked it

TabPy v1 released!

It took some time, but TabPy v1 finally happened. Comparing to the previous v0.9.0 there are not so many changes:

To install TabPy or update your TabPy instance to the latest release run the following command:

pip install --upgrade tabpy

Additional reading:

Share the post if you liked it

How to Configure Logging in TabPy?

TabPy logs are useful for investigating issues, learning about user activities, how TabPy is used and so on. In my previous post TabPy: modifying default configuration I showed how to change TabPy settings. Logging for TabPy is configured in a similar way and I am going to show and explain some details for how to configure logging.

For the most recent and complete documentation about Python logger configuration, read this documentation page – https://docs.python.org/3/howto/logging.html.

In TabPy config file logger (or rather loggers) are configured with a few sections. A logger itself associated with a logger handler which in its turn depends on a logger formatter:

  • Loggers are used in Python code to initiate logging a message. Based on the severity logger passes the message to corresponding handlers.
  • Handlers are dispatching log messages to the handler’s specified destination. There are handlers for the console, files, etc.
  • Formatters specify how a logged message should look like: order and format of the message properties.

For logger configuration file format read the documentation at https://docs.python.org/3/library/logging.config.html#configuration-file-format.

Following is the example of how to configure logging.

First, section to look at is [loggers] where names of loggers are listed (TabPy will log with all of them). The following example specifies 2 loggers:

[loggers]
keys=root,fileLogger

NOTE: there have to be a logger (it can be the only logger) named “root” in the list of loggers.

Next is the [handlers] section which lists handlers:

[handlers]
keys=consoleHandler,fileHandler

And finally formatters section [formatter] lists all the formatters (not all of them have to be used):

[formatters]
keys=consoleFormatter,fileFormatter

Now for each logger in [logger] section there is [logger_<name>] section with settings for a logger:

[logger_root]
level=WARNING
handlers=consoleHandler

[logger_fileLogger]
level=DEBUG
handlers=fileHandler
propgate=1
qualname=consoleLogger

As you can see from the example above for each logger severity level is specified which means only messages with specified severity of higher. The level can be one of DEBUG, INFO, WARNING, ERROR, CRITICAL or NOTSET (all messages will be logged).

Handlers for a logger are listed with handlers parameter. It is possible to have more than one handler for a logger.

For non-root loggers properties propgate and qualname need to be set. The first one specifies if messages need to be propagated to a handler higher up and is set to 1 or 0. And qualname sets the name for the logger so it can be referenced from Python code.

For each handler there is [handler_<name>] section:

[handler_consoleHandler]
class=StreamHandler
level=WARNING
formatter=consoleFormatter
args=(sys.stdout,)

[handler_fileHandler]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=fileFormatter
args=('tabpy_log.log', 'a', 1000000, 5)

For each formatter there is level parameter which is configured in the same way as for a logger.

With class parameter a Python class which implements a handler is specified (more details below). It is possible to use the same class for different handlers. As an example, you can have one large log file where entries are appended and the same messages logged to date specific log files.

Formatter for logged messages is set with formatter parameter. Again – the same formatter can be used with multiple handlers.

And args parameter provides logger specific parameters.

Handler classes available with Python are listed at https://docs.python.org/3/library/logging.handlers.html#module-logging.handlers, but it is possible to use any custom handlers (e.g. for colorful output in the console) with other packages installed in the Python environment. Some useful handlers are:

  • StreamHandler sends logging to a stream (e.g. console).
  • FileHandler appends messages to a file.
  • NullHandler does not log anything.
  • RotatingFileHandler logs to a file until log file size limit is reached and then creates a new file and logs to it. The handler is used in the example above and it will create a new log file when the current one reaches 1000000 bytes in size. The current file name is always tabpy_log.log, previous log file will be named tabpy_log.log.1 and so on till tabpy_log.log.5.
  • TimedRotatingFileHandler is similar to RotatingFileHandler but creates a new file after the specified time interval.
  • HTTPHandler sends messages to a web server with GET or POST command.

For each formatter printf-style string formatting string (documentation is here – https://docs.python.org/3/library/stdtypes.html#old-string-formatting) specifies how the format message is built. In the format string additional log objects can be used (list of the log objects is here – https://docs.python.org/3/library/logging.html#logging.LogRecord):

[formatter_consoleFormatter]
format=%(asctime)s: %(message)s
datefmt=%H:%M:%S

[formatter_fileFormatter]
format=%(asctime)s [%(levelname)s] (%(filename)s:%(module)s:%(lineno)d): %(message)s
datefmt=%Y-%m-%d,%H:%M:%S

In this example console formatter will output all the messages with preceding timestamp and for file logs there will be date-time stamp, severity of the message, where in the code it was logged from and the message itself.

Now the whole config file (I have it on my machine saved as c:\demo\tabpy\tabpy.conf:

[loggers]
keys=root,fileLogger

[formatters]
keys=consoleFormatter,fileFormatter

[handlers]
keys=consoleHandler,fileHandler

[logger_root]
level=WARNING
handlers=consoleHandler

[logger_fileLogger]
level=DEBUG
handlers=fileHandler
propgate=1
qualname=consoleLogger

[handler_consoleHandler]
class=StreamHandler
level=WARNING
formatter=consoleFormatter
args=(sys.stdout,)

[handler_fileHandler]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=fileFormatter
args=('tabpy_log.log', 'a', 1000000, 5)

[formatter_consoleFormatter]
format=%(asctime)s: %(message)s
datefmt=%H:%M:%S

[formatter_fileFormatter]
format=%(asctime)s [%(levelname)s] (%(filename)s:%(module)s:%(lineno)d): %(message)s
datefmt=%Y-%m-%d,%H:%M:%S

Starting TabPy with the config:

tabpy --config c:\demo\tabpy\tabpy.conf

After running a few requests against my local TabPy instance this is what I see in the console:

15:19:37: Responding with status=404, message="Unknown endpoint", info="Endpoint olek_add is not found"
15:19:37: 404 GET /endpoints/olek_add (::1) 5.03ms

And for the same TabPy there’s much much more information in tabpy_log.log file:

2019-12-04,15:14:54 [DEBUG] (app.py:app:215): Parameter port set to "9004" from default value
2019-12-04,15:14:54 [DEBUG] (app.py:app:215): Parameter server_version set to "0.8.9" from default value
2019-12-04,15:14:54 [DEBUG] (app.py:app:215): Parameter evaluate_timeout set to "30" from default value
...
2019-12-04,15:15:17 [INFO] (app.py:app:110): Initializing TabPy...
...
2019-12-04,15:15:17 [INFO] (app.py:app:93): Web service listening on port 9004

Share the post if you liked it

Haskell in Tableau? Yes, you can!

Some time ago I posted about alternative TabPy implementation (AltTabPy – lightweight and straightforward alternative to TabPy) you can use with Tableau. Using that alternative solution is possible because AltTabPy implements TabPy API and Tableau Desktop or Server simply calls that API. So Tableau is not hardcoded to use TabPy.

But if alternative implementations are possible is it also possible to implement the API for something completely different than executing Python script… like another programming language?

Yes – it is possible and people actually do it. Here’s an example of how you can use Haskell scripts in Tableau calculations – https://databoss.starschema.net/tableau-external-services-api-adding-haskell-calculations/. The post explains step by step how to implement TabPy API in Haskell and how to use the server with Tableau. Very well written post indeed and highly recommended for reading. The project itself is available on GitHub – https://github.com/tfoldi/tc19-haskell-ext/.

With those two examples (and there are few more you can find) it should be possible to implement TabPy API for any programming language (Lua, SQL, JavaScript, etc.) or even a service (imagine receiving weather data for given coordinates and date/time).

Share the post if you liked it

TabPy v0.9.0 released

As we getting closer to TabPy v1 being released (read here for more details – https://www.tableau.com/about/blog/2020/1/python-tableau-now-10) some bug fixes, documentation updates and code improvements are made. In the v0.9.0 release those are:

  • TabPy now works with Python 3.8.
  • Client.remove() method was added and now models (deployed functions) can be deleted with tabpy_tools.
  • Index page for running TabPy instance now shows the instance settings, deployed models and some useful links.
  • Bug fixes for Ctrl+C, query timeout, returning None or not returning value from a script, returning NaN in result.
  • Internals improvements for code quality, package size, test coverage.

To install this specific version run pip install tabpy==0.9.0 command.

Share the post if you liked it