def __init__(self) -> None: logger.debug(': in: Quote Service started') random_page = random.randint(0, 100) URL = 'https://www.goodreads.com/quotes?page={0}'.format(random_page) req = requests.get(URL) soupy = BeautifulSoup(req.content, 'html.parser') results = soupy.find_all('div', class_='quote', recursive=True) data = [] for elem in results: data_row = {} quoteText = elem.find('div', class_='quoteText') authorOrTitle = quoteText.find('span', class_='authorOrTitle') if None in (quoteText, authorOrTitle): # print('Check ', quoteText) # print('Wow ', authorOrTitle) continue data_row['quote'] = re.compile(r"\“(.*?)\”").search( quoteText.text.strip()).group() data_row['author'] = authorOrTitle.text.strip(' \n\r,') data.append(data_row) logger.debug(': Quote service | Setting data') self.data = data
def get_existed_data(self): result = self.sheet_service.spreadsheets().values().get( spreadsheetId=config.spreadsheet_id, range=config.range).execute() logger.debug(f"Sheet data : {result}") rows = result.get('values', [])[1:] logger.info('{0} rows retrieved.'.format(len(rows))) return rows
def __init__(self, df: DataFrame) -> None: if df.shape[0] > 0: self.df = df else: raise DataError('Dataframe has to have some data in it') self.appointment_list = [] logger.debug(': about: Quote Service started') quote_service = QuoteService() self.quote_service_quote = quote_service.__get_random_quote()
def create_ticket(): logger.info("Create ticket received !") data = request.form.to_dict() logger.debug(f"Create ticket form : {data}") text = data.get("text") trigger_id = data.get("trigger_id") logger.debug(f"Trigger ID : {trigger_id}") return open_dialog(text, trigger_id)
def build_lines(self, table_cells): if table_cells is None or len(table_cells) <= 0: return [], [] max_last_col_width_row = max(table_cells, key=lambda b: b[-1][2]) max_x = max_last_col_width_row[-1][0] + max_last_col_width_row[-1][2] max_last_row_height_box = max(table_cells[-1], key=lambda b: b[3]) max_y = max_last_row_height_box[1] + max_last_row_height_box[3] left_most_box = min(table_cells, key=lambda b: b[0][0])[0] hor_lines = [(left_most_box[0], box[0][1], max_x, box[0][1]) for box in table_cells] optimal_boxes = table_cells[0] ver_lines = [None] * max([len(i) for i in table_cells ]) # Getting number of vertical lines changed_indexes = [] # Code to get the biggest line of text within the column in question # Ultimately, fitting all of the values within the rows for row in table_cells[1:]: for cur_box in row: for index, base_box in enumerate(optimal_boxes): (x, y, w, h) = cur_box (b_x, b_y, b_w, b_h) = base_box if (x, b_y, x, max_y ) not in optimal_boxes and b_x > x >= b_x - 65: ver_lines[index] = (x, b_y, x, max_y) changed_indexes.append(index) break # As soon as it changes one, break, no way one box will change 2 # vertical lines logger.debug(': ver_lines after first population %s with size of %s', ver_lines, len(ver_lines)) # Parsing through indexes that weren't visited on the previous line construction # Indexes which are still None for index, box in enumerate(optimal_boxes): if index not in changed_indexes: (x, y, w, h) = box ver_lines[index] = (x, y, x, max_y) logger.debug( ': ver_lines after going through unchanged indexes %s with size of %s', ver_lines, len(ver_lines)) # Last vertical and horizontal line (x, y, w, h) = table_cells[0][-1] ver_lines.append((max_x, y, max_x, max_y)) (x, y, w, h) = left_most_box hor_lines.append((x, max_y, max_x, max_y)) return hor_lines, ver_lines
def break_func(): result = "" try: logger.info("Break received !") data = request.form.to_dict() logger.debug(f"Break data : {data}") username = data.get("user_name") params = data.get("text") sub_command = params.split(" ")[0] if sub_command.isdigit(): raise NotImplementedError try: result = break_sub_command_list[sub_command](username, params) except KeyError as unexpected_command: result = "Unrecognized command received : {} . Use /break help for information".format(sub_command) except Exception as e: logger.error(e, exc_info=True) result = "Unexpected error. Please contact the admin." finally: return result
def find_text_boxes(self, pre, min_text_height_limit=6, max_text_height_limit=40): # Looking for the text spots contours # OpenCV 3 # img, contours, hierarchy = cv2.findContours(pre, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # OpenCV 4 logger.debug(': Getting image contours') contours, hierarchy = cv2.findContours(pre, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) logger.debug(': Got %s total contours', len(contours)) # Getting the texts bounding boxes based on the text size assumptions and table position boxes = [] height, width = pre.shape for contour in contours: box = cv2.boundingRect(contour) (x, y, w, h) = box # print(int(height - height * .6), int(height * .9)) if (min_text_height_limit < h < max_text_height_limit and int(height - height * .55) <= y <= int(height * .9)): boxes.append(box) logger.debug( ': After filtering for area of interest, got %s total contours', len(boxes)) return boxes
def send_create_ticket_request(fullname: str, email: str, subject: str, content: str): r = session.post( "http://iimet.wwwshine.supersitedns.com/project/API/examples/ticket_form.php", data={ "page": "general", "department_id": config.department_id, "creator_full_name": fullname, "creator_email": email, "type_id": 1, "priority_id": 1, "subject": subject, "contents": content, "submit_general": "Submit" }, headers={ "Content-Type": "application/x-www-form-urlencoded" } ) logger.debug(f"Created form response code {r.status_code}") text_response = r.text logger.debug(f"Created form response text : {text_response}") response_message = re_obj.search(text_response) if not response_message: return "Could not create a ticket !" tree = html.fromstring(response_message.group()) ticket_id = tree.xpath("strong")[0].text_content() logger.debug(f"Ticket ID : {ticket_id}") bot_client.chat_postMessage( channel=config.ticket_id_delivery_channel, text=f"New ticket created ! Ticket ID : *{ticket_id}* / http://iimet.wwwshine.supersitedns.com/project/support/staff/index.php?/Tickets/Ticket/View/{ticket_id}" )
def build_events(self, subject, location, recipients, attachments, body): og_subject = subject # Needed to do this since changing subject of if statement, changes it permanently for index, actual_row in self.df.iterrows(): logger.debug( ': actual_row.start_time_converted | Literal: %s | Type: %s', actual_row.start_time_converted, type(actual_row.start_time_converted)) logger.debug(': df: %s', actual_row) start = datetime.datetime.strftime(actual_row.start_time_converted, '%Y-%m-%d %H:%M:%S') end = datetime.datetime.strftime(actual_row.end_time_converted, '%Y-%m-%d %H:%M:%S') body_fields = { 'duration': actual_row.duration, 'quote': self.quote_service_quote } # Request to know the job position (if there is) instead of boiler template name/else just boiler template if 'Alt Dept/Job' in actual_row.keys() and self._nan_check( actual_row['Alt Dept/Job']): subject = actual_row['Alt Dept/Job'] else: subject = og_subject event_instance = self.Event( start_time_datetime=actual_row.start_time_converted, start_time=start, subject=subject, end_time=end, location=location, recipients=recipients, attachments=attachments, body=body, body_fields=body_fields) self.appointment_list.append(event_instance)
def interactive(): logger.info("Interactive received !") data = request.form.to_dict() logger.debug(f"Request form : {data}") payload = json.loads(data.get("payload")) logger.debug(f"Payload : {payload}") submission = payload.get("submission") type_ = payload.get("type") logger.debug(f"Type {type_}") if type_ != "dialog_submission": return "something goes wrong" if not validators.email(submission.get("email")): return "Invalid e-mail ! Please correct your data !" thread = Thread(target=send_create_ticket_request, kwargs=submission) thread.start() return ""
def pre_process_image(self, img, save_in_file, morph_size=(8, 8)): # get rid of the color logger.debug(': Got rid of colour') pre = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Otsu threshold logger.debug(': Thresholding') pre = cv2.threshold(pre, 250, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] # dilate the text to make it solid spot logger.debug(': Copying') cpy = pre.copy() # Specify structure shape and kernel size. # Kernel size increases or decreases the area # of the rectangle to be detected. # A smaller value like (10, 10) will detect # each word instead of a sentence. logger.debug(': Getting structuring element') struct = cv2.getStructuringElement(cv2.MORPH_RECT, morph_size) logger.debug(': Dilating copy') cpy = cv2.dilate(~cpy, struct, anchor=(-1, -1), iterations=1) logger.debug(': Preparing output copy') pre = ~cpy if save_in_file is not None: logger.debug(': Outputting copy image') cv2.imwrite(save_in_file, pre) return cpy
def find_table_in_boxes(self, boxes, cell_threshold=10, min_columns=2): rows = {} cols = {} # Clustering the bounding boxes by their positions logger.debug(': Clustering the bounding boxes by their positions') for box in boxes: (x, y, w, h) = box col_key = x // cell_threshold row_key = y // cell_threshold # Needed to be added since boxes that are off by -1 to 2 px should be in the same cluster delta_values = [row_key + i for i in range(-1, 2)] delta_check = [i in cols.keys() for i in delta_values] if True in delta_check: row_key = delta_values[delta_check.index(True)] cols[row_key] = [ box ] if col_key not in cols else cols[col_key] + [box] rows[row_key] = [ box ] if row_key not in rows else rows[row_key] + [box] # Odd issue occurs that .values() does not return ALL of the boxes, having to do some extra parsing after calling such. # table_cells = [] # if len(boxes) != rows.values(): # for temp_boxes in rows.values(): # table_cells.append(temp_boxes) # else: table_cells = list(rows.values()) # Filtering out boxes that are too small logger.debug( ': Before filtering out boxes that are too small, got %s table_cells', len(table_cells)) for r_index, row in enumerate(table_cells): filtered_row = [box for box in row if box[2] > cell_threshold] table_cells[r_index] = filtered_row logger.debug( ': After filtering out boxes that are too small, got %s table_cells', len(table_cells)) # Filtering out the clusters having less than 2 cols table_cells = list(filter(lambda r: len(r) >= min_columns, table_cells)) logger.debug( ': Filtering out clusters that have less than %s, got %s rows', min_columns, len(table_cells)) # Sorting the row cells by x coord table_cells = [list(sorted(tb)) for tb in table_cells] logger.debug(': Sorting by x') # Sorting rows by the y coord table_cells = list(sorted(table_cells, key=lambda r: r[0][1])) logger.debug(': Sorting by y') return table_cells
def get_data_by_user(self, username): self.check_slack_user_exists(username) all_data = self.get_existed_data() logger.debug(f"All data : {all_data}") return ClockRowItem()
def slack(): logger.info("Event received !") logger.debug(f"Event data : {request.data}") data = request.json return data.get("challenge")
def test_google_sheet_wrapper(self): config = Config() self.assertTrue(os.path.exists("credentials.json")) self.assertTrue(config.sheet_url) from google_sheet_wrapper import google_sheet_wrapper test_data = google_sheet_wrapper.get_existed_data() self.assertIsNotNone(test_data) test_slack_users_list = [ {'id': 'USLACKBOT', 'team_id': 'TQ5DL0TQC', 'name': 'slackbot', 'deleted': False, 'color': '757575', 'real_name': 'Slackbot', 'tz': None, 'tz_label': 'Pacific Standard Time', 'tz_offset': -28800, 'profile': {'title': '', 'phone': '', 'skype': '', 'real_name': 'Slackbot', 'real_name_normalized': 'Slackbot', 'display_name': 'Slackbot', 'display_name_normalized': 'Slackbot', 'fields': None, 'status_text': '', 'status_emoji': '', 'status_expiration': 0, 'avatar_hash': 'sv41d8cd98f0', 'always_active': True, 'first_name': 'slackbot', 'last_name': '', 'image_24': 'https://a.slack-edge.com/80588/img/slackbot_24.png', 'image_32': 'https://a.slack-edge.com/80588/img/slackbot_32.png', 'image_48': 'https://a.slack-edge.com/80588/img/slackbot_48.png', 'image_72': 'https://a.slack-edge.com/80588/img/slackbot_72.png', 'image_192': 'https://a.slack-edge.com/80588/marketing/img/avatars/slackbot/avatar-slackbot.png', 'image_512': 'https://a.slack-edge.com/80588/img/slackbot_512.png', 'status_text_canonical': '', 'team': 'TQ5DL0TQC'}, 'is_admin': False, 'is_owner': False, 'is_primary_owner': False, 'is_restricted': False, 'is_ultra_restricted': False, 'is_bot': False, 'is_app_user': False, 'updated': 0}, {'id': 'UQ2NX7WHX', 'team_id': 'TQ5DL0TQC', 'name': 'formtestbot', 'deleted': False, 'color': '3c989f', 'real_name': 'formtestbot', 'tz': 'America/Los_Angeles', 'tz_label': 'Pacific Standard Time', 'tz_offset': -28800, 'profile': {'title': '', 'phone': '', 'skype': '', 'real_name': 'formtestbot', 'real_name_normalized': 'formtestbot', 'display_name': '', 'display_name_normalized': '', 'status_text': '', 'status_emoji': '', 'status_expiration': 0, 'avatar_hash': 'gea267e42fb4', 'api_app_id': 'AQAKUH940', 'always_active': False, 'bot_id': 'BPXSTE202', 'image_24': 'https://secure.gravatar.com/avatar/ea267e42fb458d5c5df5f0a9f4db65ba.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-24.png', 'image_32': 'https://secure.gravatar.com/avatar/ea267e42fb458d5c5df5f0a9f4db65ba.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-32.png', 'image_48': 'https://secure.gravatar.com/avatar/ea267e42fb458d5c5df5f0a9f4db65ba.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-48.png', 'image_72': 'https://secure.gravatar.com/avatar/ea267e42fb458d5c5df5f0a9f4db65ba.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-72.png', 'image_192': 'https://secure.gravatar.com/avatar/ea267e42fb458d5c5df5f0a9f4db65ba.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-192.png', 'image_512': 'https://secure.gravatar.com/avatar/ea267e42fb458d5c5df5f0a9f4db65ba.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-512.png', 'status_text_canonical': '', 'team': 'TQ5DL0TQC'}, 'is_admin': False, 'is_owner': False, 'is_primary_owner': False, 'is_restricted': False, 'is_ultra_restricted': False, 'is_bot': True, 'is_app_user': False, 'updated': 1573196943}, {'id': 'UQ5DL0V5J', 'team_id': 'TQ5DL0TQC', 'name': 'michael.sizonenko.17', 'deleted': False, 'color': '9f69e7', 'real_name': 'Michael Sizonenko', 'tz': 'Asia/Jerusalem', 'tz_label': 'Israel Standard Time', 'tz_offset': 7200, 'profile': {'title': '', 'phone': '', 'skype': '', 'real_name': 'Michael Sizonenko', 'real_name_normalized': 'Michael Sizonenko', 'display_name': '', 'display_name_normalized': '', 'status_text': '', 'status_emoji': '', 'status_expiration': 0, 'avatar_hash': 'g29a5bc9023f', 'first_name': 'Michael', 'last_name': 'Sizonenko', 'image_24': 'https://secure.gravatar.com/avatar/29a5bc9023fb554ad6dcdede2a7fabe8.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-24.png', 'image_32': 'https://secure.gravatar.com/avatar/29a5bc9023fb554ad6dcdede2a7fabe8.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-32.png', 'image_48': 'https://secure.gravatar.com/avatar/29a5bc9023fb554ad6dcdede2a7fabe8.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-48.png', 'image_72': 'https://secure.gravatar.com/avatar/29a5bc9023fb554ad6dcdede2a7fabe8.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png', 'image_192': 'https://secure.gravatar.com/avatar/29a5bc9023fb554ad6dcdede2a7fabe8.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-192.png', 'image_512': 'https://secure.gravatar.com/avatar/29a5bc9023fb554ad6dcdede2a7fabe8.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-512.png', 'status_text_canonical': '', 'team': 'TQ5DL0TQC'}, 'is_admin': True, 'is_owner': True, 'is_primary_owner': True, 'is_restricted': False, 'is_ultra_restricted': False, 'is_bot': False, 'is_app_user': False, 'updated': 1572956970, 'has_2fa': False}, {'id': 'UQ5TGKNCD', 'team_id': 'TQ5DL0TQC', 'name': 'dethline88', 'deleted': False, 'color': '4bbe2e', 'real_name': 'Dethline', 'tz': 'Asia/Jerusalem', 'tz_label': 'Israel Standard Time', 'tz_offset': 7200, 'profile': {'title': '', 'phone': '', 'skype': '', 'real_name': 'Dethline', 'real_name_normalized': 'Dethline', 'display_name': 'Dethline', 'display_name_normalized': 'Dethline', 'status_text': '', 'status_emoji': '', 'status_expiration': 0, 'avatar_hash': 'g1a3120f3fa3', 'image_24': 'https://secure.gravatar.com/avatar/1a3120f3fa31b374a86900fe525d0af8.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0009-24.png', 'image_32': 'https://secure.gravatar.com/avatar/1a3120f3fa31b374a86900fe525d0af8.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0009-32.png', 'image_48': 'https://secure.gravatar.com/avatar/1a3120f3fa31b374a86900fe525d0af8.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0009-48.png', 'image_72': 'https://secure.gravatar.com/avatar/1a3120f3fa31b374a86900fe525d0af8.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0009-72.png', 'image_192': 'https://secure.gravatar.com/avatar/1a3120f3fa31b374a86900fe525d0af8.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0009-192.png', 'image_512': 'https://secure.gravatar.com/avatar/1a3120f3fa31b374a86900fe525d0af8.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0009-512.png', 'status_text_canonical': '', 'team': 'TQ5DL0TQC'}, 'is_admin': False, 'is_owner': False, 'is_primary_owner': False, 'is_restricted': False, 'is_ultra_restricted': False, 'is_bot': False, 'is_app_user': False, 'updated': 1572953382, 'has_2fa': False}, {'id': 'UQ845012A', 'team_id': 'TQ5DL0TQC', 'name': 'testbot', 'deleted': False, 'color': 'e7392d', 'real_name': 'testbot', 'tz': 'America/Los_Angeles', 'tz_label': 'Pacific Standard Time', 'tz_offset': -28800, 'profile': {'title': '', 'phone': '', 'skype': '', 'real_name': 'testbot', 'real_name_normalized': 'testbot', 'display_name': '', 'display_name_normalized': '', 'status_text': '', 'status_emoji': '', 'status_expiration': 0, 'avatar_hash': 'ge69d54dab8b', 'api_app_id': 'APY0FKYHF', 'always_active': False, 'bot_id': 'BPT48B01G', 'image_24': 'https://secure.gravatar.com/avatar/e69d54dab8bf407554da095a3b8cf969.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0025-24.png', 'image_32': 'https://secure.gravatar.com/avatar/e69d54dab8bf407554da095a3b8cf969.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0025-32.png', 'image_48': 'https://secure.gravatar.com/avatar/e69d54dab8bf407554da095a3b8cf969.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0025-48.png', 'image_72': 'https://secure.gravatar.com/avatar/e69d54dab8bf407554da095a3b8cf969.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0025-72.png', 'image_192': 'https://secure.gravatar.com/avatar/e69d54dab8bf407554da095a3b8cf969.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0025-192.png', 'image_512': 'https://secure.gravatar.com/avatar/e69d54dab8bf407554da095a3b8cf969.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0025-512.png', 'status_text_canonical': '', 'team': 'TQ5DL0TQC'}, 'is_admin': False, 'is_owner': False, 'is_primary_owner': False, 'is_restricted': False, 'is_ultra_restricted': False, 'is_bot': True, 'is_app_user': False, 'updated': 1572953111}] self.assertSetEqual({'michael.sizonenko.17', 'formtestbot', 'dethline88', 'slackbot', 'testbot'}, extract_usernames(test_slack_users_list)) with self.assertRaises(UnknownSlackUser): test_user_data = google_sheet_wrapper.get_data_by_user("test") test_user_data = google_sheet_wrapper.get_data_by_user('michael.sizonenko.17') empty_row_model = ClockRowItem() self.assertEqual(test_user_data, empty_row_model) with self.assertRaises(UnknownSlackUser): google_sheet_wrapper.clock_in("test") google_sheet_wrapper.clock_in("michael.sizonenko.17") test_user_data = google_sheet_wrapper.get_data_by_user("michael.sizonenko.17") logger.debug(f"Test user data : {test_user_data}") self.assertAlmostEqual(time.mktime(test_user_data.get_clock_in().timetuple()), time.mktime(datetime.utcnow().timetuple()))
'real_name_normalized': 'testbot', 'display_name': '', 'display_name_normalized': '', 'status_text': '', 'status_emoji': '', 'status_expiration': 0, 'avatar_hash': 'ge69d54dab8b', 'api_app_id': 'APY0FKYHF', 'always_active': False, 'bot_id': 'BPT48B01G', 'image_24': 'https://secure.gravatar.com/avatar/e69d54dab8bf407554da095a3b8cf969.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0025-24.png', 'image_32': 'https://secure.gravatar.com/avatar/e69d54dab8bf407554da095a3b8cf969.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0025-32.png', 'image_48': 'https://secure.gravatar.com/avatar/e69d54dab8bf407554da095a3b8cf969.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0025-48.png', 'image_72': 'https://secure.gravatar.com/avatar/e69d54dab8bf407554da095a3b8cf969.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0025-72.png', 'image_192': 'https://secure.gravatar.com/avatar/e69d54dab8bf407554da095a3b8cf969.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0025-192.png', 'image_512': 'https://secure.gravatar.com/avatar/e69d54dab8bf407554da095a3b8cf969.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0025-512.png', 'status_text_canonical': '', 'team': 'TQ5DL0TQC'}, 'is_admin': False, 'is_owner': False, 'is_primary_owner': False, 'is_restricted': False, 'is_ultra_restricted': False, 'is_bot': True, 'is_app_user': False, 'updated': 1572953111}] self.assertSetEqual({'michael.sizonenko.17', 'formtestbot', 'dethline88', 'slackbot', 'testbot'}, extract_usernames(test_slack_users_list)) with self.assertRaises(UnknownSlackUser): test_user_data = google_sheet_wrapper.get_data_by_user("test") test_user_data = google_sheet_wrapper.get_data_by_user('michael.sizonenko.17') empty_row_model = ClockRowItem() self.assertEqual(test_user_data, empty_row_model) with self.assertRaises(UnknownSlackUser): google_sheet_wrapper.clock_in("test") google_sheet_wrapper.clock_in("michael.sizonenko.17") test_user_data = google_sheet_wrapper.get_data_by_user("michael.sizonenko.17") logger.debug(f"Test user data : {test_user_data}") self.assertAlmostEqual(time.mktime(test_user_data.get_clock_in().timetuple()), time.mktime(datetime.utcnow().timetuple())) if __name__ == "__main__": logger.debug("This is a debug message.") unittest.main()