deknowny / vkquick

Lightweight modern asynchronous framework for VK bots that can automatically build docs for your bots
https://vk.com/vkquick
MIT License
70 stars 6 forks source link

Мысли по v1.0 #3

Closed deknowny closed 3 years ago

deknowny commented 4 years ago

Структура проекта бота

├── LICENSE
├── README.md
├── data
│   ├── configs
│   │   ├── custom.toml
│   │   ├── deploy.toml
│   │   ├── dev.toml
│   │   ├── docs.toml
│   │   └── tests.toml
│   ├── logs
│   │   ├── annotype.log
│   │   ├── api.log
│   │   ├── longpoll.log
│   │   ├── reaction.log
│   │   └── validator.log
│   └── patterns
│       ├── annotype.txt
│       ├── reaction.txt
│       ├── signal.txt
│       └── validator.txt
├── docs
├── src
│   ├── annotypes
│   ├── event_handlers
│   ├── signal_handlers
│   └── validators
└── tests
    ├── test_annotypes
    ├── test_event_handlers
    ├── test_signal_handlers
    └── test_validators

Я не очень в курсе, как обычно содежат логи, поэтому если есть какое-то решение лучше -- без проблем добавим его. Конфиги по своей структуре тоже под вопросом.

Нейминг

Изменения

Мы как-то уже обсуждали гибкость аннотипов и пришли к тому, что нужно лишь несколько типов:

Возможно, мы что-то упустили, но команды бота -- это не bash, поэтому нужна ли им такая гибкость с билд типами и расширенными типами, или же можно просто обойтись аннотациями, какие они есть сейчас? (Я согласен на изменения логики внутри, меня не выткает именно использвоание)

2ой пункт полностью автоматизированный. Для 1ого и 3ьего можно передать некоторые параметры при запросе. Из этого можно составить примерную схему всей загрузки (уже используемую)

  1. Инициализация объекта по байтами/пути до файла/скачиванию по урлу/BytesIO/TextIOWrapper/ все, что возможно. Очевидно, что кажадя инициализация будет через свой статический метод
  2. Загрузка (передача параметров для метода при загрузке)
  3. Сохранение (передача параметров для метода при сохранении)

Либо объеденить 2ой и третий и запрашивать словарь парамтеров (если такое надо) на каждый из способов. Что-то в этом роде

photo = PhotoUploader.from_bytes(b'123')
await photo.upload_and_save.to_message(
    {"peer_id": 123}, {"name": "foo"}
)
...
return vq.Message(attachment=photo)

Можно не мучать точки и передать путь загрузки на 2ом этапе,

photo = PhotoUploader.from_bytes(
    b'123',
)
await photo.upload_and_save(
    "message", # PhotoUploadPath.TO_MESSAGE ???
    {"peer_id": 123}, {"name": "foo"}
)

Либо разбить на 3 этапа (можно оставить поддержку 2х способов)

photo = PhotoUploader.from_bytes(b'123',)
photo.upload_path = PhotoUploadPath.TO_MESSAGE
await photo.upload(peer_id=123)
await photo.save(name="foo")

Должен ли photo сам стать объектом (используя мутабельность) -- или он вернет уже другой объект? Наверно, это две разные вещи, поэтому переменные нужно будет перебиндить

photo = PhotoUploader.from_bytes(
    b'123',
)
# Вместо `await photo.upload_and_save(...`
photo = await photo.upload_and_save(
    "message",
    {"peer_id": 123}, {"name": "foo"}
)

Возможно, есть какие-то уже готовые инструменты, чтобы можно было обернуть байты/байтсио/что-угодно и получить сами байты.

# Используя старый нейминг
import vkquick as vq

@vq.Cmd(names=["foo"])
@vq.Reaction("message_new")
async def foo(sender: vq.Sender()):
    await api.some.method(param=1)
    ...

И где-то в тестах ты эмулируешь вызов с разными собыйтиями, на что получаешь такую схему:

