Django: проект для кіно Частина 1

Виникла ідея переробити застосунок, який допомагає робити кіно. Почав з Telegram-Bot та складність почала зростати і прийняв рішення почати спочатку та ррахувати особливості та побажання співробітників, хто був дотичний до проекту.

Отже, які зміни:

  • вся інформація зберігається в базі даних (розробка:sqlite, продакшен:postgreSQL)
  • зробити WEB-інтерфейс (Django)

ці зміни спрощують роботу над проектом та дозволяють не закидати в телеграм те, що там складно зробити (ну хоча-б на початку розробки)

route: якщо є два шлюзи

Так склалося, що сервер має два рівнозначні підключення (DHCP with GW на кожному інтерфейсі). Треба один прибрати.

Як воно було

# ip route show
default via 192.18.7.1 dev eno2
default via 10.1.1.1 dev eno1 proto dhcp src 10.1.1.21 metric 100
10.1.1.0/24 dev eno1 proto kernel scope link src 10.1.1.21
10.1.1.1 dev eno1 proto dhcp scope link src 10.1.1.21 metric 100
192.18.7.0/24 dev eno2 proto kernel scope link src 192.18.7.11

Вирішено так:

# ip route del default via 192.18.7.1 dev eno2
# ip route show
default via 10.1.1.1 dev eno1 proto dhcp src 10.1.1.21 metric 100
10.1.1.0/24 dev eno1 proto kernel scope link src 10.1.1.21
10.1.1.1 dev eno1 proto dhcp scope link src 10.1.1.21 metric 100
192.18.7.0/24 dev eno2 proto kernel scope link src 192.18.7.11

відповідно, в крон бо при переключенні чи перезавантаженні – воно буде знову тут

mdadm: Перевірка стану, заміна диску.

Сервер з mdadm Raid6 на Ubuntu 20
диски в масивах треба регулярно перевіряти. Знайшов bash-скрипт і трохи вдосконалив:
#!/bin/bash
for i in {a..q}; do
echo "Disk sd$i" $SN $MD
smartctl -i -A /dev/sd$i |grep -E "^ "5"|^"197"|^"198"|"FAILING_NOW"|"Serial""
done

Несподівано, на одному з дисків

Disk sdq
Device Model: HGST HUH721008ALE604
Serial Number: 1SGUPUEZ
5 Reallocated_Sector_Ct 0x0033 100 100 005 Pre-fail Always - 0
197 Current_Pending_Sector 0x0022 100 100 000 Old_age Always - 2048
198 Offline_Uncorrectable 0x0008 100 100 000 Old_age Offline - 1170

диск розбито так (fdisk -l):

Disk /dev/sdq: 7.28 TiB, 8001563222016 bytes, 15628053168 sectors
Disk model: HGST HUH721008AL
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disklabel type: gpt
Disk identifier: 492E1319-B4D8-4E83-94AB-C9FFD86EDB4B

Device Start End Sectors Size Type
/dev/sdq1 2048 15628053134 15628051087 7.3T Linux filesystem

новий диск розбиваю так само, все по дефолту –  створив партицію на весь диск, вона автоматично активна. Далі робота по заміні диску:

Перед вимкненням серверу – виймаю диск з масиву:

mdadm –manage /dev/md0 –fail /dev/sdq1

mdadm –manage /dev/md0 –remove /dev/sdq1

Після заміни диску – увімкнув сервер і додав до масиву (попередньо впевнився шо масив не в статусі ребілд):

mdadm –manage /dev/md0 –add /dev/sdq1

Перевіряю, що з масивом:

# cat /proc/mdstat
Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10]
md0 : active raid6 sdq1[16] sdb1[0] sdp1[14] sdo1[13] sdn1[12] sdm1[11] sdl1[10] sdk1[9] sdh1[8] sdj1[7] sdi1[6] sdg1[5] sdf1[4] sde1[3] sdd1[2] sdc1[1]
109394503680 blocks super 1.2 level 6, 512k chunk, algorithm 2 [16/15] [UUUUUUUUUUUUUUU_]
[==>..................] recovery = 10.1% (789455804/7813893120) finish=706.7min speed=165644K/sec
bitmap: 0/59 pages [0KB], 65536KB chunk

unused devices: <none>

Власне – готово.

І тут як буває – не все гаразд, при перезавантаженні не бачить md0 не збирає його автоматично.

Рішення:

fstab – монтую по ID (бо при створенні може створитись не md0 а, наприклад md170)

/dev/disk/by-uuid/74e7a502-f59d-4156-99cb-517e0bbecc70 /var/nc_data ext4 defaults,nofail,discard 0 0

Та за розкладом запускаю щось таке:

#crontab -l
*/15 * * * * /opt/raid-check.sh
# cat /opt/raid-check.sh
#!/bin/bash
file=/dev/md0
if [ ! -e "$file" ]; then
echo "File does not exist"
mdadm --assemble --scan
mount -a
else
echo "File exists"
fi
як правильно виконати задачу не знайшов, бо вирішив так (пропонують додавати в ініт свої скрипти, наприклад, що як на мене – те саме)

Додатково для зацікавлених. Довідкова інформація:

конфіг масиву:

# cat /proc/mdstat
Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10]
md0 : active raid6 sdc1[0] sdq1[15] sdp1[14] sdo1[13] sdn1[12] sdm1[11] sdl1[10] sdk1[9] sdh1[8] sdj1[7] sdi1[6] sdg1[5] sdf1[4] sde1[3] sdd1[2] sdb1[1]
109394503680 blocks super 1.2 level 6, 512k chunk, algorithm 2 [16/16] [UUUUUUUUUUUUUUUU]
bitmap: 0/59 pages [0KB], 65536KB chunk

unused devices: <none>

# cat /etc/mdadm/mdadm.conf
ARRAY /dev/md/oc-server:0 level=raid6 num-devices=16 metadata=1.2 name=oc-server:0 UUID=909ef785:11edea8c:0d9c5b27:5c3c4df3
devices=/dev/sdb1,/dev/sdc1,/dev/sdd1,/dev/sde1,/dev/sdf1,/dev/sdg1,/dev/sdh1,/dev/sdi1,/dev/sdj1,/dev/sdk1,/dev/sdl1,/dev/sdm1,/dev/sdn1,/dev/sdo1,/dev/sdp1,/dev/sdq1
DEVICE /dev/sdb1 /dev/sdc1 /dev/sdd1 /dev/sde1 /dev/sdf1 /dev/sdg1 /dev/sdh1 /dev/sdi1 /dev/sdj1 /dev/sdk1 /dev/sdl1 /dev/sdm1 /dev/sdn1 /dev/sdo1 /dev/sdp1 /dev/sdq1
CREATE owner=root group=disk mode=0660 auto=yes
ARRAY /dev/md0 UUID=909ef785:11edea8c:0d9c5b27:5c3c4df3

# cat /etc/fstab
# /etc/fstab: static file system information.
# <file system> <mount point> <type> <options> <dump> <pass>
# / was on /dev/sda2 during curtin installation
/dev/disk/by-uuid/67958d42-a663-4c1b-8a9c-f8d2e20b6a03 / ext4 defaults 0 0
# /boot/efi was on /dev/sda1 during curtin installation
/dev/disk/by-uuid/AEE0-4B33 /boot/efi vfat defaults 0 0
/swap.img none swap sw 0 0
/dev/disk/by-uuid/74e7a502-f59d-4156-99cb-517e0bbecc70 /var/nc_data ext4 defaults,nofail,discard 0 0

 

 

Telegram-bot на python. 4 Структура програми, по-новому перероблене.

Бот, з якого почав своє навчання – виявився досить складним. Прототип працює, але в деяких місцях я “зрізав кути” і при спробі перевести на робочі дані виникла потреба перебудувати (ЗНОВУ!) взаємодію об”єктів. Зрештою, треба частину коду переробити на python-style (бо начитався  книжок та надивився корисного з відосиків).

Розпочнемо з простого.

  1. Рівні виконання коду. Об”єкти треба переробити так, щоби можна було підмінити джерело даних, додати поля даних. Телеграм-функціональність має бути відокремлена (якомога краще) від обробки даних. Не використовую слова бекенд, фронтенд, бізнес-логіка бо не можу їх якісно розділити (аби самому не плутатись).
  2. Все робиться не з нуля. Мета доробити те, що є (бо воно прототип, хоча люди з ботом вже працюють).

 

Python – технології. Що вимагає ринок 07.2020

Для свого розвитку треба розуміти, які навички потрібні програмісту на Python. тут збираю що почув-побачив. Пишуть що усе те що тут є по факту готовий Senior. Тримайтеся міцніше як будете те все читати 🙂

*Сподіваюсь, зрозуміло, вказані навички вони не усі разом потрібні. Все залежить від проекту, для якого синьйора шукають.  В списку літератури є пояснення з цього приводу.

Junior – технології, фреймворки
Middle – підходи до розробки, шаблони проектування.
Senior – фокус уваги спрямовано на проекти, бізнеси та вирішення конкретних задач та проблем

Базова освіта:
Data structures and algorithms
Design patterns
System design
OOP

Технології (база):
Python3 (asyncio, concurrency, PEP8, stdlib тощо)
Django/Flask
SQL
Будь-яка RDBMS
PostgreSQL

Підвищення кваліфікації:
WEB (http, nginx, apache,  REST, SOAP, WebSockets)
NoSQL (Redis )
MongoDB
Celery
Elasticsearch
Redis
SQLalchemy
Spark
RabbitMQ (Брокери повідомлень)
Kafka (Брокери повідомлень)

Буде плюсом:
AWS
Docker
Kubernetes
Azure
Git
Linux (bash, Unix Socket)
Jira

Концептуальні знання :
Unit testing (unit, component, integration, E2E тощо)
Agile
Microservices Architecture
Test-driven development
Continuous Integration
Machine Learning
Java

Soft skills:
Communication skills
Team Player
Leadership skills

Англійська мова:
Intermediate
Upper-intermediate
Advanced

Досвід:
2+
3+
4+
5+ (Сенйор)
6+
8+
10+

література:

огляд IT 2020   DOU

Dreyfus model of skill acquisition              Модель отримання навичок братів Дрейфус

«Шаблоны корпоративных приложений» Мартина Фаулера
«Совершенный код» Стива Макконнелла
«Программист-прагматик» Эндрю Ханта и Дэвида Томаса – Эта книга стала для меня в некотором роде открытием: в ней сформулированы в четкой и выверенной форме навыки, которыми, по мнению авторов, должен обладать каждый высококлассный программист.
«Банда четырех» wiki

 

Telegram-bot на python. 3 Реалізація роботи з GoogleAPI читаю-пишу

Пишу те, що накодив сам.

Важливо: про те, як налаштувати обліковий запис та отримати json-файл для підключення до документів з правами на редагування – то читачам домашнє завдання 🙂

Для роботи створив модуль gstools куди складаю функції, які використовую для читання-запису в таблицю Google.

наприклад читання усієї сторінки:

def read_from_worksheet(work_book_name, worksheet_name):  
    scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive']  
    creds = ServiceAccountCredentials.from_json_keyfile_name('rrrrr-rrr-483eee24587fa.json', scope) 
    client = gspread.authorize(creds) 
    worksheet = client.open(work_book_name).worksheet(worksheet_name) 
    return worksheet

додати нові данні (стрічка-row)

def write_to_workbook(row_name, row_data, work_book_name, worksheet_name): 
    # document-specific operation! Be careful if copy it 
    scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive'] 
    creds = ServiceAccountCredentials.from_json_keyfile_name('rrrrr-rrr-483eee24587fa.json', scope) 
    client = gspread.authorize(creds) 
    worksheet1 = client.open(work_book_name).worksheet(worksheet_name) 
    worksheet1.update('A' + str(row_name), [row_data])

Так, є що доробляти. Але воно маленьке та працює, за умови що се прототип – переробляти недоцільно.

Модифікація даних всередині сторінки таблиці. Шукаю в таблиці що треба модифікувати та записую в потрібну комірку необхідні данні (це результат творчості для завдання – тут тільки факт того, що зробив і воно працює):

def find_and_modify_cell_by_other_cell_value(work_book_name, worksheet_name, column_number, cell_value_to_search, 
                                             column_cell_number_to_edit, data_for_enter): 
    scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive'] 
    creds = ServiceAccountCredentials.from_json_keyfile_name('rrrrr-rrr-483eee24587fa.json', scope) 
    client = gspread.authorize(creds) 
    worksheet1 = client.open(work_book_name).worksheet(worksheet_name) 
    sheet = worksheet1.get_all_values() 
    for row_number, row in enumerate(sheet): 
        if cell_value_to_search in row[column_number]: 
            worksheet1.update_cell(row_number+1, column_cell_number_to_edit, data_for_enter) 
            return worksheet_name, row[column_number], data_for_enter

Усі інші функції спираються на вищевказані та працюють IMHO коректно, поскладав у той-же файл, хоча треба було рознести по різних. При записі даних я не перевіряю дані бо перевірка проходить перед формуванням запиту на запис в таблицю. Для прототипу – норм.

В процесі роботи досить часто звертаюсь для оновлення даних з таблиць, тому застосунок інколи довго “висить” коли чекає підтвердження запису (перечитую з таблиці данні після кожного запису – це довго)

python. Telegram bot conversation

Телеграм вміє з Вами погутарити, якщо його навчити 🙂

Не одразу зрозумів як воно працює. Зараз, трохи почав розуміти. Як зрозумів – запишу а далі зміню-доповню якщо помилявся. Демонструю на прикладі що буде у самому низу сторінки.
Точка входу у розмову start_handler повертає статус розмови до якого треба перейти далі (return NAME) розмову припиняємо коли викликаємо функцію що повертає такий статус (return ConversationHandler.END)

from telegram.ext import ConversationHandler

NAME, PEOPLE_TYPE, WORKPLACE = range(3)

from ask.validators import WORKER_TYPES_MAP

@debug_requests
def start_handler(update: Update, context: CallbackContext):
.......багато..неважливого...тексту........
return NAME

@debug_requests
def name_handler(update: Update, context: CallbackContext):
   # отримати ім"я
   context.user_data[NAME] = update.message.text
   logger.info('user_data: %s', context.user_data)

   # спитати наступне питання
   inline_buttons = InlineKeyboardMarkup(
       inline_keyboard=[
        [InlineKeyboardButton(text=value, callback_data=key) for key, value in \
WORKER_TYPES_MAP.items()],

         ],
       )
   update.message.reply_text(
      text='введіть посаду співробітника',
      reply_markup=inline_buttons,
      )
return PEOPLE_TYPE

@debug_tg
def cancel_handler(update: Update, context: CallbackContext):
   """ Cancel dialog. Cancel data
   """
   update.message.reply_text('Cancel. For start over run command /start')
   return ConversationHandler.END

def main():
     logger.info('Started ask-bot')

    req = Request(
        connect_timeout=0.3,
        read_timeout=1.0,
        )
    bot = Bot(
        token=config.TG_TOKEN,
        request=req,
        base_url=config.TG_API_URL,
        )
    updater = Updater(
        bot=bot,
        use_context=True,
        )

    # перевірка підключення до Telegram API
    info = bot.get_me()
    logger.info(f'Bot info: {info}')
    # обробляємо команди розмови
    conv_handler = ConversationHandler(
        entry_points=[
            CallbackQueryHandler(start_handler, pass_user_data=True),
            ],
            states={
                NAME: [
                    MessageHandler(Filters.all, name_handler, pass_user_data=True),
                    ],
                PEOPLE_TYPE: [
                    CallbackQueryHandler(type_handler, pass_user_data=True),
                    ],
                WORKPLACE: [
                    MessageHandler(Filters.all, finish_handler, pass_user_data=True),
                    ],
                    },
                fallbacks=[
                    CommandHandler('cancel', cancel_handler),
                    ],
}
        )
updater.dispatcher.add_handler(conv_handler)
updater.dispatcher.add_handler(CommandHandler('start',start_buttons_handler))

updater.dispatcher.add_handler(MessageHandler(Filters.all, echo_handler))
# Запускаю бота в роботу
updater.start_polling()
updater.idle()
logger.info('Stopped ask-bot')

 

література

1 https://bitbucket.org/vkasatkin/tele_bot/src/master/anketa

Декоратори для роботи в python

В тексті програм для Telegram ботів зустрічаю щось таке (трохи змінив для наочності):

ADMINS = [123456, 234567]


def restrict(f):

    def inner(*args, **kwargs):
        update = args[0]
        if update and hasattr(update, 'message'):
            chat_id = update.message.chat_id
            if chat_id in ADMINS:
                print('Доступ адмінам дозволено!')
                return f(*args, **kwargs)
            else:
                print('Доступ заборонено!')
        else:
            print('Немає аргументу update')

    return inner

@restrict
def start(self, update, context):
    print('тут код функції, буде виконано якщо chat_id = update.message.chat_id')

… тут дещо пропустив ….
command_start  = CommandHandler('start', start)
updater.dispatcher.add_handler(command_start )

такий запис відповідає такому коду показаному нижче:

do_function = restrict(start)
…. тут багато пропущено ….

command_start  = CommandHandler('start', do_function)
updater.dispatcher.add_handler(command_start )

Зобов’язаний зауважити, відповідність коду визначається по результату виконання для клієнта, не гарантується, що код для виконання (бінарний код) такий самий

Telegram-bot на python. 2 Старт проекту

Для розробки використовую PyCharm на Windows x64 + python3.8
Як сервер для бота на старті проекту використовую старий ноут з FreeBSD 12.1 (minimal FreeBSD 12.1-RELEASE r354233 GENERIC i386)

Налаштування добре пояснюються на кожному першому сайті про python та  FreeBSD. Тому одразу до справи.
Створив бота в Telegram у @BotFather – добре документовано тому скажу що нам звідти треба тільки API.

Є декілька можливих модулів для створення ботів, я стартую з python-telegram-bot –  просто “знайшовся” першим. є інші, вони може кращі але  рішення прийнято. до релізу змінювати нічого не буду.

Як підключатися до гуглу взяв з допомоги гугла для python (звідти витягнув імена модулів для роботи з API googleDisk)

Налаштування оточення розробки та програм – 2-3 години робочого часу. Вивчення що треба робити брав з ютуб та текстів з пошуку.