Skip to main content

Dynamic survey/dialog for aiogram3 with aiogram_dialog and Pydantic support

Project description

Оглавление

🇺🇸 English

🇷🇺 Русский

🇺🇸 English

BriefSurvey

Universal Dynamic Survey for Telegram Bots with aiogram version 3 aiogram_dialog and Pydantic

Description

BriefSurvey is a module for quick and flexible creation of dialog-based surveys in Telegram using aiogram v3 and aiogram_dialog.

  • Questions are defined using Pydantic models to enforce strong typing and validation.
  • Final answers are automatically serialized back into a Pydantic result model.
  • Easy to extend and customize.
  • Supports different question types: text, number, single-choice, multiple-choice.
  • Simple integration and handler registration.
  • Auto-validation questions by names.Questions with names like "age", "weight" validate and send error messages automatically without validator enter.

Installation

Via pip

pip install brief-survey

From GitHub Repository

pip install git+https://github.com/Fugguri/brief_survey.git 

Download Locally and Install

git clone https://github.com/Fugguri/brief_survey.git
pip install -e brief_survey 

Quick Start

(case 1) Dynamic crate a brief

from brief_survey import BriefSurvey
from brief_survey.validators.person import age
async def save_handler(user_id: int, result: any):
    # dynamic access to survey result fields by question name.
    name = result.name
    age = result.age
    gender = result.gender

    return

survey = BriefSurvey(
    save_handler=save_handler,
    start_command='start_brief'  # customizable start command for the survey
)

# Customizable error messages
survey.info_messages.invalid_input = "Invalid data received, please try again."
# Customizable button text at the end of the survey
survey.buttons.finish_text = "Finish survey"

survey.add_question(
    text="What is your name?",
    question_type="text",
    name="name",
    media_path='storage/media/img.png'  # you can send media with text for any question (optional)
)

survey.add_question(
    text="Your age?",
    question_type="number",
    name="age",
    validator=age # You can use validators from validators path of this lib.
)


survey.add_question(
    text="Select your gender",
    question_type="choice",
    name="gender",
    choices=["Male", "Female"],
    next_questions={
        'Male': "favorite_car",
        'Female': "favorite_color",
    }
)

survey.add_question(
    text="Favorite car brand?",
    question_type="choice",
    name="favorite_car",
    choices=["MBW", "Mercedes"],
    next_question='photo'  # mandatory parameter for queries depending on choice. If not set, proceeds to next survey question
)

survey.add_question(
    text="Favorite color?",
    question_type="choice",
    name="favorite_color",
    choices=["White", "Pink", "Black"],
    next_question='photo'  # mandatory parameter for queries depending on choice. If not set, proceeds to next survey question
)

survey.add_question(
    text="Upload your photo",
    question_type="photo",
    name="photo"
)

(case 2) 1. Define your questions using Pydantic models:

from brief_survey import QuestionBase, ChoiceQuestion, MultiChoiceQuestion

questions = [
    QuestionBase(
        name="name",
        text="What is your name?",
        type="text",
        validator=lambda x: bool(x.strip()),
    ),
    ChoiceQuestion(
        name="gender",
        text="Select your gender",
        type="choice",
        choices=[("1", "Male"), ("2", "Female")],
    ),
    MultiChoiceQuestion(
        name="profession",
        text="Select your profession",
        type="multi_choice",
        choices=[
            ("1", "Athlete"),
            ("2", "Entrepreneur"),
            ("3", "Worker"),
        ],
    ),
]

2. Define a result model:

from pydantic import BaseModel
from typing import Optional

class SurveyResult(BaseModel):
    name: Optional[str]
    gender: Optional[str]
    profession: Optional[list[str]]

3. Create a function to save results:

async def save_handler(user_id: int, result: SurveyResult):
    # Save logic, e.g., store in database
    print(f"User {user_id} survey result: {result}")

4. Initialize and register the survey:

from brief_survey import BriefSurvey

survey = BriefSurvey(
    questions=questions,
    save_handler=save_handler,
    result_model=SurveyResult,
)

