From d88b8e7fe44f3695ef9371e6f54a96bc40eeee6b Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Sat, 19 Apr 2025 16:26:29 -0400 Subject: [PATCH 1/9] concept of tmdl implemented --- .gitignore | 1 + pyproject.toml | 3 ++- pytabular/__init__.py | 1 + pytabular/tdml.py | 20 ++++++++++++++++++++ 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 pytabular/tdml.py diff --git a/.gitignore b/.gitignore index b8078c0..cdcc69b 100644 --- a/.gitignore +++ b/.gitignore @@ -178,6 +178,7 @@ docs/_config.yml# Byte-compiled / optimized / DLL files *$py.class /Best_Practice_Analyzer /Tabular_Editor_2 +*.tmdl # Test files for new functionality test-notebook.ipynb diff --git a/pyproject.toml b/pyproject.toml index f37dba5..ac031ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "python_tabular" -version = "0.5.5" +version = "0.5.6" authors = [ { name="Curtis Stallings", email="curtisrstallings@gmail.com" }, ] @@ -25,6 +25,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Development Status :: 5 - Production/Stable", "Operating System :: Microsoft", "License :: OSI Approved :: MIT License" diff --git a/pytabular/__init__.py b/pytabular/__init__.py index 64bfa84..08a65b1 100644 --- a/pytabular/__init__.py +++ b/pytabular/__init__.py @@ -65,6 +65,7 @@ from .query import Connection from .pbi_helper import find_local_pbi_instances from .document import ModelDocumenter +from .tdml import Tmdl logger.info("Import successful...") diff --git a/pytabular/tdml.py b/pytabular/tdml.py new file mode 100644 index 0000000..a2c748f --- /dev/null +++ b/pytabular/tdml.py @@ -0,0 +1,20 @@ +from typing import Union + +from Microsoft.AnalysisServices.Tabular import TmdlSerializer + + +class Tmdl: + def __init__(self, model): + self.model = model + + def save_to_folder(self, path: str = "tmdl") -> bool: + TmdlSerializer.SerializeModelToFolder(self.model._object, path) + return True + + def execute(self, path: str = "tmdl", auto_save: bool = True): + model_object = TmdlSerializer.DeserializeModelFromFolder(path) + model_object.CopyTo(self.model._object) + if auto_save: + return self.model.save_changes() + else: + return True From b68b19a87051e31d7a8810e054c17b9e4b2920ed Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Sat, 19 Apr 2025 16:29:00 -0400 Subject: [PATCH 2/9] woops misspelled --- pytabular/__init__.py | 2 +- pytabular/{tdml.py => tmdl.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename pytabular/{tdml.py => tmdl.py} (100%) diff --git a/pytabular/__init__.py b/pytabular/__init__.py index 08a65b1..f49bcb9 100644 --- a/pytabular/__init__.py +++ b/pytabular/__init__.py @@ -65,7 +65,7 @@ from .query import Connection from .pbi_helper import find_local_pbi_instances from .document import ModelDocumenter -from .tdml import Tmdl +from .tmdl import Tmdl logger.info("Import successful...") diff --git a/pytabular/tdml.py b/pytabular/tmdl.py similarity index 100% rename from pytabular/tdml.py rename to pytabular/tmdl.py From 2dda3b178aa33f827f25fb4770182ac2ed475ccc Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Sat, 19 Apr 2025 22:18:32 -0400 Subject: [PATCH 3/9] tmdl poc --- .flake8 | 2 +- .github/workflows/docstr-coverage.yml | 2 +- MANIFEST.in | 1 - docs/tmdl.md | 1 + mkdocs.yml | 1 + pyproject.toml | 1 - pytabular/tmdl.py | 29 +++++++++++++++++++-- tox.ini | 36 +++++++++++++++++++++++++++ 8 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 docs/tmdl.md create mode 100644 tox.ini diff --git a/.flake8 b/.flake8 index 32ee067..70ecf2a 100644 --- a/.flake8 +++ b/.flake8 @@ -1,4 +1,4 @@ [flake8] -extend-ignore=E203 +extend-ignore=E203, D107 max-line-length=100 docstring-convention=google \ No newline at end of file diff --git a/.github/workflows/docstr-coverage.yml b/.github/workflows/docstr-coverage.yml index 2a970da..cf7c595 100644 --- a/.github/workflows/docstr-coverage.yml +++ b/.github/workflows/docstr-coverage.yml @@ -30,4 +30,4 @@ jobs: python-version: '3.10' - run: pip install --upgrade pip - run: pip install docstr-coverage==2.2.0 - - run: docstr-coverage \ No newline at end of file + - run: docstr-coverage --skip-init \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index ca9d6c4..f93f862 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,3 @@ -exclude pytabular/localsecret.py include pytabular/dll/Microsoft.AnalysisServices.AdomdClient.dll include pytabular/dll/Microsoft.AnalysisServices.Core.dll include pytabular/dll/Microsoft.AnalysisServices.dll diff --git a/docs/tmdl.md b/docs/tmdl.md new file mode 100644 index 0000000..187e8e6 --- /dev/null +++ b/docs/tmdl.md @@ -0,0 +1 @@ +:::pytabular.tmdl.Tmdl \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 662aa47..c091be9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -25,6 +25,7 @@ nav: - best_practice_analyzer: best_practice_analyzer.md - pbi_helper: pbi_helper.md - logic_utils: logic_utils.md + - tmdl: tmdl.md - Running Traces: tabular_tracing.md - Documenting Model: document.md - Contributing: CONTRIBUTING.md diff --git a/pyproject.toml b/pyproject.toml index ac031ea..c9362b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,6 @@ description = "Connect to your tabular model and perform operations programmatic readme = "README.md" requires-python = ">=3.8" classifiers = [ - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", diff --git a/pytabular/tmdl.py b/pytabular/tmdl.py index a2c748f..305b424 100644 --- a/pytabular/tmdl.py +++ b/pytabular/tmdl.py @@ -1,17 +1,42 @@ -from typing import Union +"""See [TMDL Scripting].(https://learn.microsoft.com/en-us/analysis-services/tmdl/tmdl-overview). + +Run `Tmdl(model).save_to_folder()` to save tmdl of model. +Run `Tmdl(model).execute()` to execute a specific set of tmdl scripts. +""" from Microsoft.AnalysisServices.Tabular import TmdlSerializer class Tmdl: + """Specify the specific model you want to use for scripting. + + Args: + model (Tabular): Initialize with Tabular model. + """ def __init__(self, model): self.model = model - def save_to_folder(self, path: str = "tmdl") -> bool: + def save_to_folder(self, path: str = "tmdl"): + """Runs `SerializeModelToFolder` from .net library. + + Args: + path (str, optional): directory where to save tmdl structure. + Defaults to "tmdl". + """ TmdlSerializer.SerializeModelToFolder(self.model._object, path) return True def execute(self, path: str = "tmdl", auto_save: bool = True): + """Runs `DeserializeModelFromFolder` from .net library. + + Args: + path (str, optional): directory to look for tmdl scripts. + Defaults to "tmdl". + auto_save (bool, optional): You can set to false + if you want to precheck a few things, but will need to + run `model.save_changes()`. Setting to `True` will go ahead + and execute `model.save_changes()` Defaults to True. + """ model_object = TmdlSerializer.DeserializeModelFromFolder(path) model_object.CopyTo(self.model._object) if auto_save: diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..4d45080 --- /dev/null +++ b/tox.ini @@ -0,0 +1,36 @@ +[tox] +env_list = docstring, linter, mkdocs, 3.9, 3.10, 3.11, 3.12, 3.13 + +[testenv] +description = "run unit tests" +skip_install=True +deps = + pytest +commands = + pytest + +[testenv:docstring] +description = check docstring coverage +basepython = python3.10 +skip_install=True +deps = + docstr-coverage==2.2.0 +commands = docstr-coverage --skip-init + +[testenv:linter] +description = run linters +skip_install = True +deps = flake8 + pep8-naming + flake8-docstrings +commands = flake8 --count + +[testenv:mkdocs] +description = check doc creation +skip_install = True +deps = + mkdocstrings[python] + mkdocs-material + mkdocs + mkdocs-gen-files +commands = mkdocs build \ No newline at end of file From 18bc0c399b9a45339e630dc36ae200409a4039d1 Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Sun, 20 Apr 2025 19:19:47 -0400 Subject: [PATCH 4/9] reworking test framework w/ tox --- .pre-commit-config.yaml | 21 --------------------- test/run_versions.bat | 7 ------- tox.ini | 18 ++++++++++++++---- 3 files changed, 14 insertions(+), 32 deletions(-) delete mode 100644 .pre-commit-config.yaml delete mode 100644 test/run_versions.bat diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 4d06154..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,21 +0,0 @@ -default_stages: [push] -repos: -- repo: https://github.com/pycqa/flake8 - rev: 6.0.0 - hooks: - - id: flake8 - verbose: true -- repo: https://github.com/HunterMcGushion/docstr_coverage - rev: v2.2.0 - hooks: - - id: docstr-coverage - verbose: true -- repo: local - hooks: - - id: pytest - name: pytest - entry: pytest - language: system - pass_filenames: false - types: [python] - verbose: true \ No newline at end of file diff --git a/test/run_versions.bat b/test/run_versions.bat deleted file mode 100644 index dda824e..0000000 --- a/test/run_versions.bat +++ /dev/null @@ -1,7 +0,0 @@ -@echo on -pyenv shell 3.8.9 & python3 -m pytest & -pyenv shell 3.9.13 & python3 -m pytest & -pyenv shell 3.10.6 & python3 -m pytest & -pyenv shell 3.11.9 & python3 -m pytest & -pyenv shell 3.12.6 & python3 -m pytest & -pause & pause \ No newline at end of file diff --git a/tox.ini b/tox.ini index 4d45080..f9cfb5b 100644 --- a/tox.ini +++ b/tox.ini @@ -3,18 +3,28 @@ env_list = docstring, linter, mkdocs, 3.9, 3.10, 3.11, 3.12, 3.13 [testenv] description = "run unit tests" -skip_install=True +skip_install = True +; Currently managing these dependencies in two spots... +; pyproject.toml & tox.ini +; need to figure that out +setenv = + VIRTUALENV_NO_SETUPTOOLS=1 deps = + pythonnet>=3.0.3 + clr-loader>=0.2.6 + xmltodict==0.13.0 + pandas>=1.4.3 + requests>=2.28.1 + rich>=12.5.1 pytest commands = pytest [testenv:docstring] description = check docstring coverage -basepython = python3.10 skip_install=True deps = - docstr-coverage==2.2.0 + docstr-coverage==2.3.2 commands = docstr-coverage --skip-init [testenv:linter] @@ -23,7 +33,7 @@ skip_install = True deps = flake8 pep8-naming flake8-docstrings -commands = flake8 --count +commands = flake8 pytabular test --count [testenv:mkdocs] description = check doc creation From 631fa24390c0b4c2bdf0fd9fc4b407025929baa0 Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Mon, 21 Apr 2025 08:03:36 -0400 Subject: [PATCH 5/9] basic tmdl tests --- test/test_12tmdl.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 test/test_12tmdl.py diff --git a/test/test_12tmdl.py b/test/test_12tmdl.py new file mode 100644 index 0000000..47a072d --- /dev/null +++ b/test/test_12tmdl.py @@ -0,0 +1,19 @@ +import pytabular as p +from test.conftest import TestStorage +from test.config import testing_parameters +import pytest + +path = "tmdl_testing" + +@pytest.mark.parametrize("model", testing_parameters) +def test_tmdl_save(model): + """Tests basic tmdl save to folder functionality.""" + stf = p.Tmdl(model).save_to_folder(path) + assert stf + +@pytest.mark.parametrize("model", testing_parameters) +def test_tmdl_execute(model): + """Tests basic tmdl execute functionality.""" + exec = p.Tmdl(model).execute(path, False) + p.logic_utils.remove_folder_and_contents(path) + assert exec \ No newline at end of file From 25c7a12be737a0b3a688abb987220d77bd6c2bbe Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Mon, 21 Apr 2025 08:37:48 -0400 Subject: [PATCH 6/9] clean up paramtrize into conftest --- test/conftest.py | 3 +++ test/test_10logic_utils.py | 1 - test/test_11document.py | 1 - test/test_12tmdl.py | 9 +++++---- test/test_1sanity.py | 5 +---- test/test_2object.py | 13 ------------- test/test_3tabular.py | 21 --------------------- test/test_4measure.py | 1 - test/test_5column.py | 6 ------ test/test_6table.py | 9 --------- test/test_7tabular_tracing.py | 5 ----- test/test_8bpa.py | 3 --- test/test_9custom.py | 2 -- 13 files changed, 9 insertions(+), 70 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 67f83d0..5ddb039 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -37,6 +37,9 @@ def remove_testing_table(model): model.Model.Tables.Remove(table) model.SaveChanges() +def pytest_generate_tests(metafunc): + if "model" in metafunc.fixturenames: + metafunc.parametrize("model", [local_pbix], ids=[local_pbix.Name]) def pytest_sessionstart(session): """Run at pytest start. diff --git a/test/test_10logic_utils.py b/test/test_10logic_utils.py index 3220601..3a8583b 100644 --- a/test/test_10logic_utils.py +++ b/test/test_10logic_utils.py @@ -54,7 +54,6 @@ def test_dataframe_to_dict(df): assert isinstance(logic_utils.dataframe_to_dict(df), list) -@pytest.mark.parametrize("model", testing_parameters) def test_dict_to_markdown_table(model): """Tests `dict_to_markdown_table()` function.""" dependencies = [measure.get_dependencies() for measure in model.Measures] diff --git a/test/test_11document.py b/test/test_11document.py index 8ee45f4..40a7024 100644 --- a/test/test_11document.py +++ b/test/test_11document.py @@ -8,7 +8,6 @@ from test.conftest import TestStorage -@pytest.mark.parametrize("model", testing_parameters) def test_basic_document_funcionality(model): """Tests basic documentation functionality.""" try: diff --git a/test/test_12tmdl.py b/test/test_12tmdl.py index 47a072d..fbbeea0 100644 --- a/test/test_12tmdl.py +++ b/test/test_12tmdl.py @@ -1,19 +1,20 @@ +"""Tests to cover the tmdl.py file.""" + import pytabular as p -from test.conftest import TestStorage from test.config import testing_parameters import pytest path = "tmdl_testing" -@pytest.mark.parametrize("model", testing_parameters) + def test_tmdl_save(model): """Tests basic tmdl save to folder functionality.""" stf = p.Tmdl(model).save_to_folder(path) assert stf -@pytest.mark.parametrize("model", testing_parameters) + def test_tmdl_execute(model): """Tests basic tmdl execute functionality.""" exec = p.Tmdl(model).execute(path, False) p.logic_utils.remove_folder_and_contents(path) - assert exec \ No newline at end of file + assert exec diff --git a/test/test_1sanity.py b/test/test_1sanity.py index 7fb8250..ed9b042 100644 --- a/test/test_1sanity.py +++ b/test/test_1sanity.py @@ -9,19 +9,16 @@ from test.config import testing_parameters -@pytest.mark.parametrize("model", testing_parameters) -def test_sanity_check(model): +def test_sanity_check(): """Just in case... I might be crazy.""" assert 1 == 1 -@pytest.mark.parametrize("model", testing_parameters) def test_connection(model): """Does a quick check on connection to `Tabular()` class.""" assert model.Server.Connected -@pytest.mark.parametrize("model", testing_parameters) def test_database(model): """Tests that `model.Database` is actually a Database.""" assert isinstance(model.Database, Database) diff --git a/test/test_2object.py b/test/test_2object.py index ae2fe27..3a1ffbb 100644 --- a/test/test_2object.py +++ b/test/test_2object.py @@ -5,7 +5,6 @@ from pytabular import Tabular -@pytest.mark.parametrize("model", testing_parameters) def test_rich_repr_model(model): """Tests successful `__rich_repr()` `on Tabular()` class.""" try: @@ -14,7 +13,6 @@ def test_rich_repr_model(model): pytest.fail("__rich_repr__() failed") -@pytest.mark.parametrize("model", testing_parameters) def test_rich_repr_table(model): """Tests successful `__rich_repr()` `on PyTable()` class.""" try: @@ -23,7 +21,6 @@ def test_rich_repr_table(model): pytest.fail("__rich_repr__() failed") -@pytest.mark.parametrize("model", testing_parameters) def test_rich_repr_tables(model): """Tests successful `__rich_repr()` `on PyTables()` class.""" try: @@ -32,7 +29,6 @@ def test_rich_repr_tables(model): pytest.fail("__rich_repr__() failed") -@pytest.mark.parametrize("model", testing_parameters) def test_rich_repr_column(model): """Tests successful `__rich_repr()` `on PyColumn()` class.""" try: @@ -41,7 +37,6 @@ def test_rich_repr_column(model): pytest.fail("__rich_repr__() failed") -@pytest.mark.parametrize("model", testing_parameters) def test_rich_repr_columns(model): """Tests successful `__rich_repr()` `on PyColumns()` class.""" try: @@ -50,7 +45,6 @@ def test_rich_repr_columns(model): pytest.fail("__rich_repr__() failed") -@pytest.mark.parametrize("model", testing_parameters) def test_rich_repr_partition(model): """Tests successful `__rich_repr()` `on PyPartition()` class.""" try: @@ -59,7 +53,6 @@ def test_rich_repr_partition(model): pytest.fail("__rich_repr__() failed") -@pytest.mark.parametrize("model", testing_parameters) def test_rich_repr_partitions(model): """Tests successful `__rich_repr()` `on PyPartitions()` class.""" try: @@ -68,7 +61,6 @@ def test_rich_repr_partitions(model): pytest.fail("__rich_repr__() failed") -@pytest.mark.parametrize("model", testing_parameters) def test_rich_repr_measure(model): """Tests successful `__rich_repr()` `on PyMeasure()` class.""" try: @@ -77,7 +69,6 @@ def test_rich_repr_measure(model): pytest.fail("__rich_repr__() failed") -@pytest.mark.parametrize("model", testing_parameters) def test_rich_repr_measures(model): """Tests successful `__rich_repr()` `on PyMeasures()` class.""" try: @@ -86,13 +77,11 @@ def test_rich_repr_measures(model): pytest.fail("__rich_repr__() failed") -@pytest.mark.parametrize("model", testing_parameters) def test_get_attr(model): """Tests custom get attribute from `PyObject` class.""" assert isinstance(model.Tables[0].Model, Tabular) -@pytest.mark.parametrize("model", testing_parameters) def test_iadd_tables(model): """Tests `__iadd__()` with `PyTables()`.""" a = model.Tables.find("Sales") @@ -101,7 +90,6 @@ def test_iadd_tables(model): assert len(a.find("Date")) > 0 -@pytest.mark.parametrize("model", testing_parameters) def test_iadd_table(model): """Tests `__iadd__()` with a `PyTable()`.""" a = model.Tables.find("Sales") @@ -110,7 +98,6 @@ def test_iadd_table(model): assert len(a.find("Date")) > 0 -@pytest.mark.parametrize("model", testing_parameters) def test_find_measure(model): """Tests `find()` with a `PyMeasure()`.""" a = model.Measures[0].Name diff --git a/test/test_3tabular.py b/test/test_3tabular.py index 0fc08e2..dd1221a 100644 --- a/test/test_3tabular.py +++ b/test/test_3tabular.py @@ -6,7 +6,6 @@ from test.config import testingtablename, testing_parameters, get_test_path -@pytest.mark.parametrize("model", testing_parameters) def test_basic_query(model): """Tests a basic query execution.""" int_result = model.query("EVALUATE {1}") @@ -21,7 +20,6 @@ def test_basic_query(model): ] -@pytest.mark.parametrize("model", testing_parameters) def test_datatype_query(model): """Tests the results of different datatypes.""" for query in datatype_queries: @@ -29,7 +27,6 @@ def test_datatype_query(model): assert result == query[0] -@pytest.mark.parametrize("model", testing_parameters) def test_file_query(model): """Test `query()` via a file.""" singlevaltest = get_test_path() + "\\singlevaltest.dax" @@ -38,37 +35,31 @@ def test_file_query(model): assert model.query(singlevaltest) == 1 and model.query(dfvaltest).equals(dfdupe) -@pytest.mark.parametrize("model", testing_parameters) def test_repr_str(model): """Testing successful `__repr__()` on model.""" assert isinstance(model.__repr__(), str) -@pytest.mark.parametrize("model", testing_parameters) def test_pytables_count(model): """Testing row count of testingtable.""" assert model.Tables[testingtablename].row_count() > 0 -@pytest.mark.parametrize("model", testing_parameters) def test_pytables_refresh(model): """Tests refrshing table of testingtable.""" assert len(model.Tables[testingtablename].refresh()) > 0 -@pytest.mark.parametrize("model", testing_parameters) def test_pypartition_refresh(model): """Tests refreshing partition of testingtable.""" assert len(model.Tables[testingtablename].Partitions[0].refresh()) > 0 -@pytest.mark.parametrize("model", testing_parameters) def test_pypartitions_refresh(model): """Tests refreshing partitions of testingtable.""" assert len(model.Tables[testingtablename].Partitions.refresh()) > 0 -@pytest.mark.parametrize("model", testing_parameters) def test_pyobjects_adding(model): """Tests adding a PyObject.""" table = model.Tables.find(testingtablename) @@ -76,7 +67,6 @@ def test_pyobjects_adding(model): assert len(table) == 2 -@pytest.mark.parametrize("model", testing_parameters) def test_nonetype_decimal_bug(model): """Tests the pesky nonetype decimal bug.""" query_str = """ @@ -90,40 +80,34 @@ def test_nonetype_decimal_bug(model): assert len(model.query(query_str)) == 3 -@pytest.mark.parametrize("model", testing_parameters) def test_table_last_refresh_times(model): """Really just testing the the function completes successfully and returns df.""" assert isinstance(model.Tables.last_refresh(), pd.DataFrame) -@pytest.mark.parametrize("model", testing_parameters) def test_return_zero_row_tables(model): """Testing that `find_zero_rows()`.""" assert isinstance(model.Tables.find_zero_rows(), p.pytabular.PyTables) -@pytest.mark.parametrize("model", testing_parameters) def test_get_dependencies(model): """Tests execution of `PyMeasure.get_dependencies()`.""" dependencies = model.Measures[0].get_dependencies() assert len(dependencies) > 0 -@pytest.mark.parametrize("model", testing_parameters) def test_disconnect(model): """Tests `Disconnect()` from `Tabular` class.""" model.disconnect() assert model.Server.Connected is False -@pytest.mark.parametrize("model", testing_parameters) def test_reconnect(model): """Tests `Reconnect()` from `Tabular` class.""" model.reconnect() assert model.Server.Connected is True -@pytest.mark.parametrize("model", testing_parameters) def test_reconnect_savechanges(model): """This will test the `reconnect()` gets called in `save_changes()`.""" model.disconnect() @@ -131,20 +115,17 @@ def test_reconnect_savechanges(model): assert model.Server.Connected is True -@pytest.mark.parametrize("model", testing_parameters) def test_is_process(model): """Checks that `Is_Process()` from `Tabular` class returns bool.""" assert isinstance(model.is_process(), bool) -@pytest.mark.parametrize("model", testing_parameters) def test_bad_table(model): """Checks for unable to find table exception.""" with pytest.raises(Exception): model.refresh("badtablename") -@pytest.mark.parametrize("model", testing_parameters) def test_refresh_dict(model): """Checks for refreshing dictionary.""" table = model.Tables[testingtablename] @@ -152,7 +133,6 @@ def test_refresh_dict(model): assert isinstance(refresh, pd.DataFrame) -@pytest.mark.parametrize("model", testing_parameters) def test_refresh_dict_pypartition(model): """Checks for refreshing dictionary with PyPartition.""" table = model.Tables[testingtablename] @@ -160,7 +140,6 @@ def test_refresh_dict_pypartition(model): assert isinstance(refresh, pd.DataFrame) -@pytest.mark.parametrize("model", testing_parameters) def test_bad_partition(model): """Checks for refreshing dictionary failure.""" table = model.Tables[testingtablename] diff --git a/test/test_4measure.py b/test/test_4measure.py index 5c18d38..0b2cf53 100644 --- a/test/test_4measure.py +++ b/test/test_4measure.py @@ -4,7 +4,6 @@ from test.config import testing_parameters -@pytest.mark.parametrize("model", testing_parameters) def test_create_measure(model): """Test Creating Measure.""" name = "Test Measure" diff --git a/test/test_5column.py b/test/test_5column.py index 6f1567f..025f0b4 100644 --- a/test/test_5column.py +++ b/test/test_5column.py @@ -6,41 +6,35 @@ from numpy import int64 -@pytest.mark.parametrize("model", testing_parameters) def test_values(model): """Tests for `Values()` of PyColumn class.""" vals = model.Tables[testingtablename].Columns[1].values() assert isinstance(vals, pd.DataFrame) or isinstance(vals, int) -@pytest.mark.parametrize("model", testing_parameters) def test_distinct_count_no_blank(model): """Tests No_Blank=True for `Distinct_Count()` of PyColumn class.""" vals = model.Tables[testingtablename].Columns[1].distinct_count(no_blank=True) assert isinstance(vals, int64) -@pytest.mark.parametrize("model", testing_parameters) def test_distinct_count_blank(model): """Tests No_Blank=False for `Distinct_Count()` of PyColumn class.""" vals = model.Tables[testingtablename].Columns[1].distinct_count(no_blank=False) assert isinstance(vals, int64) -@pytest.mark.parametrize("model", testing_parameters) def test_get_sample_values(model): """Tests for `get_sample_values()` of PyColumn class.""" sample_vals = model.Tables[testingtablename].Columns[1].get_sample_values() assert isinstance(sample_vals, pd.DataFrame) or isinstance(sample_vals, int) -@pytest.mark.parametrize("model", testing_parameters) def test_query_every_column(model): """Tests `Query_All()` of PyColumns class.""" assert isinstance(model.Tables[testingtablename].Columns.query_all(), pd.DataFrame) -@pytest.mark.parametrize("model", testing_parameters) def test_dependencies(model): """Tests execution of `PyColumn().get_dependencies()`.""" df = model.Tables[0].Columns[1].get_dependencies() diff --git a/test/test_6table.py b/test/test_6table.py index aa1c12e..de2ef4f 100644 --- a/test/test_6table.py +++ b/test/test_6table.py @@ -6,55 +6,46 @@ from datetime import datetime -@pytest.mark.parametrize("model", testing_parameters) def test_row_count(model): """Tests for `Row_Count()` of PyTable class.""" assert model.Tables[testingtablename].row_count() > 0 -@pytest.mark.parametrize("model", testing_parameters) def test_refresh(model): """Tests for `Refresh()` of PyTable class.""" assert isinstance(model.Tables[testingtablename].refresh(), pd.DataFrame) -@pytest.mark.parametrize("model", testing_parameters) def test_last_refresh(model): """Tests for `Last_Refresh()` of PyTable class.""" assert isinstance(model.Tables[testingtablename].last_refresh(), datetime) -@pytest.mark.parametrize("model", testing_parameters) def test_related(model): """Tests for `Related()` of PyTable class.""" assert isinstance(model.Tables[testingtablename].related(), type(model.Tables)) -@pytest.mark.parametrize("model", testing_parameters) def test_refresh_pytables(model): """Tests for `Refresh()` of PyTables class.""" assert isinstance(model.Tables.find(testingtablename).refresh(), pd.DataFrame) -@pytest.mark.parametrize("model", testing_parameters) def test_query_all_pytables(model): """Tests for `Query_All()` of PyTables class.""" assert isinstance(model.Tables.query_all(), pd.DataFrame) -@pytest.mark.parametrize("model", testing_parameters) def test_find_zero_rows_pytables(model): """Tests for `Find_Zero_Rows()` of PyTables class.""" assert isinstance(model.Tables.find_zero_rows(), type(model.Tables)) -@pytest.mark.parametrize("model", testing_parameters) def test_last_refresh_pytables_true(model): """Tests group_partition=True for `Last_Refresh()` of PyTables class.""" assert isinstance(model.Tables.last_refresh(group_partition=True), pd.DataFrame) -@pytest.mark.parametrize("model", testing_parameters) def test_last_refresh_pytables_false(model): """Tests group_partition=False for `Last_Refresh()` of PyTables class.""" assert isinstance(model.Tables.last_refresh(group_partition=False), pd.DataFrame) diff --git a/test/test_7tabular_tracing.py b/test/test_7tabular_tracing.py index 816cd89..d23f2b9 100644 --- a/test/test_7tabular_tracing.py +++ b/test/test_7tabular_tracing.py @@ -6,14 +6,12 @@ from test.conftest import TestStorage -@pytest.mark.parametrize("model", testing_parameters) def test_disconnect_for_trace(model): """Tests `disconnect()` from `Tabular` class.""" model.disconnect() assert model.Server.Connected is False -@pytest.mark.parametrize("model", testing_parameters) def test_reconnect_update(model): """This will test the `reconnect()`. @@ -24,7 +22,6 @@ def test_reconnect_update(model): assert model.Server.Connected is True -@pytest.mark.parametrize("model", testing_parameters) def test_query_monitor_start(model): """This will test the `QueryMonitor` trace and `start()` it.""" query_trace = p.QueryMonitor(model) @@ -33,14 +30,12 @@ def test_query_monitor_start(model): assert TestStorage.query_trace.Trace.IsStarted -@pytest.mark.parametrize("model", testing_parameters) def test_query_monitor_stop(model): """Tests `stop()` of `QueryMonitor` trace.""" TestStorage.query_trace.stop() assert TestStorage.query_trace.Trace.IsStarted is False -@pytest.mark.parametrize("model", testing_parameters) def test_query_monitor_drop(model): """Tests `drop()` of `QueryMonitor` trace.""" assert TestStorage.query_trace.drop() is None diff --git a/test/test_8bpa.py b/test/test_8bpa.py index b59f2f8..0cf7f9a 100644 --- a/test/test_8bpa.py +++ b/test/test_8bpa.py @@ -6,7 +6,6 @@ from os import getcwd -@pytest.mark.parametrize("model", testing_parameters) def test_bpa(model): """Testing execution of `model.analyze_bpa()`.""" te2 = p.TabularEditor(verify_download=False).exe @@ -14,13 +13,11 @@ def test_bpa(model): assert isinstance(model.analyze_bpa(te2, bpa), list) -@pytest.mark.parametrize("model", testing_parameters) def test_te2_custom_file_path(model): """Testing TE2 Class.""" assert isinstance(p.TabularEditor(getcwd()), p.TabularEditor) -@pytest.mark.parametrize("model", testing_parameters) def test_bpa_custom_file_path(model): """Testing BPA Class.""" assert isinstance(p.BPA(getcwd()), p.BPA) diff --git a/test/test_9custom.py b/test/test_9custom.py index 25f005c..87a32db 100644 --- a/test/test_9custom.py +++ b/test/test_9custom.py @@ -8,7 +8,6 @@ import pytest -@pytest.mark.parametrize("model", testing_parameters) def test_backingup_table(model): """Tests model.backup_table().""" model.backup_table(testingtablename) @@ -24,7 +23,6 @@ def test_backingup_table(model): ) -@pytest.mark.parametrize("model", testing_parameters) def test_revert_table2(model): """Tests model.revert_table().""" model.revert_table(testingtablename) From 506e00c6b40760369b31f4484107a458eaa226f6 Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Mon, 21 Apr 2025 08:45:08 -0400 Subject: [PATCH 7/9] pytest_generate_test cleanup --- test/conftest.py | 7 +++++++ test/test_10logic_utils.py | 1 - test/test_11document.py | 1 - test/test_12tmdl.py | 2 -- test/test_1sanity.py | 2 -- test/test_2object.py | 1 - test/test_3tabular.py | 2 +- test/test_4measure.py | 3 --- test/test_5column.py | 3 +-- test/test_6table.py | 3 +-- test/test_7tabular_tracing.py | 3 +-- test/test_8bpa.py | 2 -- test/test_9custom.py | 3 +-- 13 files changed, 12 insertions(+), 21 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 5ddb039..5c5b0c3 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -37,10 +37,17 @@ def remove_testing_table(model): model.Model.Tables.Remove(table) model.SaveChanges() + def pytest_generate_tests(metafunc): + """Creates the model param for most tests. + + Will update to use a semantic model via online connection + vs. connecting to a local model. + """ if "model" in metafunc.fixturenames: metafunc.parametrize("model", [local_pbix], ids=[local_pbix.Name]) + def pytest_sessionstart(session): """Run at pytest start. diff --git a/test/test_10logic_utils.py b/test/test_10logic_utils.py index 3a8583b..0ed7bd2 100644 --- a/test/test_10logic_utils.py +++ b/test/test_10logic_utils.py @@ -1,6 +1,5 @@ """pytest for the table.py file. Covers the PyTable and PyTables classes.""" -from test.config import testing_parameters import pytest from pytabular import logic_utils import pandas as pd diff --git a/test/test_11document.py b/test/test_11document.py index 40a7024..da4bb47 100644 --- a/test/test_11document.py +++ b/test/test_11document.py @@ -1,6 +1,5 @@ """Tests to cover the document.py file.""" -from test.config import testing_parameters import pytest import pytabular as p import os diff --git a/test/test_12tmdl.py b/test/test_12tmdl.py index fbbeea0..236eecc 100644 --- a/test/test_12tmdl.py +++ b/test/test_12tmdl.py @@ -1,8 +1,6 @@ """Tests to cover the tmdl.py file.""" import pytabular as p -from test.config import testing_parameters -import pytest path = "tmdl_testing" diff --git a/test/test_1sanity.py b/test/test_1sanity.py index ed9b042..2c18ca5 100644 --- a/test/test_1sanity.py +++ b/test/test_1sanity.py @@ -4,9 +4,7 @@ Or am I crazy person about to descend into madness. """ -import pytest from Microsoft.AnalysisServices.Tabular import Database -from test.config import testing_parameters def test_sanity_check(): diff --git a/test/test_2object.py b/test/test_2object.py index 3a1ffbb..81b228d 100644 --- a/test/test_2object.py +++ b/test/test_2object.py @@ -1,6 +1,5 @@ """pytest for the table.py file. Covers the PyTable and PyTables classes.""" -from test.config import testing_parameters import pytest from pytabular import Tabular diff --git a/test/test_3tabular.py b/test/test_3tabular.py index dd1221a..7ac63dc 100644 --- a/test/test_3tabular.py +++ b/test/test_3tabular.py @@ -3,7 +3,7 @@ import pytest import pandas as pd import pytabular as p -from test.config import testingtablename, testing_parameters, get_test_path +from test.config import testingtablename, get_test_path def test_basic_query(model): diff --git a/test/test_4measure.py b/test/test_4measure.py index 0b2cf53..807584c 100644 --- a/test/test_4measure.py +++ b/test/test_4measure.py @@ -1,8 +1,5 @@ """Bulk of pytests for `PyMeasure()` class.""" -import pytest -from test.config import testing_parameters - def test_create_measure(model): """Test Creating Measure.""" diff --git a/test/test_5column.py b/test/test_5column.py index 025f0b4..a44a0cd 100644 --- a/test/test_5column.py +++ b/test/test_5column.py @@ -1,7 +1,6 @@ """pytest for the column.py file. Covers the PyColumn and PyColumns classes.""" -from test.config import testing_parameters, testingtablename -import pytest +from test.config import testingtablename import pandas as pd from numpy import int64 diff --git a/test/test_6table.py b/test/test_6table.py index de2ef4f..daea7a7 100644 --- a/test/test_6table.py +++ b/test/test_6table.py @@ -1,7 +1,6 @@ """pytest for the table.py file. Covers the PyTable and PyTables classes.""" -from test.config import testing_parameters, testingtablename -import pytest +from test.config import testingtablename import pandas as pd from datetime import datetime diff --git a/test/test_7tabular_tracing.py b/test/test_7tabular_tracing.py index d23f2b9..36526a2 100644 --- a/test/test_7tabular_tracing.py +++ b/test/test_7tabular_tracing.py @@ -1,7 +1,6 @@ """pytest for the table.py file. Covers the PyTable and PyTables classes.""" -from test.config import testing_parameters, testingtablename -import pytest +from test.config import testingtablename import pytabular as p from test.conftest import TestStorage diff --git a/test/test_8bpa.py b/test/test_8bpa.py index 0cf7f9a..a153d38 100644 --- a/test/test_8bpa.py +++ b/test/test_8bpa.py @@ -1,8 +1,6 @@ """pytest for bpa.""" -import pytest import pytabular as p -from test.config import testing_parameters from os import getcwd diff --git a/test/test_9custom.py b/test/test_9custom.py index 87a32db..8a6b145 100644 --- a/test/test_9custom.py +++ b/test/test_9custom.py @@ -4,8 +4,7 @@ So seperating out... To one day sunset and remove. """ -from test.config import testing_parameters, testingtablename -import pytest +from test.config import testingtablename def test_backingup_table(model): From 8343b682292c31a5b810acaca592fd334cc58f1f Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Mon, 21 Apr 2025 09:22:55 -0400 Subject: [PATCH 8/9] drop 3.8 add 3.13 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c9362b1..c7ffbf3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ dependencies = [ ] description = "Connect to your tabular model and perform operations programmatically" readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.9" classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", From 21cbd6f183977cd85672bf7aa10c56ce9c357e82 Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Mon, 21 Apr 2025 09:33:15 -0400 Subject: [PATCH 9/9] nevermind I'll keep 3.8 --- pyproject.toml | 3 ++- tox.ini | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c7ffbf3..ac031ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,8 +18,9 @@ dependencies = [ ] description = "Connect to your tabular model and perform operations programmatically" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.8" classifiers = [ + "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", diff --git a/tox.ini b/tox.ini index f9cfb5b..9368bb2 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -env_list = docstring, linter, mkdocs, 3.9, 3.10, 3.11, 3.12, 3.13 +env_list = docstring, linter, mkdocs, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13 [testenv] description = "run unit tests"