From ad3386ca14bc6a179da247fbdfa50dbf54923176 Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Sat, 18 Feb 2023 13:37:08 -0600 Subject: [PATCH 1/7] Example for query docstring --- pytabular/pytabular.py | 44 +++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/pytabular/pytabular.py b/pytabular/pytabular.py index ca05c1b..644bb38 100644 --- a/pytabular/pytabular.py +++ b/pytabular/pytabular.py @@ -40,7 +40,7 @@ class Tabular(PyObject): - """Tabular Class to perform operations. + """This is the Tabular Class to perform operations. This is the main class to work with in PyTabular. You can connect to the other classes via the supplied attributes. @@ -50,13 +50,13 @@ class Tabular(PyObject): [link](https://learn.microsoft.com/en-us/analysis-services/instances/connection-string-properties-analysis-services) Attributes: - AdomdConnection (Connection): For querying. - This is the `Connection` class. - Tables (PyTables): See `PyTables` for more information. - Iterate through your tables in your model. - Columns (PyColumns): See `PyColumns` for more information. - Partitions (PyPartitions): See `PyPartitions` for more information. - Measures (PyMeasures): See `PyMeasures` for more information. + AdomdConnection (Connection): For querying. + This is the `Connection` class. + Tables (PyTables): See `PyTables` for more information. + Iterate through your tables in your model. + Columns (PyColumns): See `PyColumns` for more information. + Partitions (PyPartitions): See `PyPartitions` for more information. + Measures (PyMeasures): See `PyMeasures` for more information. """ def __init__(self, connection_str: str): @@ -158,7 +158,7 @@ def is_process(self) -> bool: to see if any processing is happening. Returns: - bool: True if DMV shows Process, False if not. + bool: True if DMV shows Process, False if not. """ _jobs_df = self.query("select * from $SYSTEM.DISCOVER_JOBS") return len(_jobs_df[_jobs_df["JOB_DESCRIPTION"] == "Process"]) > 0 @@ -243,7 +243,7 @@ def property_changes(property_changes_var): ) def backup_table(self, table_str: str) -> bool: - """Will be removed. + """This will be removed. Used in conjunction with `revert_table()`. """ @@ -338,7 +338,7 @@ def clone_role_permissions(): return True def revert_table(self, table_str: str) -> bool: - """Will be removed. + """This will be removed. This is used in conjunction with `backup_table()`. """ @@ -428,19 +428,31 @@ def dename(items): def query( self, query_str: str, effective_user: str = None ) -> Union[pd.DataFrame, str, int]: - """Executes query on model. + """Executes a query on model. See `Connection().query()` for details on execution. Args: query_str (str): Query string to execute. effective_user (str, optional): Pass through an effective user - if desired. It will create and store a new `Connection()` class if need, - which will help with speed if looping through multiple users in a row. - Defaults to None. + if desired. It will create and store a new `Connection()` class if need, + which will help with speed if looping through multiple users in a row. + Defaults to None. Returns: - Union[pd.DataFrame, str, int]: _description_ + Union[pd.DataFrame, str, int]: Depending on query, will return DataFrame + or single value. + Example: + ```python + model.query("EVALUATE {1}") + + model.query("EVALUATE TOPN(5, 'Customer')") + + model.query( + "EVALUATE VALUES('Sales Region'[Region])", + effective_user = "user@company.com" + ) + ``` """ if effective_user is None: return self.Adomd.query(query_str) From 94b5a853db3a79bd483a0742dca7b209ac756252 Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Sat, 18 Feb 2023 13:59:17 -0600 Subject: [PATCH 2/7] pytable docstring update --- pytabular/table.py | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/pytabular/table.py b/pytabular/table.py index 79c198e..e331ccf 100644 --- a/pytabular/table.py +++ b/pytabular/table.py @@ -8,6 +8,7 @@ from pytabular.column import PyColumn, PyColumns from pytabular.measure import PyMeasure, PyMeasures from pytabular.object import PyObjects, PyObject +from pytabular.pytabular import Tabular from logic_utils import ticks_to_datetime from datetime import datetime @@ -17,9 +18,26 @@ class PyTable(PyObject): """The main PyTable class to interact with the tables in model. - 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. + Attributes: + Name (str): Name of table. + IsHidden (bool): Is the table hidden. + Description (str): The description of the table. + Model (Tabular): The parent `Tabular()` class. + Partitions (PyPartitions): The `PyPartitions()` in the table. + Columns (PyColumns): The `PyColumns()` in the table. + Measures (PyMeasures): The `PyMeasures()` in the table. + + Example: + ```python title="Passing through PyTable to PyPartition" + + model.Tables[0].Partitions['Last Year'].refresh() # (1) + ``` + + 1. This shows the ability to travel through your model + to a specific partition and then running a refresh + for that specific partition. + `model` -> `PyTables` -> `PyTable` (1st index) -> `PyPartitions` + -> `PyPartition` (.Name == 'Last Year') -> `.refresh()` """ def __init__(self, object, model) -> None: @@ -33,17 +51,17 @@ def __init__(self, object, model) -> None: model (Tabular): The model that the table is in. """ super().__init__(object) - self.Model = model - self.Partitions = PyPartitions( + self.Model: Tabular = model + self.Partitions: PyPartitions = PyPartitions( [ PyPartition(partition, self) for partition in self._object.Partitions.GetEnumerator() ] ) - self.Columns = PyColumns( + self.Columns: PyColumns = PyColumns( [PyColumn(column, self) for column in self._object.Columns.GetEnumerator()] ) - self.Measures = PyMeasures( + self.Measures: PyMeasures = PyMeasures( [ PyMeasure(measure, self) for measure in self._object.Measures.GetEnumerator() From 9ee83128d4dd532d4bbc2f7ec0785ca550cfce03 Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Sat, 18 Feb 2023 14:30:39 -0600 Subject: [PATCH 3/7] pytable method docstring examples --- pytabular/table.py | 57 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/pytabular/table.py b/pytabular/table.py index e331ccf..abf956e 100644 --- a/pytabular/table.py +++ b/pytabular/table.py @@ -40,7 +40,7 @@ class PyTable(PyObject): -> `PyPartition` (.Name == 'Last Year') -> `.refresh()` """ - def __init__(self, object, model) -> None: + def __init__(self, object, model: Tabular) -> None: """Init extends from `PyObject` class. Also adds a few specific rows to the `rich` @@ -90,16 +90,28 @@ def row_count(self) -> int: Returns: int: Number of rows using `COUNTROWS`. + + Example: + ```python + model.Tables['Table Name'].row_count() + ``` """ return self.Model.Adomd.query(f"EVALUATE {{COUNTROWS('{self.Name}')}}") def refresh(self, *args, **kwargs) -> pd.DataFrame: - """Use this to refresh the PyTable in question. + """Use this to refresh the PyTable. - You can pass through any extra parameters. For example: - `Tabular().Tables['Table Name'].Refresh(trace = None)` Returns: pd.DataFrame: Returns pandas dataframe with some refresh details. + + Example: + ```python + model.Tables['Table Name'].refresh() + + model.Tables['Table Name'].refresh(trace = None) # (1) + ``` + + 1. You can pass through arguments to `PyRefresh`, like removing trace. """ return self.Model.refresh(self, *args, **kwargs) @@ -124,11 +136,8 @@ def related(self): class PyTables(PyObjects): """Groups together multiple tables. - See `PyObjects` class for what more it can do. - You can interact with `PyTables` straight from model. For ex: `model.Tables`. - You can even filter down with `.Find()`. - For example find all tables with `fact` in name. - `model.Tables.Find('fact')`. + You can interact with `PyTables` straight from model. + You can even filter down with `.find()`. """ def __init__(self, objects) -> None: @@ -153,6 +162,16 @@ def query_all(self, query_function: str = "COUNTROWS(_)") -> pd.DataFrame: Returns: pd.DataFrame: Returns dataframe with results + + Example: + ```python + model.Tables.find('fact').query_all() # (1) + ``` + + 1. Because `.find()` will return the `PyObjects` you are searching in, + another `PyTables` is returned, but reduced to just + the `PyTable`(s) with the 'fact' in the name. Then will + get the # of rows for each table. """ logger.info("Querying every table in PyTables...") logger.debug(f"Function to be run: {query_function}") @@ -166,8 +185,12 @@ def query_all(self, query_function: str = "COUNTROWS(_)") -> pd.DataFrame: query_str = f"{query_str[:-2]})" return self[0].Model.query(query_str) - def find_zero_rows(self): - """Returns PyTables class of tables with zero rows queried.""" + def find_zero_rows(self) -> "PyTables": + """Returns PyTables class of tables with zero rows queried. + + Returns: + PyTables: A subset of the `PyTables` that contains zero rows. + """ query_function: str = "COUNTROWS(_)" df = self.query_all(query_function) @@ -186,14 +209,14 @@ def last_refresh(self, group_partition: bool = True) -> pd.DataFrame: `model.Create_Table(p.Table_Last_Refresh_Times(model),'RefreshTimes')`. Args: - group_partition (bool, optional): Whether or not you want - the grain of the dataframe to be by table or by partition. - Defaults to True. + group_partition (bool, optional): Whether or not you want + the grain of the dataframe to be by table or by partition. + Defaults to True. Returns: - pd.DataFrame: pd dataframe with the RefreshedTime property - If group_partition == True and the table has - multiple partitions, then df.groupby(by["tables"]).max() + pd.DataFrame: pd dataframe with the RefreshedTime property + If group_partition == True and the table has + multiple partitions, then df.groupby(by["tables"]).max() """ data = { "Tables": [ From 62748946824571df83aea7f62d933313e4db740c Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Sat, 18 Feb 2023 14:31:17 -0600 Subject: [PATCH 4/7] refresh and query doc examples --- pytabular/query.py | 14 ++++++++++++++ pytabular/refresh.py | 27 ++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/pytabular/query.py b/pytabular/query.py index a3e966f..f9e6e72 100644 --- a/pytabular/query.py +++ b/pytabular/query.py @@ -1,6 +1,20 @@ """`query.py` houses a custom `Connection` class that uses the .Net AdomdConnection. `Connection` is created automatically when connecting to your model. + +Example: + ```python title="query from model" + import pytabular as p + model = p.Tabular(CONNECTION_STR) + model.query("EVALUATE {1}") + ``` + + ```python title="pass an effective user" + model.query( + "EVALUATE {1}", + effective_user = "user@company.com" + ) + ``` """ import logging import os diff --git a/pytabular/refresh.py b/pytabular/refresh.py index 2a53e55..cf76730 100644 --- a/pytabular/refresh.py +++ b/pytabular/refresh.py @@ -1,6 +1,31 @@ """`refresh.py` is the main file to handle all the components of refreshing your model. -See the `PyTable(s)` and `PyPartition(s)` classes. Use the `refresh` method from their. +You have may ways to interact with refreshing. + +Example: + ```python title="refresh from model" + import pytabular as p + model = p.Tabular(CONNECTION_STR) + model.refresh('Table Name') + ``` + + ```python title="refresh from PyTables" + model.Tables.find("fact").refresh() # (1) + ``` + + 1. Refresh all tables with 'fact' in the name. + + ```python title="refresh from PyTable" + model.Tables['Sales'].refresh() + ``` + + ```python title="refresh from PyPartitions" + model.Tables['Large Sales Fact'].Partitions.refresh() + ``` + + ```python title="refresh from PyPartition" + model.Tables['Sales'].Partitions['Last Fiscal Year'].refresh() + ``` """ from tabular_tracing import RefreshTrace, BaseTrace import logging From 3b9c83bf3690b1e7a4a9ad525448ab3cbb2e875d Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Sat, 18 Feb 2023 14:31:34 -0600 Subject: [PATCH 5/7] some type hints and docstring updates --- pytabular/partition.py | 3 ++- pytabular/pytabular.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pytabular/partition.py b/pytabular/partition.py index 3157754..3b21afe 100644 --- a/pytabular/partition.py +++ b/pytabular/partition.py @@ -5,6 +5,7 @@ import logging from pytabular.object import PyObject, PyObjects +from pytabular.table import PyTable from logic_utils import ticks_to_datetime import pandas as pd from datetime import datetime @@ -18,7 +19,7 @@ class PyPartition(PyObject): See methods for available uses. """ - def __init__(self, object, table) -> None: + def __init__(self, object, table: PyTable) -> None: """Extends from `PyObject` class. Adds a few custom rows to `rich` table for the partition. diff --git a/pytabular/pytabular.py b/pytabular/pytabular.py index 644bb38..0fc3605 100644 --- a/pytabular/pytabular.py +++ b/pytabular/pytabular.py @@ -50,13 +50,14 @@ class Tabular(PyObject): [link](https://learn.microsoft.com/en-us/analysis-services/instances/connection-string-properties-analysis-services) Attributes: - AdomdConnection (Connection): For querying. + Adomd (Connection): For querying. This is the `Connection` class. Tables (PyTables): See `PyTables` for more information. Iterate through your tables in your model. Columns (PyColumns): See `PyColumns` for more information. Partitions (PyPartitions): See `PyPartitions` for more information. Measures (PyMeasures): See `PyMeasures` for more information. + PyRefresh (PyRefresh): See `PyRefresh` for more information. """ def __init__(self, connection_str: str): @@ -85,7 +86,7 @@ def __init__(self, connection_str: str): logger.info(f"Connected to Model - {self.Model.Name}") self.Adomd: Connection = Connection(self.Server) self.effective_users: dict = {} - self.PyRefresh = PyRefresh + self.PyRefresh: PyRefresh = PyRefresh # Build PyObjects self.reload_model_info() From 3dc04fad484edc28363e02de109e58b6a5474a09 Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Sat, 18 Feb 2023 14:32:59 -0600 Subject: [PATCH 6/7] whitespace cleanup --- pytabular/query.py | 2 +- pytabular/table.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pytabular/query.py b/pytabular/query.py index f9e6e72..c06b4de 100644 --- a/pytabular/query.py +++ b/pytabular/query.py @@ -14,7 +14,7 @@ "EVALUATE {1}", effective_user = "user@company.com" ) - ``` + ``` """ import logging import os diff --git a/pytabular/table.py b/pytabular/table.py index abf956e..3c6df18 100644 --- a/pytabular/table.py +++ b/pytabular/table.py @@ -26,10 +26,10 @@ class PyTable(PyObject): Partitions (PyPartitions): The `PyPartitions()` in the table. Columns (PyColumns): The `PyColumns()` in the table. Measures (PyMeasures): The `PyMeasures()` in the table. - + Example: ```python title="Passing through PyTable to PyPartition" - + model.Tables[0].Partitions['Last Year'].refresh() # (1) ``` @@ -90,7 +90,7 @@ def row_count(self) -> int: Returns: int: Number of rows using `COUNTROWS`. - + Example: ```python model.Tables['Table Name'].row_count() @@ -103,14 +103,14 @@ def refresh(self, *args, **kwargs) -> pd.DataFrame: Returns: pd.DataFrame: Returns pandas dataframe with some refresh details. - + Example: ```python model.Tables['Table Name'].refresh() model.Tables['Table Name'].refresh(trace = None) # (1) ``` - + 1. You can pass through arguments to `PyRefresh`, like removing trace. """ return self.Model.refresh(self, *args, **kwargs) @@ -162,7 +162,7 @@ def query_all(self, query_function: str = "COUNTROWS(_)") -> pd.DataFrame: Returns: pd.DataFrame: Returns dataframe with results - + Example: ```python model.Tables.find('fact').query_all() # (1) @@ -187,7 +187,7 @@ def query_all(self, query_function: str = "COUNTROWS(_)") -> pd.DataFrame: def find_zero_rows(self) -> "PyTables": """Returns PyTables class of tables with zero rows queried. - + Returns: PyTables: A subset of the `PyTables` that contains zero rows. """ From 2ea18bee3b2d20425edb13b966f3c9cd9cf9ea45 Mon Sep 17 00:00:00 2001 From: Curtis Stallings Date: Sat, 18 Feb 2023 14:41:36 -0600 Subject: [PATCH 7/7] removed imports from circular issues --- pytabular/partition.py | 3 +-- pytabular/table.py | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pytabular/partition.py b/pytabular/partition.py index 3b21afe..3157754 100644 --- a/pytabular/partition.py +++ b/pytabular/partition.py @@ -5,7 +5,6 @@ import logging from pytabular.object import PyObject, PyObjects -from pytabular.table import PyTable from logic_utils import ticks_to_datetime import pandas as pd from datetime import datetime @@ -19,7 +18,7 @@ class PyPartition(PyObject): See methods for available uses. """ - def __init__(self, object, table: PyTable) -> None: + def __init__(self, object, table) -> None: """Extends from `PyObject` class. Adds a few custom rows to `rich` table for the partition. diff --git a/pytabular/table.py b/pytabular/table.py index 3c6df18..fee8a2c 100644 --- a/pytabular/table.py +++ b/pytabular/table.py @@ -8,7 +8,6 @@ from pytabular.column import PyColumn, PyColumns from pytabular.measure import PyMeasure, PyMeasures from pytabular.object import PyObjects, PyObject -from pytabular.pytabular import Tabular from logic_utils import ticks_to_datetime from datetime import datetime @@ -40,7 +39,7 @@ class PyTable(PyObject): -> `PyPartition` (.Name == 'Last Year') -> `.refresh()` """ - def __init__(self, object, model: Tabular) -> None: + def __init__(self, object, model) -> None: """Init extends from `PyObject` class. Also adds a few specific rows to the `rich` @@ -51,7 +50,7 @@ def __init__(self, object, model: Tabular) -> None: model (Tabular): The model that the table is in. """ super().__init__(object) - self.Model: Tabular = model + self.Model = model self.Partitions: PyPartitions = PyPartitions( [ PyPartition(partition, self)