# In your main bot file with Dispatcher dp
survey.register_handlers(
    dp=dp,
    command_start='start_survey',     # optional
    text='Start survey',               # optional
    callback_data="start_survey"       # optional
)

5. Launch the survey in Telegram with the command:

/start_survey

Important

If you have global handlers in your bot, filter states explicitly using StateFilter to avoid conflicts that can break the survey after the first message:

from aiogram.filters import StateFilter

dp.message.register(handle, StateFilter(None))          # Only outside states
dp.callback_query.register(handle_callback, StateFilter(None))

Customizing Messages and Buttons in brief_survey

The brief_survey library provides InfoMessages and InfoButtons classes that allow you to easily customize system messages and button texts used in the survey dialogs.

What can be customized (English)

InfoMessages — system messages

Field Description Default example
invalid_input Message shown when user input is invalid "Please enter valid data."
save_success Message confirming data saved successfully "Thank you! Data saved successfully."
save_fail Message shown if saving data failed "An error occurred during saving. Please try again later."
finish_text Text displayed at survey completion "Data received."
question_not_found Message shown if question is not found "Error: question not found."
pre_save_message Message shown before saving data "Saving..."
start_message Message shown at start of survey (optional) None
forced_exit_message Message shown when survey is forcefully exited "Survey exited. Entered data does not allow continuation."

InfoButtons — button labels

Field Description Default example
finish_text Text on the button to finish the survey "Finish"
multi_select_confirm Text for confirming multi-select choice "Confirm selection"
start_again Text on the button to restart the survey "Start again"

Example usage

Setting system messages
survey.info_messages.invalid_input = "Invalid data received, please try again."
survey.info_messages.save_success = "Thank you! Your responses have been saved."
survey.info_messages.save_fail = "Saving failed, please try again later."
survey.info_messages.finish_text = "Thank you for participating!"
survey.info_messages.question_not_found = "Question not found."
survey.info_messages.pre_save_message = "Saving data..."
survey.info_messages.start_message = "Let's start the survey!"
survey.info_messages.forced_exit_message = "Survey terminated due to an error."

Setting button texts
survey.buttons.finish_text = "Finish survey"
survey.buttons.multi_select_confirm = "Confirm"
survey.buttons.start_again = "Restart"

Validators

Validator Name Description Logic / Notes
validate_not_empty Checks that string is not empty (ignoring spaces) bool(value and value.strip())
validate_email Simple email check via regex Regex ^[\w\.-]+@[\w\.-]+\.\w+$
validate_zip_code Checks zip code: 5 or 6 consecutive digits ^\d{5,6}$
validate_username Username: letters, digits, underscores; length 3-30 ^\w{3,30}$
name Name: letters and hyphens only, length 1-50 ^[A-Za-zА-Яа-яЁё\-]+$
phone_ru Russian phone format: +7XXXXXXXXXX or 8XXXXXXXXXX ^(?:\+7|8)\d{10}$
phone Universal phone validation using phonenumbers library Uses phonenumbers for parsing and validation
age Age: number from 0 to 120 Integer, 0 ≤ age ≤ 120
height Height in cm: from 30 to 300 float, 30 ≤ height ≤ 300
weight Weight in kg: from 2 to 500 float, 2 ≤ weight ≤ 500
gender Gender support for RU/EN variants, case insensitive Checks membership in allowed string set
validate_positive_int Checks that value is a positive integer value.isdigit() and int(value) > 0
validate_url Simple URL format check Regex with http/https and domain checking
validate_password_strength Password strength check: min 8 chars, digit, uppercase, lowercase, special char Multiple regex checks for complexity

🇷🇺 Русский

BriefSurvey

Универсальный динамический опросник для Telegram-ботов на базе aiogram_dialog с поддержкой Pydantic-моделей вопросов и результатов.


Описание

