PyXLL The Python Excel Add-In
  • Product
    • Features
    • Get Started
    • Request Demo
    • Download
  • Pricing
  • Resources
    • Documentation
    • Blog
    • Videos
    • FAQ
    • Learn Python
    • Customer Portal
    • About Us
  • Support
    • Documentation
    • Videos
    • FAQ
    • Contact Us
  • Contact Us
Table of Contents
  • PyXLL Documentation
  • Introduction to PyXLL
  • User Guide
    • Installing PyXLL
    • Configuring PyXLL
    • Worksheet Functions
    • Macro Functions
    • Real Time Data
      • Introduction
      • RTD Generators
      • Using the RTD Class
      • RTD Data Types
      • Restarting RTD Functions
      • Throttle Interval
      • Starting RTD Functions Automatically
    • Cell Formatting
    • Charts and Plotting
    • Custom Task Panes
    • ActiveX Controls
    • Using Pandas in Excel
    • Customizing the Ribbon
    • Context Menu Functions
    • Working with Tables
    • Python as a VBA Replacement
    • Menu Functions
    • Reloading and Rebinding
    • Error Handling
    • Deploying your add-in
    • Workbook Metadata
  • Video Guides and Tutorials
  • API Reference
  • What’s new in PyXLL 5
  • Changelog
Close

Real Time Data¶

  • Introduction

  • RTD Generators

    • Async RTD Generators

  • Using the RTD Class

    • RTD Class Example

    • RTD Class Async Example

  • RTD Data Types

  • Restarting RTD Functions

    • Detaching

    • Detaching RTD Generators

    • Detaching RTD Instances

  • Throttle Interval

  • Starting RTD Functions Automatically

Introduction¶

Real Time Data (or RTD) is data that updates asynchronously, according to its own schedule rather than just when it is re-evaluated (as is the case for a regular Excel worksheet function).

Examples of real time data include stock prices and other live market data, server loads or the progress of an external task.

Real Time Data has been a first-class feature of Excel since Excel 2002. It uses a hybrid push-pull mechanism where the source of the real time data notifies Excel that new data is available, and then some small time later Excel queries the real time data source for its current value and updates the value displayed.

RTD random number generator in Excel using Python.

PyXLL provides a convenient and simple way to stream real time data to Excel without the complexity of writing (and registering) a Real Time Data COM server.

Real Time Data functions are registered in the same way as other worksheet functions using the xl_func decorator. By registering a Python generator function, or a function that returns an RTD object, streams of values can be returned to Excel as a real time data function.

RTD functions have the return type rtd.

RTD Generators¶

New in PyXLL 5.6

The simplest way to write an Excel RTD (Real Time Data) function using PyXLL is to write it as a Python generator.

A Python generator is a special type of Python function that yields a stream of results instead of just a single result.

The following is an example of a Python generator that yields a random number every 5 seconds:

import random
import time

def random_numbers():
    # Loop forever
    while True:
        # Yield a random number
        yield random.random()

        # Wait 5 seconds before continuing
        time.sleep(5)

To turn this into an RTD function in Excel all that’s needed is to add the xl_func decorator with the rtd return type:

from pyxll import xl_func
import random
import time

@xl_func(": rtd")
def random_numbers():
    while True:
        yield random.random()
        time.sleep(5)

When this random_numbers function is called from Excel it will tick every 5 seconds with a new random number.

To prevent the long running Python generator function from blocking the main Excel thread RTD generators are always run on a background thread. One thread is created for each generator. For more sophisticated thread management use a function that returns an RTD instance instead (see Using the RTD Class).

Each time the generator yields a value Excel is notified a new value is ready. Excel may not display every value as it throttles the updates that is displays (see Throttle Interval).

It’s important not to create a tight loop that constantly updates as doing so will prevent other threads from having time to run (including the main Excel thread) and will cause Excel to hang.

Warning

Unlike other PyXLL functions, RTD generators are always run on a background thread.

Async RTD Generators¶

New in PyXLL 5.6

Async RTD generators work in a similar way to the RTD generators described above. Instead of running in a separate thread async RTD generators are run on PyXLL asyncio event loop.

Async generators are well suited to IO bound tasks such as receiving updates from a remote server. Care should be taken so that the asyncio event loop isn’t blocked by ensuring that tasks are properly asynchronous and are awaited correctly.

The same example as above can be re-written as an async generator by replacing time.sleep with asyncio.sleep:

from pyxll import xl_func
import random
import asyncio

@xl_func(": rtd")
async def async_random_numbers():
    # Loop forever
    while True:
        # Yield a random number
        yield random.random()

        # Wait 5 seconds before continuing without blocking the event loop
        await asyncio.sleep(5)

When this async_random_numbers function is called from Excel it will tick every 5 seconds with a new random number.

Each time the generator yields a value Excel is notified a new value is ready. Excel may not display every value as it throttles the updates that is displays (see Throttle Interval).

Warning

Async RTD generators share the same asyncio loop as other async functions. Blocking the asyncio event loop will cause delays in other functions, or prevent them from running entirely.

See The asyncio Event Loop for more details.

