# Python Periodic Application Worker Writing

> Используй при реализации или правке периодической фоновой задачи на Python, которая владеет собственным циклом и вызывает заданную application-операцию по fixed-delay, fixed-rate или явно заданной задержке по результату: stop-aware ожидание, timeout, политика ошибок, progress heartbeat, readiness, логирование итогов итераций и отсутствие перекрывающихся запусков. Не применять для общего lifecycle процесса, NATS, HTTP, реализации application/domain-логики, портов и адаптеров.

- Skill: `nemagu/python-periodic-application-worker-writing` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add nemagu/python-periodic-application-worker-writing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/nemagu/python-periodic-application-worker-writing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Nemagu (https://skillmd.com/u/nemagu)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/nemagu/python-periodic-application-worker-writing

---


# Периодический воркер application-операции

Реализуй тонкую долгоживущую задачу планирования, которая владеет собственным
циклом, периодически вызывает уже собранную application-операцию и не знает её
внутренних зависимостей.

## Порядок работы

1. Изучи требования к ответственности конкретного воркера.
2. Определи режим расписания, единицы времени, интервалы, timeout, влияние
   публичного результата и политику ошибок.
3. Получи готовую application-операцию из composition root.
4. Реализуй внутри задачи один stop-aware цикл без бизнес-логики.
5. Добавь unit-тесты с управляемыми часами.

Если требования уже заданы, следуй им без дополнительного проектирования. Не придумывай расписание или политику повторов.

## Граница ответственности

- Ответственность конкретного воркера определяется требованиями.
- Не объединяй независимые операции по умолчанию.
- Несколько операций допустимы только как одна связная ответственность с общими lifecycle, расписанием, масштабированием и политикой отказа.
- Разные интервалы, зависимости или правила масштабирования обычно означают разные воркеры.
- Ответственность воркера должна описываться одной короткой фразой.
- Публикатор, синхронизатор и очистка могут использовать один паттерн, но остаются разными конкретными воркерами.
- Каждая задача владеет только своим циклом. Не создавай общий цикл, который на
  каждой итерации последовательно вызывает все задачи процесса.
- Runtime запускает долгоживущие задачи и наблюдает их завершение, но не планирует
  отдельные итерации.
- В новом сервисе называть класс `<OperationName>Task`, размещать его в
  `presentation/background/<operation_name>.py` и предоставлять публичный
  `run()`. Не использовать суффикс `Worker` для задачи общего runtime.
- Получать готовые operation, stop event, heartbeat/readiness и параметры
  расписания через конструктор; не создавать внутри task composition root.

## Application-операция

- Composition root собирает операцию со всеми портами и передаёт воркеру готовый callable.
- Воркер не создаёт Unit of Work, репозитории, адаптеры или use case внутри итерации.
- Воркер не знает, какие порты нужны операции.
- Воркер не интерпретирует предметные данные результата.
- Воркер может сопоставлять варианты публичного DTO-перечисления с заранее
  заданными действиями планирования, readiness и наблюдаемости.
- Допустимы `None`, DTO-перечисление или небольшой публичный результат с
  техническими показателями, если это предусмотрено контрактом.
- Отсутствие работы является нормальным успешным результатом.
- Результат влияет на следующий момент запуска только при явно заданной таблице
  вариантов; не выводи политику из имени или внутреннего смысла операции.
- Для каждого варианта результата отдельно определить влияние на readiness. Не
  считать любой нормально возвращённый DTO техническим успехом автоматически:
  результат, требующий повтора из-за невыполненной операции, может увеличивать
  счётчик ошибок без выбрасывания исключения.

## Расписание

- Поддерживай явно выбранный требованиями `fixed-delay`, `fixed-rate` или
  result-aware delay.
- Не выбирай режим по умолчанию, если он не определён.
- Не допускай одновременных запусков одной операции.
- При `fixed-delay` отсчитывай интервал после завершения операции.
- При `fixed-rate` используй монотонные часы и не накапливай пропущенные запуски.
- Startup delay и jitter добавляй только по требованиям.
- Все ожидания должны прерываться stop event.
- Retry backoff не подменяет рабочий интервал.
- При result-aware delay для каждого варианта публичного результата явно задай
  немедленный запуск или stop-aware задержку. Ошибочный исход и timeout описывай
  отдельно от успешных вариантов DTO.
- В именах конфигурационных полей всегда указывай единицу времени. Преобразуй её
  в секунды только у вызова API `asyncio`, не смешивая значения разных единиц.

Подробная семантика приведена в [periodic-worker-design.md](references/periodic-worker-design.md).

## Timeout и отмена

- Timeout одной итерации задавай только по требованиям и храни отдельно от интервала и shutdown timeout.
- После timeout полностью заверши или отмени текущую операцию до следующего запуска.
- Не подавляй `CancelledError`.
- При остановке дай текущей операции согласованное время на завершение; дальнейшая принудительная отмена принадлежит runtime.
- Не повторяй автоматически операцию, если результат внешнего побочного эффекта неизвестен.

## Политика ошибок

Политика определяется публичным контрактом operation и требованиями:

- какие ожидаемые ошибки завершают только текущую итерацию;
- какие допускают повтор с отдельным backoff;
- какие являются фатальными для задачи;
- как обрабатывается timeout.

Если иное не задано, неожиданная ошибка должна завершать задачу, чтобы runtime сработал fail-fast. Не создавай скрытый бесконечный retry. Внутренние ошибки нижних слоёв не должны пересекать публичную границу application-операции.

## Логирование

Применяй `python-service-logging-writing` и профиль конкретного процесса.
Периодическая task владеет итогом каждой выполненной итерации и переходами
readiness, которые вычисляет из результата operation.

- Создавай operation context только после получения идентификаторов текущей
  работы и очищай его в `finally`.
- Измеряй длительность итерации монотонными часами.
- Сопоставляй каждый публичный результат, ожидаемую ошибку и timeout с одним
  согласованным событием, уровнем, outcome и следующей задержкой.
- Записывай неожиданный окончательный сбой один раз перед его пробросом в runtime;
  runtime не должен повторять этот stack trace.
- Записывай переход readiness только при изменении состояния, не на каждой
  итерации.
- Не логируй полный DTO результата, пустой цикл, каждое ожидание и progress
  heartbeat, если профиль прямо этого не требует.
- Не логируй transport-детали исходящего адаптера как итог application-операции.

## Progress heartbeat и readiness

- Если heartbeat означает завершение итерации, обновляй его после каждого
  завершённого цикла согласно требованиям. Не подменяй его независимым таймером.
- Счётчик последовательных ошибок и переходы readiness храни внутри конкретной
  задачи. Ожидаемый успешный вариант, включая отсутствие работы, не считай
  ошибкой.
- Технические порты heartbeat и readiness получай готовыми из composition root;
  не связывай задачу с HTTP, файлом или конкретной системой метрик.

## Границы

В область скила входят:

- фабрика периодической task;
- fixed-delay и fixed-rate;
- stop-aware ожидание;
- отсутствие overlap;
- timeout итерации;
- политика ошибок и retry backoff;
- технические показатели результата;
- сопоставление DTO-результата с задержкой, progress heartbeat и readiness;
- unit-тесты расписания и вызова операции.

Не входят:

- lifecycle процесса, сигналы и управление ресурсами;
- NATS, HTTP, PostgreSQL и другие технологии;
- реализация application-операции и её портов;
- Unit of Work, outbox и репозитории;
- domain-логика.

## Проверка результата

- Граница ответственности соответствует требованиям.
- Операция передаётся готовой извне.
- Режим расписания задан явно.
- Запуски не перекрываются, пропущенные fixed-rate интервалы не накапливаются.
- Остановка прерывает ожидание.
- Timeout и retry отделены от рабочего интервала.
- Неожиданные ошибки не скрываются.
- Итоги итераций, длительности и переходы readiness соответствуют logging-профилю
  и имеют по одной записи.
- Unit-тесты покрывают оба расписания, остановку, отсутствие overlap, пропуски, no-work, ошибки, timeout и отмену.
- Для result-aware delay тесты покрывают каждый вариант DTO, progress heartbeat
  и переходы readiness.
- Имя, размещение и публичный `run()` соответствуют общему runtime-каркасу.