BriefSurvey — это модуль для быстрой и гибкой реализации диалоговых опросников в Telegram с помощью aiogram 3-й версии и aiogram_dialog.

  • Вопросы описываются Pydantic-моделями для строгой типизации и валидации.
  • Итоговые ответы автоматически сериализуются обратно в Pydantic-модель результата.
  • Легко расширяется и настраивается.
  • Позволяет реализовать опросник с разными типами вопросов: текст, число, выбор одного или нескольких вариантов.
  • Простое подключение и регистрация обработчиков.
  • Автоматическая валидация по имени вопроса. Поля типа "age", "weight" проходят автоматическую валидацию и присылают сообщение об ошибке, без указания валидаторов .

Установка

Github Repo

pip install git+https://github.com/Fugguri/brief_survey.git 

Скачать локально и установить

git clone https://github.com/Fugguri/brief_survey.git
pip install -e brief_survey 

Быстрый старт

(1 вариант) Динамическое добавление вопросов

from brief_survey import BriefSurvey
async def save_handler(user_id: int, result: any):
    #динамическое обращение к полям результата опроса по имени вопроса. 
    name = result.mame
    age = result.age
    gender = result.gender 
    return 
survey = BriefSurvey(
    save_handler=save_handler,
    start_command='start_brief' # Можно настраивать команду начала опроса
)


#Можно настраивать сообщения об ошибках
survey.info_messages.invalid_input = "Получены неверные данные, попробуйте еще раз"

#Можно настраивать сообщени и кнопку в конце опроса
survey.info_messages.invalid_input = "Получены неверные данные, попробуйте еще раз"
survey.buttons.finish_text = "Завершить опрос" 
# Если необходимо можете отправить сообщение перед началом опроса
survey.info_messages.start_message = 'Пройдите небольшой опрос перед началом работы с ботом.'
from brief_survey.validators.person import age  
survey.add_question(
    text="Как вас зовут?",
    question_type="text",
    name="name",
    media_path='storage/media/img.png'# Можете отправлять фотографии вместе с вопросом
)
survey.add_question(
    text="Ваш возраст?",
    question_type="number",
    name="age", # 
    # validator=age # Вы можете использовать готовые валидаторы из раздела validators или использовать автоматические валидаторы по имени вопроса 
)
survey.add_question(
    text="Выберите пол",
    question_type="choice",
    name="gender",
    choices=["Мужской", "Женский"],
    
    next_questions={
    'Мужской': "favorite_car",
    'Женский': "favorite_color",
    }
    
)
survey.add_question(
    text="Любимая марка автомобиля?",
    question_type="choice",
    name="favorite_car",
    choices=["MBW", "Mercedes"],
    next_question='photo' # Обязательный параметр для вариантов зависящих от выбора. Если не указать, пойдет дальше по опросу

)
survey.add_question(
    text="Любимый цвет?",
    question_type="choice",
    name="favorite_car",
    choices=["Белый", "Розовый", "Черный"],
    next_question='photo' # Обязательный параметр для вариантов зависящих от выбора. Если не указать, пойдет дальше по опросу
)

survey.add_question(
    text="Загрузите ваше фото",
    question_type="photo",
    name="photo"
)

(2 вариант)

  1. Определите вопросы (используйте модели из основного модуля):
from brief_survey import QuestionBase, ChoiceQuestion, MultiChoiceQuestion

questions = [
    QuestionBase(
        name="name",
        text="Как вас зовут?",
        type="text",
        validator=lambda x: bool(x.strip()),
    ),
    ChoiceQuestion(
        name="gender",
        text="Выберите пол",
        type="choice",
        choices=[("1", "Мужской"), ("2", "Женский")],
    ),
    MultiChoiceQuestion(
        name="gender",
        text="Выберите род деятельности",
        type="multi_choice",
        choices=[("1", "Спортсмен"), 
                 ("2", "Предприниматель"),
                 ("3", "Простой работник")
                 ],
    )
]

2. Определите модель результата:

from pydantic import BaseModel
from typing import Optional


class SurveyResult(BaseModel):
    name: Optional[str]
    gender: Optional[str]

3. Создайте функцию для сохранения результатов:

