Канонический reference-документ (issue #171 / эпик #167, #97). Пользовательские сценарии (режимы, CLI-флаги, web/IDE, скачивание задачи) — в grader-workflow.md; установка и OAuth — в installation.md; карта документации — в docs/README.md; инварианты ядра — в
CLAUDE.md.
Здесь собран весь справочный материал: параметры конфигурации, форматы тест-кейсов (единственный канонический источник), ограничения и модель безопасности локального запуска, а также диагностика конфигурационных ошибок.
- Где что настраивается
[tool.stepik-grader]вpyproject.tomlstepik_config.json— корневая папка задач- Таймауты
- Замер памяти дочернего процесса
- Лимит тест-кейсов для microbench
- Формат тест-кейсов
- Ограничения и безопасность
- Диагностика конфигурационных ошибок
| Что | Где | Формат |
|---|---|---|
| Параметры грейдинга (таймауты, память, пороги, кэш) | 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 |
Единая точка правды для параметров грейдинга — 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] молча никогда не читался):
- Переменная окружения
STEPIK_GRADER_CONFIG— если указывает на существующий файл, используется он (высший приоритет). Невалидное значение переменной не поднимает исключение — резолюция просто продолжается со следующего источника. - Поиск
pyproject.tomlот текущей рабочей директории (cwd) вверх до корня файловой системы — первый найденный файл выигрывает (паттерн pip/ruff). - Legacy-fallback: путь относительно расположения самого пакета
(
src/stepik_grader/config.py→ на два уровня выше, Issue #35) — применяется, только если ни env, ни поиск отcwdничего не дали (сохраняет поведение при запуске тестов из корня репозитория). - Если ничего не найдено — дефолты
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.jsonlvs.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перекрывают дефолты.GraderConfig—frozen: изменить его в рантайме нельзя (мутация →FrozenInstanceError). Полный список инвариантов ядра — вCLAUDE.md.
При первом запуске downloader.py предложит указать корневую папку для задач и
путь к secrets.json:
Укажи корневую папку для всех задач Stepik [StepikTasks]:
Укажи путь к secrets.json [secrets.json]:
Значения сохраняются в stepik_config.json (в .gitignore — не коммитится).
Структура директорий внутри корневой папки:
StepikTasks/
└── <курс>/<секция>/<урок>/<NN>/ или <NN-шаг>/
Подробнее о том, как downloader.py раскладывает файлы задачи и ищет
тест-кейсы, — в grader-workflow.md § Шаг скачивания задачи.
Константа TIMEOUT_SECONDS в core/grader_core.py (значение из
CONFIG.timeout_seconds, по умолчанию 10.0 с) защищает от зависания решения —
передаётся в timeout= у proc.communicate():
TIMEOUT_SECONDS: float = 10.0 # секундЗамер режима 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_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).
| Значение в файле | Когда создаётся | Поведение |
|---|---|---|
| (файл отсутствует) | 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_test→core/runner.pyLocalRunner): решение запускается напрямую через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 изолируются разом. Без--sandboxweb-сервер (как и 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 |