Tm1py: TM1py and Windows Credential Manager

Created on 21 Aug 2020  路  6Comments  路  Source: cubewise-code/tm1py

I am working on a command line application that I would give to co-workers/junior consultants that are not familiar with Python. The idea of the application is to document items in the model (i.e. Dimensions, Cubes, Rules, Subsets, etc.). The end-result would be an EXE file with arguments to output the items.

The code below is intended to:

  • Check for a config, and create if it doesn't exist
  • Check for a section housing the connection information, create if it doesn't exist
  • Otherwise, it should parse the arguments and run the required function(s)

If I include the password in the config.ini file, everything works as intended. If I try to use the keyring library, it saves the password, but then fails the login using TM1Service. I think the issue is related to keyring, but am not sure.

Python code:

import argparse
import keyring
import os
from configparser import ConfigParser
from getpass import getpass
from TM1py.Services import TM1Service

config = ConfigParser()

def create_config_file():
    config.write(open('config.ini', 'w'))


def get_password(server, user):
    password = keyring.get_password(server, user)
    if not password:
        password = getpass(f"Enter password for '{user}' on '{server}': ")
    keyring.set_password(server, user, password)
    return password


def get_info(server):
    while True:
        cloud = input(f"Is '{server}' an IBM Cloud instance (Y/N): ")
        if not cloud.strip():
            print("Input required.")
            continue
        elif cloud.upper() != "Y" and cloud.upper() != 'N':
            print("Input must be 'Y' or 'N'")
            continue
        else:
            break

    if cloud.upper() == 'Y':
        while True:
            base_url = input(f"Enter base URL for '{server}': ")
            if not base_url:
                print("Input is required.")
            else:
                break
    else:
        while True:
            address = input(f"Enter address for '{server}': ")
            if not address:
                print("Input is required")
                continue
            else:
                break

        while True:
            port = input(f"Enter HTTPPort for '{server}': ")
            if not port:
                print("Input is required.")
                continue
            else:
                break

        while True:
            ssl = input(f"SSL Enabled on '{server}' (Y/N): ")
            if not ssl:
                print("Input is required")
                continue
            elif ssl.upper() != 'Y' and ssl.upper() != 'N':
                print("Input must be 'Y' or 'N'.")
                continue
            else:
                break

        if ssl.upper() == 'Y':
            ssl = True
        else:
            ssl = False

    while True:
        user = input(f"Enter username for '{server}': ")
        if not user:
            print("Input is required.")
            continue
        else:
            break

    password = keyring.get_password(server, user)
    if not password:
        while True:
            password = getpass(f"Enter password for '{user}' on '{server}': ")
            if not password:
                print("Input is required")
                continue
            else:
                break
    keyring.set_password(server, user, password)

    if cloud.upper() == 'Y':
        config[server] = {"base_url": base_url, "user": user, "namespace": "LDAP", "ssl": True,
                          "verify": True, "async_requests_mode": True}
    else:
        config[server] = {"address": address, "port": port, "ssl": ssl, "user": user}


def create_config(server):
    create_config_file()
    get_info(server)


def create_section(server):
    get_info(server)


def get_cubes(server):
    user = config[server]["user"]
    password = get_password(server, user)
    with TM1Service(**config[server]) as tm1:
        all_cubes = tm1.cubes.get_all_names()
        with open('cubes.csv', 'w') as file:
            for cube in all_cubes:
                print(cube, file=file)


def get_dimensions(server):
    print("get_dims")
    user = config[server]["user"]
    password = get_password(server, user)
    with TM1Service(**config[server]) as tm1:
        all_dimensions = tm1.dimensions.get_all_names()
        with open('dimensions.csv', 'w') as file:
            for dimension in all_dimensions:
                print(dimension, file=file)