async def save_handler(user_id: int, result: SurveyResult):
    # Логика сохранения, например, в базу
    print(f"Результат опроса пользователя {user_id}: {result}")

4. Инициализируйте и зарегистрируйте опросник:

from brief_survey import BriefSurvey

survey = BriefSurvey(
    questions=questions,
    save_handler=save_handler,
    result_model=SurveyResult,
)

# в основном файле с ботом (Dispatcher dp) регистрация в Dispatcher
survey.register_handlers(dp=dp,
                         command_start='start_survey', #опционально
                         text='Начать опрос', #опционально
                         callback_data="start_survey" #опционально
                         )

5.Запускайте команду в Telegram:

/start_survey

Важно

Если у вас есть глобальный handler, фильтруйте state вручную, при помощи StateFilter. Неясные конфликты и после первого сообщения опросник перестает работать.

from aiogram.filters import StateFilter
dp.message.register(handle,StateFilter(None))  # только вне состояний!
dp.callback_query.register(handle_callback,StateFilter(None))

Настройка сообщений и кнопок в brief_survey

В библиотеке brief_survey доступны классы InfoMessages и InfoButtons, которые позволяют легко настраивать тексты системных сообщений и кнопок, используемых в диалогах опросника.

Что можно настраивать

InfoMessages — системные сообщения

Поле Описание Пример по умолчанию
invalid_input Сообщение при неверном вводе "Пожалуйста, введите корректные данные."
save_success Сообщение об успешном сохранении "Спасибо! Данные успешно сохранены."
save_fail Сообщение об ошибке при сохранении "Произошла ошибка при сохранении. Попробуйте позже."
finish_text Текст при завершении опроса "Данные приняты."
question_not_found Сообщение при ошибке отсутствия вопроса "Ошибка: вопрос не найден."
pre_save_message Сообщение перед отправкой данных на сохранение "Сохраняю"
start_message Сообщение при начале опроса (опционально) None
forced_exit_message Сообщение при принудительном выходе из опроса "Выход из опроса.Введенные данные не позволяют продолжить опрос"

InfoButtons — тексты кнопок

Поле Описание Пример по умолчанию
finish_text Надпись на кнопке завершения опроса "Завершить"
multi_select_confirm Текст кнопки для подтверждения выбора (множественный выбор) "Подтвердить выбор"
start_again Текст кнопки для перезапуска опроса "Начать сначала"

Пример использования

survey.info_messages.invalid_input = "Получены неверные данные, попробуйте еще раз"
survey.info_messages.save_success = "Спасибо! Ваши ответы сохранены."
survey.info_messages.save_fail = "Ошибка при сохранении, попробуйте позже."
survey.info_messages.finish_text = "Спасибо за участие!"
survey.info_messages.question_not_found = "Вопрос не найден."
survey.info_messages.pre_save_message = "Данные сохраняются..."
survey.info_messages.start_message = "Начинаем опрос!"
survey.info_messages.forced_exit_message = "Опрос прерван из-за ошибки."

Настройка текстов кнопок
survey.buttons.finish_text = "Завершить опрос"
survey.buttons.multi_select_confirm = "Подтвердить"
survey.buttons.start_again = "Начать заново"

Валидаторы

pre_brief_check

pre_brief_check - это функция предварительной проверки, которая вызывается перед началом опроса. Она позволяет проверить условия у пользователя перед тем, как начать опрос.

  • Принимает в качестве аргумента объект message от aiogram
  • Может быть как Callable, так и Awaitable
  • Должна возвращать True, если опрос не может быть начат
  • Если функция возвращает True, опрос не начнется и пользователю будет показано сообщение pre_brief_check_fail

Сообщение об ошибке можно настроить через survey.info_messages.pre_brief_check_fail.

Пример использования:

async def check_user_status(message: types.Message) -> bool:
    # Проверяем, завершил ли пользователь предыдущий опрос
    user_id = message.from_user.id
    if user_id in completed_surveys:
        return True  # Опрос не будет начат
    return False

survey = BriefSurvey(
    save_handler=save_handler,
    pre_brief_check=check_user_status
)

