Skip to content

Commit

Permalink
Fixes and refactor (#63)
Browse files Browse the repository at this point in the history
* Pydantic v2

* Remove testing

* Beauty logging

* Update handlers.py

* Back testing

* Update settings.py

* Mmm, nice linter 🤡

* Mmm, nice second linter 🤡

* Add doc string
  • Loading branch information
annndruha authored Jul 17, 2023
1 parent 218061f commit 8a3f9d0
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 52 deletions.
7 changes: 4 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
python-telegram-bot
SQLAlchemy
alembic
requests
pydantic
alembic
SQLAlchemy
psycopg2-binary
pydantic
pydantic-settings
2 changes: 2 additions & 0 deletions src/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
tg_logger.propagate = False
tg_logger.addHandler(tg_log_handler)

logging.getLogger("httpx").setLevel(logging.WARNING)

logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
level=logging.INFO,
Expand Down
89 changes: 49 additions & 40 deletions src/handlers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Marakulin Andrey https://github.com/Annndruha
# 2023

import functools
import logging
import traceback
from io import BytesIO
Expand All @@ -22,7 +23,7 @@


settings = Settings()
engine = create_engine(url=settings.DB_DSN, pool_pre_ping=True, isolation_level="AUTOCOMMIT")
engine = create_engine(url=str(settings.DB_DSN), pool_pre_ping=True, isolation_level="AUTOCOMMIT")
Session = sessionmaker(bind=engine)
session = Session()

Expand All @@ -33,13 +34,13 @@ def reconnect_session():
global session

session.rollback()
engine = create_engine(url=settings.DB_DSN, pool_pre_ping=True, isolation_level="AUTOCOMMIT")
engine = create_engine(url=str(settings.DB_DSN), pool_pre_ping=True, isolation_level="AUTOCOMMIT")
Session = sessionmaker(bind=engine)
session = Session()
session.rollback()


async def native_error_handler(update, context):
async def native_error_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
pass


Expand All @@ -60,36 +61,58 @@ async def wrapper(update: Update, context: ContextTypes.DEFAULT_TYPE):
return wrapper


def log_name(update):
"""
Get from update user id, username and message id.
Created for short code in logging
:param update:
:return: String with
"""
if update.message is None:
ucq = update.callback_query
return f"[{ucq.from_user.id} {ucq.from_user.full_name}] [{ucq.message.id}]"
return f'[{update.message.from_user.id} {update.message.from_user.full_name}]'


def log_formatter(func):
@functools.wraps(func)
async def wrapper(update: Update, context: ContextTypes.DEFAULT_TYPE):
if update.callback_query is None:
logging.info(f'{log_name(update)} [{func.__name__}]: {repr(update.message.text)}')
else:
logging.info(f'{log_name(update)} [{func.__name__}] callback_data: {update.callback_query.data}')
await func(update, context)

return wrapper


@error_handler
@log_formatter
async def handler_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
keyboard_base = [[InlineKeyboardButton(ans["about"], callback_data="to_about")]]
text, reply_markup = __change_message_by_auth(update, ans["hello"], keyboard_base)
await update.message.reply_text(text=text, reply_markup=reply_markup, disable_web_page_preview=True)
logging.info(f"[{update.message.from_user.id} {update.message.from_user.full_name}] call /start")


@error_handler
@log_formatter
async def handler_help(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(ans["help"], disable_web_page_preview=True, parse_mode=ParseMode("HTML"))
logging.info(f"[{update.message.from_user.id} {update.message.from_user.full_name}] call /help")


@error_handler
@log_formatter
async def handler_auth(update: Update, context: ContextTypes.DEFAULT_TYPE):
requisites = __auth(update)
if requisites is None:
await update.message.reply_text(ans["val_need"])
else:
await update.message.reply_text(ans["val_info"].format(*requisites), parse_mode=ParseMode("HTML"))
logging.info(f"[{update.message.from_user.id} {update.message.from_user.full_name}] call /auth")


@error_handler
@log_formatter
async def handler_button_browser(update: Update, context: CallbackContext) -> None:
logging.info(
f"[{update.callback_query.from_user.id} {update.callback_query.from_user.full_name}]"
f"[{update.callback_query.message.id}] button pressed with callback_data={update.callback_query.data}"
)
if update.callback_query.data == "to_hello":
keyboard_base = [[InlineKeyboardButton(ans["about"], callback_data="to_about")]]
text, reply_markup = __change_message_by_auth(update, ans["hello"], keyboard_base)
Expand Down Expand Up @@ -118,39 +141,33 @@ async def handler_button_browser(update: Update, context: CallbackContext) -> No


@error_handler
@log_formatter
async def handler_unknown_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(ans["unknown_command"])
logging.info(
f"[{update.message.from_user.id} {update.message.from_user.full_name}]"
f" unknown_command: {repr(update.message.text)}"
)


@error_handler
@log_formatter
async def handler_print(update: Update, context: ContextTypes.DEFAULT_TYPE):
requisites = __auth(update)
if requisites is None:
await context.bot.send_message(chat_id=update.message.chat.id, text=ans["doc_not_accepted"])
logging.warning(f"[{update.message.from_user.id} {update.message.from_user.full_name}] try print with no auth")
logging.warning(f"{log_name(update)} try print with no auth")
return
try:
filebytes, filename = await __get_attachments(update, context)
logging.info(f"[{update.message.from_user.id} {update.message.from_user.full_name}] get attachments OK")
logging.info(f"{log_name(update)} get attachments OK")
except FileSizeError:
await update.message.reply_text(
text=ans["file_size_error"].format(update.message.document.file_name),
reply_to_message_id=update.message.id,
parse_mode=ParseMode("HTML"),
)
logging.warning(
f"[{update.message.from_user.id} {update.message.from_user.full_name}] get attachments" f"FileSizeError"
)
logging.warning(f"{log_name(update)} get attachments" f"FileSizeError")
return
except TelegramError:
await update.message.reply_text(text=ans["download_error"], reply_to_message_id=update.message.id)
logging.warning(
f"[{update.message.from_user.id} {update.message.from_user.full_name}] get attachments" f"download_error"
)
logging.warning(f"{log_name(update)} get attachments" f"download_error")
return

r = requests.post(
Expand Down Expand Up @@ -187,7 +204,7 @@ async def handler_print(update: Update, context: ContextTypes.DEFAULT_TYPE):
disable_web_page_preview=True,
parse_mode=ParseMode("HTML"),
)
logging.info(f"[{update.message.from_user.id} {update.message.from_user.full_name}] print success")
logging.info(f"{log_name(update)} print success")
marketing.print_success(
tg_id=update.message.chat.id,
surname=requisites[1],
Expand All @@ -201,35 +218,36 @@ async def handler_print(update: Update, context: ContextTypes.DEFAULT_TYPE):
reply_to_message_id=update.message.id,
parse_mode=ParseMode("HTML"),
)
logging.warning(f"[{update.message.from_user.id} {update.message.from_user.full_name}] print api 413 SizeErr")
logging.warning(f"{log_name(update)} print api 413 SizeErr")
return
await context.bot.send_message(
chat_id=update.effective_user.id,
text=ans["print_err"],
parse_mode=ParseMode("HTML"),
)
logging.warning(f"[{update.message.from_user.id} {update.message.from_user.full_name}] print unknown error")
logging.warning(f"{log_name(update)} print unknown error")


@error_handler
@log_formatter
async def handler_mismatch_doctype(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(ans["only_pdf"])
logging.warning(f"[{update.message.from_user.id} {update.message.from_user.full_name}] mismatch_doctype")
marketing.print_exc_format(tg_id=update.message.chat_id)


@error_handler
@log_formatter
async def handler_register(update: Update, context: ContextTypes.DEFAULT_TYPE):
text = update.message.text
chat_id = update.message.chat.id

async def text_fail():
if session.query(TgUser).filter(TgUser.tg_id == chat_id).one_or_none() is None:
await context.bot.send_message(chat_id=chat_id, text=ans["val_need"])
logging.warning(f"[{update.message.from_user.id} {update.message.from_user.full_name}] val_need")
logging.warning(f"{log_name(update)} val_need")
else:
await context.bot.send_message(chat_id=chat_id, text=ans["val_update_fail"])
logging.warning(f"[{update.message.from_user.id} {update.message.from_user.full_name}] val_update_fail")
logging.warning(f"{log_name(update)} val_update_fail")

if text is None:
await text_fail()
Expand All @@ -253,28 +271,19 @@ async def text_fail():
session.commit()
await context.bot.send_message(chat_id=chat_id, text=ans["val_pass"])
marketing.register(tg_id=chat_id, surname=surname, number=number)
logging.info(
f"[{update.message.from_user.id} {update.message.from_user.full_name}] register OK: "
f"{surname} {number}"
)
logging.info(f"{log_name(update)} register OK: {surname} {number}")
return True
elif r.json() and data is not None:
data.surname = surname
data.number = number
await context.bot.send_message(chat_id=chat_id, text=ans["val_update_pass"])
marketing.re_register(tg_id=chat_id, surname=surname, number=number)
logging.info(
f"[{update.message.from_user.id} {update.message.from_user.full_name}] register repeat OK: "
f"{surname} {number}"
)
logging.info(f"{log_name(update)} register repeat OK: {surname} {number}")
return True
elif r.json() is False:
await context.bot.send_message(chat_id=chat_id, text=ans["val_fail"])
marketing.register_exc_wrong(tg_id=chat_id, surname=surname, number=number)
logging.info(
f"[{update.message.from_user.id} {update.message.from_user.full_name}] register val_fail: "
f"{surname} {number}"
)
logging.info(f"{log_name(update)} register val_fail: {surname} {number}")


async def __print_settings_solver(update: Update, context: CallbackContext):
Expand Down
15 changes: 6 additions & 9 deletions src/settings.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
from pydantic import AnyUrl, BaseSettings, PostgresDsn
from pydantic import ConfigDict, PostgresDsn
from pydantic_settings import BaseSettings


class Settings(BaseSettings):
"""Application settings"""

BOT_TOKEN: str
DB_DSN: PostgresDsn
MARKETING_URL: AnyUrl
PRINT_URL: AnyUrl
PRINT_URL_QR: AnyUrl
MARKETING_URL: str
PRINT_URL: str
PRINT_URL_QR: str
MAX_PDF_SIZE_MB: float

class Config:
"""Pydantic BaseSettings config"""

case_sensitive = True
env_file = ".env"
model_config = ConfigDict(case_sensitive=True, env_file=".env", extra="allow")

0 comments on commit 8a3f9d0

Please sign in to comment.