def update_session(self) -> None: """Switch to a dev mode session and checkout the desired branch.""" if not self.project: raise FonzException( "No Looker project name provided. " "Please include the desired project name with --project") if not self.branch: raise FonzException( "No git branch provided. " "Please include the desired git branch name with --branch") logger.debug("Updating session to use development workspace.") url = utils.compose_url(self.base_url, path=["session"]) body = {"workspace_id": "dev"} response = self.session.patch(url=url, json=body) try: response.raise_for_status() except requests.exceptions.HTTPError as error: raise ConnectionError( f"Unable to update session to development workspace.\n" f'Error raised: "{error}"') logger.debug(f"Setting git branch to: {self.branch}") url = utils.compose_url(self.base_url, path=["projects", self.project, "git_branch"]) body = {"name": self.branch} response = self.session.put(url=url, json=body) try: response.raise_for_status() except requests.exceptions.HTTPError as error: raise ConnectionError( f'Unable to set git branch to "{self.branch}".\n' f'Error raised: "{error}"')
def get_explores(self) -> List[JsonDict]: """Get all explores from the LookmlModel endpoint.""" logger.debug("Getting all explores in Looker instance.") url = utils.compose_url(self.base_url, path=["lookml_models"]) response = self.session.get(url=url) try: response.raise_for_status() except requests.exceptions.HTTPError as error: raise FonzException( f'Unable to retrieve explores.\nError raised: "{error}"') explores = [] logger.debug(f"Filtering explores for project: {self.project}") for model in response.json(): if model["project_name"] == self.project: for explore in model["explores"]: explores.append({ "model": model["name"], "explore": explore["name"] }) return explores
def run_query(self, query_id: int) -> List[JsonDict]: """Run a Looker query by ID and return the JSON result.""" logger.debug(f"Running query {query_id}") url = utils.compose_url(self.base_url, path=["queries", query_id, "run", "json"]) response = self.session.get(url=url) try: response.raise_for_status() except requests.exceptions.HTTPError as error: raise FonzException( f'Failed to run query "{query_id}".\nError raised: "{error}"') query_result = response.json() return query_result
def get_query_sql(self, query_id: int) -> str: """Collect the SQL string for a Looker query.""" logger.debug(f"Getting SQL for query {query_id}") url = utils.compose_url(self.base_url, path=["queries", query_id, "run", "sql"]) response = self.session.get(url=url) try: response.raise_for_status() except requests.exceptions.HTTPError as error: raise FonzException( f'Failed to obtain SQL for query "{query_id}".\nError raised: "{error}"' ) sql = response.text return sql
def connect(self) -> None: """Authenticate, start a dev session, check out specified branch.""" logger.info("Authenticating Looker credentials. \n") url = utils.compose_url(self.base_url, path=["login"]) body = { "client_id": self.client_id, "client_secret": self.client_secret } response = self.session.post(url=url, data=body) try: response.raise_for_status() except requests.exceptions.HTTPError as error: raise ConnectionError( f"Failed to authenticate to {url}\n" f'Attempted authentication with client ID "{self.client_id}"\n' f'Error raised: "{error}"') access_token = response.json()["access_token"] self.session.headers = {"Authorization": f"token {access_token}"}
def create_query(self, model: str, explore_name: str, dimensions: List[str]) -> int: """Build a Looker query using all the specified dimensions.""" logger.debug(f"Creating query for {explore_name}") url = utils.compose_url(self.base_url, path=["queries"]) body = { "model": model, "view": explore_name, "fields": dimensions, "limit": 1 } response = self.session.post(url=url, json=body) try: response.raise_for_status() except requests.exceptions.HTTPError as error: raise FonzException( f'Unable to create a query for "{model}/{explore_name}".\n' f'Error raised: "{error}"') query_id = response.json()["id"] return query_id
def get_dimensions(self, model: str, explore_name: str) -> List[str]: """Get dimensions for an explore from the LookmlModel endpoint.""" logger.debug(f"Getting dimensions for {explore_name}") url = utils.compose_url( self.base_url, path=["lookml_models", model, "explores", explore_name]) response = self.session.get(url=url) try: response.raise_for_status() except requests.exceptions.HTTPError as error: raise FonzException( f'Unable to get dimensions for explore "{explore_name}".\n' f'Error raised: "{error}"') dimensions = [] for dimension in response.json()["fields"]["dimensions"]: if "fonz: ignore" not in dimension["sql"]: dimensions.append(dimension["name"]) return dimensions
import pytest import ast import requests import requests_mock from fonz.utils import compose_url from tests.constants import TEST_BASE_URL from urllib.parse import parse_qs BASE_URL_30 = compose_url(TEST_BASE_URL + ":19999", path=["api", "3.0"]) lookml_models = [ { "name": "model_one", "project_name": "test_project", "explores": [{ "name": "explore_one" }, { "name": "explore_two" }], }, { "name": "model_two", "project_name": "not_test_project", "explores": [{ "name": "explore_three" }, { "name": "explore_four" }], }, ]
def test_compose_url_multiple_path_components(): url = utils.compose_url(TEST_BASE_URL, ["api", "3.0", "login", "42", "auth", "27"]) assert url == "https://test.looker.com/api/3.0/login/42/auth/27"
def test_compose_url_one_path_component(): url = utils.compose_url(TEST_BASE_URL, ["api"]) assert url == "https://test.looker.com/api"
def test_compose_url_with_extra_slashes(): url = utils.compose_url(TEST_BASE_URL + "/", ["/api//", "3.0/login/"]) assert url == "https://test.looker.com/api/3.0/login"