Extending Tableau with Ople.ai and new functions

In my previous post Haskell in Tableau? Yes, you can! I mentioned an example of Analytics Extention API implementation in the way it allowed to use Haskell in Tableau calculations.

And there are more examples of how Tableau calculations can be extended:

Do you know of any other examples worth sharing?

Share the post if you liked it
  •  
  •  
  •  
  •  
  •  
  •  

Tableau Server 2020.1: Analytics Extensions Improvement

Similarly to Tableau Desktop 2020.1 improvements for analytics extension (formerly referred to as external service) Tableau Server 2020.1 also presents some improvements.

Specifically for configuring an analytics extension connection for Tableau Server there is no need to provide a certificate for tsm security vizql-extsvc-ssl enable command and --cert-file parameter is not supported anymore.

The certificate validation works in exactly the same way as it does for Tableau Desktop which means root certificate, self-signed certificate or the whole certificate chain (for Rserve) has to be installed as trusted on Tableau Server nodes.

With this improvement, there is no need to share an analytics extension certificate and it can be validated against what is configured on client machines (nodes running Tableau Server).

More details about configuring a connection for Tableau Server can be found at https://help.tableau.com/current/server-linux/en-us/cli_security_tsm.htm#tsm_security_vizql-extsvc-ssl-enable.

Share the post if you liked it
  •  
  •  
  •  
  •  
  •  
  •  

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
  •  
  •  
  •  
  •  
  •  
  •