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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ model.Tables['Table Name'].Refresh()
model.Tables['Table Name'].Partitions['Partition Name'].Refresh()

#Default Tracing happens automatically, but can be removed by --
model.Refresh(['Table1','Table2'], trace = None)
model.Refresh(['Table1','Table2'], Tracing = None)
```

It's not uncommon to need to run through some checks on specific Tables, Partitions, Columns, Etc...
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ nav:
- Queries: Queries.md
- Best Practice Analyzer: Best Practice Analyzer.md
- Tabular Editor: Tabular Editor 2.md
- PBI Helper: PBI Helper.md
- Logic Utils: Logic Utils.md
- Examples: Examples.md
theme: readthedocs
2 changes: 2 additions & 0 deletions mkgendocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ pages:
- Download_Tabular_Editor
classes:
- Tabular_Editor
- page: "PBI Helper.md"
- source: 'pytabular/pbi_helper.py'
- page: "Examples.md"
source: 'pytabular/basic_checks.py'
functions:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "python_tabular"
version = "0.2.5"
version = "0.2.6"
authors = [
{ name="Curtis Stallings", email="curtisrstallings@gmail.com" },
]
Expand Down
1 change: 1 addition & 0 deletions pytabular/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,6 @@
from .tabular_editor import Tabular_Editor
from .best_practice_analyzer import BPA
from .query import Connection
from .pbi_helper import find_local_pbi_instances

logger.debug(f"Import successful...")
86 changes: 86 additions & 0 deletions pytabular/pbi_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import pytabular as p
import subprocess


def get_msmdsrv() -> list:
p.logger.debug("Retrieving msmdsrv.exe(s)")
msmdsrv = subprocess.check_output(
[
"powershell",
"""Get-CimInstance -ClassName Win32_Process -Property * -Filter "Name = 'msmdsrv.exe'" | Select-Object -Property ProcessId -ExpandProperty ProcessId""",
]
)
msmdsrv_id = msmdsrv.decode().strip().splitlines()
p.logger.debug(f"ProcessId for msmdsrv.exe {msmdsrv_id}")
return msmdsrv_id


def get_port_number(msmdsrv: str) -> str:
port = subprocess.check_output(
[
"powershell",
f"""Get-NetTCPConnection -State Listen -OwningProcess {msmdsrv} | Select-Object -Property LocalPort -First 1 -ExpandProperty LocalPort""",
]
)
port_number = port.decode().strip()
p.logger.debug(f"Listening port - {port_number} for msmdsrv.exe - {msmdsrv}")
return port_number


def get_parent_id(msmdsrv: str) -> str:
parent = subprocess.check_output(
[
"powershell",
f"""Get-CimInstance -ClassName Win32_Process -Property * -Filter "ProcessId = {msmdsrv}" | Select-Object -Property ParentProcessId -ExpandProperty ParentProcessId""",
]
)
parent_id = parent.decode().strip()
p.logger.debug(f"ProcessId - {parent_id} for parent of msmdsrv.exe - {msmdsrv}")
return parent_id


def get_parent_title(parent_id: str) -> str:
pbi_title_suffixes: list = [
" \u002D Power BI Desktop", # Dash Punctuation - minus hyphen
" \u2212 Power BI Desktop", # Math Symbol - minus sign
" \u2011 Power BI Desktop", # Dash Punctuation - non-breaking hyphen
" \u2013 Power BI Desktop", # Dash Punctuation - en dash
" \u2014 Power BI Desktop", # Dash Punctuation - em dash
" \u2015 Power BI Desktop", # Dash Punctuation - horizontal bar
]
title = subprocess.check_output(
["powershell", f"""(Get-Process -Id {parent_id}).MainWindowTitle"""]
)
title_name = title.decode().strip()
for suffix in pbi_title_suffixes:
title_name = title_name.replace(suffix, "")
p.logger.debug(f"Title - {title_name} for {parent_id}")
return title_name


def create_connection_str(port_number: str) -> str:
connection_str = f"Data Source=localhost:{port_number}"
p.logger.debug(f"Local Connection Str - {connection_str}")
return connection_str


def find_local_pbi_instances() -> list:
"""The real genius is from this [Dax Studio PowerBIHelper](https://github.com/DaxStudio/DaxStudio/blob/master/src/DaxStudio.UI/Utils/PowerBIHelper.cs).
I just wanted it in python not C#, so reverse engineered what DaxStudio did.
It will run some powershell scripts to pull the appropriate info.
Then will spit out a list with tuples inside.
You can use the connection string to connect to your model with pytabular.

Returns:
list: Example - `[('PBI File Name1','localhost:{port}'),('PBI File Name2','localhost:{port}')]`
"""
instances = get_msmdsrv()
pbi_instances = []
for instance in instances:
p.logger.debug(f"Building connection for {instance}")
port = get_port_number(instance)
parent = get_parent_id(instance)
title = get_parent_title(parent)
connect_str = create_connection_str(port)
pbi_instances += [(title, connect_str)]
return pbi_instances
2 changes: 1 addition & 1 deletion pytabular/pytabular.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def __init__(self, CONNECTION_STR: str):
self.Database = [
database
for database in self.Server.Databases.GetEnumerator()
if database.Name == self.Catalog
if database.Name == self.Catalog or self.Catalog is None
][0]
except Exception:
err_msg = f"Unable to find Database... {self.Catalog}"
Expand Down
10 changes: 7 additions & 3 deletions pytabular/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ class Connection(AdomdConnection):

def __init__(self, Server, Effective_User=None) -> None:
super().__init__()
connection_string = (
f"{Server.ConnectionString}Password='{Server.ConnectionInfo.Password}'"
)
if Server.ConnectionInfo.Password is None:
connection_string = Server.ConnectionString
else:
connection_string = (
f"{Server.ConnectionString}Password='{Server.ConnectionInfo.Password}'"
)
logger.debug(f"{connection_string}")
if Effective_User is not None:
connection_string += f";EffectiveUserName={Effective_User}"
self.ConnectionString = connection_string
Expand Down
13 changes: 13 additions & 0 deletions test/test_2tabular.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import local
import pytest
import pandas as pd
import pytabular as p
from test.config import testingtablename, testing_parameters


Expand Down Expand Up @@ -69,3 +70,15 @@ 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(p.Table_Last_Refresh_Times(model), pd.DataFrame) is True


@pytest.mark.parametrize("model", testing_parameters)
def test_Return_Zero_Row_Tables(model):
"""Testing that `Return_Zero_Row_Tables`"""
assert isinstance(p.Return_Zero_Row_Tables(model), list) is True