Skip to content

Dev#1

Open
MaxMaximov123 wants to merge 6 commits intomainfrom
dev
Open

Dev#1
MaxMaximov123 wants to merge 6 commits intomainfrom
dev

Conversation

@MaxMaximov123
Copy link

Лучше поздно, чем никогда)

Copilot AI review requested due to automatic review settings March 1, 2026 18:49
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Добавляет TUI/CLI клиент для управления ботами ITS Reg: конфигурация через env/.env, HTTP-клиент на httpx, интерактивное создание/просмотр/удаление ботов и экспорт ответов, а также dev-обвязку (Makefile, pre-commit, OpenAPI спецификация).

Changes:

  • Добавлен Python-пакет itsreg_cli (TUI меню, сервисный слой, API клиент, модели, импорт из JSON).
  • Добавлены OpenAPI-спека и конфиг для генерации клиента.
  • Добавлены файлы для локальной разработки/запуска: Makefile, requirements.txt, .pre-commit-config.yaml, pre-commit.sh, расширен .gitignore, обновлён README.md.

Reviewed changes

Copilot reviewed 15 out of 23 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
requirements.txt Базовые runtime-зависимости для CLI/TUI
pre-commit.sh Скрипт локальных проверок (syntax/flake8/isort/black/mypy)
openapi-config.json Конфиг генерации OpenAPI клиента
itsreg_cli/tui/menus.py TUI меню управления ботами + экспорт ответов
itsreg_cli/services/bot_service.py Интерактивное создание сценария/бота
itsreg_cli/main.py Точка входа CLI, загрузка настроек, проверки, запуск TUI
itsreg_cli/json_importer.py Импорт бота из JSON и создание через API
itsreg_cli/domain/models.py Pydantic-модели Bot/Script/Node/Edge/Message
itsreg_cli/config/settings.py Настройки через pydantic-settings (.env/env/CLI overrides)
itsreg_cli/api/client.py HTTP-клиент к ITS Reg API
itsreg_cli/main.py python -m itsreg_cli entrypoint
bot_schema.json Пример/шаблон JSON схемы бота
api/openapi/bots.yaml OpenAPI спецификация API ботов
README.md Инструкция по установке/запуску/функционалу
Makefile Команды установки/генерации клиента/запуска
.pre-commit-config.yaml Конфиг pre-commit хуков (black/isort/flake8/…)
.gitignore Игнор IDE/OS файлов и сгенерированного OpenAPI клиента

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

install: $(VENV)
$(PIP) install --upgrade pip
$(PIP) install -r requirements.txt
$(VENV)/bin/pre-commit install
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make install вызывает $(VENV)/bin/pre-commit install, но pre-commit не устанавливается (в requirements.txt его нет). Из-за этого установка падает на чистом окружении. Либо добавьте pre-commit в зависимости (отдельный dev-requirements тоже подойдёт), либо уберите/условно выполняйте этот шаг.

Suggested change
$(VENV)/bin/pre-commit install
if [ -x "$(VENV)/bin/pre-commit" ]; then "$(VENV)/bin/pre-commit" install; fi

Copilot uses AI. Check for mistakes.
Makefile Outdated
Comment on lines +14 to +19
generate-client: $(VENV)
rm -rf its_reg_api_client itsreg_api_client openapi_client itsreg_api
$(OPENAPI_CLIENT) generate \
--url https://raw.githubusercontent.com/bmstu-itstech/itsreg/main/api/openapi/bots.yaml \
--config openapi-config.json \
--meta none
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Таргет generate-client использует $(VENV)/bin/openapi-python-client, но этот пакет нигде не устанавливается, поэтому make run (зависит от generate-client) сломается на чистой машине. Решение: добавить openapi-python-client в зависимости (лучше dev), либо не генерировать клиент при каждом запуске/коммитить сгенерированный клиент.

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +44
import json

print("\n=== DEBUG: Payload being sent to API ===")
print(json.dumps(payload, indent=2, ensure_ascii=False))
print("=== End DEBUG ===\n")
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В create_bot включён безусловный debug-print отправляемого payload, который содержит Telegram token бота. Это утечка чувствительных данных в stdout/логи и будет мешать обычному использованию CLI. Сделайте вывод опциональным (флаг/ENV), или уберите целиком.

