diff --git a/dist/python_tabular-0.0.90-py3-none-any.whl b/dist/python_tabular-0.0.90-py3-none-any.whl new file mode 100644 index 0000000..f03cc01 Binary files /dev/null and b/dist/python_tabular-0.0.90-py3-none-any.whl differ diff --git a/dist/python_tabular-0.0.90.tar.gz b/dist/python_tabular-0.0.90.tar.gz new file mode 100644 index 0000000..e3f817b Binary files /dev/null and b/dist/python_tabular-0.0.90.tar.gz differ diff --git a/docs/Best Practice Analyzer.md b/docs/Best Practice Analyzer.md index 352ecae..32c0d6f 100644 --- a/docs/Best Practice Analyzer.md +++ b/docs/Best Practice Analyzer.md @@ -2,7 +2,7 @@ ## BPA -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/best_practice_analyzer.py\#L34) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/best_practice_analyzer.py\#L35) ```python BPA( File_Path: str = 'Default' @@ -18,7 +18,7 @@ Setting BPA Class for future work... ### Download_BPA_File -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/best_practice_analyzer.py\#L8) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/best_practice_analyzer.py\#L9) ```python .Download_BPA_File( Download_Location: str = 'https: //raw.githubusercontent.com/microsoft/Analysis-Services/master/BestPracticeRules/BPARules.json', diff --git a/docs/Examples.md b/docs/Examples.md index fe076d8..464ef0d 100644 --- a/docs/Examples.md +++ b/docs/Examples.md @@ -2,7 +2,7 @@ ### Return_Zero_Row_Tables -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/basic_checks.py\#L8) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/basic_checks.py\#L9) ```python .Return_Zero_Row_Tables( model: pytabular.Tabular @@ -27,7 +27,7 @@ Returns list of table names of those that are returning isna() ### Table_Last_Refresh_Times -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/basic_checks.py\#L22) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/basic_checks.py\#L23) ```python .Table_Last_Refresh_Times( model: pytabular.Tabular, group_partition: bool = True @@ -56,7 +56,7 @@ If group_partition == True and the table has multiple partitions, then df.groupb ### BPA_Violations_To_DF -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/basic_checks.py\#L50) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/basic_checks.py\#L51) ```python .BPA_Violations_To_DF( model: pytabular.Tabular, te2: str, bpa: str @@ -78,3 +78,36 @@ Runs BPA Analyzer from TE2 and outputs result into a DF. * **DataFrame** : Super simple right now. Just splits into two columns.. The object in violation and the rule. + +---- + + +### Last_X_Interval +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/basic_checks.py\#L67) +```python +.Last_X_Interval( + Model: pytabular.Tabular, Measure: Union[str, pytabular.pytabular.Measure], + Column_Name: Union[str, None] = None, + Date_Column_Identifier: str = "'Date'[DATE_DTE_KEY]", + Number_Of_Intervals: int = 90, Interval: str = 'DAY' +) +``` + +--- +Pulls the Last X Interval (Ex Last 90 Days) of a specific measure. + + +**Args** + +* **Model** (pytabular.Tabular) : Tabular Model to perform query on. +* **Measure** (Union[str,pytabular.pytabular.Measure]) : Measure to query. If string, will first check for a measure in the model with that name, otherwise will assume it is a DAX Expression (Ex SUM(FactTable[ColumnValue]) ) and perform that as expression +* **Column_Name** (Union[str,None], optional) : Column Name to be outputted in DataFrame. You can provide your own otherwise will take from the Measure Name. Defaults to "Result". +* **Date_Column_Identifier** (str, optional) : Date column dax identifier. Defaults to "'Date'[DATE_DTE_KEY]". +* **Number_Of_Intervals** (int, optional) : This is used to plug in the variables for [DATESINPERIOD](https://docs.microsoft.com/en-us/dax/datesinperiod-function-dax). Defaults to 90. +* **Interval** (str, optional) : Sames as Number_Of_Intervals. Used to plug in parameters of DAX function [DATESINPERIOD](https://docs.microsoft.com/en-us/dax/datesinperiod-function-dax). Defaults to "DAY". Possible options are "DAY", "MONTH", "QUARTER", and "YEAR" + + +**Returns** + +* **DataFrame** : Pandas DataFrame of results. + diff --git a/docs/Logic Utils.md b/docs/Logic Utils.md index 6281c7e..fd2bbba 100644 --- a/docs/Logic Utils.md +++ b/docs/Logic Utils.md @@ -2,7 +2,7 @@ ### ticks_to_datetime -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/logic_utils.py\#L11) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/logic_utils.py\#L12) ```python .ticks_to_datetime( ticks: int @@ -27,7 +27,7 @@ Converts a C# System DateTime Tick into a Python DateTime ### pandas_datatype_to_tabular_datatype -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/logic_utils.py\#L22) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/logic_utils.py\#L23) ```python .pandas_datatype_to_tabular_datatype( df: pd.DataFrame @@ -52,7 +52,7 @@ WiP takes dataframe columns and gets respective tabular column datatype. ([NumP ### pd_dataframe_to_m_expression -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/logic_utils.py\#L76) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/logic_utils.py\#L77) ```python .pd_dataframe_to_m_expression( df: pd.DataFrame @@ -94,7 +94,7 @@ Source ### remove_folder_and_contents -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/logic_utils.py\#L120) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/logic_utils.py\#L121) ```python .remove_folder_and_contents( folder_location @@ -109,3 +109,29 @@ Internal used in tabular_editor.py and best_practice_analyzer.py. * **folder_location** (str) : Folder path to remove directory and contents. + +---- + + +### remove_suffix +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/logic_utils.py\#L132) +```python +.remove_suffix( + input_string, suffix +) +``` + +--- +Adding for >3.9 compatiblity. (Stackoverflow Answer)[https://stackoverflow.com/questions/66683630/removesuffix-returns-error-str-object-has-no-attribute-removesuffix] + + +**Args** + +* **input_string** (str) : input string to remove suffix from +* **suffix** (str) : suffix to be removed + + +**Returns** + +* **str** : input_str with suffix removed + diff --git a/docs/Tabular Editor 2.md b/docs/Tabular Editor 2.md index 7753d02..cf0441c 100644 --- a/docs/Tabular Editor 2.md +++ b/docs/Tabular Editor 2.md @@ -2,7 +2,7 @@ ## Tabular_Editor -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_editor.py\#L38) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_editor.py\#L39) ```python Tabular_Editor( EXE_File_Path: str = 'Default' @@ -18,7 +18,7 @@ Setting Tabular_Editor Class for future work. ### Download_Tabular_Editor -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_editor.py\#L8) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_editor.py\#L9) ```python .Download_Tabular_Editor( Download_Location: str = 'https: //github.com/TabularEditor/TabularEditor/releases/download/2.16.7/TabularEditor.Portable.zip', @@ -39,5 +39,5 @@ Runs a request.get() to retrieve the zip file from web. Will unzip response and **Returns** -* **str** : _description_ +* **str** : File path of TabularEditor.exe diff --git a/docs/Tabular.md b/docs/Tabular.md index 42e6068..b051f9a 100644 --- a/docs/Tabular.md +++ b/docs/Tabular.md @@ -2,7 +2,7 @@ ## Tabular -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L22) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L26) ```python Tabular( CONNECTION_STR: str @@ -16,7 +16,7 @@ Tabular Class to perform operations: [Microsoft.AnalysisServices.Tabular](https: **Args** -* **CONNECTION_STR** (str) : [Connection String](https://docs.microsoft.com/en-us/analysis-services/instances/connection-string-properties-analysis-services?view=asallproducts-allversions) +* **CONNECTION_STR** (str) : Valid [Connection String](https://docs.microsoft.com/en-us/analysis-services/instances/connection-string-properties-analysis-services?view=asallproducts-allversions) for connecting to a Tabular Model. @@ -24,7 +24,7 @@ Tabular Class to perform operations: [Microsoft.AnalysisServices.Tabular](https: ### .Reload_Model_Info -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L54) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L58) ```python .Reload_Model_Info() ``` @@ -39,7 +39,7 @@ Runs on __init__ iterates through details, can be called after any model changes ### .Disconnect -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L65) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L70) ```python .Disconnect() ``` @@ -54,26 +54,42 @@ Disconnects from Model ### .Refresh -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L80) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L78) ```python .Refresh( - Object: Union[str, Table, Partition, Iterable], RefreshType = RefreshType.Full, - Run: bool = True + Object: Union[str, Table, Partition, Dict[str, Any]], + RefreshType: RefreshType = RefreshType.Full, Tracing = False ) ``` --- -Input Object(s) to be refreshed in the tabular model. Combine with .SaveChanges() to actually run the refresh on the model. +Refreshes table(s) and partition(s). **Args** -* **Object** (Union[str,Table,Partition,Iterable]) : Can be str(table name only), Table object, Partition object, or an iterable combination of the three. -* **RefreshType** (_type_, optional) : [RefreshType](https://docs.microsoft.com/en-us/dotnet/api/microsoft.analysisservices.tabular.refreshtype?view=analysisservices-dotnet). Defaults to RefreshType.Full. +* **Object** (Union[ str, Table, Partition, Dict[str, Any], Iterable[str, Table, Partition, Dict[str, Any]] ]) : Designed to handle a few different ways of selecting a refresh. +* **RefreshType** (RefreshType, optional) : See [RefreshType](https://docs.microsoft.com/en-us/dotnet/api/microsoft.analysisservices.tabular.refreshtype?view=analysisservices-dotnet). Defaults to RefreshType.Full. +* **Tracing** (bool, optional) : Currently just some basic tracing to track refreshes. Defaults to False. +str == 'Table_Name' +Table == Table Object +Partition == Partition Object +Dict[str, Any] == A way to specify a partition of group of partitions. For ex. {'Table_Name':'Partition1'} or {'Table_Name':['Partition1','Partition2']}. NOTE you can also change out the strings for partition or tables objects. + + +**Raises** + +* **Exception** : Raises exception if unable to find table or partition via string. + + + +**Returns** + +* **WIP** : WIP ### .Update -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L110) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L171) ```python .Update( UpdateOptions: UpdateOptions = UpdateOptions.ExpandFull @@ -95,22 +111,14 @@ Input Object(s) to be refreshed in the tabular model. Combine with .SaveChanges( ### .SaveChanges -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L121) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L182) ```python .SaveChanges() ``` ---- -TODO need to clean this up and add more flexibility. -Just a simple wrapper to call self.Model.SaveChanges() - - -**Returns** - -bool: ### .Backup_Table -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L131) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L204) ```python .Backup_Table( table_str: str @@ -134,7 +142,7 @@ Refresh is performed from source during backup. ### .Revert_Table -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L198) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L271) ```python .Revert_Table( table_str: str @@ -162,7 +170,7 @@ Example scenario -> ### .Query -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L263) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L336) ```python .Query( Query_Str: str @@ -175,7 +183,9 @@ Executes Query on Model and Returns Results in Pandas DataFrame **Args** -* **Query_Str** (str) : Dax Query. Note, needs full syntax (ex: EVALUATE). See https://docs.microsoft.com/en-us/dax/dax-queries +* **Query_Str** (str) : Dax Query. Note, needs full syntax (ex: EVALUATE). See (DAX Queries)[https://docs.microsoft.com/en-us/dax/dax-queries]. +Will check if query string is a file. If it is, then it will perform a query on whatever is read from the file. +It is also possible to query DMV. For example. Query("select * from $SYSTEM.DISCOVER_TRACE_EVENT_CATEGORIES"). See (DMVs)[https://docs.microsoft.com/en-us/analysis-services/instances/use-dynamic-management-views-dmvs-to-monitor-analysis-services?view=asallproducts-allversions] **Returns** @@ -184,7 +194,7 @@ Executes Query on Model and Returns Results in Pandas DataFrame ### .Query_Every_Column -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L293) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L377) ```python .Query_Every_Column( query_function: str = 'COUNTROWS(VALUES(_))' @@ -207,7 +217,7 @@ This will dynamically create a query to pull all columns from the model and run ### .Query_Every_Table -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L315) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L399) ```python .Query_Every_Table( query_function: str = 'COUNTROWS(_)' @@ -230,7 +240,7 @@ It will replace the _ with the table to run. ### .Analyze_BPA -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L335) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L419) ```python .Analyze_BPA( Tabular_Editor_Exe: str, Best_Practice_Analyzer: str @@ -255,7 +265,7 @@ Takes your Tabular Model and performs TE2s BPA. Runs through Command line. ### .Create_Table -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L359) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/pytabular.py\#L443) ```python .Create_Table( df: pd.DataFrame, table_name: str diff --git a/docs/Traces.md b/docs/Traces.md index a780792..7458084 100644 --- a/docs/Traces.md +++ b/docs/Traces.md @@ -2,7 +2,7 @@ ## Base_Trace -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L10) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L11) ```python Base_Trace( Tabular_Class, Trace_Events: List[TraceEvent], @@ -29,7 +29,7 @@ Generates Trace to be run on Server. This is the base class to customize the typ ### .Build -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L37) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L38) ```python .Build() ``` @@ -44,7 +44,7 @@ Run on initialization. This will take the inputed arguments for the class and at ### .Arguments -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L62) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L63) ```python .Arguments( Trace_Events: List[TraceEvent], Trace_Event_Columns: List[TraceColumn], @@ -54,7 +54,7 @@ Run on initialization. This will take the inputed arguments for the class and at ### .Add -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L65) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L66) ```python .Add() ``` @@ -69,7 +69,7 @@ Runs on initialization. Adds built Trace to the Server. ### .Update -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L74) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L75) ```python .Update() ``` @@ -84,7 +84,7 @@ Runs on initialization. Syncs with Server. ### .Start -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L83) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L84) ```python .Start() ``` @@ -99,7 +99,7 @@ Call when you want to start the Trace ### .Stop -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L92) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L93) ```python .Stop() ``` @@ -114,7 +114,7 @@ Call when you want to stop the Trace ### .Drop -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L101) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L102) ```python .Drop() ``` @@ -129,7 +129,7 @@ Call when you want to drop the Trace ### .Query_DMV_For_Event_Categories -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L110) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L111) ```python .Query_DMV_For_Event_Categories() ``` @@ -147,7 +147,7 @@ Internal use. Called during the building process to locate allowed columns for e ## Refresh_Trace -[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L138) +[source](https://github.com/Curts0/PyTabular\blob\master\pytabular/tabular_tracing.py\#L139) ```python Refresh_Trace( Tabular_Class, @@ -158,7 +158,7 @@ Refresh_Trace( TraceColumn.CurrentTime, TraceColumn.ObjectName, TraceColumn.ObjectPath, TraceColumn.DatabaseName, TraceColumn.SessionID, TraceColumn.TextData, TraceColumn.EventClass, TraceColumn.ProgressTotal], - Handler: Callable = default_refresh_handler + Handler: Callable = refresh_handler ) ``` diff --git a/docs/index.md b/docs/index.md index 99ae859..fa477f2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,7 +6,7 @@ ### What is it? -PyTabular is a python package that allows for programmatic execution on your tabular models! This is possible thanks to [Pythonnet](https://pythonnet.github.io/) and Microsoft's [.Net APIs on Azure Analysis Services](https://docs.microsoft.com/en-us/dotnet/api/microsoft.analysisservices?view=analysisservices-dotnet). The package should have the dll files included when you import it. See [Documentation Here](https://curts0.github.io/PyTabular/) +PyTabular is a python package that allows for programmatic execution on your tabular models! This is possible thanks to [Pythonnet](https://pythonnet.github.io/) and Microsoft's [.Net APIs on Azure Analysis Services](https://docs.microsoft.com/en-us/dotnet/api/microsoft.analysisservices?view=analysisservices-dotnet). The package should have the dll files included when you import it. See [Documentation Here](https://curts0.github.io/PyTabular/). PyTabular is still considered alpha while I'm working on building out the proper tests and testing environments, so I can ensure some kind of stability in features. ### Getting Started @@ -21,25 +21,49 @@ In your python environment, import pytabular and call the main Tabular Class. On model = pytabular.Tabular(CONNECTION_STR) ``` -DAX Query +Query Model ```python - model.Query(DAX_QUERY) - # Returns a Pandas DataFrame + #Run basic queries + DAX_QUERY = "EVALUATE TOPN(100, 'Table1')" + model.Query(DAX_QUERY) #returns pd.DataFrame() + + #or... + DMV_QUERY = "select * from $SYSTEM.DISCOVER_TRACE_EVENT_CATEGORIES" + model.Query(DMV_QUERY) #returns pd.DataFrame() + + #or... + SINGLE_VALUE_QUERY_EX = "EVALUATE {1}" + model.Query(SINGLE_VALUE_QUERY_EX) #returns 1 ``` -[Refresh Tables and Partitions](https://curts0.github.io/PyTabular/Tabular/#refresh) +See [Refresh Tables and Partitions](https://curts0.github.io/PyTabular/Tabular/#refresh). ```python - #Can be str(table name only), Table object, Partition object, or an iterable combination of the three. + #You have a few options when refreshing. model.Refresh('Table Name') - tables_to_refresh = ['Table Name 1', 'Table Name 2', , ] - #Queue up the tables and partitions that you want to refresh. - model.Refresh(tables_to_refresh) - #NOTE if you monitor the logs you will notice a Trace is executed on the refreshes. + + #or... + model.Refresh(['Table1','Table2','Table3']) + + #or... + model.Refresh(
) + + #or... + model.Refresh() + + #or... + model.Refresh({'Table Name':'Partition Name'}) + + #or any kind of weird combination like + model.Refresh([{
:,'Table Name':['Partition1','Partition2']},'Table Name','Table Name2']) + + #Add Tracing=True for simple Traces tracking the refresh. + model.Refresh(['Table1','Table2'], Tracing=True) + ``` -Built In Dax Query Helpers +Built In Dax Query Helpers. In-case you want to run some quick queries similar to what vertipaq analyzer will do when getting row counts. ```python #Query Every Column @@ -57,14 +81,14 @@ Built In Dax Query Helpers ''' ``` -Backup & Revert a Table in Memory +Backup & Revert a Table in Memory. USE WITH CAUTION, obviously not in PROD. I have been experimenting with this concept. ```python model.Backup_Table('TableName') #This will backup the table with surround items (columns,measures,relationships,roles,hierarchies,etc.) and will add a suffix of '_backup' #Make any changes to your original table and then revert or delete backup as necessary model.Revert_Table('TableName') #This will essentially replace your original with _backup ``` -Run BPA from TE2 +Run BPA from TE2. Roadmap to make this more robust, and allow you to run all the command line interfaces that TE2 has to offer. ```python TE2 = pytabular.TE2() #Feel free to input your TE2 File path or this will download for you. BPA = pytabular.BPA() #Fee free to input your own BPA file or this will download for you from: https://raw.githubusercontent.com/microsoft/Analysis-Services/master/BestPracticeRules/BPARules.json diff --git a/pyproject.toml b/pyproject.toml index 0ae510f..f084d43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "python_tabular" -version = "0.0.80" +version = "0.0.90" authors = [ { name="Curtis Stallings", email="curtisrstallings@gmail.com" }, ] diff --git a/pytabular/__init__.py b/pytabular/__init__.py index 3200ec4..0398f68 100644 --- a/pytabular/__init__.py +++ b/pytabular/__init__.py @@ -1,16 +1,26 @@ import logging -logging.basicConfig(level=logging.DEBUG,format='%(asctime)s :: %(module)s :: %(levelname)s :: %(message)s') +import os +import sys +import platform + +logging.basicConfig(level=logging.DEBUG, + format='%(asctime)s::%(module)s::%(funcName)s::%(levelname)s::%(message)s', + datefmt='%y/%m/%d %H:%M:%S %z') logger = logging.getLogger('PyTabular') logger.setLevel(logging.DEBUG) -logger.debug('Logging configured...') -logger.debug(f'To update PyTabular logger...') -logger.debug(f'>>> import logging') -logger.debug(f'>>> pytabular.logger.setLevel(level=logging.INFO)') -logger.debug(f'Visit https://docs.python.org/3/library/logging.html#logging-levels for info...') +logger.info('Logging configured...') +logger.info(f'To update PyTabular logger...') +logger.info(f'>>> import logging') +logger.info(f'>>> pytabular.logger.setLevel(level=logging.INFO)') +logger.info(f'See https://docs.python.org/3/library/logging.html#logging-levels') + + +logger.debug(f'Python Version::{sys.version}') +logger.debug(f'Python Location::{sys.exec_prefix}') +logger.debug(f'Package Location::{__file__}') +logger.debug(f'Working Directory::{os.getcwd()}') +logger.debug(f'Platform::{sys.platform}-{platform.release()}') -logger.debug(f'Setting up file paths for {__file__}') -import os -import sys dll = os.path.join(os.path.dirname(__file__),"dll") sys.path.append(dll) sys.path.append(os.path.dirname(__file__)) @@ -31,4 +41,4 @@ from . tabular_tracing import Base_Trace, Refresh_Trace from . tabular_editor import Tabular_Editor from . best_practice_analyzer import BPA -logging.debug(f'Import successful...') \ No newline at end of file +logger.info(f'Import successful...') \ No newline at end of file diff --git a/pytabular/logic_utils.py b/pytabular/logic_utils.py index 2bccca6..ef29bcd 100644 --- a/pytabular/logic_utils.py +++ b/pytabular/logic_utils.py @@ -141,4 +141,15 @@ def remove_suffix(input_string, suffix): ''' if suffix and input_string.endswith(suffix): return input_string[:-len(suffix)] - return input_string \ No newline at end of file + return input_string + +def sql_wrap_count_around_query(original_query:str) -> str: + '''Simple string formating to get the total row count of a sql query. + + Args: + original_query (str): Regular sql query to get count of. + + Returns: + str: f"SELECT COUNT(1) FROM ({original_query}) temp_table" + ''' + return f"SELECT COUNT(1) FROM ({original_query}) temp_table" \ No newline at end of file diff --git a/pytabular/pytabular.py b/pytabular/pytabular.py index 485fe85..62b603b 100644 --- a/pytabular/pytabular.py +++ b/pytabular/pytabular.py @@ -17,6 +17,8 @@ import os import subprocess import atexit + + from logic_utils import pd_dataframe_to_m_expression, pandas_datatype_to_tabular_datatype, ticks_to_datetime, remove_suffix from tabular_tracing import Refresh_Trace @@ -349,7 +351,6 @@ def Query(self,Query_Str:str) -> Union[pd.DataFrame,str,int]: Query_Str = str(file.read()) - logger.debug(f'Beginning to Query...') try: logger.debug(f'Attempting to Open Adomd Connection...') self.DaxConnection.Open() @@ -357,7 +358,7 @@ def Query(self,Query_Str:str) -> Union[pd.DataFrame,str,int]: except: logger.debug(f'Connection skipped already connected...') pass - logger.info(f'Querying Model with Query...') + logger.info(f'Querying Model...') Query = AdomdCommand(Query_Str, self.DaxConnection).ExecuteReader() logger.debug(f'Determining Field Count...') Column_Headers = [(index,Query.GetName(index)) for index in range(0,Query.FieldCount)] diff --git a/pytabular/tabular_tracing.py b/pytabular/tabular_tracing.py index f068dd4..e85493c 100644 --- a/pytabular/tabular_tracing.py +++ b/pytabular/tabular_tracing.py @@ -130,7 +130,7 @@ def Query_DMV_For_Event_Categories(self): return Event_Categories -def default_refresh_handler(source, args): +def refresh_handler(source, args): if args.EventSubclass == TraceEventSubclass.ReadData: logger.debug(f'{args.ProgressTotal} - {args.ObjectPath}') else: @@ -144,5 +144,5 @@ class Refresh_Trace(Base_Trace): ''' def __init__(self, Tabular_Class, Trace_Events: List[TraceEvent] = [TraceEventClass.ProgressReportBegin,TraceEventClass.ProgressReportCurrent,TraceEventClass.ProgressReportEnd,TraceEventClass.ProgressReportError], Trace_Event_Columns: List[TraceColumn] = [TraceColumn.EventSubclass,TraceColumn.CurrentTime, TraceColumn.ObjectName, TraceColumn.ObjectPath, TraceColumn.DatabaseName, TraceColumn.SessionID, TraceColumn.TextData, TraceColumn.EventClass, TraceColumn.ProgressTotal], - Handler: Callable = default_refresh_handler) -> None: + Handler: Callable = refresh_handler) -> None: super().__init__(Tabular_Class, Trace_Events, Trace_Event_Columns, Handler) \ No newline at end of file diff --git a/test/test_tabular.py b/test/test_tabular.py index f252f2c..f614d03 100644 --- a/test/test_tabular.py +++ b/test/test_tabular.py @@ -23,8 +23,9 @@ def test_database(model): @pytest.mark.parametrize("model",testing_parameters) def test_query(model): - result = model.Query('EVALUATE {1}') - assert result == 1 + int_result = model.Query('EVALUATE {1}') + text_result = model.Query('EVALUATE {"Hello World"}') + assert int_result == 1 and text_result == 'Hello World' def remove_testing_table(model): @@ -41,7 +42,8 @@ def test_pre_table_checks(model): @pytest.mark.parametrize("model",testing_parameters) def test_create_table(model): df = pd.DataFrame(data={'col1':[1,2,3],'col2':['four','five','six']}) - assert model.Create_Table(df,testingtable) + model.Create_Table(df,testingtable) + assert len(model.Query(f"EVALUATE {testingtable}")) == 3 @pytest.mark.parametrize("model",testing_parameters) def test_backingup_table(model):