В одной из прошлых статей я показывал как вызывать oData при помощи python. В этот раз попробуем решить более прикладную задачу, используя приобретенные знания.
Задача
Задача состоит в том, что python скрипт в определенный промежуток времени получал актуальную информацию о занятой памяти Вашей системы, и если какой-то порог был преодолён, то отправляется сообщение подписанным пользователям в телеграмм-боте.
Итак, что бы реализовать такую задачу нужно будет сделать следующее
- Создать oData сервис, который возвращает данные о занятой памяти
- Создать бота, который бы обрабатывал команды со стороны Телеграмма
- Реализовать периодический вызов oData и систему рассылки конкретным пользователям мессенджера
Создаем oData сервис
Сервис будет возвращать всего один параметр и будет иметь всего одно поле — size

Тип поля size будет у нас string
После генерации классов, реализуем GetEntitySet (Query)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
data: query type string. query = |select ( sum(TOTAL_MEMORY_USED_SIZE) / 1024 / 1024 / 1024 ) as "GB" FROM M_SERVICE_MEMORY |. data: data_tab type ref to data. field-symbols: <size_tab> type standard table. create data data_tab type table of int8. zcl_sql_executor=>result( exporting i_query = query changing c_data_table = data_tab ). assign data_tab->* to <size_tab>. data: MEMORY_SIZE type i. MEMORY_SIZE = round( val = value int4( <size_tab>[ 1 ] optional ) dec = 0 ). append value #( size = MEMORY_SIZE ) to et_entityset. |
Для определения занятой памяти используется стандартный view M_SERVICE_MEMORY. С полученными данными производятся не большие преобразования, затем они возвращается в результирующей внутренней таблице et_entityset. Конечная цифра будет в формате гигабайтов (считается прямо в селекте). Реализация класса zcl_sql_executor была описана здесь.

На картинке выше результат работы сервиса. Цифры «9362» как раз то, что нам нужно.
Создаем бота
Для реализации бота будем использовать библиотеку Telebot
установка библиотеки
pip install pyTelegramBotAPI
Далее регистрируем бота в телеграмме при помощи @BotFather. Не будем на этом заострять внимание, в интернете сотни инструкций по этой теме. Кратко об этом можно почитать тут. После регистрации бота и выполнения всех инструкций нам нужен токен, он вернется в чате общения с @BotFather.
Выглядит он примерно следующим образом.
5698781513:AAF9KT00P6skJPL1wBiFUMUqaihoOzEURhz
Следующим шагом будет создание простой логики обработки команд бота, что бы прощупать как это работает.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import telebot from telebot import types bot = telebot.TeleBot('5698781513:AAF9KT00P6skJPL1wBiFUMUqaihoOzEURhz') @bot.message_handler(commands=['start']) def start_command(message): key_board = types.ReplyKeyboardMarkup(resize_keyboard=True) item1 = types.KeyboardButton("Приветствие") item2 = types.KeyboardButton("Функциональная кнопка") key_board.add(item1, item2) bot.send_message(message.chat.id, "Hello! \nЗдесь какое-то приветствие", reply_markup=key_board) @bot.message_handler(content_types=['text']) def text_messages(message): try: if message.text == 'Приветствие': bot.send_message(message.chat.id, "Hello") elif message.text == 'Функциональная кнопка': mess = "Какая-то логика" bot.send_message(message.chat.id, mess) except Exception as e: bot.send_message(message.chat.id, "Что-то пошло ни так, попробуйте еще раз!" + repr(e)) bot.infinity_polling(timeout=10, long_polling_timeout=5) |
При запуске скрипта идем в телеграмм и открываем своего бота, которого мы регистрировали в @BotFather.
Вводим в чате команду /start

Получили ответ и интерфейс с кнопками, нажмем на кнопку «Приветствие»

