diff --git a/README.md b/README.md index 6028e00..714e6b8 100644 --- a/README.md +++ b/README.md @@ -244,5 +244,20 @@ model.Query(query_str, Effective_User = user_email) #So you won't have to reconnect on every query ``` +#### Refresh Related Tables +Ever need to refresh related tables of a Fact? Now should be a lot easier. +```python +import pytabular as p + +#Connect to model +model = p.Tabular(CONNECTION_STR) + +#Get related tables +tables = model.Tables[TABLE_NAME].Related() + +#Now just refresh like usual... +tables.Refresh() +``` + ### Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index e39a4e0..7586768 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "python_tabular" -version = "0.2.2" +version = "0.2.3" authors = [ { name="Curtis Stallings", email="curtisrstallings@gmail.com" }, ] diff --git a/pytabular/object.py b/pytabular/object.py index 265df1e..e839ec4 100644 --- a/pytabular/object.py +++ b/pytabular/object.py @@ -33,9 +33,12 @@ def __getattr__(self, attr): class PyObjects: def __init__(self, objects) -> None: self._objects = objects + self._display = Table(title="PyObject Collection") + for index, obj in enumerate(self._objects): + self._display.add_row(str(index), obj.Name) - def __repr__(self) -> str: - return f"{len(self._objects)}" + def __rich_repr__(self) -> str: + Console().print(self._display) def __getitem__(self, object): if isinstance(object, str): diff --git a/pytabular/pytabular.py b/pytabular/pytabular.py index ab05f49..2d5178e 100644 --- a/pytabular/pytabular.py +++ b/pytabular/pytabular.py @@ -26,6 +26,7 @@ from partition import PyPartitions from column import PyColumns from measure import PyMeasures +from relationship import PyRelationship, PyRelationships from object import PyObject from refresh import PyRefresh from query import Connection @@ -115,6 +116,12 @@ def Reload_Model_Info(self) -> bool: self.Tables = PyTables( [PyTable(table, self) for table in self.Model.Tables.GetEnumerator()] ) + self.Relationships = PyRelationships( + [ + PyRelationship(relationship, self) + for relationship in self.Model.Relationships.GetEnumerator() + ] + ) self.Partitions = PyPartitions( [partition for table in self.Tables for partition in table.Partitions] ) diff --git a/pytabular/refresh.py b/pytabular/refresh.py index e643d91..bb63f47 100644 --- a/pytabular/refresh.py +++ b/pytabular/refresh.py @@ -8,7 +8,7 @@ import pandas as pd from logic_utils import ticks_to_datetime from typing import Union, Dict, Any -from table import PyTable +from table import PyTable, PyTables from partition import PyPartition from abc import ABC @@ -275,6 +275,8 @@ def _request_refresh(self, object): logger.debug(f"Requesting Refresh for {object}") if isinstance(object, str): self._refresh_table(self._find_table(object)) + elif isinstance(object, PyTables): + [self._refresh_table(table) for table in object] elif isinstance(object, Dict): self._refresh_dict(object) elif isinstance(object, PyTable): diff --git a/pytabular/relationship.py b/pytabular/relationship.py new file mode 100644 index 0000000..153f6c9 --- /dev/null +++ b/pytabular/relationship.py @@ -0,0 +1,73 @@ +import logging +from object import PyObject +from pytabular.object import PyObjects +from pytabular.table import PyTable, PyTables + +from Microsoft.AnalysisServices.Tabular import ( + CrossFilteringBehavior, + SecurityFilteringBehavior, +) + +from typing import Union + +logger = logging.getLogger("PyTabular") + + +class PyRelationship(PyObject): + """Wrapper for [Microsoft.AnalysisServices.Tabular.Table](https://learn.microsoft.com/en-us/dotnet/api/microsoft.analysisservices.tabular.table?view=analysisservices-dotnet). + With a few other bells and whistles added to it. You can use the table to access the nested Columns and Partitions. WIP + + Attributes: + Model: Reference to Tabular class + Partitions: Reference to Table Partitions + Columns: Reference to Table Columns + """ + + def __init__(self, object, model) -> None: + super().__init__(object) + self.Model = model + self.CrossFilteringBehavior = CrossFilteringBehavior( + self.CrossFilteringBehavior.value__ + ).ToString() + self.SecurityFilteringBehavior = SecurityFilteringBehavior( + self.SecurityFilteringBehavior.value__ + ).ToString() + self.To_Table = self.Model.Tables[self.ToTable.Name] + self.To_Column = self.To_Table.Columns[self.ToColumn.Name] + self.From_Table = self.Model.Tables[self.FromTable.Name] + self.From_Column = self.From_Table.Columns[self.FromColumn.Name] + self._display.add_row("Is Active", str(self.IsActive)) + self._display.add_row("Cross Filtering Behavior", self.CrossFilteringBehavior) + self._display.add_row( + "Security Filtering Behavior", self.SecurityFilteringBehavior + ) + self._display.add_row( + "From", f"'{self.From_Table.Name}'[{self.From_Column.Name}]" + ) + self._display.add_row("To", f"'{self.To_Table.Name}'[{self.To_Column.Name}]") + + +class PyRelationships(PyObjects): + """Iterator to handle tables. Accessible via `Tables` attribute in Tabular class. + + Args: + PyTable: PyTable class + """ + + def __init__(self, objects) -> None: + super().__init__(objects) + + def Related(self, object: Union[PyTable, str]): + table_to_find = object if isinstance(object, str) else object.Name + to_tables = [ + rel.To_Table + for rel in self._objects + if rel.From_Table.Name == table_to_find + ] + from_tables = [ + rel.From_Table + for rel in self._objects + if rel.To_Table.Name == table_to_find + ] + to_tables += from_tables + return PyTables(to_tables) diff --git a/pytabular/table.py b/pytabular/table.py index 0a936cb..a6f4afc 100644 --- a/pytabular/table.py +++ b/pytabular/table.py @@ -71,6 +71,9 @@ def Refresh(self, *args, **kwargs) -> pd.DataFrame: """ return self.Model.Refresh(self, *args, **kwargs) + def Related(self): + return self.Model.Relationships.Related(self) + class PyTables(PyObjects): """Iterator to handle tables. Accessible via `Tables` attribute in Tabular class. @@ -81,3 +84,7 @@ class PyTables(PyObjects): def __init__(self, objects) -> None: super().__init__(objects) + + def Refresh(self, *args, **kwargs): + model = self._objects[0].Model + return model.Refresh(self, *args, **kwargs)