Skip to content

Latest commit

 

History

History
327 lines (263 loc) · 26.5 KB

File metadata and controls

327 lines (263 loc) · 26.5 KB

Конфигурация и справочник (reference)

Канонический reference-документ (issue #171 / эпик #167, #97). Пользовательские сценарии (режимы, CLI-флаги, web/IDE, скачивание задачи) — в grader-workflow.md; установка и OAuth — в installation.md; карта документации — в docs/README.md; инварианты ядра — в CLAUDE.md.

Здесь собран весь справочный материал: параметры конфигурации, форматы тест-кейсов (единственный канонический источник), ограничения и модель безопасности локального запуска, а также диагностика конфигурационных ошибок.

Оглавление


Где что настраивается

Что Где Формат
Параметры грейдинга (таймауты, память, пороги, кэш) pyproject.toml[tool.stepik-grader] TOML, читается в GraderConfig/CONFIG
Корневая папка задач и путь к secrets.json stepik_config.json (в текущей папке) JSON, пишется downloader.py
OAuth-токены Stepik secrets.json JSON, пишется storage.save_secrets() (см. installation.md)
Кэш результатов проверки .grader_cache/results.json (в CWD) JSON, opt-in (--cache)
Поведение pytest-плагина pyproject.toml[tool.pytest.ini_options] grader_mode TOML

[tool.stepik-grader] в pyproject.toml

Единая точка правды для параметров грейдинга — dataclass GraderConfig (frozen=True, потокобезопасно) в src/stepik_grader/config.py. CONFIG вычисляется лениво при первом обращении (module __getattr__, issue #142) — load_config() читает секцию [tool.stepik-grader] из pyproject.toml и кэширует результат; голый import stepik_grader.config диск не трогает. Если файла или секции нет — используются дефолты. Незнакомые ключи молча игнорируются (в GraderConfig попадают только объявленные поля).

Порядок разрешения пути к pyproject.toml (issue #258 — при установке через pipx/wheel путь относительно расположения пакета указывал внутрь окружения, и [tool.stepik-grader] молча никогда не читался):

  1. Переменная окружения STEPIK_GRADER_CONFIG — если указывает на существующий файл, используется он (высший приоритет). Невалидное значение переменной не поднимает исключение — резолюция просто продолжается со следующего источника.
  2. Поиск pyproject.toml от текущей рабочей директории (cwd) вверх до корня файловой системы — первый найденный файл выигрывает (паттерн pip/ruff).
  3. Legacy-fallback: путь относительно расположения самого пакета (src/stepik_grader/config.py → на два уровня выше, Issue #35) — применяется, только если ни env, ни поиск от cwd ничего не дали (сохраняет поведение при запуске тестов из корня репозитория).
  4. Если ничего не найдено — дефолты GraderConfig(), без ошибок.
[tool.stepik-grader]
timeout_seconds = 10.0
similar_threshold = 1.15
much_slower_threshold = 1.50
measure_child_memory = true
microbench_max_cases = 5

Полный список параметров:

Ключ Тип Дефолт Назначение
timeout_seconds float 10.0 Таймаут subprocess одного тест-кейса (режимы 1–3), защита от зависания.
similar_threshold float 1.15 Порог вердикта SIMILAR в бенчмарке (относительно быстрейшего).
much_slower_threshold float 1.50 Порог вердикта MUCH SLOWER в бенчмарке.
measure_child_memory bool true true — мониторинг дочернего процесса через psutil (честнее, медленнее); false — RSS родителя (быстро, грубо).
microbench_max_cases int 5 Максимум тест-кейсов при timeit-замерах (режим 4) для стабильного std-dev.
encoding str "utf-8" Кодировка чтения файлов решений и тестов.
max_memory_mb int | None 1024 Best-effort лимит памяти дочернего процесса (POSIX-only, RLIMIT_AS); None — без лимита. См. Ограничения и безопасность.
use_cache bool false Включить кэш результатов по умолчанию (эквивалент --cache, issue #56). Отдельный запуск форсируется --no-cache.
glossary_store str | None None Путь к локальной JSON-базе карточек глоссария (issue #126); None — веб-слой откатывается на компактный core/glossary.py. См. glossary.md.
glossary_missing_queue str ".grader_glossary_missing.json" Путь к очереди пополнения глоссария (J7 — недостающие карточки). См. glossary.md.
job_workers int 2 Размер пула воркеров async job-модели --serve (POST /api/v1/runs, issue #262) — сколько bench/microbench-задач исполняются параллельно. Не CLI-флаг.
record_stats bool false Включить локальную статистику запусков по умолчанию (эквивалент --stats, issue #268). Отдельный запуск форсируется --no-stats.
record_history bool false Писать историю прогонов в SQLite-базу .grader_history.db по умолчанию (эквивалент --history, issue #344). Отдельный запуск форсируется --no-history. Основа разделов «Правила»/«Подучить» (эпик #342).
insights_window_n int 10 Окно последних N прогонов для статуса карточек «Подучить» (эпик #342, issue #347, core/insights.py) — по номерам прогонов, не по календарю.
insights_active_threshold_t int 2 Порог активности T: ≥T попаданий ключа ошибки в окне N → карточка «активна».
insights_clean_streak_k int 3 Чистая серия K: ≥K подряд чистых прогонов → карточка уходит в «архив побед».
sandbox_max_cpu_seconds float 10.0 --sandbox (issue #266): жёсткий лимит CPU-времени решения (backstop под общим timeout_seconds).
sandbox_max_processes int 32 --sandbox: лимит числа процессов решения (anti-fork-bomb). На Linux под bwrap — абсолютное значение; на голом POSIX/macOS — бюджет сверх текущего числа процессов пользователя. См. SECURITY.md.
sandbox_max_output_bytes int 10485760 (10 МБ) --sandbox: лимит суммарного размера stdout+stderr решения.
max_output_bytes int 10485760 (10 МБ) Потолок накопления stdout+stderr на обычном пути (без --sandbox, issue #629). Вывод сверх лимита отбрасывается (в stderr добавляется пометка), чтение продолжается, процесс доживает до своего timeout_seconds — так ограничивается память хоста, а не время жизни решения. Действует на пути с отменой (веб-прогоны и песочница).
max_active_runs int 20 Back-pressure async job-модели --serve (POST /api/v1/runs, issue #429): максимум одновременных нетерминальных job'ов; превышение → 429 too_many_runs. Настройка сервера, не CLI-флаг и не параметр запроса. Фундамент server mode (#151).
ai_base_url str | None None --ai-hints (issue #435, ADR-0003): базовый URL OpenAI-совместимого эндпоинта ({ai_base_url}/chat/completions, на requests, без SDK). None — AI выключен (graceful skip). Работает и с облаком, и с локальным ollama.
ai_model str | None None Имя модели для --ai-hints.
ai_api_key_env str "STEPIK_GRADER_AI_KEY" Имя env-переменной с API-ключом (не сам ключ). Значение ключа никогда не в конфиге/файлах — читается из окружения в момент вызова и редактируется в логах (diag_log). Локальному провайдеру (ollama) ключ не нужен.
ai_max_tokens int 400 Лимит токенов ответа AI-подсказки.
ai_timeout_seconds float 20.0 Таймаут запроса к AI-эндпоинту; при истечении/сетевой ошибке — тихий пропуск, грейдинг не падает.

Приватность (record_stats/--stats, issue #268). Статистика — только локальный файл .grader_stats.jsonl в текущей директории (режимы, вердикты, ОС, суммарное время прогона). Никаких сетевых отправок — данные не покидают машину ни при каком значении этой настройки; проект в принципе не содержит кода, отправляющего телеметрию куда-либо. Файл — в .gitignore, не коммитить. Просмотр сводки — stepik-grader --stats-summary.

История прогонов (record_history/--history, issue #344). Тот же принцип приватности: локальная SQLite-база .grader_history.db (прогоны, per-case вердикты/время/класс ошибки), только на машине, в .gitignore, без сети. По умолчанию выключена — файл не создаётся, пока не задан --history или record_history = true (#134). Основа разделов «Правила»/«Подучить» (эпик #342); построена на sqlite3 из stdlib (WAL, миграции), best-effort — битая база не роняет проверку.

.grader_stats.jsonl vs .grader_history.db (issue #431). Это два независимых опциональных журнала, не дубликаты: stats.jsonl (#268) — плоская JSON-Lines сводка «режим/вердикты/ОС/время» на прогон (быстрый --stats-summary); history.db (#344) — структурированная per-case база для агрегатных выборок «Подучить»/«Правила», TTFG (--insights, --export-progress) и lint-истории. Метрики прогресса и инсайты читают только history.db; stats.jsonl оставлен как есть для обратной совместимости и лёгкой сводки.

Значения из pyproject.toml перекрывают дефолты. GraderConfigfrozen: изменить его в рантайме нельзя (мутация → FrozenInstanceError). Полный список инвариантов ядра — в CLAUDE.md.


stepik_config.json — корневая папка задач

При первом запуске downloader.py предложит указать корневую папку для задач и путь к secrets.json:

Укажи корневую папку для всех задач Stepik [StepikTasks]:
Укажи путь к secrets.json [secrets.json]:

Значения сохраняются в stepik_config.json.gitignore — не коммитится). Структура директорий внутри корневой папки:

StepikTasks/
└── <курс>/<секция>/<урок>/<NN>/ или <NN-шаг>/

Подробнее о том, как downloader.py раскладывает файлы задачи и ищет тест-кейсы, — в grader-workflow.md § Шаг скачивания задачи.


Таймауты

Таймаут subprocess (режимы 1–3)

Константа TIMEOUT_SECONDS в core/grader_core.py (значение из CONFIG.timeout_seconds, по умолчанию 10.0 с) защищает от зависания решения — передаётся в timeout= у proc.communicate():

TIMEOUT_SECONDS: float = 10.0  # секунд

Microbench (режим 4)

Замер режима 4 обёрнут фиксированным subprocess.run(timeout=60) вокруг всего цикла (5 повторов × N итераций). Локального per-call таймаута нет — см. Ограничения и безопасность.


Замер памяти дочернего процесса

MEASURE_CHILD_MEMORY: bool = True  # False — быстрее, но грубее
  • True (по умолчанию) — мониторинг дочернего процесса через psutil в отдельном потоке (честнее, но медленнее).
  • False — RSS родительского процесса (быстро, приблизительно).

Режим 4 (micro-bench) для stdin-блоков меряет пик Python-heap через tracemalloc (колонка Py-heap), а для function-блоков — RSS. tracemalloc не видит аллокации C-расширений (numpy и т.п.) — для чистого Python это приемлемо (issue #66).


Лимит тест-кейсов для microbench

MICROBENCH_MAX_CASES = 5

Ограничивает число тест-кейсов при timeit-замерах (режим 4) для стабильного std-dev.


Формат тест-кейсов

Единственный канонический источник по форматам тестов. Остальные документы (README, grader-workflow.md, CONTRIBUTING.md) ссылаются сюда, а не дублируют.

Тест-кейсы лежат в папке tests/ рядом с файлом(ами) решения:

module1/
└── task1/
    ├── task1_1.py        # основное решение
    ├── task1_2.py        # альтернативное решение 1
    └── tests/
        ├── 1             # входные данные теста №1 (stdin)
        ├── 1.clue        # ожидаемый вывод теста №1
        ├── 1.type        # тип теста: файл присутствует только для function-style задач,
        │                 # содержит строку "function"
        ├── 2
        ├── 2.clue
        └── ...

Файлы тестов читаются в кодировке UTF-8 (CONFIG.encoding).

Типы тестов (*.type)

Значение в файле Когда создаётся Поведение
(файл отсутствует) stdin-задача входные данные подаются через stdin
function function-style задача входные данные — объявление переменной (x = 5), передаётся через exec

Три формата (автодетект)

Грейдер автоматически распознаёт три формата тест-кейсов:

Формат Файлы Источник
1 — Legacy 1, 1.clue, 2, 2.clue в tests/ Stepik ZIP / downloader.py (создаётся автоматически при скачивании)
2 — Именованные input_1.txt + expected_1.txt, input_2.txt + expected_2.txt, … ручное добавление
3 — python-generation (приоритет) tests/input.txt + tests/output.txt с маркерами # TEST_N: репозитории python-generation

Format 3 используется репозиториями python-generation/Professional, python-generation/OOP, python-generation/Samurai. Stepik ZIP-архивы автоматически конвертируются в Format 3 при скачивании через downloader.py; GitHub-ссылки в тексте задачи обрабатываются автоматически.

При скачивании задачи через downloader.py файлы tests/N, tests/N.clue и при необходимости tests/N.type создаются автоматически из ZIP-архива или HTML-таблицы в тексте задачи. Если ни ZIP, ни таблицы нет — папку tests/ нужно заполнить вручную. См. grader-workflow.md § Шаг скачивания задачи.

Вердикты тест-кейсов

Полный набор — Verdict в core/result.py (6 значений); канон полей — result-contract.md.

Вердикт Значение
AC Accepted — вывод совпал с ожидаемым
WA Wrong Answer — вывод не совпал
TLE Time Limit Exceeded — превышен таймаут
RE Runtime Error — процесс завершился с ненулевым кодом
CANCELLED Прогон отменён (async job --serve через RunSpec.cancel_event), а не по таймауту (issue #262).
SANDBOX_VIOLATION Под --sandbox нарушен лимит песочницы (память/процессы/размер вывода) — аддитивный вердикт, не ломающий AC/WA/TLE/RE (issue #266).

Ограничения и безопасность

Threat model: решения запускаются БЕЗ полноценного sandbox на уровне ОС. Дочерний процесс имеет тот же доступ к файловой системе, сети и переменным окружения, что и сам grader. Защита по времени выполнения есть всегда (таймаут); на POSIX (Linux/macOS) есть ещё best-effort лимит памяти (GraderConfig.max_memory_mb, по умолчанию 1024 МБ — resource через RLIMIT_AS по pid после spawn); на Windows этого лимита нет (resource недоступен), решение может использовать сколько угодно памяти. Ограничений диска или сети нет ни на одной платформе. Запускай только доверенные решения (свои собственные или скачанные из Stepik as-is) — grader не предназначен для проверки произвольного untrusted-кода («нет sandbox на уровне ОС» задокументировано в CLAUDE.md и докстринге core/runner.py).

  • Режимы 1–3 (grader_core.run_single_testcore/runner.py LocalRunner): решение запускается напрямую через subprocess.Popen (для function-mode — во временном wrapper-скрипте, импортирующем функцию решения). Единственная защита — timeout= у proc.communicate() (grader_core.TIMEOUT_SECONDS, по умолчанию 10 с).
  • Режим 4 (core/microbench_runner.py): решения запускаются через subprocess (python -c) с timeit.repeat, защищены фиксированным subprocess.run(timeout=60). Исходник передаётся через временный файл; stdin сбрасывается перед каждой итерацией, а stdout решения перенаправляется в os.devnull на время замера, чтобы его вывод не смешивался с числами-таймингами.
  • Microbench: локальный per-call таймаут отсутствует — решение, зависающее внутри одного вызова (не в бесконечном цикле верхнего уровня), упрётся в общий 60-секундный subprocess.run(timeout=60) вокруг всего замера (5 повторов × N итераций), а не в индивидуальный лимит на итерацию. Сообщение об ошибке при таймауте указывает number=<N> (сколько итераций было в замере), чтобы хотя бы приблизительно понять масштаб зависания (issue #47 R-01).
  • --sandbox работает и с --serve (issue #396). Опциональный SandboxRunner (--sandbox, issue #266) изолирует CLI-исполнение (режимы 1–4), а с issue #396 — и web: stepik-grader --serve --sandbox ставит SandboxRunner активным runner'ом до старта сервера, поэтому grade/playground/microbench изолируются разом. Без --sandbox web-сервер (как и CLI) исполняет код обычным LocalRunner'ом без изоляции — тот же дефолт «нет изоляции». Исключение: пошаговый трейс под --sandbox недоступен — трассировщик требует пакет проекта в исполняющем процессе (core/tracer.py). Если backend недоступен на машине (нет bwrap и т.п.), запуск завершается ошибкой, а не откатывается молча на незащищённое исполнение.

Диагностика конфигурационных ошибок

Симптом Причина Что делать
⚠️ Тесты не найдены для: <name> нет папки tests/ рядом с решением или неверный формат создать tests/ (см. Формат тест-кейсов) или скачать через python -m stepik_grader.downloader
Параметры из pyproject.toml не применились опечатка в имени ключа (незнакомые ключи молча игнорируются) сверить имена с таблицей [tool.stepik-grader]
Лимит памяти не срабатывает Windows (resource недоступен) ограничение POSIX-only — см. Ограничения и безопасность
Предупреждение об «осиротевших» файлах Формата 1/2 при Формате 3 в tests/ смешаны форматы; лишние файлы игнорируются оставить один формат тест-кейсов на папку
Проблемы с токеном/авторизацией Stepik secrets.json/OAuth python -m stepik_grader.diagnostic_stepik — см. installation.md