diff --git a/README.md b/README.md index 41acfc8..6028e00 100644 --- a/README.md +++ b/README.md @@ -210,5 +210,39 @@ model.Refresh( ) ``` +#### Query as Another User +There are plenty of tools that allow you to query as an 'Effective User' inheriting their security when querying. This is an extremely valuable concept built natively into the .Net apis. My only gripe is they were all UI based. This allows you to programmatically connect as an effective user and query in Python. You could easily loop through all your users to run tests on their security. +```python +import pytabular as p + +#Connect to your model like usual... +model = p.Tabular(CONNECTION_STR) + +#This will be the query I run... +query_str = ''' +EVALUATE +SUMMARIZE( + 'Product Dimension', + 'Product Dimension'[Product Name], + "Total Product Sales", [Total Sales] +) +''' +#This will be the user I want to query as... +user_email = 'user1@company.com' + +#Base line, to query as the user connecting to the model. +model.Query(query_str) + +#Option 1, Connect via connection class... +user1 = p.Connection(model.Server, Effective_User = user_email) +user1.Query(query_str) + +#Option 2, Just add Effective_User +model.Query(query_str, Effective_User = user_email) + +#PyTabular will do it's best to handle multiple accounts... +#So you won't have to reconnect on every query +``` + ### Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index fb8f840..7ed11e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "python_tabular" -version = "0.1.9.4" +version = "0.2.1" authors = [ { name="Curtis Stallings", email="curtisrstallings@gmail.com" }, ] diff --git a/pytabular/__init__.py b/pytabular/__init__.py index 50937f3..cb506f2 100644 --- a/pytabular/__init__.py +++ b/pytabular/__init__.py @@ -60,5 +60,6 @@ from .tabular_tracing import Base_Trace, Refresh_Trace from .tabular_editor import Tabular_Editor from .best_practice_analyzer import BPA +from .query import Connection logger.debug(f"Import successful...") diff --git a/pytabular/pytabular.py b/pytabular/pytabular.py index 76f69b6..61922ba 100644 --- a/pytabular/pytabular.py +++ b/pytabular/pytabular.py @@ -74,7 +74,7 @@ def __init__(self, CONNECTION_STR: str): self.Model = self.Database.Model logger.info(f"Connected to Model - {self.Model.Name}") self.Adomd: Connection = Connection(self.Server) - + self.Effective_Users = dict() # Build PyObjects self.Reload_Model_Info() @@ -422,18 +422,32 @@ def dename(items): self.SaveChanges() return True - def Query(self, Query_Str: str) -> Union[pd.DataFrame, str, int]: + def Query( + self, Query_Str: str, Effective_User: str = None + ) -> Union[pd.DataFrame, str, int]: """Executes Query on Model and Returns Results in Pandas DataFrame Args: 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] + Effective_User (str): User you wish to query as. Returns: pd.DataFrame: Returns dataframe with results """ - return self.Adomd.Query(Query_Str) + if Effective_User is None: + return self.Adomd.Query(Query_Str) + else: + try: + conn = self.Effective_Users[Effective_User] + if isinstance(conn, Connection): + conn.Query(Query_Str) + except Exception: + conn = Connection(self.Server, Effective_User=Effective_User) + self.Effective_Users[Effective_User] = conn + + return conn.Query(Query_Str) def Query_Every_Column( self, query_function: str = "COUNTROWS(VALUES(_))" diff --git a/pytabular/query.py b/pytabular/query.py index 488125c..7c2b764 100644 --- a/pytabular/query.py +++ b/pytabular/query.py @@ -16,11 +16,14 @@ class Connection(AdomdConnection): AdomdConnection (_type_): _description_ """ - def __init__(self, Server) -> None: + def __init__(self, Server, Effective_User=None) -> None: super().__init__() - self.ConnectionString = ( + connection_string = ( f"{Server.ConnectionString}Password='{Server.ConnectionInfo.Password}'" ) + if Effective_User is not None: + connection_string += f";EffectiveUserName={Effective_User}" + self.ConnectionString = connection_string def Query(self, Query_Str: str) -> Union[pd.DataFrame, str, int]: """Executes Query on Model and Returns Results in Pandas DataFrame