Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ Run the following to see all available commands:
tm1cli --help
```

### Output formatting

By default, `exists` commands print a human-readable result, e.g. `✅ Cube exists!`.
Pass the global `--output-raw` flag before the command to get the raw `True`/`False`
value instead, e.g. for scripting:

```bash
tm1cli --output-raw cube exists <cube_name>
```

### Configuration

Connection settings are stored in a _databases.yaml_ file. Here's an example:
Expand Down
620 changes: 315 additions & 305 deletions poetry.lock

Large diffs are not rendered by default.

15 changes: 11 additions & 4 deletions tests/test_cmd_cubes.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,16 @@ def test_cube_list(mocker, command):
assert result.stdout == "Cube1\nCube2\n"


def test_cube_exists(mocker):
mocker.patch("tm1cli.commands.cube.TM1Service", MockedTM1Service)
result = runner.invoke(app, ["cube", "exists", "Cube1"])
@pytest.mark.parametrize(
"raw_option,expected_output",
[(None, "✅ Cube exists!\n"), ("--output-raw", "True\n")],
)
def test_cube_exists(mocker, raw_option, expected_output):
mocker.patch("tm1cli.utils.generic.TM1Service", MockedTM1Service)
if raw_option:
result = runner.invoke(app, [raw_option, "cube", "exists", "Cube1"])
else:
result = runner.invoke(app, ["cube", "exists", "Cube1"])
assert result.exit_code == 0
assert isinstance(result.stdout, str)
assert result.stdout == "True\n"
assert result.stdout == expected_output
15 changes: 11 additions & 4 deletions tests/test_cmd_dimension.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,16 @@ def test_dimension_list(mocker, command):
assert result.stdout == "Dimension1\nDimension2\nDimension3\n"


def test_dimension_exists(mocker):
mocker.patch("tm1cli.commands.dimension.TM1Service", MockedTM1Service)
result = runner.invoke(app, ["dimension", "exists", "Dimension1"])
@pytest.mark.parametrize(
"raw_option,expected_output",
[(None, "✅ Dimension exists!\n"), ("--output-raw", "True\n")],
)
def test_dimension_exists(mocker, raw_option, expected_output):
mocker.patch("tm1cli.utils.generic.TM1Service", MockedTM1Service)
if raw_option:
result = runner.invoke(app, [raw_option, "dimension", "exists", "Dimension1"])
else:
result = runner.invoke(app, ["dimension", "exists", "Dimension1"])
assert result.exit_code == 0
assert isinstance(result.stdout, str)
assert result.stdout == "True\n"
assert result.stdout == expected_output
17 changes: 13 additions & 4 deletions tests/test_cmd_subset.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,18 @@ def test_subset_list(mocker, command):
assert result.stdout == "Subset1\nSubset2\nSubset3\n"


def test_subset_exists(mocker):
mocker.patch("tm1cli.commands.subset.TM1Service", MockedTM1Service)
result = runner.invoke(app, ["subset", "exists", "Dimension1", "Subset1"])
@pytest.mark.parametrize(
"raw_option,expected_output",
[(None, "✅ Subset exists!\n"), ("--output-raw", "True\n")],
)
def test_subset_exists(mocker, raw_option, expected_output):
mocker.patch("tm1cli.utils.generic.TM1Service", MockedTM1Service)
if raw_option:
result = runner.invoke(
app, [raw_option, "subset", "exists", "Dimension1", "Subset1"]
)
else:
result = runner.invoke(app, ["subset", "exists", "Dimension1", "Subset1"])
assert result.exit_code == 0
assert isinstance(result.stdout, str)
assert result.stdout == "True\n"
assert result.stdout == expected_output
25 changes: 16 additions & 9 deletions tests/test_cmd_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,25 @@


@pytest.mark.parametrize(
"options",
[("not", "", "False"), ("example", "-p", "True"), ("not", "--private", "False")],
"view_name,private_flag,exists_result",
[("not", "", False), ("example", "-p", True), ("not", "--private", False)],
)
def test_view_exists(mocker, options):
mocker.patch("tm1cli.commands.view.TM1Service", MockedTM1Service)
if options[1]:
result = runner.invoke(app, ["view", "exists", "example_cube", options[0]])
else:
result = runner.invoke(app, ["view", "exists", "example_cube", options[:1]])
@pytest.mark.parametrize("raw_option", [None, "--output-raw"])
def test_view_exists(mocker, view_name, private_flag, exists_result, raw_option):
mocker.patch("tm1cli.utils.generic.TM1Service", MockedTM1Service)
args = [raw_option] if raw_option else []
args += ["view", "exists", "example_cube", view_name]
if private_flag:
args.append(private_flag)
result = runner.invoke(app, args)
assert result.exit_code == 0
assert isinstance(result.stdout, str)
assert result.stdout == f"{options[2]}\n"
if raw_option:
assert result.stdout == f"{exists_result}\n"
else:
icon = "✅" if exists_result else "❌"
word = "exists" if exists_result else "does not exist"
assert result.stdout == f"{icon} View {word}!\n"


def test_view_list(mocker):
Expand Down
9 changes: 7 additions & 2 deletions tm1cli/commands/cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from TM1py.Services import TM1Service

from tm1cli.utils.cli_param import DATABASE_OPTION, INTERVAL_OPTION, WATCH_OPTION
from tm1cli.utils.generic import execute_exists
from tm1cli.utils.various import resolve_database
from tm1cli.utils.watch import watch_option

Expand Down Expand Up @@ -46,5 +47,9 @@ def exists(
"""
Check if cube exists
"""
with TM1Service(**resolve_database(ctx, database)) as tm1:
print(tm1.cubes.exists(cube_name))
execute_exists(
"cubes",
ctx,
database,
cube_name=cube_name,
)
9 changes: 7 additions & 2 deletions tm1cli/commands/dimension.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from TM1py.Services import TM1Service

from tm1cli.utils.cli_param import DATABASE_OPTION, INTERVAL_OPTION, WATCH_OPTION
from tm1cli.utils.generic import execute_exists
from tm1cli.utils.various import resolve_database
from tm1cli.utils.watch import watch_option

Expand Down Expand Up @@ -46,5 +47,9 @@ def exists(
"""
Check if dimension exists
"""
with TM1Service(**resolve_database(ctx, database)) as tm1:
print(tm1.dimensions.exists(dimension_name))
execute_exists(
"dimensions",
ctx,
database,
dimension_name=dimension_name,
)
11 changes: 8 additions & 3 deletions tm1cli/commands/process.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import json
from pathlib import Path
from typing import Annotated

import typer
from rich import print # pylint: disable=redefined-builtin
from TM1py.Objects import Process
from TM1py.Services import TM1Service
from typing_extensions import Annotated

from tm1cli.utils.cli_param import DATABASE_OPTION, INTERVAL_OPTION, WATCH_OPTION
from tm1cli.utils.generic import execute_exists
from tm1cli.utils.tm1yaml import dump_process, load_process
from tm1cli.utils.various import print_error_and_exit, resolve_database
from tm1cli.utils.watch import watch_option
Expand Down Expand Up @@ -51,8 +52,12 @@ def exists(
Shows if process exists
"""

with TM1Service(**resolve_database(ctx, database)) as tm1:
print(tm1.processes.exists(name))
execute_exists(
"processes",
ctx,
database,
name=name,
)


@app.command()
Expand Down
11 changes: 9 additions & 2 deletions tm1cli/commands/subset.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from TM1py.Services import TM1Service

from tm1cli.utils.cli_param import DATABASE_OPTION, INTERVAL_OPTION, WATCH_OPTION
from tm1cli.utils.generic import execute_exists
from tm1cli.utils.various import resolve_database
from tm1cli.utils.watch import watch_option

Expand Down Expand Up @@ -45,5 +46,11 @@ def exists(
Check if subset exists
"""

with TM1Service(**resolve_database(ctx, database)) as tm1:
print(tm1.views.exists(dimension_name, subset_name, is_private))
execute_exists(
"subsets",
ctx,
database,
dimension_name=dimension_name,
subset_name=subset_name,
private=is_private,
)
11 changes: 9 additions & 2 deletions tm1cli/commands/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from TM1py.Services import TM1Service

from tm1cli.utils.cli_param import DATABASE_OPTION, INTERVAL_OPTION, WATCH_OPTION
from tm1cli.utils.generic import execute_exists
from tm1cli.utils.various import resolve_database
from tm1cli.utils.watch import watch_option

Expand Down Expand Up @@ -44,5 +45,11 @@ def exists(
Check if view exists
"""

with TM1Service(**resolve_database(ctx, database)) as tm1:
print(tm1.views.exists(cube_name, view_name, is_private))
execute_exists(
"views",
ctx,
database,
cube_name=cube_name,
view_name=view_name,
private=is_private,
)
19 changes: 13 additions & 6 deletions tm1cli/main.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import os
import importlib.metadata
import json
import os
from typing import Annotated

import importlib.metadata
import typer
import yaml
from rich import print
from rich import print # pylint: disable=redefined-builtin
from rich.console import Console
from rich.table import Table
from TM1py import TM1Service
from typing_extensions import Annotated

import tm1cli.commands as commands
from tm1cli.utils.cli_param import DATABASE_OPTION
from tm1cli.utils.various import resolve_database


console = Console()
app = typer.Typer()

Expand All @@ -30,7 +29,13 @@


@app.callback()
def main(ctx: typer.Context):
def main(
ctx: typer.Context,
raw: Annotated[
bool,
typer.Option("--output-raw", help="Raw output without formatting"),
] = False,
):
"""
CLI tool to interact with TM1 using TM1py.
"""
Expand All @@ -52,6 +57,8 @@ def main(ctx: typer.Context):
}
ctx.obj = {"configs": {}, "default_db_config": default_db_config}

ctx.obj["raw"] = raw


@app.command()
def version():
Expand Down
41 changes: 41 additions & 0 deletions tm1cli/utils/generic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import typer
from rich import print # pylint: disable=redefined-builtin
from TM1py import TM1Service

from tm1cli.utils.various import resolve_database

SINGULAR_NAMES = {
"cubes": "Cube",
"dimensions": "Dimension",
"processes": "Process",
"subsets": "Subset",
"views": "View",
}


def execute_exists(attribute_name, ctx, database, **args):
"""
Util function to execute an exists function
"""

database_config = resolve_database(ctx, database)

try:
with TM1Service(**database_config) as tm1:
attribute = getattr(tm1, attribute_name)
output = attribute.exists(**args)
except typer.Exit:
raise
except Exception as e: # pylint: disable=broad-except
print(f"[bold red]{type(e).__name__}:[/bold red] {e}")
raise typer.Exit(code=1) from e

if ctx.obj["raw"]:
print(output)
return

output_name = SINGULAR_NAMES[attribute_name]
if output:
print(f":white_check_mark: {output_name} exists!")
else:
print(f":x: {output_name} does not exist!")
Loading