Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- class SlackBlocksComposer:
- """
- Подходит для формирования blocks= (для основного сообщения) или attachment={blocks: ...}.
- Код не умеет создавать интерактивные элементы и загружать файлы. Если нужна справка, то можно
- посмотреть документацию по адресу https://api.slack.com/reference/block-kit/blocks
- Что тут происходит: элементы blocks добавляются с помощью вызова методов. Все добавленные
- элементы сохраняются как атрибуты созанного экземпляра, а в качестве имён используются
- числа в порядке возрастания (т.е. их порядковые номера). Метод .compose() пробегает по
- всем именам в порядке возрастания и складывает элементы в список. Этот список
- и является тем, что нужно передать в параметр blocks оператора SlackWebhookOperator.
- Пример вызова:
- (SlackBlocksComposer().add_header('Произошла ошибка :angry:')
- .add_divider()
- .add_fields(['*Task_id*', '*DAG*', '*Время выполнения*'],
- ['problematic_task', 'dag', '10 февраля, 10:00'])
- .add_mrkdwn('<https://example.com|По этой ссылке можно найти логи>')
- .compose())
- """
- current_element = None
- blocks_by_ids = None
- def __init__(self):
- self.current_element = 0
- self.blocks_by_ids = {}
- def insert_body_to_block(self, body, s):
- """
- Вспомогательный метод. Вызывать самостоятельно не нужно
- """
- if s:
- block_dict = getattr(self, f'block_{s}')
- if block_dict['type'] == 'section':
- # fields и text не пишутся в accessory, а добавляются к словарю самой секции
- if (isinstance(body, dict)
- and ('fields' in body.keys() or ('text' in body.keys() and len(body) == 1))):
- block_dict.update(body)
- else:
- # у одной секции может быть только один 'accessory'.
- # если по ошибке накидать несколько таких, то будет сохранён последний
- block_dict['accessory'] = body
- elif block_dict['type'] in ('actions', 'context'):
- action_elements_list = block_dict.get('elements', [])
- action_elements_list.append(body)
- block_dict['elements'] = action_elements_list
- return
- setattr(self, str(self.current_element), {'type': 'section', **body})
- setattr(self, 'current_element', self.current_element + 1)
- def add_divider(self):
- """
- Добавляет горизонтальную черту
- """
- setattr(self, str(self.current_element), {'type': 'divider'})
- setattr(self, 'current_element', self.current_element + 1)
- return self
- def add_header(self, header:str):
- """
- Добавляет заголовок
- """
- setattr(self, str(self.current_element),
- {'type': 'header', 'text': {'type': 'plain_text', 'text': str(header)}})
- setattr(self, 'current_element', self.current_element + 1)
- return self
- def add_block(self, block, s):
- """
- Метод создаёт в том месте, в котором вызван, пустой контейнер переданного в аргументе
- block типа (один из 'section', 'actions', 'context') с идентификатором 's'. После этого
- блок можно заполнять элементами, передавая соответствующим методам параметр 's'.
- """
- self.blocks_by_ids.update({self.current_element: s})
- setattr(self, 'current_element', self.current_element + 1)
- setattr(self, f'block_{s}', {'type': block})
- return self
- def add_text(self, text, s = None, emoji = True):
- """
- Добавляет неформатированный текст в отдельную секцию или в переданный
- блок (блок должен быть типа 'context' или 'section')
- """
- body = {'text': {'type': 'plain_text', 'text': text, 'emoji': emoji}}
- self.insert_body_to_block(body, s)
- return self
- def add_mrkdwn(self, text, s = None, verbatim = False):
- """
- Добавляет текст, считая, что он написан с использованием markdown
- в отдельную секцию или в переданный блок (блок должен быть типа 'context' или
- 'section', и стоит держать в уме, что у Slack'а собственная реализация markdown)
- """
- body = {'text': {'type': 'mrkdwn', 'text': text, 'verbatim': verbatim}}
- self.insert_body_to_block(body, s)
- return self
- def add_fields(self, *lists_of_rows, s = None, f_type = 'mrkdwn'):
- """
- Принимает неограниченное число списков и отображает каждый из них колонкой.
- Все значения в списках приводятся к str.
- В ширину экрана Slack умещает ровно 2 колонки, так что если списков больше,
- чем два, то они будут отображены друг под другом. Чтобы получилась колонка,
- элементы связываются друг с другом с помощью '\n'.join(). Результат вставляется
- в блок "s" (если передан, то должен быть секцией) или в новую секцию.
- """
- body = {'fields': []}
- for l_of_rows in lists_of_rows:
- body['fields'].append({'type': f_type, 'text': '\n'.join([str(row) for row in l_of_rows])})
- self.insert_body_to_block(body, s)
- return self
- def add_image(self, image_url, alt_text = ' ', s = None, title = None):
- """
- Добавляет картинку. В title можно передать подпись, которая будет размещена сверху, а
- в alt_text - подпись, которая будет видна при наведению на картинку.
- """
- body = {'type': 'image', 'image_url': image_url, 'alt_text': alt_text}
- if title and not s:
- # заголовок можно добавить только к такой картинке, которая не будет вставлена в блок
- body.update({'title': {'type': 'plain_text', 'text': title}})
- if s:
- self.insert_body_to_block(body, s)
- return self
- setattr(self, str(self.current_element), body)
- setattr(self, 'current_element', self.current_element + 1)
- return self
- def add_remote_file(self, external_id):
- """
- Чтобы добавить сторонний файл и получить на него external_id,
- нужно пользоваться SlackAPIOperator.
- Документация по адресу https://api.slack.com/messaging/files/remote#sharing
- """
- setattr(self, str(self.current_element), {'type': 'file', 'external_id': external_id,
- 'source': 'remote'})
- setattr(self, 'current_element', self.current_element + 1)
- return self
- def compose(self):
- """
- Метод compose достаёт значения всех атрибутов в порядке возрастания их имён и
- кладёт их один за другим в список. Результирующий список и является тем, что нужно
- передать в blocks оператора SlackWebhookOperator.
- """
- blocks, sections_to_elements = [], {}
- # связываю блоки (содержание блоков, а не их "s") с их порядковыми номерами
- for i, sec_i in self.blocks_by_ids.items():
- sections_to_elements[i] = getattr(self, f'block_{sec_i}')
- # наполняю blocks
- for i in range(0, self.current_element):
- blocks.append(getattr(self, str(i), sections_to_elements.get(i)))
- return blocks
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement