def compute_session_result(self, session_id: int, alert_operator: str, alert_threshold: str, result_data: pandas.DataFrame): """Compute aggregated results for the indicator session.""" log.info('Compute session results.') nb_records = len(result_data) nb_records_alert = len(result_data.loc[result_data['Alert'] == True]) # pylint: disable=C0121 nb_records_no_alert = len( result_data.loc[result_data['Alert'] == False]) # pylint: disable=C0121 # Post results to database mutation = '''mutation{createSessionResult(input:{sessionResult:{ alertOperator:"alert_operator",alertThreshold:alert_threshold,nbRecords:nb_records, nbRecordsAlert:nb_records_alert,nbRecordsNoAlert:nb_records_no_alert,sessionId:session_id}}){sessionResult{id}}}''' # Use replace() instead of format() because of curly braces mutation = mutation.replace('alert_operator', alert_operator) mutation = mutation.replace('alert_threshold', str(alert_threshold)) mutation = mutation.replace( 'nb_records_no_alert', str(nb_records_no_alert) ) # Order matters to avoid replacing other strings nb_records mutation = mutation.replace( 'nb_records_alert', str(nb_records_alert) ) # Order matters to avoid replacing other strings nb_records mutation = mutation.replace( 'nb_records', str(nb_records) ) # Order matters to avoid replacing other strings nb_records mutation = mutation.replace('session_id', str(session_id)) utils.execute_graphql_request(mutation) return nb_records_alert
def get_data_frame(self, data_source: pandas.DataFrame, request: str, dimensions: str, measures: str): """Get data from data source. Return a formatted data frame according to dimensions and measures parameters.""" # Get data source credentials query = '{dataSourceByName(name:"data_source"){id,connectionString,login,dataSourceTypeId}}' query = query.replace('data_source', data_source) response = utils.execute_graphql_request(query) # Get connection object if response['data']['dataSourceByName']: data_source_id = response['data']['dataSourceByName']['id'] data_source_type_id = response['data']['dataSourceByName'][ 'dataSourceTypeId'] connection_string = response['data']['dataSourceByName'][ 'connectionString'] login = response['data']['dataSourceByName']['login'] # Get data source password query = 'query{allDataSourcePasswords(condition:{id:data_source_id}){nodes{password}}}' query = query.replace( 'data_source_id', str(data_source_id) ) # Use replace() instead of format() because of curly braces response = utils.execute_graphql_request(query) if response['data']['allDataSourcePasswords']['nodes'][0]: data_source = response['data']['allDataSourcePasswords']['nodes'][ 0] password = data_source['password'] log.info('Connect to data source.') data_source = DataSource() connection = data_source.get_connection(data_source_type_id, connection_string, login, password) else: error_message = f'Data source {data_source} does not exist.' log.error(error_message) raise Exception(error_message) # Get data frame log.info('Execute request on data source.') data_frame = pandas.read_sql(request, connection) connection.close() if data_frame.empty: error_message = f'Request on data source {data_source} returned no data.' log.error(error_message) log.debug('Request: %s.', request) raise Exception(error_message) # Format data frame log.debug('Format data frame.') column_names = dimensions + measures data_frame.columns = column_names for column in dimensions: data_frame[column] = data_frame[column].astype( str) # Convert dimension values to string return data_frame
def test(self, data_source_id): log.info( 'Test connectivity to data source Id {data_source_id}.'.format( data_source_id=data_source_id)) # Get data source log.debug('Get data source.') query = '''query{dataSourceById(id:data_source_id){dataSourceTypeId,connectionString,login,password}}''' query = query.replace( 'data_source_id', str(data_source_id) ) # Use replace() instead of format() because of curly braces response = utils.execute_graphql_request(query) if response['data']['dataSourceById']: data_source = response['data']['dataSourceById'] data_source_type_id = data_source['dataSourceTypeId'] connection_string = data_source['connectionString'] login = data_source['login'] password = data_source['password'] # Test connectivity try: log.debug('Connect to data source.') self.get_connection(data_source_type_id, connection_string, login, password) log.info('Connection to data source succeeded.') mutation = '''mutation{updateDataSourceById(input:{id:data_source_id,dataSourcePatch:{connectivityStatus:"Success"}}){dataSource{connectivityStatus}}}''' mutation = mutation.replace( 'data_source_id', str(data_source_id) ) # Use replace() instead of format() because of curly braces utils.execute_graphql_request(mutation) except Exception: log.error('Connection to data source failed.') error_message = traceback.format_exc() log.error(error_message) # Update connectivity status mutation = '''mutation{updateDataSourceById(input:{id:data_source_id,dataSourcePatch:{connectivityStatus:"Failed"}}){dataSource{connectivityStatus}}}''' mutation = mutation.replace( 'data_source_id', str(data_source_id) ) # Use replace() instead of format() because of curly braces utils.execute_graphql_request(mutation) else: error_message = 'Data source Id {data_source_id} does not exist.'.format( data_source_id=data_source_id) log.error(error_message) raise Exception(error_message)
def post(self): """ Execute GraphQL queries and mutations Use this endpoint to send http request to the GraphQL API. """ payload = request.json # Execute request on GraphQL API status, data = utils.execute_graphql_request(payload['query']) # Execute batch of indicators if status == 200 and 'executeBatch' in payload['query']: if 'id' in data['data']['executeBatch']['batch']: batch_id = str(data['data']['executeBatch']['batch']['id']) batch = Batch() batch.execute(batch_id) else: message = "Batch Id attribute is mandatory in the payload to be able to trigger the batch execution. Example: {'query': 'mutation{executeBatch(input:{indicatorGroupId:1}){batch{id}}}'" abort(400, message) # Test connectivity to a data source if status == 200 and 'testDataSource' in payload['query']: if 'id' in data['data']['testDataSource']['dataSource']: data_source_id = str( data['data']['testDataSource']['dataSource']['id']) data_source = DataSource() data = data_source.test(data_source_id) else: message = "Data Source Id attribute is mandatory in the payload to be able to test the connectivity. Example: {'query': 'mutation{testDataSource(input:{dataSourceId:1}){dataSource{id}}}'" abort(400, message) if status == 200: return jsonify(data) else: abort(500, data)
def update_session_status(self, authorization: str, session_id: int, session_status: str): """Update a session status.""" query = 'mutation updateSessionStatus($id: Int!, $sessionPatch: SessionPatch!){updateSessionById(input:{id: $id, sessionPatch: $sessionPatch}){session{status}}}' variables = {'id': session_id, 'sessionPatch': {'status': session_status}} payload = {'query': query, 'variables': variables} response = utils.execute_graphql_request(authorization, payload) return response
def update_session_status(session_id: int, session_status: str): """Update a session status.""" mutation = '''mutation{updateSessionById(input:{id:session_id,sessionPatch:{status:"session_status"}}){session{status}}}''' mutation = mutation.replace('session_id', str( session_id)) # Use replace() instead of format() because of curly braces # Use replace() instead of format() because of curly braces mutation = mutation.replace('session_status', str(session_status)) data = utils.execute_graphql_request(mutation) return data
def update_batch_status(self, authorization: str, batch_id: int, batch_status: str): """Update a batch status.""" query = 'mutation updateBatchStatus($id: Int!, $batchPatch: BatchPatch!){updateBatchById(input:{id: $id, batchPatch: $batchPatch}){batch{status}}}' variables = {'id': batch_id, 'batchPatch': {'status': batch_status}} payload = {'query': query, 'variables': variables} response = utils.execute_graphql_request(authorization, payload) return response
def verify_indicator_parameters(self, authorization: str, indicator_type_id: int, parameters: List[dict]): """Verify if the list of indicator parameters is valid and return them as a dictionary.""" # Build dictionary of parameter types referential query = 'query{allParameterTypes{nodes{id, name}}}' payload = {'query': query} response = utils.execute_graphql_request(authorization, payload) parameter_types_referential = {} for parameter_type in response['data']['allParameterTypes']['nodes']: parameter_types_referential[ parameter_type['id']] = parameter_type['name'] # Build dictionary of indicator parameters indicator_parameters = {} for parameter in parameters: indicator_parameters[ parameter['parameterTypeId']] = parameter['value'] # Verify mandatory parameters exist # Alert operator, Alert threshold, Distribution list, Dimensions, Measures, Target, Target request missing_parameters = [] for parameter_type_id in [1, 2, 3, 4, 5, 8, 9]: if parameter_type_id not in indicator_parameters: parameter_type = parameter_types_referential[parameter_type_id] missing_parameters.append(parameter_type) # Verify parameters specific to completeness and latency indicator types # Source, Source request if indicator_type_id in [ IndicatorType.COMPLETENESS, IndicatorType.LATENCY ]: for parameter_type_id in [6, 7]: if parameter_type_id not in indicator_parameters: parameter_type = parameter_types_referential[ parameter_type_id] missing_parameters.append(parameter_type) if missing_parameters: missing_parameters = ', '.join(missing_parameters) error_message = f'Missing parameters: {missing_parameters}.' log.error(error_message) raise Exception(error_message) # Convert distribution list, dimensions and measures parameters to python list indicator_parameters[3] = literal_eval( indicator_parameters[3]) # Distribution list indicator_parameters[4] = literal_eval( indicator_parameters[4]) # Dimensions indicator_parameters[5] = literal_eval( indicator_parameters[5]) # Measures return indicator_parameters
def update_batch_status(self, batch_id: int, batch_status: str): """Update a batch status.""" mutation = '''mutation{updateBatchById(input:{id:batch_id,batchPatch:{status:"batch_status"}}){batch{status}}}''' mutation = mutation.replace( 'batch_id', str(batch_id) ) # Use replace() instead of format() because of curly braces mutation = mutation.replace( 'batch_status', str(batch_status) ) # Use replace() instead of format() because of curly braces data = utils.execute_graphql_request(mutation) return data
def compute_session_result(self, authorization: str, session_id: int, alert_operator: str, alert_threshold: str, result_data: pandas.DataFrame): """Compute aggregated results for the indicator session.""" log.info('Compute session results.') nb_records = len(result_data) nb_records_alert = len(result_data.loc[result_data['Alert'] == True]) # pylint: disable=C0121 nb_records_no_alert = len(result_data.loc[result_data['Alert'] == False]) # pylint: disable=C0121 # Post results to database query = 'mutation updateSessionResults($id: Int!, $sessionPatch: SessionPatch!){updateSessionById(input:{id: $id, sessionPatch: $sessionPatch}){session{id}}}' variables = {} variables['id'] = session_id variables['sessionPatch'] = {} variables['sessionPatch']['alertOperator'] = alert_operator variables['sessionPatch']['alertThreshold'] = float(alert_threshold) # Alert threshold is stored as string in parameters and needs to be converted to float variables['sessionPatch']['nbRecordsNoAlert'] = nb_records_no_alert variables['sessionPatch']['nbRecordsAlert'] = nb_records_alert variables['sessionPatch']['nbRecords'] = nb_records payload = {'query': query, 'variables': variables} utils.execute_graphql_request(authorization, payload) return nb_records_alert
def update_batch_status(self, authorization: str, batch_id: int, batch_status: str): """Update a batch status.""" mutation = 'mutation{updateBatchById(input:{id:batch_id,batchPatch:{status:"batch_status"}}){batch{status}}}' mutation = mutation.replace( 'batch_id', str(batch_id) ) # Use replace() instead of format() because of curly braces mutation = mutation.replace( 'batch_status', str(batch_status) ) # Use replace() instead of format() because of curly braces mutation = {'query': mutation} # Convert to dictionary data = utils.execute_graphql_request(authorization, mutation) return data
def get_data_frame(self, data_source, request, dimensions, measures): """Get data from data source. Return a formatted data frame according to dimensions and measures parameters.""" # Get data source credentials query = '''{dataSourceByName(name:"data_source"){connectionString,login,password,dataSourceTypeId}}''' query = query.replace('data_source', data_source) response = utils.execute_graphql_request(query) # Get connection object if response['data']['dataSourceByName']: data_source_type_id = response['data']['dataSourceByName'][ 'dataSourceTypeId'] connection_string = response['data']['dataSourceByName'][ 'connectionString'] login = response['data']['dataSourceByName']['login'] password = response['data']['dataSourceByName']['password'] log.info('Connect to data source {data_source}.'.format( data_source=data_source)) data_source = DataSource() connection = data_source.get_connection(data_source_type_id, connection_string, login, password) else: error_message = 'Data source {data_source} does not exist.'.format( data_source=data_source) log.error(error_message) raise Exception(error_message) # Get data frame log.info( 'Execute request on data source.'.format(data_source=data_source)) data_frame = pandas.read_sql(request, connection) connection.close() if data_frame.empty: error_message = 'Request on data source {data_source} returned no data.'.format( data_source=data_source) log.error(error_message) log.debug('Request: {request}.'.format(request=request)) raise Exception(error_message) # Format data frame log.debug('Format data frame.') column_names = dimensions + measures data_frame.columns = column_names for column in dimensions: data_frame[column] = data_frame[column].astype( str) # Convert dimension values to string return data_frame
def get_data_frame(self, authorization: str, data_source: pandas.DataFrame, request: str, dimensions: str, measures: str): """Get data from data source. Return a formatted data frame according to dimensions and measures parameters.""" # Get data source credentials query = 'query getDataSource($name: String!){dataSourceByName(name: $name){id, connectionString, login, dataSourceTypeId}}' variables = {'name': data_source} payload = {'query': query, 'variables': variables} response = utils.execute_graphql_request(authorization, payload) # Get connection object if response['data']['dataSourceByName']: data_source_id = response['data']['dataSourceByName']['id'] data_source_type_id = response['data']['dataSourceByName'][ 'dataSourceTypeId'] connection_string = response['data']['dataSourceByName'][ 'connectionString'] login = response['data']['dataSourceByName']['login'] # Get data source password data_source = DataSource() password = data_source.get_password(authorization, data_source_id) # Connect to data source log.info('Connect to data source.') connection = data_source.get_connection(data_source_type_id, connection_string, login, password) # Get data frame log.info('Execute request on data source.') data_frame = pandas.read_sql(request, connection) connection.close() if data_frame.empty: error_message = f'Request on data source {data_source} returned no data.' log.error(error_message) log.debug('Request: %s.', request) raise Exception(error_message) # Format data frame log.debug('Format data frame.') column_names = dimensions + measures data_frame.columns = column_names for column in dimensions: data_frame[column] = data_frame[column].astype( str) # Convert dimension values to string return data_frame
def get_password(self, authorization: str, data_source_id: int): """Get unencrypted password of a data source. Return the password.""" query = 'query getDataSourcePassword($id: Int){allDataSourcePasswords(condition:{id: $id}){nodes{password}}}' variables = {'id': data_source_id} payload = {'query': query, 'variables': variables} response = utils.execute_graphql_request(authorization, payload) if response['data']['allDataSourcePasswords']['nodes']: data_source = response['data']['allDataSourcePasswords']['nodes'][0] password = data_source['password'] else: error_message = f'Data source {data_source_id} does not exist.' log.error(error_message) raise Exception(error_message) return password
def test(self, authorization: str, data_source_id: int): """Test connectivity to a data source and update its connectivity status.""" log.info('Test connectivity to data source Id %i.', data_source_id) # Set connectivity test to running query = 'mutation updateDataSourceStatus($id: Int!, $dataSourcePatch: DataSourcePatch!){updateDataSourceById(input:{id: $id, dataSourcePatch: $dataSourcePatch}){dataSource{connectivityStatus}}}' variables = {'id': data_source_id, 'dataSourcePatch': {'connectivityStatus': 'Running'}} payload = {'query': query, 'variables': variables} utils.execute_graphql_request(authorization, payload) # Get data source log.debug('Get data source.') query = 'query getDataSource($id: Int!){dataSourceById(id: $id){dataSourceTypeId, connectionString, login}}' variables = {'id': data_source_id} payload = {'query': query, 'variables': variables} response = utils.execute_graphql_request(authorization, payload) if response['data']['dataSourceById']: data_source = response['data']['dataSourceById'] data_source_type_id = data_source['dataSourceTypeId'] connection_string = data_source['connectionString'] login = data_source['login'] # Get data source password password = self.get_password(authorization, data_source_id) # Test connectivity try: log.debug('Connect to data source.') self.get_connection(data_source_type_id, connection_string, login, password) log.info('Connection to data source succeeded.') query = 'mutation updateDataSourceStatus($id: Int!, $dataSourcePatch: DataSourcePatch!){updateDataSourceById(input:{id: $id, dataSourcePatch: $dataSourcePatch}){dataSource{connectivityStatus}}}' variables = {'id': data_source_id, 'dataSourcePatch': {'connectivityStatus': 'Success'}} payload = {'query': query, 'variables': variables} utils.execute_graphql_request(authorization, payload) except Exception: # Pylint: disable=broad-except log.error('Connection to data source failed.') error_message = traceback.format_exc() log.error(error_message) # Update connectivity status query = 'mutation updateDataSourceStatus($id: Int!, $dataSourcePatch: DataSourcePatch!){updateDataSourceById(input:{id: $id, dataSourcePatch: $dataSourcePatch}){dataSource{connectivityStatus}}}' variables = {'id': data_source_id, 'dataSourcePatch': {'connectivityStatus': 'Failed'}} payload = {'query': query, 'variables': variables} utils.execute_graphql_request(authorization, payload)
def test(self, data_source_id): container_name = 'data-quality-test-data-source-{data_source_id}'.format( data_source_id=data_source_id) client = docker.from_env() client.containers.run( name=container_name, image='data-quality-scripts', network='data-quality-network', links={'data-quality-graphql': 'data-quality-graphql'}, command=['python', 'run.py', 'test_data_source', data_source_id], stream=True, remove=True) # Get connectivity test result query = '''query{dataSourceById(id:data_source_id){id,connectivityStatus}}''' query = query.replace( 'data_source_id', str(data_source_id) ) # Use replace() instead of format() because of curly braces data = utils.execute_graphql_request(query) return data
def execute(self, authorization: str, batch_id: int): log.info('Start execution of batch Id %i.', batch_id) # Get list of indicator sessions log.debug('Get list of indicator sessions.') query = 'query{allSessions(condition:{batchId:batch_id},orderBy:ID_ASC){nodes{id,batchId,indicatorId,userGroupId,indicatorByIndicatorId{name,indicatorTypeId,indicatorTypeByIndicatorTypeId{module,class,method},parametersByIndicatorId{nodes{parameterTypeId,value}}}}}}' query = query.replace( 'batch_id', str(batch_id) ) # Use replace() instead of format() because of curly braces query = {'query': query} # Convert to dictionary response = utils.execute_graphql_request(authorization, query) if response['data']['allSessions']['nodes']: # Update batch status to running log.debug('Update batch status to Running.') self.update_batch_status(authorization, batch_id, 'Running') is_error = False # Variable used to update batch status to Failed if one indicator fails # For each indicator session execute corresponding method for session in response['data']['allSessions']['nodes']: try: module_name = session['indicatorByIndicatorId'][ 'indicatorTypeByIndicatorTypeId']['module'] class_name = session['indicatorByIndicatorId'][ 'indicatorTypeByIndicatorTypeId']['class'] method_name = session['indicatorByIndicatorId'][ 'indicatorTypeByIndicatorTypeId']['method'] class_instance = getattr(sys.modules[module_name], class_name)() getattr(class_instance, method_name)(authorization, session) except Exception: # pylint: disable=broad-except is_error = True error_message = traceback.format_exc() log.error(error_message) # Update session status session_id = session['id'] update_session_status(authorization, session_id, 'Failed') # Get error context and send error e-mail indicator_id = session['indicatorId'] indicator_name = session['indicatorByIndicatorId']['name'] for parameter in session['indicatorByIndicatorId'][ 'parametersByIndicatorId']['nodes']: if parameter[ 'parameterTypeId'] == 3: # Distribution list distribution_list = literal_eval( parameter['value']) utils.send_error(indicator_id, indicator_name, session_id, distribution_list, error_message) # Update batch status if is_error: log.debug('Update batch status to Failed.') self.update_batch_status(authorization, batch_id, 'Failed') log.warning('Batch Id %i completed with errors.', batch_id) else: log.debug('Update batch status to Success.') self.update_batch_status(authorization, batch_id, 'Success') log.info('Batch Id %i completed successfully.', batch_id) else: error_message = f'Batch Id {batch_id} does not exist or has no indicator session.' log.error(error_message) raise Exception(error_message)
def execute(self, authorization: str, batch_id: int): log.info('Start execution of batch Id %i.', batch_id) # Get list of indicator sessions log.debug('Get list of indicator sessions.') query = 'query getAllSessions($batchId: Int){allSessions(condition:{batchId: $batchId}, orderBy:ID_ASC){nodes{id, batchId, indicatorId, userGroupId, indicatorByIndicatorId{name, indicatorTypeId, indicatorTypeByIndicatorTypeId{module, class, method}, parametersByIndicatorId{nodes{parameterTypeId, value}}}}}}' variables = {'batchId': batch_id} payload = {'query': query, 'variables': variables} response = utils.execute_graphql_request(authorization, payload) if response['data']['allSessions']['nodes']: # Update batch status to running log.debug('Update batch status to Running.') self.update_batch_status(authorization, batch_id, 'Running') is_error = False # Variable used to update batch status to Failed if one indicator fails # Loop over each indicator session for session in response['data']['allSessions']['nodes']: try: # Recreate custom log handler to add session Id to context root_log = logging.getLogger() custom_handler = root_log.handlers[1] root_log.removeHandler(custom_handler) root_log.addHandler( utils.CustomLogHandler(authorization, batch_id=batch_id, session_id=session['id'])) # For each session execute indicator type method module_name = session['indicatorByIndicatorId'][ 'indicatorTypeByIndicatorTypeId']['module'] class_name = session['indicatorByIndicatorId'][ 'indicatorTypeByIndicatorTypeId']['class'] method_name = session['indicatorByIndicatorId'][ 'indicatorTypeByIndicatorTypeId']['method'] class_instance = getattr(sys.modules[module_name], class_name)() getattr(class_instance, method_name)(authorization, session) except Exception: # pylint: disable=broad-except is_error = True error_message = traceback.format_exc() log.error(error_message) # Update session status session_id = session['id'] Session().update_session_status(authorization, session_id, 'Failed') # Get error context and send error e-mail indicator_id = session['indicatorId'] indicator_name = session['indicatorByIndicatorId']['name'] for parameter in session['indicatorByIndicatorId'][ 'parametersByIndicatorId']['nodes']: if parameter[ 'parameterTypeId'] == 3: # Distribution list distribution_list = literal_eval( parameter['value']) utils.send_error(indicator_id, indicator_name, session_id, distribution_list, error_message) # Update batch status if is_error: log.debug('Update batch status to Failed.') self.update_batch_status(authorization, batch_id, 'Failed') log.warning('Batch Id %i completed with errors.', batch_id) else: log.debug('Update batch status to Success.') self.update_batch_status(authorization, batch_id, 'Success') log.info('Batch Id %i completed successfully.', batch_id) else: error_message = f'Batch Id {batch_id} does not exist or has no indicator session.' log.error(error_message) raise Exception(error_message)
def test(self, authorization: str, data_source_id: int): """Test connectivity to a data source and update its connectivity status.""" log.info('Test connectivity to data source Id %i.', data_source_id) # Get data source log.debug('Get data source.') query = 'query{dataSourceById(id:data_source_id){dataSourceTypeId,connectionString,login}}' query = query.replace( 'data_source_id', str(data_source_id) ) # Use replace() instead of format() because of curly braces query = {'query': query} # Convert to dictionary response = utils.execute_graphql_request(authorization, query) if response['data']['dataSourceById']: data_source = response['data']['dataSourceById'] data_source_type_id = data_source['dataSourceTypeId'] connection_string = data_source['connectionString'] login = data_source['login'] # Get data source password query = 'query{allDataSourcePasswords(condition:{id:data_source_id}){nodes{password}}}' query = query.replace( 'data_source_id', str(data_source_id) ) # Use replace() instead of format() because of curly braces query = {'query': query} # Convert to dictionary response = utils.execute_graphql_request(authorization, query) if response['data']['allDataSourcePasswords']['nodes'][0]: data_source = response['data']['allDataSourcePasswords']['nodes'][ 0] password = data_source['password'] # Test connectivity try: log.debug('Connect to data source.') self.get_connection(data_source_type_id, connection_string, login, password) log.info('Connection to data source succeeded.') mutation = 'mutation{updateDataSourceById(input:{id:data_source_id,dataSourcePatch:{connectivityStatus:"Success"}}){dataSource{connectivityStatus}}}' mutation = mutation.replace( 'data_source_id', str(data_source_id) ) # Use replace() instead of format() because of curly braces mutation = {'query': mutation} # Convert to dictionary utils.execute_graphql_request(authorization, mutation) except Exception: # Pylint: disable=broad-except log.error('Connection to data source failed.') error_message = traceback.format_exc() log.error(error_message) # Update connectivity status mutation = 'mutation{updateDataSourceById(input:{id:data_source_id,dataSourcePatch:{connectivityStatus:"Failed"}}){dataSource{connectivityStatus}}}' mutation = mutation.replace( 'data_source_id', str(data_source_id) ) # Use replace() instead of format() because of curly braces mutation = {'query': mutation} # Convert to dictionary utils.execute_graphql_request(authorization, mutation) else: error_message = f'Data source Id {data_source_id} does not exist.' log.error(error_message) raise Exception(error_message)