{
    "all_validators_passed": True,
    "actions": { # Вызов апи, запросы к бд, еще что-нибудь, что происходит внутри реакции
        "api_calling": [ # Все ретурны и илды также расцениваются как вызов апи метода
            {
                "method_name": "some.method",
                "parameters": {
                    "param": 1
                }
            }
        ],
    }

    "arguments": {
        "sender": <vq.Sender object at /dev/null lalala>
    }
    # Либо
    # "arguments": [
    #     {
    #         "argument_name": "sender",
    #         "content": <vq.Sender object at /dev/null lalala>
    #     }
    # ]
}

Как добавлять свои actions? Либо оборачивать нужный класс/функцию специальной оберткой, либо хз. Честно. Но если придумать, как создавать свои штукенции для actions, можно будет обеспечить шедевральную тестируемость, полностью совместимую с возможностями питеста.

Откуда брать события, которыми эмулируется вызов? Было бы круто, если бы vkquick сам записывал все события вызова команды, а потом сам предлагал на выбор какое-либо из них, а там уже, если надо, поля можно будет поменять под свои данные.

Ketre3 commented 4 years ago

Хочу пару копеек вставить, как я это вижу сам:

DefaultType = SomeTypeBuilder()



Просто есть желание писать максимально близко к питоновским аннотациям, без постоянных созданий инстансов.

Через декораторы такое себе - будет как раз-таки нагромождение, когда декораторов больше трех на одну функцию имхо читать уже неудобно.

- **Насчет поддержки разных api и ее разных версии.**
Понятно, что здесь действительно нужно делать прослойку для чуть более удобного взаимодействия, но у меня два вопроса:
1) Мы будем запрещать поднимать бота одновременно на longpoll api и на callback api, например? Если нет, как будем проверять совместимости запросов, как будем логировать и др? 
2) Где ты видишь вообще в архитектуре, в каком месте стоит делать обработку? Будет ли она для каждого api своя? Где будем учитывать разные версии? Насколько потенциально сложно будет это обрабатывать?

- **Насчет фотографий.**
Я вот что предлагаю сделать вообще:
1) Написать маленьких методов на поэтапную загрузку картинки (с указанием, куда пикчу загружаем, и т.д.)
2) Написать сверху пару методов, которые будут делать это внутри себя, но уже сами укажут типы и т.д.
3) В самом классе Message (ну и в подобных, где есть взаимодействие с пикчами) сделать метод, который будет создавать внутри себя объект Photo и работать уже с ним, сделать менее явным крч, вот.
Так будет и спрятанно, и выглядеть аккуратно, при этом сам разработчик при необходимости вытащит все, что ему нужно (понятно через документацию)

- **Тестирование бота**
Давай разделим сразу 2 вещи:
1) Тестировать клиентскую логику
Это лучше оставить самому клиенту, можно дать пример в документации, но очень хочется, чтобы была возможность работы со всем этим как в django (т.е. дать обертку для взаимодействиями с реакциями/сигналами/валидаторами и др), минимально mock'ать объекты, благо такая возможность есть вроде как.
Дать инструменты, а там хоть unittest пусть использует, хоть pytest, все что угодно. Правда сразу нужно контролировать запуск тестов и обычный запуск, но в целом проблема решается проверкой на импорте, да.
Делать сверху свою логику - плохая идея - долго, муторно, сложность с поддержкой, вот это все. Лучше пусть сам клиент делает так, как нужно, да.
2) Тестировать самого бота (наша уже задача)
Предлагаю порефакторить все проблемные места (вчера мы с тобой обговорили), залить все туда юнит тестами, потом повесить на сам репозиторий пайплайн вместе с интеграционнами тестами. Кажется, что так будет лучше и проще всего, есче будем мониторить вк группу/чат и здесь issue (как проект, надеюсь, увеличится, будем просить сюда писать, да).

- **Зависимости.**
Предлагаю просто хранить два файла с зависимостями: requirements.txt и requirements-dev.txt (ну или poetry, без разницы), их будет достаточно. Главное, чтоб ставилось проще некуда, поэтому лучше держать стандартный (дедовский) метод.
Со всем остальным более 
Ketre3 commented 4 years ago

Кстати, предлагаю нарисовать архитектуру всего проекта, чтоб быстрее можно было бы освоиться будущим контрибьютерам, вот.