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
4 changes: 4 additions & 0 deletions .docstr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
paths: ["pytabular"]
verbose: 3
skip_init: True
fail_under: 100
31 changes: 31 additions & 0 deletions .github/workflows/docstr-coverage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This is a basic workflow to help you get started with Actions

name: docstr-coverage

# Controls when the workflow will run
on:
pull_request:
branches: [ master ]
push:
branches: [ master ]

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-python@v2
- run: pip install --upgrade pip
- run: pip install docstr-coverage
- run: docstr-coverage
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.9"
version = "0.3.0"
authors = [
{ name="Curtis Stallings", email="curtisrstallings@gmail.com" },
]
Expand Down
6 changes: 6 additions & 0 deletions pytabular/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
"""
Welcome to PyTabular.
__init__.py will start to setup the basics.
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
Expand Down
13 changes: 13 additions & 0 deletions pytabular/best_practice_analyzer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
"""
best_practice_analyzer is currently just a POC.
You can call the BPA class to download or specify your own BPA file.
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
Expand Down Expand Up @@ -42,6 +49,12 @@ class BPA:
"""Setting BPA Class for future work..."""

def __init__(self, File_Path: str = "Default") -> None:
"""BPA class to be used with the TE2 class.

Args:
File_Path (str, optional): See `Download_BPA_File()`. Defaults to "Default".
If "Default, then will run `Download_BPA_File()` without args.
"""
logger.debug(f"Initializing BPA Class:: {File_Path}")
if File_Path == "Default":
self.Location: str = Download_BPA_File()
Expand Down
20 changes: 17 additions & 3 deletions pytabular/column.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
"""`column.py` houses the main `PyColumn` and `PyColumns` class.
Once connected to your model, interacting with column(s) will be done through these classes.
"""
import logging
import pandas as pd
from object import PyObject, PyObjects
Expand All @@ -7,8 +10,11 @@