Как видите, бот работает. Пару комментариев по коду.
При создании бота Вам необходимо подставить свой токен
bot = telebot.TeleBot(‘ВАШ ТОКЕН’)
Далее нужно обратить внимание на аннотации
@bot.message_handler(commands=[‘start’])
start означает обработку команды /start, text — обработку текста, который ввел пользователь. По мимо этих обработчиков есть еще их огромное количество. Для видео, файлов, видео записок (note), картинок и т.д. Для любого контента, который можно отправить в мессенджере. Подробнее читайте об этом в документации.
При помощи ReplyKeyboardMarkup создается клавиатура снизу. Кнопки «Приветствие» и «Функциональная кнопка»
Теперь, когда мы имеем бота, нужно нарастить на него функциональности
Реализовываем вызов oData и рассылку
Разобьем условно логику нашего проекта по файлам.
- bot.py — код для запуска бота и обработки его команд, т.е. как раз то, что было описано в разделе выше
- thread_work.py — тут у нас будет вынесена логика для работы периодической проверки актуальной информации об занятой памяти в SAP системе.
- sap_connector.py — логика вызова oData, разъяснение работы кода было в предыдущей статье.
Начнем с обновленного bot.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import telebot from telebot import types import sap_connector as connector import thread_work from threading import Thread bot = telebot.TeleBot('5698781513:AAF9KT00P6skJPL1wBiFUMUqaihoOzEURhz') @bot.message_handler(commands=['start']) def start_command(message): key_board = types.ReplyKeyboardMarkup(resize_keyboard=True) item1 = types.KeyboardButton("Приветствие") item2 = types.KeyboardButton("Функциональная кнопка") key_board.add(item1, item2) bot.send_message(message.chat.id, "Hello! \nЗдесь какое-то приветствие", reply_markup=key_board) @bot.message_handler(content_types=['text']) def text_messages(message): try: if message.text == 'Приветствие': bot.send_message(message.chat.id, "Hello") elif message.text == 'Функциональная кнопка': mess = "Какая-то логика" bot.send_message(message.chat.id, mess) except Exception as e: bot.send_message(message.chat.id, "Что-то пошло ни так, попробуйте еще раз!" + repr(e)) thread1 = Thread(target=thread_work.see_memory, args=(300, bot)) thread1.start() bot.infinity_polling(timeout=10, long_polling_timeout=5) |
Как можно заметить, от предыдущего примера этот код отличается только запуском треда для отслеживания актуальной занятой памяти. Эта логика может быть реализована любым способом, главное должно быть соблюдено некоторые условия. Этот метод должен запускаться параллельно либо асинхронно, как Вам больше нравится. Запуск должен быть ДО поллинга.
Продолжим кодом thread_work.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import time import sap_connector as connector from datetime import datetime, timedelta stadnikov_id = {ID пользователя} gb_accept = 10800 message_alert = 3600 def see_memory(seconds, bot): last_alert = datetime.now() - timedelta(hours=1) users = [stadnikov_id] while True: memory_set = connector.get_oData_set('SystemMemorySet', '') memory = memory_set[0]['size'] print(memory) if int(memory) >= gb_accept: date_now = datetime.now() seconds_dif = (date_now - last_alert).seconds if seconds_dif >= message_alert: last_alert = datetime.now() for user in users: bot.send_message(user, ('Заканчивается память' + ' ' + memory + 'GB')) time.sleep(seconds) |
В этом файле сразу можно заметить несколько констант
- stadnikov_id — тут я разместил уникальные ID для рассылок (для примера оставил только свой) У каждого пользователя, беседы, группы в телеграмм есть свой уникальный ID. Его можно узнавать при инициализации, когда юзер вводит /start, либо вынести эту функциональность в отдельное месте. Так же есть более казуальные способы, например воспользоваться другим ботом. @getmyid_bot — может вернуть ваш ID и любого Вашего собеседника.
- gb_accept — верхняя граница в гигабайтах, после которой производится рассылка оповещений
- message_alert — это минимальный промежуток времени в секундах, в этот отрезок сообщения отправляться не могут, сделано это для того что бы оповещения не спамили слишком часто.
По мимо этого тут есть еще два входящих параметра метода see_memory
Напомню, метод вызывается в этой строке
thread1 = Thread(target=thread_work.see_memory, args=(300, bot))
- seconds — промежуток времени в секундах, это параметр определяет как часто будет производиться проверка занятой памяти в системе SAP
- bot — экземпляр класса нашего бота
Алгоритм весьма простой, в бесконечном цикле производится считывание данных по oData, если полученное число оказалось выше gb_accept и со времени последней рассылки прошло больше секунд чем в константе message_alert, то производится рассылка. В конце цикла тред засыпает на параметр seconds секунд.
Так же еще есть одно важное условие. Человек, которому будет отправлено сообщение должен обязательно быть зарегистрирован в боте т.е. он предварительно должен ввести в боте команду /start
В конце посмотрим на файл sap_connector.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
import json import requests from requests.auth import HTTPBasicAuth sap_opt = { 'user': '{ВАШ ЛОГИН}', 'passwd': '{ВАШ ПАРОЛЬ}', 'baseurl': 'http://{ВАШ IP}:{ВАШ ПОРТ}/sap/opu/odata/SAP/Z92_BOT_DATA_SRV/', 'verify': True, 'mandt': 200, 'name': 'SAP' } def sapCreateSession(): # Создание сессии для запросов oDATA # Конектимся к SAP s = requests.Session() s.headers.update({'Connection': 'keep-alive', 'X-CSRF-TOKEN': 'Fetch'}) try: r = s.get(sap_opt['baseurl'], auth=(sap_opt['user'], sap_opt['passwd']), verify=sap_opt['verify']) except: message = "Нет соединения с системой %s %s" % (sap_opt['mandt'], sap_opt['name']) return ('NO TOKEN', 'NoSession', message) token = r.headers['x-csrf-token'] session = s sess = (token, session, None) return sess def get_oData_set(set, filter, format='$format=json'): session = sapCreateSession() # Создаю сессию token = session[0] ss = session[1] url = sap_opt['baseurl'] + set + '?' + filter + '&' + format headers = {'Content-type': 'application/json;charset=utf-8', 'X-CSRF-TOKEN': token} auth = HTTPBasicAuth(sap_opt['user'], sap_opt['passwd']) get = ss.get(url, headers=headers, auth=auth, verify=sap_opt['verify']) # Запрос данных jdata = json.loads(get.text) if jdata and 'error' not in jdata: jdata = jdata.get('d').get('results') else: jdata = '' return jdata |
Все что тут происходит было описано в прошлой статье, код без изменения. Эти методы получают данные из SAP.
Итак, исходя из настроек, наш бот вызывает oData каждые 5 минут. Если занятая память в системе оказалась больше 10800 GB, то пользователи получают оповещения. Бот может отправлять сообщение не чаще раза в час.
При таких параметрах, вот что можно увидеть в рассылке

Идея заключается в том, что пользователь при получении информации о проблеме примет меры по ее решению. Для получения информации достаточно смартфона.
В заключении
В последних трех снипитах представлен полный код для решения задачи рассылки оповещения людям в не SAP’а при каком-то триггере. Решение полностью рабочее, на момент написания статьи оно у меня отработало больше месяца без перезапусков.
Применение python для прикладных задач связанных с SAP, мне кажется очень перспективным направлением. В конце хотел бы дать еще один совет, не показывайте это безопасникам, что бы их не расстраивать.