Skip to content
Merged
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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## v0.1.5 - 2025-16-01

### Features

- New command: `cube exists`
- New command: `view exists`
- New command: `dimension list`
- New command: `dimension exists`
- New command: `subset list`
- New command: `subset exists`


### Chore

- Improved testing

## v0.1.4 - 2024-11-29

### Features
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,16 @@ tm1cli process dump <name> --folder <path> --format <json|yaml>
tm1cli process load <name> --folder <path> --format <json|yaml>

tm1cli cube list
tm1cli cube exists <cube_name>

tm1cli dimension list
tm1cli dimension exists <dimension_name>

tm1cli view list <cube_name>
tm1cli view exists <cube_name> <view_name>

tm1cli subset list <dimension_name>
tm1cli subset exists <dimension_name> <subset_name>
```

### All Available Commands
Expand Down
19 changes: 18 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "tm1cli"
version = "0.1.4"
version = "0.1.5"
description = "A command-line interface (CLI) tool for interacting with TM1 servers using TM1py."
authors = ["onefloid <onefloid@gmx.de>"]
license = "MIT License"
Expand All @@ -17,6 +17,7 @@ pyyaml = "^6.0.2"
isort = "^5.13.2"
pytest = "^8.3.3"
pylint = "^3.3.1"
pytest-mock = "^3.14.0"

[tool.poetry.scripts]
tm1cli = "tm1cli.main:app"
Expand Down
Empty file added tests/__init__.py
Empty file.
77 changes: 77 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from TM1py import Process


class MockedCubeService:
cubes = ["Cube1", "Cube2"]

def get_all_names(self, cube_name: str):
return self.cubes

def exists(self, cube_name: str):
return cube_name in self.cubes


class MockedViewService:

def get_all_names(self, cube_name: str):
return ["View1", "View2", "View3"]

def exists(self, cube_name: str, view_name: str, private: bool):
if "not" in view_name.lower():
return False
else:
return True


class MockedDimensionService:
def get_all_names(self, skip_control_dims: bool):
return ["Dimension1", "Dimension2", "Dimension3"]

def exists(self, dimension_name: str):
if "not" in dimension_name.lower():
return False
else:
return True


class MockedSubsetService:
def get_all_names(self, dimension_name: str):
return ["Subset1", "Subset2", "Subset3"]

def exists(self, dimension_name: str, subset_name: str, private: bool):
if "not" in subset_name.lower():
return False
else:
return True


class MockedProcessService:
def exists(self, process_name: str):
return False if "not" in process_name else True

def get(self, process_name: str):
return Process(process_name)

def update_or_create(self, process: Process):
return f"Process {process.name} was tested mocked."


class MockedTM1Service:
def __init__(self, **kwargs) -> None:
self.views = MockedViewService()
self.processes = MockedProcessService()
self.cubes = MockedCubeService()
self.dimensions = MockedDimensionService()
self.subsets = MockedSubsetService()

def __enter__(self):
"""
Context manager entry point.
"""
return self

def __exit__(self, exc_type, exc_value, traceback):
"""
Context manager exit point. Clean up resources if needed.
"""
pass
13 changes: 12 additions & 1 deletion tests/test_cmd_cubes.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import pytest
from typer.testing import CliRunner

from tests.conftest import MockedTM1Service
from tm1cli.main import app

runner = CliRunner()


@pytest.mark.parametrize("command", ["list", "ls"])
def test_cube_list(command):
def test_cube_list(mocker, command):
mocker.patch("tm1cli.commands.cube.TM1Service", MockedTM1Service)
result = runner.invoke(app, ["cube", command])
assert result.exit_code == 0
assert isinstance(result.stdout, str)
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"])
assert result.exit_code == 0
assert isinstance(result.stdout, str)
assert result.stdout == "True\n"
24 changes: 24 additions & 0 deletions tests/test_cmd_dimension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import pytest
from typer.testing import CliRunner

from tests.conftest import MockedTM1Service
from tm1cli.main import app

runner = CliRunner()


@pytest.mark.parametrize("command", ["list", "ls"])
def test_dimension_list(mocker, command):
mocker.patch("tm1cli.commands.dimension.TM1Service", MockedTM1Service)
result = runner.invoke(app, ["dimension", command])
assert result.exit_code == 0
assert isinstance(result.stdout, str)
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"])
assert result.exit_code == 0
assert isinstance(result.stdout, str)
assert result.stdout == "True\n"
24 changes: 24 additions & 0 deletions tests/test_cmd_subset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import pytest
from typer.testing import CliRunner

from tests.conftest import MockedTM1Service
from tm1cli.main import app

runner = CliRunner()


@pytest.mark.parametrize("command", ["list", "ls"])
def test_subset_list(mocker, command):
mocker.patch("tm1cli.commands.subset.TM1Service", MockedTM1Service)
result = runner.invoke(app, ["subset", command, "Dimension1"])
assert result.exit_code == 0
assert isinstance(result.stdout, str)
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"])
assert result.exit_code == 0
assert isinstance(result.stdout, str)
assert result.stdout == "True\n"
32 changes: 22 additions & 10 deletions tests/test_cmd_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,30 @@

from tm1cli.main import app

from .conftest import MockedTM1Service

runner = CliRunner()


@pytest.mark.parametrize("command", ["list", "ls"])
def test_cube_list(command):
result = runner.invoke(
app,
[
"view",
command,
"TM1py_tests_annotations_0f680909_74b1_11ef_b4ba_546ceb97bbfb",
],
)
@pytest.mark.parametrize(
"options",
[("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]])
assert result.exit_code == 0
assert isinstance(result.stdout, str)
assert result.stdout == f"{options[2]}\n"


def test_view_list(mocker):
mocker.patch("tm1cli.commands.view.TM1Service", MockedTM1Service)
result = runner.invoke(app, ["view", "list", "example_cube"])

assert result.exit_code == 0
assert isinstance(result.stdout, str)
assert result.stdout == "View1\nView2\nView3\n"
4 changes: 2 additions & 2 deletions tm1cli/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from . import cube, process, view
from . import cube, dimension, process, subset, view

__all__ = ["process", "cube", "view"]
__all__ = ["process", "cube", "view", "dimension", "subset"]
13 changes: 13 additions & 0 deletions tm1cli/commands/cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,16 @@ def list_cube(
with TM1Service(**resolve_database(ctx, database)) as tm1:
for cube in tm1.cubes.get_all_names(skip_control_cubes):
print(cube)


@app.command()
def exists(
ctx: typer.Context,
cube_name: str,
database: Annotated[str, DATABASE_OPTION] = None,
):
"""
Check if cube exists
"""
with TM1Service(**resolve_database(ctx, database)) as tm1:
print(tm1.cubes.exists(cube_name))
46 changes: 46 additions & 0 deletions tm1cli/commands/dimension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from typing import Annotated

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

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

app = typer.Typer()


@app.command(name="ls", help="Alias for list")
@app.command(name="list")
def list_dimension(
ctx: typer.Context,
database: Annotated[str, DATABASE_OPTION] = None,
skip_control_dims: Annotated[
bool,
typer.Option(
"-s",
"--skip-control-cubes",
help="Flag for not printing control cubes.",
),
] = False,
):
"""
List dimensions
"""

with TM1Service(**resolve_database(ctx, database)) as tm1:
for dim in tm1.dimensions.get_all_names(skip_control_dims):
print(dim)


@app.command()
def exists(
ctx: typer.Context,
dimension_name: str,
database: Annotated[str, DATABASE_OPTION] = None,
):
"""
Check if dimension exists
"""
with TM1Service(**resolve_database(ctx, database)) as tm1:
print(tm1.dimensions.exists(dimension_name))
45 changes: 45 additions & 0 deletions tm1cli/commands/subset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from typing import Annotated

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

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

app = typer.Typer()


@app.command(name="ls", help="Alias for list")
@app.command(name="list")
def list_subset(
ctx: typer.Context,
dimension_name: str,
# hierarchy_name: str = None,
database: Annotated[str, DATABASE_OPTION] = None,
):
"""
List subsets
"""

with TM1Service(**resolve_database(ctx, database)) as tm1:
for subset in tm1.subsets.get_all_names(dimension_name):
print(subset)


@app.command()
def exists(
ctx: typer.Context,
dimension_name: str,
subset_name: str,
is_private: Annotated[
bool, typer.Option("-p", "--private", help="Flag to specify if view is private")
] = False,
database: Annotated[str, DATABASE_OPTION] = None,
):
"""
Check if subset exists
"""

with TM1Service(**resolve_database(ctx, database)) as tm1:
print(tm1.views.exists(dimension_name, subset_name, is_private))
Loading
Loading