Suggested change
import json
print("\n=== DEBUG: Payload being sent to API ===")
print(json.dumps(payload, indent=2, ensure_ascii=False))
print("=== End DEBUG ===\n")

Copilot uses AI. Check for mistakes.
Comment on lines +86 to +176
def _build_node_recursive(
self, nodes_by_state: dict[int, Node], state: int
) -> Node:
if state in nodes_by_state:
return nodes_by_state[state]
title = (
questionary.text(f"Узел {state}: Название:", default=f"state-{state}").ask()
or f"state-{state}"
)
messages: List[Message] = []
while True:
message_text = questionary.text(f"Узел {state}: Текст сообщения:").ask()
if message_text:
messages.append(Message(text=message_text))
if not questionary.confirm(
f"Узел {state}: Добавить ещё сообщение?", default=False
).ask():
break
if not messages:
messages.append(Message(text=""))
options: List[str] = []
while questionary.confirm(
f"Узел {state}: Добавить кнопку-опцию?", default=False
).ask():
option_text = questionary.text(f"Узел {state}: Текст опции:").ask()
if option_text:
options.append(option_text)
edges: List[Edge] = []
edge_index = 1
while questionary.confirm(
f"Узел {state}: Добавить исходящее ребро?", default=False
).ask():
predicate_type = (
questionary.select(
f"Узел {state}, Ребро #{edge_index}: Тип условия:",
choices=[
questionary.Choice(title="Всегда", value="always"),
questionary.Choice(title="Точный текст", value="exact"),
questionary.Choice(title="Регекс", value="regex"),
],
default="exact",
).ask()
or "exact"
)
if predicate_type == "always":
predicate = {"type": "always"}
elif predicate_type == "regex":
pattern = (
questionary.text(
f"Узел {state}, Ребро #{edge_index}: Regex-паттерн:",
default=".*",
).ask()
or ".*"
)
predicate = {"type": "regex", "pattern": pattern}
else:
text_value = (
questionary.text(
f"Узел {state}, Ребро #{edge_index}: Ожидаемый текст:",
default="Далее",
).ask()
or "Далее"
)
predicate = {"type": "exact", "text": text_value}
to_state_str = questionary.text(
f"Узел {state}, Ребро #{edge_index}: Целевое состояние:",
default=str(state + 1),
).ask() or str(state + 1)
try:
to_state = int(to_state_str)
except ValueError:
to_state = state + 1
if to_state == 0:
to_state = 1
operation = (
questionary.select(
f"Узел {state}, Ребро #{edge_index}: Операция:",
choices=["noop", "save", "append"],
default="noop",
).ask()
or "noop"
)
edges.append(Edge(predicate=predicate, to=to_state, operation=operation))
if to_state not in nodes_by_state:
self._build_node_recursive(nodes_by_state, to_state)
edge_index += 1
node = Node(
state=state, title=title, messages=messages, edges=edges, options=options
)
nodes_by_state[state] = node
return node
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Рекурсивная сборка узлов может уйти в бесконечную рекурсию при цикле (например, ребро на текущий state или A→B→A): текущий state добавляется в nodes_by_state только в конце, поэтому проверка if state in nodes_by_state не спасает. Нужно помечать узел как "в процессе" до рекурсивных вызовов (или отказаться от рекурсивного автосоздания / хранить visited set).

Copilot uses AI. Check for mistakes.
Comment on lines +100 to +105
if not questionary.confirm(
f"Узел {state}: Добавить ещё сообщение?", default=False
).ask():
break
if not messages:
messages.append(Message(text=""))
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если пользователь не добавил ни одного сообщения, создаётся Message(text=""). По OpenAPI (пример ошибки message-empty-text) сервер ожидает непустой текст, из-за чего создание бота может стабильно падать с 400. Лучше требовать хотя бы одно непустое сообщение или повторно спрашивать ввод вместо подстановки пустой строки.