Using the RTD Class¶

For more control over the RTD behaviour of an Real Time Data function an RTD object can be returned from an RTD function instead of using a generator.

The RTD class has a value property. Setting this property notifies Excel that a new value is ready. RTD functions that use the RTD class return an RTD object and update the value on that returned object each time a new value is available (for example, by scheduling an update function on a background thread). Typically this is done by writing a derived class that handles updating its own value.

If multiple function calls from different cells return the same RTD object then those cells are subscribed to the same object. All the cells will update whenever the value property of the one RTD object is set.

RTD Class Example¶

The following example shows a class derived from RTD that periodically updates its value to the current time.

Read time data streamed into Excel from Python.

It uses a separate thread to set the value property, which notifies Excel that new data is ready.

from pyxll import xl_func, RTD
from datetime import datetime
import threading
import logging
import time

_log = logging.getLogger(__name__)


class CurrentTimeRTD(RTD):
    """CurrentTimeRTD periodically updates its value with the current
    date and time. Whenever the value is updated Excel is notified and
    when Excel refreshes the new value will be displayed.
    """

    def __init__(self, format):
        initial_value = datetime.now().strftime(format)
        super(CurrentTimeRTD, self).__init__(value=initial_value)
        self.__format = format
        self.__running = True
        self.__thread = threading.Thread(target=self.__thread_func)
        self.__thread.start()

    def connect(self):
        # Called when Excel connects to this RTD instance, which occurs
        # shortly after an Excel function has returned an RTD object.
        _log.info("CurrentTimeRTD Connected")

    def disconnect(self):
        # Called when Excel no longer needs the RTD instance. This is
        # usually because there are no longer any cells that need it
        # or because Excel is shutting down.
        self.__running = False
        _log.info("CurrentTimeRTD Disconnected")

    def __thread_func(self):
        while self.__running:
            # Setting 'value' on an RTD instance triggers an update in Excel
            new_value = datetime.now().strftime(self.__format)
            if self.value != new_value:
                self.value = new_value
            time.sleep(0.5)

In order to access this real time data in Excel all that’s required is a worksheet function that returns an instance of this CurrentTimeRTD class.

@xl_func("string format: rtd")
def rtd_current_time(format="%Y-%m-%d %H:%M:%S"):
    """Return the current time as 'real time data' that
    updates automatically.

    :param format: datetime format string
    """
    return CurrentTimeRTD(format)

Note that the return type of this function is rtd.

When this function is called from Excel the value displayed will periodically update, even though the function rtd_current_time isn’t volatile and only gets called once.

=rtd_current_time()

RTD Class Async Example¶

Instead of managing your own background threads and thread pools when writing RTD functions, you can use PyXLL’s asyncio event loop instead (new in PyXLL 4.2 and requires Python 3.5.1 or higher).

This can be useful if you have RTD functions that are waiting on IO a lot of the time. If you can take advantage of Python’s async and await keywords so as not to block the event loop then making your RTD function run on the asyncio event loop can make certain things much simpler.

The methods RTD.connect and RTD.disconnect can both be async methods. If they are then PyXLL will schedule them automatically on it’s asyncio event loop.

The example below shows how using the event loop can eliminate the need for your own thread management.

See The asyncio Event Loop for more details.

from pyxll import RTD, xl_func
import asyncio

class AsyncRTDExample(RTD):

    def __init__(self):
        super().__init__(value=0)
        self.__stopped = False

    async def connect(self):
        while not self.__stopped:
            # Yield to the event loop for 1s
            await asyncio.sleep(1)

            # Update value (which notifies Excel)
            self.value += 1

    async def disconnect(self):
        self.__stopped = True


@xl_func(": rtd<int>")
def async_rtd_example():
    return AsyncRTDExample()

RTD Data Types¶

RTD functions can return all the same data types as normal Worksheet Functions, including array types and cached Python objects.

By default, the rtd return type will use the same logic as a worksheet function with no return type specified or the var type.

To specify the return type explicity you have to include it in the function signature as a parameter to the rtd type.

For example, the following is how an RTD function that returns Python objects via the internal object cache would be declared:

@xl_func("string x: rtd<object>")
def rtd_object_func(x):
    # MyRTD sets self.value to a non-trivial Python object
    return MyRTD(x)

RTD data types can be used for RTD generators in the same way.

Although RTD functions can return array types, they cannot be automatically resized and so the array formula needs to be entered manually using Ctrl+Shift+Enter (see Array Functions).

Restarting RTD Functions¶

Each time your RTD function ticks, Excel re-evaluates the RTD function. PyXLL keeps track of your RTD generator or RTD instance to ensure that each time Excel calls the RTD function the correct, already running, object is returned.

Without this caching behavior your Python function would be called each time the RTD function ticks, causing it to always restart and never progress past the initial value.

If the arguments to an RTD function are changed, or if it is moved from one cell to another, the Python function will be called and the RTD function will restart. Because the RTD object has to be cached the usual methods of forcing a cell to recalculate such as pressing F2 to edit the cell and then entering, or force recalculating by pressing Ctrl+Alt+F9, do not work for RTD functions.

