From ada120d51808ad27a1b59d777f40d6e708978370 Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Sun, 27 Nov 2022 11:35:24 -0600 Subject: [PATCH 1/4] relationship poc --- pytabular/object.py | 1 + pytabular/pytabular.py | 4 +++ pytabular/relationship.py | 56 +++++++++++++++++++++++++++++++++++++++ pytabular/table.py | 2 ++ 4 files changed, 63 insertions(+) create mode 100644 pytabular/relationship.py diff --git a/pytabular/object.py b/pytabular/object.py index 265df1e..3e26608 100644 --- a/pytabular/object.py +++ b/pytabular/object.py @@ -58,3 +58,4 @@ def Find(self, object_str): for object in self._objects if object_str.lower() in object.Name.lower() ] + diff --git a/pytabular/pytabular.py b/pytabular/pytabular.py index ab05f49..ee09f0a 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,9 @@ 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/relationship.py b/pytabular/relationship.py new file mode 100644 index 0000000..ddd0ada --- /dev/null +++ b/pytabular/relationship.py @@ -0,0 +1,56 @@ +import logging +from object import PyObject +from pytabular.object import PyObjects +from pytabular.table import PyTable, PyTables +from pytabular.column import PyColumn, PyColumns +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 to_tables diff --git a/pytabular/table.py b/pytabular/table.py index 0a936cb..989522c 100644 --- a/pytabular/table.py +++ b/pytabular/table.py @@ -70,6 +70,8 @@ def Refresh(self, *args, **kwargs) -> pd.DataFrame: pd.DataFrame: Returns pandas dataframe with some refresh details """ return self.Model.Refresh(self, *args, **kwargs) + def Related(self): + return self.Model.Relationships.Related(self) class PyTables(PyObjects): From 3011de0b4db79dbec5eb6b666322c7f2427896ea Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Sun, 27 Nov 2022 12:42:19 -0600 Subject: [PATCH 2/4] related refresh poc --- pytabular/object.py | 7 +++++-- pytabular/refresh.py | 4 +++- pytabular/relationship.py | 2 +- pytabular/table.py | 4 ++++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/pytabular/object.py b/pytabular/object.py index 3e26608..7267c9e 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/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 index ddd0ada..c66d94a 100644 --- a/pytabular/relationship.py +++ b/pytabular/relationship.py @@ -53,4 +53,4 @@ def Related(self, object: Union[PyTable, str]): 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 to_tables + return PyTables(to_tables) diff --git a/pytabular/table.py b/pytabular/table.py index 989522c..ae8c48b 100644 --- a/pytabular/table.py +++ b/pytabular/table.py @@ -83,3 +83,7 @@ class PyTables(PyObjects): def __init__(self, objects) -> None: super().__init__(objects) + def Refresh(self): + model = self._objects[0].Model + return model.Refresh(self) + From bfb5760095c9e02a4a5cbc6ef04f94ded8fbd742 Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Sun, 27 Nov 2022 12:42:36 -0600 Subject: [PATCH 3/4] 0.2.3 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" }, ] From 69ff19cded3e743aeae23111db247b6c5e4f9332 Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Sun, 27 Nov 2022 13:30:57 -0600 Subject: [PATCH 4/4] pytables refresh poc --- README.md | 15 +++++++++++++++ pytabular/object.py | 1 - pytabular/pytabular.py | 5 ++++- pytabular/relationship.py | 35 ++++++++++++++++++++++++++--------- pytabular/table.py | 7 ++++--- 5 files changed, 49 insertions(+), 14 deletions(-) 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/pytabular/object.py b/pytabular/object.py index 7267c9e..e839ec4 100644 --- a/pytabular/object.py +++ b/pytabular/object.py @@ -61,4 +61,3 @@ def Find(self, object_str): for object in self._objects if object_str.lower() in object.Name.lower() ] - diff --git a/pytabular/pytabular.py b/pytabular/pytabular.py index ee09f0a..2d5178e 100644 --- a/pytabular/pytabular.py +++ b/pytabular/pytabular.py @@ -117,7 +117,10 @@ def Reload_Model_Info(self) -> bool: [PyTable(table, self) for table in self.Model.Tables.GetEnumerator()] ) self.Relationships = PyRelationships( - [PyRelationship(relationship, self) for relationship in self.Model.Relationships.GetEnumerator()] + [ + 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/relationship.py b/pytabular/relationship.py index c66d94a..153f6c9 100644 --- a/pytabular/relationship.py +++ b/pytabular/relationship.py @@ -2,10 +2,10 @@ from object import PyObject from pytabular.object import PyObjects from pytabular.table import PyTable, PyTables -from pytabular.column import PyColumn, PyColumns + from Microsoft.AnalysisServices.Tabular import ( CrossFilteringBehavior, - SecurityFilteringBehavior + SecurityFilteringBehavior, ) from typing import Union @@ -26,16 +26,24 @@ class PyRelationship(PyObject): 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.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( + "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}]") @@ -48,9 +56,18 @@ class PyRelationships(PyObjects): 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 = [ + 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) + return PyTables(to_tables) diff --git a/pytabular/table.py b/pytabular/table.py index ae8c48b..a6f4afc 100644 --- a/pytabular/table.py +++ b/pytabular/table.py @@ -70,6 +70,7 @@ def Refresh(self, *args, **kwargs) -> pd.DataFrame: pd.DataFrame: Returns pandas dataframe with some refresh details """ return self.Model.Refresh(self, *args, **kwargs) + def Related(self): return self.Model.Relationships.Related(self) @@ -83,7 +84,7 @@ class PyTables(PyObjects): def __init__(self, objects) -> None: super().__init__(objects) - def Refresh(self): + + def Refresh(self, *args, **kwargs): model = self._objects[0].Model - return model.Refresh(self) - + return model.Refresh(self, *args, **kwargs)