def get_unused_dimensions(server):
    with TM1Service(**config[server]) as tm1:
        all_dimensions = tm1.dimensions.get_all_names()
        all_cubes = tm1.cubes.get_all()
        used_dimensions = set()

        for cube in all_cubes:
            used_dimensions.update(cube.dimensions)
        unused_dimensions = set(all_dimensions) - used_dimensions
        with open('unused_dimensions.csv', 'w') as file:
            for dimension in unused_dimensions:
                print(dimension, file=file)


def get_rule_stats(server):
    with TM1Service(**config[server]) as tm1:
        all_cubes = tm1.cubes.get_all()
        with open('rule_stats.csv', 'w') as file:

            # Cubes with SKIPCHECK
            cubes_with_skipcheck = [cube.name for cube in all_cubes if cube.skipcheck]
            print("Cubes with SKIPCHECK:", file=file)
            print(cubes_with_skipcheck, file=file)

            # Cubes with UNDEFVALS
            cubes_with_undefvals = [cube.name for cube in all_cubes if cube.undefvals]
            print("Cubes with UNDEFVALS:", file=file)
            print(cubes_with_undefvals, file=file)

            # Cubes ordered by number of rule statements
            all_cubes.sort(key=lambda cube: len(cube.rules.rule_statements) if cube.has_rules else 0, reverse=True)
            print("Cubes sorted by number os rule statements:", file=file)
            print([cube.name for cube in all_cubes], file=file)

            # Cubes sorted by number of feeder statements
            all_cubes.sort(key=lambda cube: len(cube.rules.feeder_statements) if cube.has_rules else 0, reverse=True)
            print("Cubes sorted by number of feeders statements:", file=file)
            print([cube.name for cube in all_cubes], file=file)


def get_subsets(server):
    with TM1Service(**config[server]) as tm1:
        all_dimensions = tm1.dimensions.get_all_names()
        with open('dimension_subsets.csv', 'w') as file:
            for dim in all_dimensions:
                private_subsets = tm1.dimensions.subsets.get_all_names(
                    dimension_name=dim,
                    hierarchy_name=dim,
                    private=True)
                print("Private Subests:", file=file)
                for subset in private_subsets:
                    subset = tm1.dimensions.subsets.get(
                        dimension_name=dim,
                        hierarchy_name=dim,
                        subset_name=subset,
                        private=True)
                    print(subset.name, file=file)

                public_subsets = tm1.dimensions.subsets.get_all_names(
                    dimension_name=dim,
                    hierarchy_name=dim,
                    private=False)
                print("Public Subsets:")
                for subset in public_subsets:
                    subset = tm1.dimensions.subsets.get(
                        dimension_name=dim,
                        hierarchy_name=dim,
                        subset_name=subset,
                        private=False
                    )
                    print(subset.name, file=file)


def main():
    parser = argparse.ArgumentParser(description="TM1 Documentation Tool by GLCS.")
    parser.add_argument("server", type=str, help="TM1 Server name")
    parser.add_argument("-c", "--cubes", default=False, action="store_true", help="Output list of all cubes")
    parser.add_argument("-d", "--dimensions", default=False, action="store_true", help="Output list of all dimensions")
    parser.add_argument("-r", "--rules", default=False, action="store_true", help="List cubes by number of rules")
    parser.add_argument("-s", "--subsets", default=False, action="store_true", help="List all dimension subsets")
    parser.add_argument("-u", "--unused", default=False, action="store_true", help="Output list of unused dimensions")

    args = parser.parse_args()

    svr = args.server
    print(svr)

    if not os.path.exists('config.ini'):
        create_config(svr)
        create_config_file()
    else:
        config.read(r'config.ini')

    if not config.has_section(svr):
        create_section(svr)
        create_config_file()
    else:
        if args.cubes:
            get_cubes(svr)
        elif args.dimensions:
            get_dimensions(svr)
        elif args.rules:
            get_rule_stats(svr)
        elif args.subsets:
            get_subsets(svr)
        elif args.unused:
            get_unused_dimensions(svr)
        else:
            pass


if __name__ == "__main__":
    main()

Config.ini:

