def add_share(self, shares, *, can_edit=False): """Share a cube to new groups and users By default will give query access. Set can_edit to True for editor access. Args: shares (list[Group,User]): Users and groups to share the cube to can_edit (bool): (Optional) True for edit privileges """ shares = PySenseUtils.make_iterable(shares) curr_shares_arr = self.get_shares_json() rule = 'r' if can_edit is False else 'w' curr_id_arr = [] for share in curr_shares_arr: party_id = share['partyId'] curr_id_arr.append(party_id) del share['partyId'] share['party'] = party_id for share in shares: share_id = share.get_id() if share_id is None: raise PySenseException.PySenseException( 'No id found for {}'.format(share)) elif share_id in curr_id_arr: index = curr_id_arr.index(share_id) curr_shares_arr[index]['permission'] = rule elif isinstance(share, PySenseUser.User): curr_shares_arr.append({ 'party': share.get_id(), 'type': 'user', 'permission': rule }) elif isinstance(share, PySenseGroup.Group): curr_shares_arr.append({ 'party': share.get_id(), 'type': 'group', 'rule': rule }) else: raise PySenseException.PySenseException( 'Add Share expected User or group, got {}'.format( type(share))) self.py_client.connector.rest_call( 'put', 'api/elasticubes/{}/{}/permissions'.format( self.server_address, self.get_title(url_encoded=True)), json_payload=curr_shares_arr)
def start_build(self, build_type, *, row_limit=None): """Initiates a build of the data model Only supported on Linux Args: build_type (str): Type of build (schema_changes, by_table, full, publish) row_limit (int): (Optional) Number of rows to build Returns: BuildTask: The build task object for the build """ PySenseUtils.validate_version(self.py_client, SisenseVersion.Version.LINUX, 'start_build') build_type = build_type.lower() if build_type not in ['schema_changes', 'by_table', 'full', 'publish']: raise PySenseException.PySenseException('Unsupported build type {}'.format(build_type)) json_payload = { 'datamodelId': self.get_oid(), 'buildType': build_type } if row_limit is not None: json_payload['rowLimit'] = row_limit resp_json = self.py_client.connector.rest_call('post', 'api/v2/builds', json_payload=json_payload) return PySenseBuildTask.BuildTask(self.py_client, resp_json)
def remove_shares(self, shares): """Unshare a cube to groups and users To unshare a cube we have to: - Query for the whom the cube is currently shared with - Delete the users/groups we want to unshare with - Re upload the reduced share Args: shares (list[Group,User]): Users and groups to unshare the cube to """ curr_shares_arr = self.get_shares_json() curr_id_arr = [] for share in curr_shares_arr: curr_id_arr.append(share['partyId']) for share in PySenseUtils.make_iterable(shares): share_id = share.get_id() if share_id is None: raise PySenseException.PySenseException( 'No id found for {}'.format(share)) elif share_id in curr_id_arr: index = curr_id_arr.index(share_id) del curr_shares_arr[index] del curr_id_arr[index] self.py_client.connector.rest_call( 'put', 'api/elasticubes/{}/{}/permissions'.format( self.server_address, self.get_title(url_encoded=True)), json_payload=curr_shares_arr)
def __init__(self, host, token, version, *, debug=False, verify=True, param_dict=None, connector=None): """ Initializes a PySense instance Args: host (str): host address token (json): a json bearer token with format {'authorization': "Bearer yourlongaccesstokenstringthatyougotfromapreviouslogin"} version (str): version (either 'Windows' or 'Linux') debug (bool): (Optional) If true, prints detailed REST API logs to console. False by default. verify (bool): (Optional) If false, disables SSL Certification. True by default. param_dict (dict): (Optional) For passing in additional parameters connector (RestConnector): (Optional) Pass in your own connector (normally used for mock tests) """ if param_dict is None: self.param_dict = {} else: self.param_dict = param_dict default_dict = {'CUBE_CACHE_TIMEOUT_SECONDS': 60} for key, value in default_dict.items(): if key not in self.param_dict: self.param_dict[key] = value # Verify version if version.lower() == 'windows': self.version = SisenseVersion.Version.WINDOWS elif version.lower() == 'linux': self.version = SisenseVersion.Version.LINUX else: raise PySenseException.PySenseException( '{} not a valid OS. Please select Linux or Windows'.format( version)) # Initiate PySense Connection if connector is not None: self.connector = connector else: self.connector = PySenseRestConnector.RestConnector( host, token, debug, verify) # Set up Roles roles = self.connector.rest_call('get', 'api/roles') self.roles = {} for role in roles: if role['name'] in [ 'dataDesigner', 'super', 'dataAdmin', 'admin', 'contributor', 'consumer' ]: self.roles[SisenseRole.Role.from_str( role['name'])] = role['_id']
def get_oid(self): """Returns the Elasticube id""" if 'oid' in self.json: return self.json['oid'] else: raise PySenseException( 'Cube {} is not currently running so this action cannot be performed' )
def get_role_by_id(self, role_id): """Get role from id Args: role_id (str): The role id for the role to find Returns: Role: The role with the given role id """ for role in self.roles: if self.roles[role] == role_id: return role raise PySenseException.PySenseException( 'No role with id {} found'.format(role_id))
def get_user_by_email(self, email): """Returns a single user based on email Args: email (str): The email of the user to get Returns: User: The user with the email address """ users = self.get_users(email=email) if len(users) == 0: None elif len(users) > 1: raise PySenseException.PySenseException('{} users with email {} found. '.format(len(users), email)) else: return users[0]
def from_str(role): """Returns the Role for a given string""" role = role.lower().replace(" ", "") if role in ['datadesigner', 'data_designer']: return Role.DATA_DESIGNER elif role in ['super', 'sysadmin', 'sys_admin']: return Role.SYS_ADMIN elif role in ['dataadmin', 'data_admin']: return Role.DATA_ADMIN elif role in ['admin']: return Role.ADMIN elif role in ['contributor', 'designer']: return Role.DESIGNER elif role in ['consumer', 'viewer']: return Role.VIEWER else: raise PySenseException.PySenseException( 'No such role {} found'.format(role))
def generate_token(host, username, password, verify=True): """Creates a new PySense client with the username and password Args: host (str): The Sisense server address username (str): Sisense username password (str): Sisense password verify (bool): SSL Verification Returns: JSON: A json authorization header """ host = PySenseUtils.format_host(host) data = {'username': username, 'password': password} resp = requests.post('{}/api/v1/authentication/login'.format(host), verify=verify, data=data) if resp.status_code not in [200, 201, 204]: raise PySenseException.PySenseException( 'ERROR: {}: {}\nURL: {}'.format(resp.status_code, resp.content, resp.url)) return {'authorization': "Bearer " + resp.json()['access_token']}
def validate_version(py_client, expected_version, function_name): if py_client.version != expected_version: raise PySenseException.PySenseException( '{} is only supported on {}'.format(function_name, expected_version))
def parse_response(response): """Parses response and throw exception if not successful.""" if response.status_code not in [200, 201, 204]: raise PySenseException.PySenseException( 'ERROR: {}: {}\nURL: {}'.format(response.status_code, response.content, response.url))
def does_cube_exist(py_client, cube_name, server_name): if py_client.get_elasticube_by_name(cube_name) is None: raise PySenseException.PySenseException( 'No PySense Elasticube found on {} server.'.format(server_name) )
def rest_call(self, action_type, url, *, data=None, json_payload=None, query_params=None, path=None, file=None, raw=False): """Run an arbitrary rest command against your Sisense instance Args: action_type (str): REST request type (get, post, patch, put, delete) url (str): api end point, example api/v1/app_database/encrypt_database_password or api/branding data (Any): (Optional) The data portion of the payload json_payload (JSON): (Optional) The JSON portion of the payload query_params (dict[Str,Any]): (Optional) A dictionary of query values to be added to the end of the url Method will strip out None dictionary values. path (str): (Optional) If set, response will be downloaded to file path file (str): (Optional) Path to files to be uploaded. Only usable with post. raw (bool): (Optional) If true, write raw data to file. Writes JSON by default Returns: JSON: Returns the json content blob by default. If path is set, returns nothing """ action_type = action_type.lower() if query_params is not None: query_string = build_query_string(query_params) else: query_string = '' full_url = '{}/{}{}'.format(self.host, url, query_string) if self.debug: print('{}: {}'.format(action_type, full_url)) if data is not None: print('Data: {}'.format(data)) if json_payload is not None: print('JSON: {}'.format(json_payload)) if file is not None: if action_type != 'post': raise PySenseException.PySenseException('Files can only be uploaded via post') file = {'file': open(file, 'rb')} if path is not None: with requests.request( action_type, full_url, stream=True, headers=self.token, data=data, json=json_payload, verify=self.verify ) as response: if raw: with open(path, 'wb') as f: shutil.copyfileobj(response.raw, f) else: PySenseUtils.dump_json(response.json(), path) else: response = requests.request( action_type, full_url, headers=self.token, data=data, json=json_payload, verify=self.verify, files=file ) parse_response(response) if len(response.content) == 0: return None else: try: return response.json() except ValueError as e: return response.content
def add_data_security_rule(self, table, column, data_type, *, shares=None, members=None, server_address=None, exclusionary=False, all_members=None): """Define a data security rule Args: table (str): The table to apply security on column (str): The column to apply security on data_type (str): The data type of the column shares (list[Group,User]): (Optional) The users or groups to assign the security rule to. If none, will be 'everyone else' rule. members (list[str]): (Optional) An array of values which users should have access to If left blank, user will get 'Nothing'. server_address (str): (Optional) The server address of the ElastiCube. Set this to your server ip if this method fails without it set. Use 'Set' for Elasticube Set. Required for elasticube sets. exclusionary (bool): (Optional) Set to True if exclusionary rule all_members (bool): (Optional) Set to True to set 'Everything' rule Returns: Rule: The new security rule """ server_address = server_address if server_address else self.server_address rule_json = [{ "column": column, "datatype": data_type, "table": table, "elasticube": self.get_title(), "server": server_address, "exclusionary": exclusionary, "allMembers": all_members }] shares_json = [] if shares is None: # Default rule rule_json[0]['shares'] = [{"type": "default"}] else: for party in PySenseUtils.make_iterable(shares): if isinstance(party, PySenseUser.User): shares_json.append({ 'party': party.get_id(), 'type': 'user' }) elif isinstance(party, PySenseGroup.Group): shares_json.append({ 'party': party.get_id(), 'type': 'group' }) else: raise PySenseException.PySenseException( '{} is not a user or a group object'.format(party)) rule_json[0]['shares'] = shares_json member_arr = [] if members is None: rule_json[0]['members'] = [] rule_json[0][ 'allMembers'] = False if all_members is None else all_members else: rule_json[0]['allMembers'] = None for member in members: member_arr.append(str(member)) rule_json[0]['members'] = member_arr resp_json = self.py_client.connector.rest_call( 'post', 'api/elasticubes/{}/{}/datasecurity'.format( server_address, self.get_title(url_encoded=True)), json_payload=rule_json) return PySenseRule.Rule(self.py_client, resp_json[0])