There are situations though where an RTD function only returns a fixed number of values before stopping. In those cases you may want the RTD function to restart the next time Excel calculates the cell.

For example, one common case is an RTD function that returns just one value after a delay:

@xl_func("str x: rtd")
async def fetch_from_database(x):
    # Let the user know we're working on it
    yield "Please wait..."

    # Fetch some data
    data = await async_fetch_data_from_database(x)

    # Finally yield the data to Excel
    yield data

In this case, if the user was to attempt to recalculate the cell after the data has been returned they might expect that the data be re-fetched. But, the default behaviour is that the RTD generator is cached and so its same current state is returned again.

Detaching¶

New in PyXLL 5.9

The solution is to detach the RTD generator or RTD object once completed. This detaching removes the RTD generator or object from PyXLL’s RTD cache so that the next time the cell is evaluated there is no cached RTD instance. The Python function is called again, and the Excel RTD function restarts.

Detaching RTD Generators¶

When using an RTD generator or async generator you can tell PyXLL to automatically detach the RTD object when the generator stops.

To do that, add the auto_detach=True type paramter to the rtd return type.

For example:

@xl_func("str x: rtd<auto_detach=True>")
async def fetch_from_database(x): # Let the user know we're working on it yield "Please wait..." # Fetch some data data = await async_fetch_data_from_database(x) # Finally yield the data to Excel. # The generator stops after this line. yield data

Once the generator has stopped, if the user then recalculates the cell it will restart.

Tip

If you want this auto_detach behaviour to be the default for all of your RTD generators you can set the following in your pyxll.cfg file:

[PYXLL]
rtd_auto_detach = 1

Detaching RTD Instances¶

If you are using the RTD class you can detach the RTD object by calling the RTD.detach method.

After calling RTD.detach, the RTD instance will no longer be cached and the next call to your RTD function will call your Python function, restarting the RTD function.

For example, if in the AsyncRTDExample code from above, if we wanted our RTD function to restart after the first few ticks we would do the following:

from pyxll import RTD, xl_func
import asyncio

class AsyncRTDExample(RTD):

    def __init__(self):
        super().__init__(value=0)
        self.__stopped = False

    async def connect(self):
# Send just a few ticks
for i in range(5):
if self.__stopped: break # Yield to the event loop for 1s await asyncio.sleep(1) # Update value (which notifies Excel) self.value += 1
# And then detach.
# The next time =async_rtd_example is called the Python
# function will be called, starting a new AsyncRTDExample instance.
self.detach()
async def disconnect(self): self.__stopped = True @xl_func(": rtd<int>") def async_rtd_example(): return AsyncRTDExample()

Throttle Interval¶

Excel throttles the rate of updates made via RTD functions. Instead of updating every time it is notified of new data it waits for a period of time and then updates all cells with new data at once.

The default throttle time is 2,000 milliseconds (2 seconds). This means that even if you are setting value on an RTD instance or yielding values from a generator more frequently you will not see the value in Excel updating more often than once every two seconds.

The throttle interval can be changed by setting Application.RTD.ThrottleInterval (in milliseconds). Setting the throttle interval is persistent across Excel sessions (meaning that if you close and restart Excel then the value you set the interval to will be remembered).

The following code shows how to set the throttle interval in Python.

from pyxll import xl_func, xl_app

@xl_func("int interval: string")
def set_throttle_interval(interval):
    xl = xl_app()
    xl.RTD.ThrottleInterval = interval
    return "OK"

Alternatively it can be set in the registry by modifying the following key. It is a DWORD in milliseconds.

HKEY_CURRENT_USER\Software\Microsoft\Office\10.0\Excel\Options\RTDThrottleInterval

Starting RTD Functions Automatically¶

When you enter an RTD function in an Excel formula is begins ticking automatically because the function has been called. When loading a workbook containing RTD functions however, they will not start ticking until the function is called.

To enable RTD functions to begin ticking as soon as a workbook is opened PyXLL RTD functions can be marked as needed to be recalculated when the workbook opens by using the Recalculating On Open feature of PyXLL.

To make a function recalculate when the workbook is loaded pass recalc_on_open=True to xl_func. If applied to an RTD function this will cause the RTD function to start ticking when the workbook is loaded.

You can change the default behaviour so that recalc_on_open is True by default for RTD functions (unless explicitly marked otherwise) by setting recalc_rtd_on_open = 1, e.g.

[PYXLL]
recalc_rtd_on_open = 1

Warning

The default behaviour for RTD functions has changed between PyXLL 4 and PyXLL 5.

From PyXLL 5 onwards RTD functions will no longer start automatically when a workbook is opened unless configured as above. This is consistent with other UDFs that are not called automatically when workbooks open by default.

« Macro Functions
Cell Formatting »
  • Home
  • Product
  • Features
  • Documentation
  • Download
  • Pricing
  • Support
  • Documentation
  • Videos
  • FAQ
  • Learn Python
  • Contact Us
  • About
  • About Us
  • Legal
  • Blog
© Copyright PyXLL Ltd