Suggested change
if not questionary.confirm(
f"Узел {state}: Добавить ещё сообщение?", default=False
).ask():
break
if not messages:
messages.append(Message(text=""))
# Предлагаем добавить ещё сообщение только после того,
# как было добавлено хотя бы одно непустое сообщение.
if messages and not questionary.confirm(
f"Узел {state}: Добавить ещё сообщение?", default=False
).ask():
break

Copilot uses AI. Check for mistakes.
).ask()
if action == "Показать сценарий":
console.print("Сценарий:", style="cyan")
console.print_json(json.dumps(bot.script.model_dump(), ensure_ascii=False))
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В ветке "Показать сценарий" вызывается bot.script.model_dump() без проверки на None, хотя в модели Bot поле script допускает None. Это приведёт к AttributeError при неполных данных от API. Добавьте guard (например, показать сообщение "Сценарий отсутствует") или сделайте script обязательным на уровне модели/валидации.

Suggested change
console.print_json(json.dumps(bot.script.model_dump(), ensure_ascii=False))
if bot.script is not None:
console.print_json(
json.dumps(bot.script.model_dump(), ensure_ascii=False)
)
else:
console.print("Сценарий отсутствует.", style="yellow")

Copilot uses AI. Check for mistakes.
try:
print(f"Загрузка бота из {json_path}...")
print(f"ID: {bot.id}")
print(f"Token: {bot.token[:20]}...")
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

print(f"Token: {bot.token[:20]}...") упадёт с TypeError, если в JSON нет поля token (в модели Bot token допускает None). Нужен guard/маскирование (например, печатать <missing> или пустую строку), либо сделать token обязательным при загрузке JSON.

Suggested change
print(f"Token: {bot.token[:20]}...")
token_preview = "<missing>" if not bot.token else f"{bot.token[:20]}..."
print(f"Token: {token_preview}")

Copilot uses AI. Check for mistakes.
ensure_api_available(settings.api_url)
ensure_token(settings)
except ValidationError as exc:
raise SystemExit("ITSREG_JWT_TOKEN не задан") from exc
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В except ValidationError всегда выбрасывается ITSREG_JWT_TOKEN не задан, даже если ошибка валидации относится к другим полям (например, некорректный URL). Это усложняет диагностику. Лучше вывести exc.errors()/str(exc) или сформировать сообщение в зависимости от конкретного поля.

Suggested change
raise SystemExit("ITSREG_JWT_TOKEN не задан") from exc
raise SystemExit(f"Ошибка в настройках: {exc}") from exc

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +22
```bash
git clone https://github.com/bmstu-itstech/itsreg-cli
cd itsreg-cli
make install
```

Команда `make install` создаст виртуальное окружение и установит все зависимости.

Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В README предлагается make install/make run, но текущий Makefile требует pre-commit и openapi-python-client в venv, которых нет в requirements.txt. В итоге шаги установки/запуска из документации не воспроизводимы. Обновите README в соответствии с фактическими зависимостями/процессом.

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +31
find . -name "*.py" -not -path "./venv/*" -not -path "./.venv/*" -not -path "*/__pycache__/*" | while read -r file; do
if ! python -m py_compile "$file" >/dev/null 2>&1; then
error "Syntax error in $file"
python -m py_compile "$file" 2>&1 | head -10
fi
done
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ERRORS не будет выставлен при синтаксических ошибках: цикл while read выполняется в сабшелле из-за пайпа find ... | while ..., поэтому изменения переменной теряются и скрипт может завершиться с кодом 0 даже при ошибках. Перепишите на while ...; do ...; done < <(find ...) или используйте другой способ обхода файлов без пайпа в цикл.

Suggested change
find . -name "*.py" -not -path "./venv/*" -not -path "./.venv/*" -not -path "*/__pycache__/*" | while read -r file; do
if ! python -m py_compile "$file" >/dev/null 2>&1; then
error "Syntax error in $file"
python -m py_compile "$file" 2>&1 | head -10
fi
done
while IFS= read -r file; do
if ! python -m py_compile "$file" >/dev/null 2>&1; then
error "Syntax error in $file"
python -m py_compile "$file" 2>&1 | head -10
fi
done < <(find . -name "*.py" -not -path "./venv/*" -not -path "./.venv/*" -not -path "*/__pycache__/*")

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants