chancellery
Канцелярия — библиотека русского склонения и сборки служебных
документов. Склоняет по падежам ФИО, должности и воинские звания (с
автоопределением рода и согласованием слов во фразе) и собирает готовый
docx по шаблону с русской разметкой, где падеж задаётся местом
подстановки, а не данными.
Изначальное видение и границы — в CONCEPT.md.
Статус: 0.1.0. Движок переехал из
«Дьяка» 0.3.3, где обкатан на настоящих
кадровых документах; поведение сверено с эталоном донора поабзацно.
План переноса — в
specs/T001-engine-extraction/spec.md.
Потребители: «Дьяк» (кадровые документы пачкой из таблицы) и «Летопись» (приказы из кадровой базы).
Быстрый старт
uv add 'chancellery>=0.1.0,<0.2.0'
Зафиксируйте минорную версию. Пока библиотека в 0.x, обратная
совместимость публичного API не обещается: ломающие правки контракта
выходят в минорной версии (0.2.0), а не в мажорной. С открытой
границей очередной выпуск подтянется молча и уронит потребителя — чаще
всего не на машине разработчика, а при сборке у пользователя. Верхнюю
границу поднимайте осознанно, прочитав CHANGELOG.md.
Сценарий целиком — таблица со строками данных плюс размеченный шаблон
docx дают папку готовых документов, по одному на строку:
from chancellery import generate_documents
written = generate_documents(
table='employees.xlsx',
template='order_template.docx',
out='out',
filename='{{ Фамилия }}.docx', # необязательно: по умолчанию — по ФИО
)
Если сценарий не подходит (источник данных не таблица, свой цикл, своя
обработка ошибок) — те же слои доступны по отдельности: read_table,
build_context, render_document, check_table. А если документы не
нужны вовсе, а нужно только склонение:
from chancellery import Case, Person, PetrovichInflector, PhraseInflector, build_context
context = build_context(
Person(
cells={
'Фамилия': 'Иванов',
'Имя': 'Пётр',
'Отчество': 'Семёнович',
'Должность': 'старший механик-водитель',
}
),
roles={
'surname': 'Фамилия',
'name': 'Имя',
'patronymic': 'Отчество',
'position': 'Должность',
},
inflector=PetrovichInflector(),
position_inflector=PhraseInflector(),
)
context['ФИО'].inflect(Case.DATV) # Иванову Петру Семёновичу
context['Должность'].inflect(Case.DATV) # старшему механику-водителю
Разметка шаблона
Падеж задаётся местом подстановки, а не данными: один и тот же человек в шапке стоит в именительном, а в теле приказа — в дательном, и шаблон говорит об этом фильтром.
Падежные фильтры:
{{ ФИО | ип }} именительный Иванов Пётр Семёнович
{{ ФИО | рд }} родительный Иванова Петра Семёновича
{{ ФИО | дт }} дательный Иванову Петру Семёновичу
{{ ФИО | вн }} винительный Иванова Петра Семёновича
{{ ФИО | тв }} творительный Ивановым Петром Семёновичем
{{ ФИО | пр }} предложный Иванове Петре Семёновиче
Тег без фильтра даёт именительный. Фильтр применим к любому склоняемому значению — ФИО, части имени, должности, званию, произвольной колонке.
Согласование по полу — фильтр согл, мужская форма первой; пол
берётся из ФИО (определяется автоматически по имени и отчеству):
{{ ФИО | согл('ознакомлен', 'ознакомлена') }}
Теги ФИО. Колонка «ФИО» разбирается на части, а части собираются обратно, поэтому доступны сразу все формы:
{{ ФИО }} Иванов Пётр Семёнович
{{ Фамилия }} Иванов {{ Имя }}, {{ Отчество }} — так же
{{ Инициалы }} Иванов П. С.
{{ Инициалы_впереди }} П. С. Иванов
{{ Инициалы_слитно }} Иванов П.С.
{{ Имя_инициал }} П. одна буква с точкой
Инициалы тоже склоняются: {{ Инициалы | рд }} → «Иванова П. С.».
Пустое отчество не роняет рендер — форма просто становится короче.
Остальные колонки доступны по своему заголовку: {{ Должность | дт }},
{{ Номер приказа }}. Заголовок нормализуется (пробелы → подчёркивания,
спецсимволы вычищаются), поэтому «л/н» в таблице пишется в шаблоне как
{{ л_н }}.
Неизвестная переменная — ошибка, а не пустое место в готовом приказе: шаблон рендерится в строгом режиме. Пустое значение, наоборот, убирается вместе с осиротевшей пунктуацией.
Разработка
Менеджер зависимостей и окружения: uv.
uv sync # поставить зависимости
uv run pytest # прогнать тесты
Зависимости
uv add <pkg> # runtime
uv add --dev <pkg> # dev
Ошибки
Все ожидаемые ошибки библиотеки наследуют ChancelleryError:
ConfigError, TableError, TemplateError (и его частный случай
UndefinedVariableError), ReverseError. Один обработчик ловит весь
слой:
from chancellery import ChancelleryError
try:
generate_documents(table, template, out)
except ChancelleryError as exc:
print(f'Ошибка: {exc}')
Если у вашего приложения есть свой корень ошибок, его удобно
подвесить под библиотечный — тогда один except ловит оба слоя:
class MyAppError(ChancelleryError):
"""Корень ошибок приложения."""
Ловушка, на которой спотыкаются при переходе на библиотеку.
Наследование корня само по себе не чинит уже написанные
обработчики. Код, который ловил except MyAppError, перестанет
ловить ошибки движка: они наследуют ChancelleryError, а не ваш
корень — наследование идёт в другую сторону. Обработчики верхнего
уровня надо переписать на except ChancelleryError, иначе вместо
понятного сообщения пользователь получит трейсбек. Ровно это всплыло
при переводе «Дьяка»: четыре обработчика в его CLI перестали ловить
TableError и TemplateError.
Выпуск версии
scripts/publish.sh --test # репетиция на TestPyPI (нужен PYPI_TEST_TOKEN)
scripts/publish.sh # боевой PyPI
Скрипт сам гоняет четыре гейта, собирает колесо и архив исходников,
проверяет артефакты twine и выкладывает. Токен читается из .secrets
(под git-ignore, образец — .secrets.example) и машину не покидает.
Порядок: закрыть версию в CHANGELOG.md, поднять version в
pyproject.toml, слить, поставить git-тег вида 0.1.0, выложить.
Проверки перед push
uv run ruff check .
uv run ruff format --check .
uv run mypy src
scripts/pytest-guard.sh --cov=src --cov-report=term-missing --cov-fail-under=80
Все четыре должны проходить с 0 ошибок. Обходные манёвры (# noqa,
# type: ignore, расширение ignore-секции) — только по согласованию.
Структура проекта
src/— корень исходников.CONCEPT.md— изначальное видение проекта (immutable).DECISIONS.md— архитектурные решения с обоснованиями (ADR-Lite).BOARD.md— рабочая Kanban-доска (To Do / Doing / Done).BACKLOG.md— парковка идей и побочных находок.CHANGELOG.md— журнал заметных изменений.specs/— спецификации крупных фич.CLAUDE.md— проектные правила для Claude (Claude Code).
Методика работы
Проект создан из шаблона vlakir/dreamteam. Подробное описание методики (scope discipline, ритуал spec/clarify/analyze для крупных фич, pre-push контроль) — см. репозиторий шаблона.
Release files for chancellery 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| chancellery-0.1.1.tar.gz | 188.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| chancellery-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 262.8 kB
Release files / chancellery-0.1.1.tar.gz
| Download URL | chancellery-0.1.1.tar.gz |
|---|---|
| Size | 188.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2aea4267538ea2ff1abf80ac5e1d37b37f9bcf20fe1ae6aa0a2eb7003a542966
|
|
BLAKE2b-256 checksum How to use checksums |
c04b8fb6e588ee3ff06eedb70186d0632347c7e1917222d694f48b3297ecffe9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / chancellery-0.1.1-py3-none-any.whl
| Download URL | chancellery-0.1.1-py3-none-any.whl |
|---|---|
| Size | 74.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
279d6176188343b743af7bd0b37d906342c74d6df3c21029a6c6ac28efc5674d
|
|
BLAKE2b-256 checksum How to use checksums |
58da470d52ee8804ab7f3924a9c5a1a248132d7c8c863afdb01eca505125f1dc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|