class PyColumn(PyObject):
"""Wrapper for [Microsoft.AnalysisServices.Tabular.Column](https://learn.microsoft.com/en-us/dotnet/api/microsoft.analysisservices.tabular.column?view=analysisservices-dotnet).
With a few other bells and whistles added to it. WIP
"""Wrapper for [Column](https://learn.microsoft.com/en-us/dotnet/api/microsoft.analysisservices.tabular.column?view=analysisservices-dotnet).
With a few other bells and whistles added to it.
Notice the `PyObject` magic method `__getattr__()` will search in `self._object`
if it is unable to find it in the default attributes.
This let's you also easily check the default .Net properties.

Args:
Table: Parent Table to the Column
Expand Down Expand Up @@ -89,11 +95,19 @@ def Values(self) -> pd.DataFrame:


class PyColumns(PyObjects):
"""Groups together multiple columns. See `PyObjects` class for what more it can do.
You can interact with `PyColumns` straight from model. For ex: `model.Columns`.
Or through individual tables `model.Tables[TABLE_NAME].Columns`.
You can even filter down with `.Find()`. For example find all columns with `Key` in name.
`model.Columns.Find('Key')`.
"""

def __init__(self, objects) -> None:
super().__init__(objects)

def Query_All(self, query_function: str = "COUNTROWS(VALUES(_))") -> pd.DataFrame:
"""This will dynamically create a query to pull all columns from the model and run the query function. It will replace the _ with the column to run.
"""This will dynamically create a query to pull all columns from the model and run the query function.
It will replace the _ with the column to run.

Args:
query_function (str, optional): Dax query is dynamically building a query with the UNION & ROW DAX Functions.
Expand Down
13 changes: 8 additions & 5 deletions pytabular/culture.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"""`culture.py` is used to house the `PyCulture`, `PyCultures`, and `PyObjectTranslations` classes.
"""
import logging
from object import PyObject, PyObjects

logger = logging.getLogger("PyTabular")


class PyCulture(PyObject):
"""Wrapper for [Microsoft.AnalysisServices.Cultures]
"""Wrapper for [Cultures](https://learn.microsoft.com/en-us/dotnet/api/microsoft.analysisservices.tabular.culture?view=analysisservices-dotnet).

Args:
Table: Parent Table to the Object Translations
Expand All @@ -24,10 +26,7 @@ def __init__(self, object, model) -> None:


class PyObjectTranslation(PyObject):
"""Wrapper for [Microsoft.AnalysisServices.Cultures]
Args:
Table: Child item of the Culture.
"""
"""Wrapper for [ObjectTranslation](https://learn.microsoft.com/en-us/dotnet/api/microsoft.analysisservices.tabular.objecttranslation?view=analysisservices-dotnet)"""

def __init__(self, object, culture) -> None:
self.Name = object.Object.Name
Expand All @@ -40,10 +39,14 @@ def __init__(self, object, culture) -> None:


class PyCultures(PyObjects):
"""Houses grouping of `PyCulture`."""

def __init__(self, objects) -> None:
super().__init__(objects)


class PyObjectTranslations(PyObjects):
"""Houses grouping of `PyObjectTranslation`."""

def __init__(self, objects) -> None:
super().__init__(objects)
2 changes: 2 additions & 0 deletions pytabular/logic_utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""`logic_utils` used to store multiple functions that are used in many different files.
"""
import logging
import datetime
import os
Expand Down
12 changes: 12 additions & 0 deletions pytabular/measure.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
"""
`measure.py` houses the main `PyMeasure` and `PyMeasures` class.
Once connected to your model, interacting with measure(s) will be done through these classes.
"""
import logging
import pandas as pd
from object import PyObject, PyObjects
Expand Down Expand Up @@ -29,5 +33,13 @@ def get_dependencies(self) -> pd.DataFrame:


class PyMeasures(PyObjects):
"""
Groups together multiple measures. See `PyObjects` class for what more it can do.
You can interact with `PyMeasures` straight from model. For ex: `model.Measures`.
Or through individual tables `model.Tables[TABLE_NAME].Measures`.
You can even filter down with `.Find()`. For example find all measures with `ratio` in name.
`model.Measures.Find('ratio')`.
"""

def __init__(self, objects) -> None:
super().__init__(objects)
49 changes: 47 additions & 2 deletions pytabular/object.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
"""
`object.py` stores the main parent classes `PyObject` and `PyObjects`.
These classes are used with the others (Tables, Columns, Measures, Partitions, etc.).
"""
from abc import ABC
from rich.console import Console
from rich.table import Table
from collections.abc import Iterable


class PyObject(ABC):
"""The main parent class for your (Tables, Columns, Measures, Partitions, etc.).
Notice the magic methods. `__rich_repr__()` starts the baseline for displaying your model.
It uses the amazing `rich` python package and
builds your display from the `self._display`.
`__getattr__()` will check in `self._object`, if unable to find anything in `self`.
This will let you pull properties from the main .Net class.
"""

def __init__(self, object) -> None:
self._object = object
self._display = Table(title=self.Name)
Expand All @@ -24,26 +36,40 @@ def __init__(self, object) -> None:
)

def __rich_repr__(self) -> str:
"""See [Rich Repr Protocol](https://rich.readthedocs.io/en/stable/pretty.html#rich-repr-protocol)"""
Console().print(self._display)

def __getattr__(self, attr):
"""Searches in `self.__dict__` first, then `self._object`."""
if attr in self.__dict__:
return getattr(self, attr)
else:
return getattr(self._object, attr)


class PyObjects:
"""
The main parent class for grouping your (Tables, Columns, Measures, Partitions, etc.).
Notice the magic methods. `__rich_repr__()` starts the baseline for displaying your model.
It uses the amazing `rich` python package and
builds your display from the `self._display`.
Still building out the magic methods to give `PyObjects` more flexibility.
"""

def __init__(self, objects) -> None:
self._objects = objects
self._display = Table(title=str(self.__class__.mro()[0]))
for index, obj in enumerate(self._objects):
self._display.add_row(str(index), obj.Name)

def __rich_repr__(self) -> str:
"""See [Rich Repr Protocol](https://rich.readthedocs.io/en/stable/pretty.html#rich-repr-protocol)"""
Console().print(self._display)

def __getitem__(self, object):
"""Checks if item is str or int. If string will iterate through and try to find matching name.
Otherwise, will call into `self._objects[int]` to retrieve item.
"""
if isinstance(object, str):
return [pyobject for pyobject in self._objects if object == pyobject.Name][
-1
Expand All @@ -52,12 +78,22 @@ def __getitem__(self, object):
return self._objects[object]

def __iter__(self):
"""Default `__iter__()` to iterate through `PyObjects`."""
yield from self._objects

def __len__(self):
def __len__(self) -> int:
"""Default `__len__()`

Returns:
int: Number of PyObject in PyObjects
"""
return len(self._objects)

def __iadd__(self, obj):
"""
Add a `PyObject` or `PyObjects` to your current `PyObjects` class.
This is useful for building out a custom `PyObjects` class to work with.
"""
if isinstance(obj, Iterable):
self._objects.__iadd__(obj._objects)
else:
Expand All @@ -66,7 +102,16 @@ def __iadd__(self, obj):
self.__init__(self._objects)
return self

def Find(self, object_str):
def Find(self, object_str: str):
"""Finds any or all `PyObject` inside of `PyObjects` that match the `object_str`.
It is case insensitive.

Args:
object_str (str): str to lookup in `PyObjects`

Returns:
PyObjects: Returns a `PyObjects` class with all `PyObject` where the `PyObject.Name` matches `object_str`.
"""
items = [
object
for object in self._objects
Expand Down
16 changes: 14 additions & 2 deletions pytabular/partition.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
"""
`partition.py` houses the main `PyPartition` and `PyPartitions` class.
Once connected to your model, interacting with partition(s) will be done through these classes.
"""
import logging

from object import PyObject, PyObjects
Expand All @@ -9,8 +13,8 @@


class PyPartition(PyObject):
"""Wrapper for [Microsoft.AnalysisServices.Partition](https://learn.microsoft.com/en-us/dotnet/api/microsoft.analysisservices.tabular.partition?view=analysisservices-dotnet).
With a few other bells and whistles added to it. WIP
"""Wrapper for [Partition](https://learn.microsoft.com/en-us/dotnet/api/microsoft.analysisservices.tabular.partition?view=analysisservices-dotnet).
With a few other bells and whistles added to it.

Args:
Table: Parent Table to the Column
Expand Down Expand Up @@ -46,5 +50,13 @@ def Refresh(self, *args, **kwargs) -> pd.DataFrame:


class PyPartitions(PyObjects):
"""
Groups together multiple partitions. See `PyObjects` class for what more it can do.
You can interact with `PyPartitions` straight from model. For ex: `model.Partitions`.
Or through individual tables `model.Tables[TABLE_NAME].Partitions`.
You can even filter down with `.Find()`. For example find all partition with `prev-year` in name.
`model.Partitions.Find('prev-year')`.
"""

def __init__(self, objects) -> None:
super().__init__(objects)
6 changes: 6 additions & 0 deletions pytabular/pbi_helper.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
"""`pbi_helper.py` was reverse engineered from [DaxStudio PowerBiHelper.cs](https://github.com/DaxStudio/DaxStudio/blob/master/src/DaxStudio.UI/Utils/PowerBIHelper.cs).
So all credit and genius should go to DaxStudio.
I just wanted it in python...
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

Expand Down
Loading