"""Model flow. Provides functions to sync over model entities from main looker instance to client looker instance. Doesn't cover actual .model.lkml files in a repo. Refer to guthub_tools.py module for that. """ import requests from looker_updater import config def get_main_instance_model_json(model_name): """Get's json representation of a given model. Args: model_name (str): name of the model and (at the same time) its unique identifier. Should come in snake_case, without file extension. Example: 'core_metadata' Returns: dict: json representation of a model. bool: False if something went wrong and http response was other than 200. """ response = requests.get( f'{config.LOOKER_MAIN_INSTANCE_URL}/lookml_models/{model_name}', headers=config.LOOKER_MAIN_VALIDATION ) if response.status_code == 200: return response.json() return False def change_allowed_db_connection(model_json): """Changes allowed_db_connection_names value in a model json for a client instance appropriate one. Args: model_json: json representation of a model. Comes as a return value from get_main_instance_model_json. Returns: dict: json representation of a model. """ model_json['allowed_db_connection_names'] = ['looker_high_performance'] return model_json def check_model_name_on_client_instance(model_name): """"Checks if model exists on a client looker instance. Args: model_name (str): name of the model. Comes in snake_case without file extension. Example: core_metadata. Returns: bool: True if model exists, False otherwise. """ response = requests.get( f'{config.LOOKER_CLIENT_INSTANCE_URL}/lookml_models/{model_name}', headers=config.LOOKER_CLIENT_VALIDATION ) if response.status_code == 200: return True return False def update_model_on_client_instance(model_name, model_json): """Updates model on client looker instance. Args: model_name (str): name of the model. Comes in snake_case without file extension. Example: core_metadata. model_json (dict): json representation of a model. Comes as a return value of change_allowed_db_connection function. Returns: bool: True if model was successfully updated, False otherwise. """ response = requests.patch( f'{config.LOOKER_CLIENT_INSTANCE_URL}/lookml_models/{model_name}', headers=config.LOOKER_CLIENT_VALIDATION, json=model_json ) if response.status_code == 200: return True return False def post_model_to_client_instance(model_json): """Creates a model on client looker instance. Args: model_json (dict): json representation of a model. Comes as a return value of change_allowed_db_connection function. Returns: bool: True if successfully POSTed, False if something went wrong and http response was other than 200. """ response = requests.post( f'{config.LOOKER_CLIENT_INSTANCE_URL}/lookml_models', headers=config.LOOKER_CLIENT_VALIDATION, json=model_json ) if response.status_code == 200: return True return False def sync_lookml_model(model_name, model_json): """Syncs over lookml model. "One button" function for syncing lookml model. Checks if model with given name exists on client instance and updates it if that's the case. Otherwise, creates a new model on client instance. Notion: this only syncs _lookml_ model, not .view.lookml or .model.lkml files. Refer to github_tools.py for those. Args: model_name (str): name of the model and (at the same time) its unique identifier. Should come in snake_case, without file extension. Example: 'core_metadata' model_json (dic): json representation of a model. Returns: calls respective function. """ client_model_json = change_allowed_db_connection(model_json) if check_model_name_on_client_instance(model_name): return update_model_on_client_instance(model_name, client_model_json) else: return post_model_to_client_instance(client_model_json) def get_model_explores(model_json): """Gets list of all explores of a given model. Args: model_json (dict): json representation of a model. Comes as a return value of get_main_instance_model_json function. Returns: list: list of json representations of model explores. """ return model_json['explores'] def get_names_of_all_model_explores(model_explore): """Gets all explore explore_names of a model. Args: model_explore (list): list of json representations of model explores. Comes as a return value from get_model_explores function. Returns: set: all model explore explore_names for a given model. Items are strings. """ explore_names = set() for explore in model_explore: explore_names.add(explore['name']) return explore_names def get_set_of_explore_names(model_json): """Put together function for getting set of explore names of a model. Args: model_json (dict): json representation of a model. Comes as a return value of get_main_instance_model_json function. Returns: list: set of strings, strings are explore names. """ model_explores = get_model_explores(model_json) set_of_explore_names = get_names_of_all_model_explores(model_explores) return set_of_explore_names def get_model_explore_json(model_name, model_explore_name): """Gets a json representation of a model explore. Args: model_name (str): name of the model and (at the same time) its unique identifier. Should come in snake_case, without file extension. Example: 'core_metadata' model_explore_name (str): name of a model explore. Comes as an item from return value of get_names_of_all_model_explores function. Returns: dict: json representation of a specific model explore. bool: False if something went wrong and http response was other than 200. """ response = requests.get( f'{config.LOOKER_MAIN_INSTANCE_URL}/lookml_models/{model_name}/explores/{model_explore_name}', headers=config.LOOKER_MAIN_VALIDATION ) if response.status_code == 200: return response.json() return False def get_all_view_files(model_explore_json): """Gets names of all view files included as joins in a given model explore. Some joins have their names aliased, in which case there won't be a corresponding file where alias is used instead of a filename. For this case function first checks if there is a 'from' field for a join. If there is, then the value of 'from' field is stored in a resulting set. If there is no 'from' field in the join it means that 'name' field is an actual filename and not an alias, so it's added to resulting set. Args: model_explore_json (dict): json representation of a model explore. Comes as a return value of get_model_explore_json function. Returns: set: non-duplicate list of all .view.lkml file names included in a given explore as joins. Used further for syncing all these files through github_tools.py module. Notion! Contents of the set are file names WITHOUT file extensions. """ set_of_names = set() for join_field in model_explore_json['joins']: optional = join_field.get('from', '') if optional: set_of_names.add(optional) else: set_of_names.add(join_field['name']) return set_of_names def get_list_of_views_for_explore(model_name, model_explore_name): """Gets list of all joined view files for a given explore. Args: model_name (str): name of the model in snake_case without file extension. Example: core_metadata model_explore_name (str): name of a model explore. Returns: set: non-duplicate list of all .view.lkml file names included in a given explore as joins. Used further for syncing all these files through github_tools.py module. Notion! Contents of the set are file names WITHOUT file extensions. """ explore_json = get_model_explore_json(model_name, model_explore_name) set_of_file_names = get_all_view_files(explore_json) return set_of_file_names