Advertisement
Artemii_Kravtsov

SlackBlocksComposer

Aug 21st, 2021
176
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.26 KB | None | 0 0
  1. class SlackBlocksComposer:
  2.     """
  3.    Подходит для формирования blocks= (для основного сообщения) или attachment={blocks: ...}.
  4.    Код не умеет создавать интерактивные элементы и загружать файлы. Если нужна справка, то можно
  5.    посмотреть документацию по адресу https://api.slack.com/reference/block-kit/blocks
  6.    
  7.    Что тут происходит: элементы blocks добавляются с помощью вызова методов. Все добавленные
  8.    элементы сохраняются как атрибуты созанного экземпляра, а в качестве имён используются
  9.    числа в порядке возрастания (т.е. их порядковые номера). Метод .compose() пробегает по
  10.    всем именам в порядке возрастания и складывает элементы в список. Этот список
  11.    и является тем, что нужно передать в параметр blocks оператора SlackWebhookOperator.
  12.    
  13.    Пример вызова:
  14.    (SlackBlocksComposer().add_header('Произошла ошибка :angry:')
  15.                          .add_divider()
  16.                          .add_fields(['*Task_id*', '*DAG*', '*Время выполнения*'],
  17.                                      ['problematic_task', 'dag', '10 февраля, 10:00'])
  18.                          .add_mrkdwn('<https://example.com|По этой ссылке можно найти логи>')
  19.                          .compose())
  20.    
  21.    """
  22.    
  23.     current_element = None
  24.     blocks_by_ids = None
  25.    
  26.     def __init__(self):
  27.         self.current_element = 0
  28.         self.blocks_by_ids = {}
  29.    
  30.     def insert_body_to_block(self, body, s):
  31.         """
  32.        Вспомогательный метод. Вызывать самостоятельно не нужно
  33.        """
  34.         if s:
  35.             block_dict = getattr(self, f'block_{s}')
  36.             if block_dict['type'] == 'section':
  37.                 # fields и text не пишутся в accessory, а добавляются к словарю самой секции
  38.                 if (isinstance(body, dict)
  39.                     and ('fields' in body.keys() or ('text' in body.keys() and len(body) == 1))):
  40.                     block_dict.update(body)
  41.                 else:
  42.                     # у одной секции может быть только один 'accessory'.
  43.                     # если по ошибке накидать несколько таких, то будет сохранён последний
  44.                     block_dict['accessory'] = body
  45.             elif block_dict['type'] in ('actions', 'context'):
  46.                 action_elements_list = block_dict.get('elements', [])
  47.                 action_elements_list.append(body)
  48.                 block_dict['elements'] = action_elements_list
  49.             return
  50.         setattr(self, str(self.current_element), {'type': 'section', **body})
  51.         setattr(self, 'current_element', self.current_element + 1)
  52.        
  53.     def add_divider(self):
  54.         """
  55.        Добавляет горизонтальную черту
  56.        """
  57.         setattr(self, str(self.current_element), {'type': 'divider'})
  58.         setattr(self, 'current_element', self.current_element + 1)
  59.         return self
  60.    
  61.     def add_header(self, header:str):
  62.         """
  63.        Добавляет заголовок
  64.        """
  65.         setattr(self, str(self.current_element),
  66.                 {'type': 'header', 'text': {'type': 'plain_text', 'text': str(header)}})
  67.         setattr(self, 'current_element', self.current_element + 1)
  68.         return self
  69.    
  70.     def add_block(self, block, s):
  71.         """
  72.        Метод создаёт в том месте, в котором вызван, пустой контейнер переданного в аргументе
  73.        block типа (один из 'section', 'actions', 'context') с идентификатором 's'. После этого
  74.        блок можно заполнять элементами, передавая соответствующим методам параметр 's'.
  75.        """
  76.         self.blocks_by_ids.update({self.current_element: s})
  77.         setattr(self, 'current_element', self.current_element + 1)
  78.         setattr(self, f'block_{s}', {'type': block})
  79.         return self
  80.    
  81.     def add_text(self, text, s = None, emoji = True):
  82.         """
  83.        Добавляет неформатированный текст в отдельную секцию или в переданный
  84.        блок (блок должен быть типа 'context' или 'section')
  85.        """
  86.         body = {'text': {'type': 'plain_text', 'text': text, 'emoji': emoji}}
  87.         self.insert_body_to_block(body, s)
  88.         return self
  89.        
  90.     def add_mrkdwn(self, text, s = None, verbatim = False):
  91.         """
  92.        Добавляет текст, считая, что он написан с использованием markdown
  93.        в отдельную секцию или в переданный блок (блок должен быть типа 'context' или
  94.        'section', и стоит держать в уме, что у Slack'а собственная реализация markdown)
  95.        """
  96.         body = {'text': {'type': 'mrkdwn', 'text': text, 'verbatim': verbatim}}
  97.         self.insert_body_to_block(body, s)
  98.         return self
  99.    
  100.     def add_fields(self, *lists_of_rows, s = None, f_type = 'mrkdwn'):
  101.         """
  102.        Принимает неограниченное число списков и отображает каждый из них колонкой.
  103.        Все значения в списках приводятся к str.
  104.        В ширину экрана Slack умещает ровно 2 колонки, так что если списков больше,
  105.        чем два, то они будут отображены друг под другом. Чтобы получилась колонка,
  106.        элементы связываются друг с другом с помощью '\n'.join(). Результат вставляется
  107.        в блок "s" (если передан, то должен быть секцией) или в новую секцию.
  108.        """
  109.         body = {'fields': []}
  110.         for l_of_rows in lists_of_rows:
  111.             body['fields'].append({'type': f_type, 'text': '\n'.join([str(row) for row in l_of_rows])})
  112.         self.insert_body_to_block(body, s)
  113.         return self
  114.    
  115.     def add_image(self, image_url, alt_text = ' ', s = None, title = None):
  116.         """
  117.        Добавляет картинку. В title можно передать подпись, которая будет размещена сверху, а
  118.        в alt_text - подпись, которая будет видна при наведению на картинку.
  119.        """
  120.         body = {'type': 'image', 'image_url': image_url, 'alt_text': alt_text}
  121.         if title and not s:
  122.             # заголовок можно добавить только к такой картинке, которая не будет вставлена в блок
  123.             body.update({'title': {'type': 'plain_text', 'text': title}})
  124.         if s:
  125.             self.insert_body_to_block(body, s)
  126.             return self
  127.         setattr(self, str(self.current_element), body)
  128.         setattr(self, 'current_element', self.current_element + 1)
  129.         return self
  130.    
  131.     def add_remote_file(self, external_id):
  132.         """
  133.        Чтобы добавить сторонний файл и получить на него external_id,
  134.        нужно пользоваться SlackAPIOperator.
  135.        Документация по адресу https://api.slack.com/messaging/files/remote#sharing
  136.        """
  137.         setattr(self, str(self.current_element), {'type': 'file', 'external_id': external_id,
  138.                                                   'source': 'remote'})
  139.         setattr(self, 'current_element', self.current_element + 1)
  140.         return self
  141.    
  142.    
  143.     def compose(self):
  144.         """
  145.        Метод compose достаёт значения всех атрибутов в порядке возрастания их имён и
  146.        кладёт их один за другим в список. Результирующий список и является тем, что нужно
  147.        передать в blocks оператора SlackWebhookOperator.
  148.        """
  149.         blocks, sections_to_elements = [], {}
  150.         # связываю блоки (содержание блоков, а не их "s") с их порядковыми номерами
  151.         for i, sec_i in self.blocks_by_ids.items():
  152.             sections_to_elements[i] = getattr(self, f'block_{sec_i}')
  153.         # наполняю blocks
  154.         for i in range(0, self.current_element):
  155.             blocks.append(getattr(self, str(i), sections_to_elements.get(i)))
  156.         return blocks
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement