run commands in ThreadPool

This commit is contained in:
Dmitry Afanasyev 2023-04-08 14:49:46 +03:00
parent 3ea5b0c1ee
commit 031a7fade7
8 changed files with 74 additions and 57 deletions

View File

@ -1,14 +1,17 @@
TELEGRAM_API_TOKEN= TELEGRAM_API_TOKEN="123456789:AABBCCDDEEFFaabbccddeeff-1234567890"
# webhook settings # webhook settings
WEBHOOK_HOST= WEBHOOK_HOST="https://mydomain.com"
WEBHOOK_PATH= WEBHOOK_PATH="/transport"
# webserver settings # webserver settings
WEBAPP_HOST=127.0.0.1 WEBAPP_HOST="127.0.0.1"
WEBAPP_PORT=8084 WEBAPP_PORT="8080"
# set to 1 to start with webhook. Else bot will start on polling method # set to true to start with webhook. Else bot will start on polling method
START_WITH_WEBHOOK= START_WITH_WEBHOOK="true"
GECKO_DRIVER_VERSION=0.31.0 GECKO_DRIVER_VERSION="0.32.0"
# chat ids for scheduler tasks
CHAT_IDS="123456789,987654321"

View File

@ -1,4 +1,5 @@
import asyncio import asyncio
from concurrent.futures.thread import ThreadPoolExecutor
from dataclasses import dataclass from dataclasses import dataclass
from aiogram import Bot, types from aiogram import Bot, types
@ -9,6 +10,8 @@ from aiogram.utils.callback_data import CallbackData
from app.core.parse_web import WebParser from app.core.parse_web import WebParser
from app.settings import TELEGRAM_API_TOKEN from app.settings import TELEGRAM_API_TOKEN
executor = ThreadPoolExecutor(10)
@dataclass @dataclass
class TransportBot: class TransportBot:
@ -46,16 +49,14 @@ class TransportBot:
async def home_office( async def home_office(
query: types.CallbackQuery, callback_data: dict[str, str] query: types.CallbackQuery, callback_data: dict[str, str]
) -> types.Message: ) -> types.Message:
driver = WebParser.get_driver() url = 'https://yandex.ru/maps/213/moscow/stops/stop__9640740/?ll=37.527924%2C55.823470&tab=overview&z=21'
text = WebParser.parse_yandex_maps( message = 'Остановка Б. Академическая ул, д. 15'
driver=driver, buses = [
url='https://yandex.ru/maps/213/moscow/stops/stop__9640740/?ll=37.527924%2C55.823470&tab=overview&z=21', '300',
message='Остановка Б. Академическая ул, д. 15', 'т19',
buses=[ ]
'300',
'т19', text = await TransportBot._get_buses_data(url=url, message=message, buses=buses)
],
)
return await TransportBot.bot.send_message( return await TransportBot.bot.send_message(
query.message.chat.id, text, reply_markup=TransportBot.get_keyboard() query.message.chat.id, text, reply_markup=TransportBot.get_keyboard()
@ -66,16 +67,14 @@ class TransportBot:
async def office_home( async def office_home(
query: types.CallbackQuery, callback_data: dict[str, str] query: types.CallbackQuery, callback_data: dict[str, str]
) -> types.Message: ) -> types.Message:
driver = WebParser.get_driver() url = 'https://yandex.ru/maps/213/moscow/stops/stop__9640288/?ll=37.505402%2C55.800214&tab=overview&z=21'
text = WebParser.parse_yandex_maps( message = 'Остановка Улица Алабяна'
driver=driver, buses = [
url='https://yandex.ru/maps/213/moscow/stops/stop__9640288/?ll=37.505402%2C55.800214&tab=overview&z=21', '300',
message='Остановка Улица Алабяна', 'т19',
buses=[ ]
'300',
'т19', text = await TransportBot._get_buses_data(url=url, message=message, buses=buses)
],
)
return await TransportBot.bot.send_message( return await TransportBot.bot.send_message(
query.message.chat.id, text, reply_markup=TransportBot.get_keyboard() query.message.chat.id, text, reply_markup=TransportBot.get_keyboard()
@ -97,16 +96,15 @@ class TransportBot:
if not chat_ids: if not chat_ids:
return None return None
driver = WebParser.get_driver() url = 'https://yandex.ru/maps/213/moscow/stops/stop__9640740/?ll=37.527924%2C55.823470&tab=overview&z=21'
text = WebParser.parse_yandex_maps( message = 'Остановка Б. Академическая ул, д. 15'
driver=driver, buses = [
url='https://yandex.ru/maps/213/moscow/stops/stop__9640740/?ll=37.527924%2C55.823470&tab=overview&z=21', '300',
message='Остановка Б. Академическая ул, д. 15', 'т19',
buses=[ ]
'300',
'т19', text = await TransportBot._get_buses_data(url=url, message=message, buses=buses)
],
)
kwargs = {'reply_markup': TransportBot.get_keyboard()} if show_keyboard else {} kwargs = {'reply_markup': TransportBot.get_keyboard()} if show_keyboard else {}
await asyncio.gather( await asyncio.gather(
@ -120,3 +118,11 @@ class TransportBot:
for chat_id in chat_ids for chat_id in chat_ids
] ]
) )
@staticmethod
async def _get_buses_data(url: str, message: str, buses: list[str]) -> str:
driver = WebParser.get_driver()
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
executor, WebParser.parse_yandex_maps, url, message, buses, driver
)

View File

@ -15,15 +15,14 @@ from app.settings import DRIVER_SESSION_TTL
class WebParser: class WebParser:
@staticmethod @staticmethod
def parse_yandex_maps( def parse_yandex_maps(
*,
url: str, url: str,
message: str, message: str,
buses: list[str], buses: list[str],
driver: WebDriver | None = None, driver: WebDriver | None,
) -> str: ) -> str:
if not driver: if not driver:
logger.error('Driver is not configured') logger.error('Web driver is not configured')
return 'Что-то пошло не так. :( Драйвер Firefox не сконфигурирован.' return 'Что-то пошло не так. :( Веб драйвер не сконфигурирован.'
driver.get(url) driver.get(url)
time.sleep(1) time.sleep(1)
@ -63,7 +62,7 @@ class WebParser:
@staticmethod @staticmethod
@timed_cache(seconds=DRIVER_SESSION_TTL) @timed_cache(seconds=DRIVER_SESSION_TTL)
def get_driver() -> WebDriver: def get_driver() -> WebDriver | None:
opt = webdriver.ChromeOptions() opt = webdriver.ChromeOptions()
opt.add_argument('--headless') opt.add_argument('--headless')
driver = webdriver.Remote( driver = webdriver.Remote(

View File

@ -4,6 +4,7 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler
from app.core.bot import TransportBot from app.core.bot import TransportBot
from app.core.utils import logger from app.core.utils import logger
from settings import CHAT_IDS
bot_cron_jobs = { bot_cron_jobs = {
'morning_home->work_bus': { 'morning_home->work_bus': {
@ -39,10 +40,7 @@ bot_cron_jobs = {
}, },
], ],
'func_kwargs': { 'func_kwargs': {
'chat_ids': [ 'chat_ids': CHAT_IDS,
417070387, # me
# 431571617, # Lenok
]
}, },
} }
} }

View File

@ -1,6 +1,6 @@
from pathlib import Path from pathlib import Path
from decouple import AutoConfig from decouple import AutoConfig, Csv
# Build paths inside the project like this: BASE_DIR.joinpath('some') # Build paths inside the project like this: BASE_DIR.joinpath('some')
# `pathlib` is better than writing: dirname(dirname(dirname(__file__))) # `pathlib` is better than writing: dirname(dirname(dirname(__file__)))
@ -30,4 +30,6 @@ WEBAPP_PORT = config('WEBAPP_PORT', cast=int, default=8084)
START_WITH_WEBHOOK = config('START_WITH_WEBHOOK', cast=bool, default=False) START_WITH_WEBHOOK = config('START_WITH_WEBHOOK', cast=bool, default=False)
CHAT_IDS = config('CHAT_IDS', cast=Csv(int), default=[]) # chat ids for scheduler tasks
DRIVER_SESSION_TTL = 28 # selenium driver session cache ttl in seconds DRIVER_SESSION_TTL = 28 # selenium driver session cache ttl in seconds

View File

@ -4,8 +4,8 @@
# Removing some headers for improved security: # Removing some headers for improved security:
header -Server header -Server
route /transport/* { route {$WEBHOOK_PATH}/* {
reverse_proxy transport_bot:8084 reverse_proxy transport_bot:8080
} }
} }

View File

@ -45,6 +45,8 @@ services:
args: args:
USER: web USER: web
restart: unless-stopped restart: unless-stopped
env_file:
- app/config/.env
depends_on: depends_on:
- selenoid - selenoid
volumes: volumes:
@ -53,7 +55,7 @@ services:
transport_bot_network: transport_bot_network:
ipv4_address: 200.20.0.11 ipv4_address: 200.20.0.11
expose: expose:
- "8084" - "8080"
command: bash start-bot.sh command: bash start-bot.sh

View File

@ -2,9 +2,16 @@
echo "starting the bot" echo "starting the bot"
gunicorn main:create_app \ if [[ "${START_WITH_WEBHOOK}" == "true" ]]
--bind 0.0.0.0:8084 \ then
--worker-class aiohttp.GunicornWebWorker \ echo "Starting bot in webhook mode..."
--timeout 150 \ gunicorn main:create_app \
--max-requests 2000 \ --bind ${WEBAPP_HOST}:${WEBAPP_PORT} \
--max-requests-jitter 400 --worker-class aiohttp.GunicornWebWorker \
--timeout 150 \
--max-requests 2000 \
--max-requests-jitter 400
else
echo "Starting bot in polling mode..."
python main.py
fi