diff --git a/pyproject.toml b/pyproject.toml index 3b0a900..33487a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta" [project] name = "python_tabular" -version = "0.5.2" +version = "0.5.3" authors = [ { name="Curtis Stallings", email="curtisrstallings@gmail.com" }, ] dependencies = [ - "pythonnet==3.0.0a2", - "clr-loader==0.1.7", + "pythonnet==3.0.3", + "clr-loader==0.2.6", "xmltodict==0.13.0", "pandas>=1.4.3", "requests>=2.28.1", @@ -18,10 +18,14 @@ dependencies = [ ] description = "Connect to your tabular model and perform operations programmatically" readme = "README.md" -requires-python = ">=3.7" +requires-python = ">=3.8" classifiers = [ + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", - "Development Status :: 3 - Alpha", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Development Status :: 5 - Production/Stable", "Operating System :: Microsoft", "License :: OSI Approved :: MIT License" ] diff --git a/pytabular/__init__.py b/pytabular/__init__.py index a379ba3..64bfa84 100644 --- a/pytabular/__init__.py +++ b/pytabular/__init__.py @@ -4,6 +4,7 @@ It will setup logging and make sure Pythonnet is good to go. Then it will begin to import specifics of the module. """ + # flake8: noqa import logging import os diff --git a/pytabular/best_practice_analyzer.py b/pytabular/best_practice_analyzer.py index 85c42b5..62a15db 100644 --- a/pytabular/best_practice_analyzer.py +++ b/pytabular/best_practice_analyzer.py @@ -4,6 +4,7 @@ It is used with tabular_editor.py to run BPA. I did not want to re-invent the wheel, so just letting TE2 work it's magic. """ + import logging import requests as r import atexit @@ -20,7 +21,8 @@ def download_bpa_file( "https://raw.githubusercontent.com/microsoft/Analysis-Services/master/BestPracticeRules/BPARules.json" # noqa: E501 ), folder: str = "Best_Practice_Analyzer", - auto_remove=True, + auto_remove: bool = True, + verify: bool = False, ) -> str: """Download a BPA file from local or web. @@ -34,6 +36,7 @@ def download_bpa_file( folder (str, optional): New folder string. Defaults to 'Best_Practice_Analyzer'. auto_remove (bool, optional): Auto Remove when script exits. Defaults to True. + verify (bool, optional): Passthrough argument for `r.get`. Need to update later. Returns: str: File path for the newly downloaded BPA. @@ -42,7 +45,7 @@ def download_bpa_file( folder_location = os.path.join(os.getcwd(), folder) if os.path.exists(folder_location) is False: os.makedirs(folder_location) - response = r.get(download_location) + response = r.get(download_location, verify=verify) file_location = os.path.join(folder_location, download_location.split("/")[-1]) with open(file_location, "w", encoding="utf-8") as bpa: json.dump(response.json(), bpa, ensure_ascii=False, indent=4) @@ -55,7 +58,9 @@ def download_bpa_file( class BPA: """Setting BPA Class for future work...""" - def __init__(self, file_path: str = "Default") -> None: + def __init__( + self, file_path: str = "Default", verify_download: bool = True + ) -> None: """BPA class to be used with the TE2 class. You can create the BPA class without any arguments. @@ -64,10 +69,12 @@ def __init__(self, file_path: str = "Default") -> None: Args: file_path (str, optional): See `Download_BPA_File()`. Defaults to "Default". + verify_download (bool, optional): Passthrough argument for `r.get`. + Need to update later. """ logger.debug(f"Initializing BPA Class:: {file_path}") if file_path == "Default": - self.location: str = download_bpa_file() + self.location: str = download_bpa_file(verify=verify_download) else: self.location: str = file_path pass diff --git a/pytabular/column.py b/pytabular/column.py index 384e651..ccd5284 100644 --- a/pytabular/column.py +++ b/pytabular/column.py @@ -2,6 +2,7 @@ Once connected to your model, interacting with column(s) will be done through these classes. """ + import logging import pandas as pd from pytabular.object import PyObject, PyObjects diff --git a/pytabular/culture.py b/pytabular/culture.py index d53f0a9..2480c67 100644 --- a/pytabular/culture.py +++ b/pytabular/culture.py @@ -1,4 +1,5 @@ """`culture.py` is used to house the `PyCulture`, and `PyCultures` classes.""" + import logging from pytabular.object import PyObject, PyObjects from typing import List @@ -29,9 +30,9 @@ def set_translation(self) -> List[dict]: { "object_translation": translation.Value, "object_name": translation.Object.Name, - "object_parent_name": translation.Object.Parent.Name - if translation.Object.Parent - else "", + "object_parent_name": ( + translation.Object.Parent.Name if translation.Object.Parent else "" + ), "object_type": str(translation.Property), } for translation in self._object.ObjectTranslations diff --git a/pytabular/document.py b/pytabular/document.py index 617e9d9..6947817 100644 --- a/pytabular/document.py +++ b/pytabular/document.py @@ -2,6 +2,7 @@ This module can generate pages in markdown for use in Docusaurus. """ + import logging from pathlib import Path diff --git a/pytabular/logic_utils.py b/pytabular/logic_utils.py index b43d320..58f718a 100644 --- a/pytabular/logic_utils.py +++ b/pytabular/logic_utils.py @@ -1,4 +1,5 @@ """`logic_utils` used to store multiple functions that are used in many different files.""" + import logging import datetime import os diff --git a/pytabular/measure.py b/pytabular/measure.py index 853a235..98bcf82 100644 --- a/pytabular/measure.py +++ b/pytabular/measure.py @@ -3,6 +3,7 @@ Once connected to your model, interacting with measure(s) will be done through these classes. """ + import logging import pandas as pd from pytabular.object import PyObject, PyObjects diff --git a/pytabular/object.py b/pytabular/object.py index da8ee5f..7d47cce 100644 --- a/pytabular/object.py +++ b/pytabular/object.py @@ -2,6 +2,7 @@ These classes are used with the others (Tables, Columns, Measures, Partitions, etc.). """ + from __future__ import annotations from abc import ABC from rich.console import Console diff --git a/pytabular/partition.py b/pytabular/partition.py index 3157754..1d2ddaa 100644 --- a/pytabular/partition.py +++ b/pytabular/partition.py @@ -2,6 +2,7 @@ Once connected to your model, interacting with partition(s) will be done through these classes. """ + import logging from pytabular.object import PyObject, PyObjects diff --git a/pytabular/pbi_helper.py b/pytabular/pbi_helper.py index 19609d0..1a21055 100644 --- a/pytabular/pbi_helper.py +++ b/pytabular/pbi_helper.py @@ -5,6 +5,7 @@ The main function is `find_local_pbi_instances()`. It will find any open PBIX files on your computer and spit out a connection string for you. """ + import pytabular as p import subprocess diff --git a/pytabular/pytabular.py b/pytabular/pytabular.py index 36eed7d..79fa790 100644 --- a/pytabular/pytabular.py +++ b/pytabular/pytabular.py @@ -2,6 +2,7 @@ Main class is `Tabular()`. Use that for connecting with your models. """ + import logging from Microsoft.AnalysisServices.Tabular import ( @@ -491,7 +492,7 @@ def analyze_bpa( logger.debug("Beginning request to talk with TE2 & Find BPA...") bim_file_location = f"{os.getcwd()}\\Model.bim" atexit.register(remove_file, bim_file_location) - cmd = f'{tabular_editor_exe} "Provider=MSOLAP;\ + cmd = f'"{tabular_editor_exe}" "Provider=MSOLAP;\ {self.Adomd.ConnectionString}" {self.Database.Name} -B "{bim_file_location}" \ -A {best_practice_analyzer} -V/?' logger.debug("Command Generated") diff --git a/pytabular/query.py b/pytabular/query.py index c06b4de..d656738 100644 --- a/pytabular/query.py +++ b/pytabular/query.py @@ -16,6 +16,7 @@ ) ``` """ + import logging import os from typing import Union diff --git a/pytabular/refresh.py b/pytabular/refresh.py index cf76730..c5dc484 100644 --- a/pytabular/refresh.py +++ b/pytabular/refresh.py @@ -27,6 +27,7 @@ model.Tables['Sales'].Partitions['Last Fiscal Year'].refresh() ``` """ + from tabular_tracing import RefreshTrace, BaseTrace import logging from Microsoft.AnalysisServices.Tabular import ( diff --git a/pytabular/relationship.py b/pytabular/relationship.py index 6c486ce..9f6feda 100644 --- a/pytabular/relationship.py +++ b/pytabular/relationship.py @@ -3,6 +3,7 @@ Once connected to your model, interacting with relationship(s) will be done through these classes. """ + import logging from pytabular.object import PyObject, PyObjects from pytabular.table import PyTable, PyTables diff --git a/pytabular/table.py b/pytabular/table.py index 2c8a255..8baa85e 100644 --- a/pytabular/table.py +++ b/pytabular/table.py @@ -2,6 +2,7 @@ Once connected to your model, interacting with table(s) will be done through these classes. """ + import logging import pandas as pd from pytabular.partition import PyPartition, PyPartitions diff --git a/pytabular/tabular_editor.py b/pytabular/tabular_editor.py index 5d692d9..f8bbf0e 100644 --- a/pytabular/tabular_editor.py +++ b/pytabular/tabular_editor.py @@ -2,6 +2,7 @@ Or you can input your own location. """ + import logging import os import requests as r @@ -18,6 +19,7 @@ def download_tabular_editor( ), folder: str = "Tabular_Editor_2", auto_remove=True, + verify=False, ) -> str: """Runs a request.get() to retrieve the zip file from web. @@ -31,6 +33,7 @@ def download_tabular_editor( folder (str, optional): New Folder Location. Defaults to "Tabular_Editor_2". auto_remove (bool, optional): Boolean to determine auto removal of files once script exits. Defaults to True. + verify (bool, optional): Passthrough argument for `r.get`. Need to update later. Returns: str: File path of TabularEditor.exe @@ -38,7 +41,7 @@ def download_tabular_editor( logger.info("Downloading Tabular Editor 2...") logger.info(f"From... {download_location}") folder_location = os.path.join(os.getcwd(), folder) - response = r.get(download_location) + response = r.get(download_location, verify=verify) file_location = f"{os.getcwd()}\\{download_location.split('/')[-1]}" with open(file_location, "wb") as te2_zip: te2_zip.write(response.content) @@ -59,7 +62,9 @@ class TabularEditor: Mainly runs `download_tabular_editor()` """ - def __init__(self, exe_file_path: str = "Default") -> None: + def __init__( + self, exe_file_path: str = "Default", verify_download: bool = True + ) -> None: """Init for `TabularEditor()` class. This is mostly a placeholder right now. @@ -69,10 +74,12 @@ def __init__(self, exe_file_path: str = "Default") -> None: exe_file_path (str, optional): File path where TE2 lives. Defaults to "Default". If "Default", it will run `download_tabular_editor()` and download from github. + verify_download (bool, optional): Passthrough argument for `r.get`. + Need to update later. """ logger.debug(f"Initializing Tabular Editor Class:: {exe_file_path}") if exe_file_path == "Default": - self.exe: str = download_tabular_editor() + self.exe: str = download_tabular_editor(verify=verify_download) else: self.exe: str = exe_file_path pass diff --git a/pytabular/tabular_tracing.py b/pytabular/tabular_tracing.py index 8b65f5b..bd946f8 100644 --- a/pytabular/tabular_tracing.py +++ b/pytabular/tabular_tracing.py @@ -25,6 +25,7 @@ 2. If you want to see the FULL query then set logging to DEBUG. 3. You can drop on your own, or will get dropped on script exit. """ + import logging import random import xmltodict diff --git a/test/adventureworks/AdventureWorks Sales.pbix b/test/adventureworks/AdventureWorks Sales.pbix index 53ec09f..7e6b5f8 100644 Binary files a/test/adventureworks/AdventureWorks Sales.pbix and b/test/adventureworks/AdventureWorks Sales.pbix differ diff --git a/test/config.py b/test/config.py index b928e72..66f0bfd 100644 --- a/test/config.py +++ b/test/config.py @@ -1,4 +1,5 @@ """Custom configurations for pytest.""" + import pytabular as p import os import pandas as pd diff --git a/test/conftest.py b/test/conftest.py index d42a7c3..67f83d0 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -4,6 +4,7 @@ This file also has functions that give instructions on start and finish of pytest. See `config.py` for more testing configurations. """ + from test.config import ( local_pbix, testingtablename, diff --git a/test/run_versions.bat b/test/run_versions.bat index 8694c70..dda824e 100644 --- a/test/run_versions.bat +++ b/test/run_versions.bat @@ -1,2 +1,7 @@ @echo on -pyenv shell 3.7.9 & python3 -m pytest & pyenv shell 3.8.9 & python3 -m pytest & pyenv shell 3.9.13 & python3 -m pytest & pyenv shell 3.10.6 & python3 -m pytest & pause & pause \ No newline at end of file +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/test/test_10logic_utils.py b/test/test_10logic_utils.py index e12efa5..3220601 100644 --- a/test/test_10logic_utils.py +++ b/test/test_10logic_utils.py @@ -1,4 +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 diff --git a/test/test_11document.py b/test/test_11document.py index c89f9b1..8ee45f4 100644 --- a/test/test_11document.py +++ b/test/test_11document.py @@ -1,4 +1,5 @@ """Tests to cover the document.py file.""" + from test.config import testing_parameters import pytest import pytabular as p diff --git a/test/test_1sanity.py b/test/test_1sanity.py index 496c18d..7fb8250 100644 --- a/test/test_1sanity.py +++ b/test/test_1sanity.py @@ -3,6 +3,7 @@ Does 1 actually equal 1? Or am I crazy person about to descend into madness. """ + import pytest from Microsoft.AnalysisServices.Tabular import Database from test.config import testing_parameters diff --git a/test/test_2object.py b/test/test_2object.py index 6a4c751..ae2fe27 100644 --- a/test/test_2object.py +++ b/test/test_2object.py @@ -1,4 +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 1705d09..0fc08e2 100644 --- a/test/test_3tabular.py +++ b/test/test_3tabular.py @@ -1,4 +1,5 @@ """Bulk of pytests for `Tabular()` class.""" + import pytest import pandas as pd import pytabular as p diff --git a/test/test_4measure.py b/test/test_4measure.py index 2c7a54c..5c18d38 100644 --- a/test/test_4measure.py +++ b/test/test_4measure.py @@ -1,4 +1,5 @@ """Bulk of pytests for `PyMeasure()` class.""" + import pytest from test.config import testing_parameters diff --git a/test/test_5column.py b/test/test_5column.py index 0d1211c..6f1567f 100644 --- a/test/test_5column.py +++ b/test/test_5column.py @@ -1,4 +1,5 @@ """pytest for the column.py file. Covers the PyColumn and PyColumns classes.""" + from test.config import testing_parameters, testingtablename import pytest import pandas as pd diff --git a/test/test_6table.py b/test/test_6table.py index 1c736dd..aa1c12e 100644 --- a/test/test_6table.py +++ b/test/test_6table.py @@ -1,4 +1,5 @@ """pytest for the table.py file. Covers the PyTable and PyTables classes.""" + from test.config import testing_parameters, testingtablename import pytest import pandas as pd diff --git a/test/test_7tabular_tracing.py b/test/test_7tabular_tracing.py index 1cd6557..816cd89 100644 --- a/test/test_7tabular_tracing.py +++ b/test/test_7tabular_tracing.py @@ -1,4 +1,5 @@ """pytest for the table.py file. Covers the PyTable and PyTables classes.""" + from test.config import testing_parameters, testingtablename import pytest import pytabular as p diff --git a/test/test_8bpa.py b/test/test_8bpa.py index 3701cc8..b59f2f8 100644 --- a/test/test_8bpa.py +++ b/test/test_8bpa.py @@ -1,4 +1,5 @@ """pytest for bpa.""" + import pytest import pytabular as p from test.config import testing_parameters @@ -8,8 +9,8 @@ @pytest.mark.parametrize("model", testing_parameters) def test_bpa(model): """Testing execution of `model.analyze_bpa()`.""" - te2 = p.TabularEditor().exe - bpa = p.BPA().location + te2 = p.TabularEditor(verify_download=False).exe + bpa = p.BPA(verify_download=False).location assert isinstance(model.analyze_bpa(te2, bpa), list) diff --git a/test/test_9custom.py b/test/test_9custom.py index 11a0ace..25f005c 100644 --- a/test/test_9custom.py +++ b/test/test_9custom.py @@ -3,6 +3,7 @@ These were designed selfishly for my own uses. So seperating out... To one day sunset and remove. """ + from test.config import testing_parameters, testingtablename import pytest