# Настройка кастомного сообщения
survey.info_messages.pre_brief_check_fail = "Вы уже проходили данный опрос ранее."

Важно: Метод корректно обрабатывает как асинхронные, так и синхронные функции. Для асинхронных функций используется await, для синхронных - прямой вызов. Результат проверки обрабатывается в методе _pre_brief_checker, который вызывается в start. Если проверка не проходит, опрос не начинается.

В последнем обновлении появились валидаторы по имени вопроса

Имя валидатора Описание Логика / примечание
validate_not_empty Проверяет, что строка не пустая (с учетом пробелов) bool(value и value.strip())
validate_email Простая проверка email через регулярное выражение Регулярное выражение ^[\w\.-]+@[\w\.-]+\.\w+$
validate_zip_code Проверяет почтовый индекс: 5 или 6 цифр подряд ^\d{5,6}$
validate_username Имя пользователя: буквы, цифры и подчеркивания, длина 3-30 символов ^\w{3,30}$
name Имя: только буквы и дефисы, длина 1-50 ^[A-Za-zА-Яа-яЁё\-]+$
phone_ru Российский телефон: +7XXXXXXXXXX или 8XXXXXXXXXX ^(?:\+7|8)\d{10}$
phone Универсальная проверка телефона с помощью библиотеки phonenumbers Использует phonenumbers для парсинга и проверки
age Возраст: число от 0 до 120 Целое число, 0 ≤ age ≤ 120
height Рост в сантиметрах: от 30 до 300 float, 30 ≤ height ≤ 300
weight Вес в кг: от 2 до 500 float, 2 ≤ weight ≤ 500
gender Гендер с поддержкой русских и английских вариантов, регистр неважен Проверка принадлежности к набору допустимых строк
validate_positive_int Проверяет, что значение — положительное целое число value.isdigit() и int(value) > 0
validate_url Простая проверка URL Регулярное выражение с http/https и доменом
validate_password_strength Проверка сложности пароля: минимум 8 символов, цифра, заглавная, строчная, спецсимво Несколько регулярных выражений проверки

ToDo

  • add media list handler
  • add 2 type logging
  • multichoice result splitters chane to ;
  • check same questions name in list
  • add survey database saver. To save complete survey_to database. And call by his id
  • add validator sections in readme

for any errors send me a telegram message to @fugguri.

☕️bye me a coffe appreciated

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

brief_survey-0.2.10.2.tar.gz (42.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

brief_survey-0.2.10.2-py3-none-any.whl (33.8 kB view details)

Uploaded Python 3

File details

Details for the file brief_survey-0.2.10.2.tar.gz.

File metadata

  • Download URL: brief_survey-0.2.10.2.tar.gz
  • Upload date:
  • Size: 42.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for brief_survey-0.2.10.2.tar.gz
Algorithm Hash digest
SHA256 591d481eb9c0ed7e17c3926bd7cb2cea590534b2e3fc3dc16e0714d2d5d3d5d1
MD5 98fdf29448a698eab305650f272fe768
BLAKE2b-256 bad67f62de4e60f223f910c0302f67796e5a0856472c8d5e325a615322f03480

See more details on using hashes here.

Provenance

The following attestation bundles were made for brief_survey-0.2.10.2.tar.gz:

Publisher: python-publish.yml on Fugguri/brief_survey

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file brief_survey-0.2.10.2-py3-none-any.whl.

File metadata

File hashes

Hashes for brief_survey-0.2.10.2-py3-none-any.whl
Algorithm Hash digest
SHA256 8c08a1d5970277ac5f8ee0c0931d4e869fff64f05a54ea756ba8f4d2fcd65a88
MD5 c448d9be8865db17acdd4d2d79a276dc
BLAKE2b-256 11d86317de8630fb229db16d533ad63a6fe5b3b76b43b7089eab2109ed00666b

See more details on using hashes here.

Provenance

The following attestation bundles were made for brief_survey-0.2.10.2-py3-none-any.whl:

Publisher: python-publish.yml on Fugguri/brief_survey

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page