[GLCS]
address = 192.168.1.31
port = 17471
ssl = False
user = admin
decode_b64 = False

I have been running from the Terminal window as follows:
python main.py GLCS --dimensions

This very well may be a Python coding issue, as I am relatively new with Python (1-2 years off and on).

What am I doing wrong?

Thanks,

Chad

question

All 6 comments

I realize this is a big ask. To narrow the issue down in the example, executing the function get_dimensions is where I get the "Unauthorized-Headers" error indicating a failed login. If I add the password to the config.ini file, no errors are reported and the function executes.

Hi @harveyca307,

In the get_dimensions function.. in line 3 you are getting the password and never passing it to the TM1Service in line 4.

As a good practice, please post the stack trace.

Please see the traceback below:

Traceback (most recent call last):
  File "main.py", line 244, in <module>
    main()
  File "main.py", line 232, in main
    get_dimensions(svr)
  File "main.py", line 124, in get_dimensions
    with TM1Service(**config[server]) as tm1:
  File "C:\Users\harve\PycharmProjects\TM1_Docs\venv\lib\site-packages\TM1py\Services\TM1Service.py", line 13, in __init__
    self._tm1_rest = RestService(**kwargs)
  File "C:\Users\harve\PycharmProjects\TM1_Docs\venv\lib\site-packages\TM1py\Services\RestService.py", line 165, in __init__
    self._start_session(
  File "C:\Users\harve\PycharmProjects\TM1_Docs\venv\lib\site-packages\TM1py\Services\RestService.py", line 302, in _start_session
    response = self.GET(url=url)
  File "C:\Users\harve\PycharmProjects\TM1_Docs\venv\lib\site-packages\TM1py\Services\RestService.py", line 76, in wrapper
    self.verify_response(response=response)
  File "C:\Users\harve\PycharmProjects\TM1_Docs\venv\lib\site-packages\TM1py\Services\RestService.py", line 372, in verify_response
    raise TM1pyRestException(response.text,
TM1py.Exceptions.Exceptions.TM1pyRestException: Text:  Status Code: 401 Reason: Unauthorized Headers: {'Content-Type': 'text/plain', 'Content-Length': '0', 'Connection': 'keep-alive', 'Set-Cookie': 'TM1SessionId=nlTj-EwgI1PoEGNdXxbWYnu
GRYU; Path=/api/; HttpOnly', 'WWW-Authenticate': 'Basic realm="TM1"'}

As I mentioned earlier, you are not passing the password to the TM1Service. Change your get_dimension function like below:

def get_dimensions(server):
    print("get_dims")
    user = config[server]["user"]
    password = get_password(server, user)
    with TM1Service(**config[server], password=password) as tm1:
        all_dimensions = tm1.dimensions.get_all_names()
        with open('dimensions.csv', 'w') as file:
            for dimension in all_dimensions:
                print(dimension, file=file)

I was asking the stack trace for future posts.

I ended up adding a line to the get_password function:

config.set(server, "password", password)
 ```
 This updated the config settings without writing the password to the file.  Everything is working as expected now.  Not sure if this is best practices, but it is working for the time being.  Thanks for the input.

Final code:

import argparse
import os
from configparser import ConfigParser
from getpass import getpass

import keyring
from TM1py.Services import TM1Service

config = ConfigParser()

def create_config_file():
config.write(open('config.ini', 'w'))

def get_password(server, user):
password = keyring.get_password(server, user)
if not password:
password = getpass(f"Enter password for '{user}' on '{server}': ")
keyring.set_password(server, user, password)
config.set(server, 'password', password)

def get_info(server):
while True:
cloud = input(f"Is '{server}' an IBM Cloud instance (Y/N): ")
if not cloud.strip():
print("Input required.")
continue
elif cloud.upper() != "Y" and cloud.upper() != 'N':
print("Input must be 'Y' or 'N'")
continue
else:
break

if cloud.upper() == 'Y':
    while True:
        base_url = input(f"Enter base URL for '{server}': ")
        if not base_url:
            print("Input is required.")
        else:
            break
else:
    while True:
        address = input(f"Enter address for '{server}': ")
        if not address:
            print("Input is required")
            continue
        else:
            break

    while True:
        port = input(f"Enter HTTPPort for '{server}': ")
        if not port:
            print("Input is required.")
            continue
        else:
            break

    while True:
        ssl = input(f"SSL Enabled on '{server}' (Y/N): ")
        if not ssl:
            print("Input is required")
            continue
        elif ssl.upper() != 'Y' and ssl.upper() != 'N':
            print("Input must be 'Y' or 'N'.")
            continue
        else:
            break

    if ssl.upper() == 'Y':
        ssl = True
    else:
        ssl = False

while True:
    user = input(f"Enter username for '{server}': ")
    if not user:
        print("Input is required.")
        continue
    else:
        break

password = keyring.get_password(server, user)
if not password:
    while True:
        password = getpass(f"Enter password for '{user}' on '{server}': ")
        if not password:
            print("Input is required")
            continue
        else:
            break
keyring.set_password(server, user, password)

if cloud.upper() == 'Y':
    config[server] = {"base_url": base_url, "user": user, "namespace": "LDAP", "ssl": True,
                      "verify": True, "async_requests_mode": True}
else:
    config[server] = {"address": address, "port": port, "ssl": ssl, "user": user}

def create_config(server):
create_config_file()
get_info(server)

def create_section(server):
get_info(server)

def get_cubes(server):
user = config[server]["user"]
get_password(server, user)
with TM1Service(**config[server]) as tm1:
all_cubes = tm1.cubes.get_all_names()
with open(server + '_cubes.csv', 'w') as file:
for cube in all_cubes:
print(cube, file=file)
file.close()

def get_dimensions(server):
user = config[server]["user"]
get_password(server, user)
with TM1Service(**config[server]) as tm1:
all_dimensions = tm1.dimensions.get_all_names()
with open(server + '_dimensions.csv', 'w') as file:
for dimension in all_dimensions:
print(dimension, file=file)
file.close()

def get_unused_dimensions(server):
user = config[server]["user"]
password = get_password(server, user)
with TM1Service(**config[server]) as tm1:
all_dimensions = tm1.dimensions.get_all_names()
all_cubes = tm1.cubes.get_all()
used_dimensions = set()

    for cube in all_cubes:
        used_dimensions.update(cube.dimensions)
    unused_dimensions = set(all_dimensions) - used_dimensions
    with open(server + '_unused_dimensions.csv', 'w') as file:
        for dimension in unused_dimensions:
            print(dimension, file=file)
    file.close()

def get_rule_stats(server):
user = config[server]["user"]
password = get_password(server, user)
with TM1Service(**config[server]) as tm1:
all_cubes = tm1.cubes.get_all()
with open(server + '_rule_statistics.csv', 'w') as file:

        # Cubes with SKIPCHECK
        cubes_with_skipcheck = [cube.name for cube in all_cubes if cube.skipcheck]
        print("Cubes with SKIPCHECK:", file=file)
        for cube in cubes_with_skipcheck:
            print("\t" + cube, file=file)

        # Cubes with UNDEFVALS
        cubes_with_undefvals = [cube.name for cube in all_cubes if cube.undefvals]
        print("Cubes with UNDEFVALS:", file=file)
        for cube in cubes_with_undefvals:
            print("\t" + cube, file=file)

        # Cubes ordered by number of rule statements
        all_cubes.sort(key=lambda cube: len(cube.rules.rule_statements) if cube.has_rules else 0, reverse=True)
        print("Cubes sorted by number of rule statements:", file=file)
        # print([cube.name for cube in all_cubes], file=file)
        for cube in all_cubes:
            print("\t" + cube.name, file=file)

        # Cubes sorted by number of feeder statements
        all_cubes.sort(key=lambda cube: len(cube.rules.feeder_statements) if cube.has_rules else 0, reverse=True)
        print("Cubes sorted by number of feeders statements:", file=file)
        # print([cube.name for cube in all_cubes], file=file)
        for cube in all_cubes:
            print("\t" + cube.name, file=file)
    file.close()

def get_subsets(server):
user = config[server]["user"]
password = get_password(server, user)
with TM1Service(**config[server]) as tm1:
all_dimensions = tm1.dimensions.get_all_names()
with open(server + '_dimension_subsets.csv', 'w') as file:
for dim in all_dimensions:
print(dim, file=file)
private_subsets = tm1.dimensions.subsets.get_all_names(
dimension_name=dim,
hierarchy_name=dim,
private=True)
print("\tPrivate Subests:", file=file)
for subset in private_subsets:
subset = tm1.dimensions.subsets.get(
dimension_name=dim,
hierarchy_name=dim,
subset_name=subset,
private=True)
print("\t\t"+subset.name, file=file)

            public_subsets = tm1.dimensions.subsets.get_all_names(
                dimension_name=dim,
                hierarchy_name=dim,
                private=False)
            print("\tPublic Subsets:", file=file)
            for subset in public_subsets:
                subset = tm1.dimensions.subsets.get(
                    dimension_name=dim,
                    hierarchy_name=dim,
                    subset_name=subset,
                    private=False
                )
                print("\t\t"+subset.name, file=file)
    file.close()

def main():
parser = argparse.ArgumentParser(description="TM1 Documentation Tool by GLCS.")
parser.add_argument("server", type=str, help="TM1 Server name")
parser.add_argument("-a", "--all", default=False, action="store_true", help="Output all files")
parser.add_argument("-c", "--cubes", default=False, action="store_true", help="Output list of all cubes")
parser.add_argument("-d", "--dimensions", default=False, action="store_true", help="Output list of all dimensions")
parser.add_argument("-r", "--rules", default=False, action="store_true", help="List cubes by number of rules")
parser.add_argument("-s", "--subsets", default=False, action="store_true", help="List all dimension subsets")
parser.add_argument("-u", "--unused", default=False, action="store_true", help="Output list of unused dimensions")

args = parser.parse_args()

svr = args.server

if not os.path.exists('config.ini'):
    create_config(svr)
    create_config_file()
else:
    config.read(r'config.ini')

if not config.has_section(svr):
    create_section(svr)
    create_config_file()
else:
    pass

if args.all:
    print("Outputting Cubes...")
    get_cubes(svr)
    print("Outputting Dimensions...")
    get_dimensions(svr)
    print("Outputting Dimension Subsets...")
    get_subsets(svr)
    print("Outputting Rule Statistics...")
    get_rule_stats(svr)
    print("Outputting Unused Dimensions...")
    get_unused_dimensions(svr)
    print("Output complete.")
elif args.cubes:
    print("Outputting Cubes...")
    get_cubes(svr)
    print("Output complete.")
elif args.dimensions:
    print("Outputting Dimensions...")
    get_dimensions(svr)
    print("Output complete.")
elif args.rules:
    print("Outputting Rule Statistics...")
    get_rule_stats(svr)
    print("Output complete.")
elif args.subsets:
    print("Outputting Dimension Subsets...")
    get_subsets(svr)
    print("Output complete.")
elif args.unused:
    print("Outputting Unused Dimensions...")
    get_unused_dimensions(svr)
    print("Output complete.")
else:
    pass

if __name__ == "__main__":
main()
```

Don't worry about the "best practices".. you will get there soon enough 馃憤

Was this page helpful?
0 / 5 - 0 ratings

Related issues

SachinAnalytics picture SachinAnalytics  路  3Comments

cubewise-gng picture cubewise-gng  路  3Comments

hermie64 picture hermie64  路  4Comments

Andy-Hofelmeyer picture Andy-Hofelmeyer  路  3Comments

beckyconning picture beckyconning  路  3Comments