Search code examples
pythontableau-api

nested function calls using with statements for authentication


I have a couple of functions that I'm trying to write for tableau (not that that should matter necessarily). The important bit is that it's hierarchical so projects contain workbooks which contain views. project/workbook/view So I have a function for each get_project, get_workbook, and get_view. In order to get view, I first have to get a workbook and before I get a workbook I first have to get a project.

def get_view():
    # need to get the workbook before I get the view
    workbook = get_workbook() #can't have this in the with statement or the connection closes
    with sign_in():
        # do some stuff

# and get workbook looks like 
def get_workbook():
    # need to get the project before I can the workbook
    project = get_project()
    with sign_in():
        # do some stuff

tableau signs out after the with statement. But I'm wondering if there's a better way to have these statements so that I can keep the same connection open instead of opening and closing? This github comment is what got me worried about it.

Right now my only thought is to just pass a live_cxn flag but it's a lot of boilerplate. I don't know if some decorator or something would make this more pythonic. It works, but I don't get any of the benefit of the with statements, mainly if anything errors out my connection is left open.

def get_view(live_cxn=False):
    if not live_cxn:
        sign_in()
    workbook = get_workbook(live_cxn=True)
    # do some stuff
    if not live_cxn:
        sign_out()

# and get workbook looks like 
def get_workbook(live_cxn=False):
    if not live_cxn:
        sign_in()
    project= get_project(live_cxn=True)
    # do some stuff
    if not live_cxn:
        sign_out()

Solution

  • I ended up just making a decorator. Not sure this is the best, but it's still better than the boilerplate I was adding.

    import inspect
    from functools import wraps
    def check_sign_in(func):
        """a decorator to sign in and out of tableau online
        THE DECORATED FUNCTION MUST HAVE A KWARG 'has_cxn'
        and the calling function must call it as a kwarg
        """
        sig = inspect.signature(func)
        default_kwargs = {
            kw: value.default for kw, value in sig.parameters.items() if value.default != inspect.Signature.empty
        }
        if "has_cxn" not in default_kwargs.keys():
            raise ValueError("The decorated function must have a kwarg 'has_cxn'")
    
        @wraps(func)
        def wrapper(*args, **kwargs):
            has_cxn = {**default_kwargs, **kwargs}.get("has_cxn", False)
            if has_cxn:
                r = func(*args, **kwargs)
            else:
                logger.debug("Creating a new connection to Tableau Online")
                with server.auth.sign_in(tableau_auth):
                    r = func(*args, **kwargs)
            return r
        return wrapper