| docslib | ||
| examples | ||
| tests | ||
| .gitignore | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
SharedDocsLib
SharedDocsLib — небольшой Python-фреймворк для документации. Страницы и HTML-компоненты описываются функциями с декораторами, а библиотека собирает Obsidian-подобный автономный клиент и отдаёт его через FastAPI.
Возможности
- страницы с номерами
1,1.1,1.1.2и автоматическим деревом; - собственные
@Componentи набор безопасных базовых компонентов; - поиск по всему контенту, светлая/тёмная тема, breadcrumbs, Previous/Next;
- изображения из
assets_dir, единожды закодированные в base64 внутри manifest; - полностью автономный
index.html: CSS, JavaScript и начальный manifest встроены; - хранение manifest в IndexedDB и одна проверка обновления при загрузке;
index.zip, содержащий толькоindex.html;- live-preview с перекомпиляцией изменившегося Python-модуля.
UI не содержит языко-зависимых служебных подписей: поиск, тема и мобильные меню обозначены пиктограммами, а в статусе показываются ISO-дата manifest и короткий revision. Светлая палитра нейтральная, тёмная использует настоящий AMOLED-чёрный. На мобильных устройствах header-кнопки открываются в отдельном правом drawer высотой 100dvh.
Установка и запуск примера
python3 -m venv .venv
source .venv/bin/activate
pip install -e '.[dev]'
python examples/basic.py
После запуска доступны:
GET http://127.0.0.1:1234/manifest.jsonGET http://127.0.0.1:1234/index.htmlGET http://127.0.0.1:1234/index.zipGET http://127.0.0.1:1234/live(только приlive_preview=True)
Минимальный пример
from docslib import HeaderButton, run
from docslib.hooks import Component, Page
from docslib.components import H1, Image, LocalLink, P
@Component
def Lead(text):
return f'<p class="lead">{text}</p>'
@Page("1.", "Page title")
def page1():
return [
H1("Hello"),
P("Documentation text"),
Image("photo.png", "Description"),
LocalLink("Open nested page", subpage1),
]
@Page("1.1", "Sub-page")
def subpage1():
return [H1("Nested page")]
if __name__ == "__main__":
run(
port=1234,
host="0.0.0.0",
title="My docs",
assets_dir="./assets",
custom_css="./assets/inject.css",
manifest_path="http://127.0.0.1:1234/",
live_preview=True,
header_buttons=[
HeaderButton("Знакомство", page1),
HeaderButton("API reference", subpage1),
HeaderButton.external("Project", "https://example.com")
],
)
manifest_path принимает полный путь к JSON либо базовый URL, к которому будет добавлен manifest.json. Для совместимости также принят вариант manifest_parh из первоначального API, но в новом коде лучше использовать правильное имя.
Компоненты
Доступны H1, H2, H3, P, MarkdownText, Image, Link, LocalLink (LocLink), Code, InlineCode, Quote, Callout, List, Table, Divider, Badge, Details, Json и RawHTML.
Обычные компоненты экранируют пользовательский текст. RawHTML и HTML из собственных @Component считаются доверенными и вставляются как есть.
Функция страницы может вернуть строку, список/генератор строк, вложенные списки или None.
Ссылки между страницами
LocalLink создаёт обычную браузерную ссылку с hash-route. Поэтому ссылку можно копировать, открывать в новой вкладке и активировать с клавиатуры. Клиент перехватывает только обычный клик и показывает нужную страницу без перезагрузки.
# По объекту функции страницы — предпочтительный вариант:
LocalLink("Установка", install_page)
# По точному номеру:
LocalLink("Установка", "1.2")
# По уникальному названию:
LocalLink("Установка", "Install")
# По названию внутри раздела, если названия повторяются:
LocalLink("Установка для сервера", "Install", section="2")
Цель по функции разрешается во время компиляции, когда все @Page уже зарегистрированы, поэтому можно ссылаться и на функцию, объявленную ниже по файлу. Если название неоднозначно или страница отсутствует, компиляция завершится понятной ошибкой. LocLink является коротким псевдонимом LocalLink.
Ключевые кнопки в header
Для навигации верхнего уровня используйте HeaderButton. Локальная цель поддерживает те же варианты, что и LocalLink: объект функции страницы, номер, уникальный заголовок или заголовок внутри указанного раздела.
run(
header_buttons=[
# Короткая форма по функции страницы:
HeaderButton("Знакомство", introduction_page),
# Явная форма по номеру:
HeaderButton.local("API reference", "3"),
# По названию в определённом разделе:
HeaderButton.local("Установка", "Install", section="2"),
# Внешняя ссылка:
HeaderButton.external("GitHub", "https://github.com/example/project"),
]
)
Локальная header-кнопка компилируется в обычный href="#/page/…", открывает страницу без перезагрузки и подсвечивается на всех вложенных страницах своего раздела. Старые словари остаются совместимыми:
header_buttons=[
{"label": "Знакомство", "page": introduction_page},
{"label": "API", "page": "API reference", "section": "3"},
{"label": "GitHub", "href": "https://github.com/", "target": "_blank"},
]
Manifest
Manifest имеет версию схемы, заголовок, revision, updated_at, страницы, настройки UI и словарь ресурсов:
{
"schema_version": 1,
"title": "My docs",
"revision": "…",
"updated_at": "…",
"pages": [{"number": "1", "title": "Home", "content": "<h1>…</h1>"}],
"assets": {"photo.png": {"mime": "image/png", "data": "base64…"}},
"ui": {"header_buttons": []}
}
При первом открытии встроенный manifest сохраняется в IndexedDB. Затем клиент один раз запрашивает manifest_path. Если revision или updated_at отличаются, новый manifest заменяет старый и страница перезагружается. При недоступном сервере продолжает работать кеш или встроенная копия.
Live-preview
В live-режиме браузер опрашивает только /live. Сервер следит за файлами, в которых объявлены страницы, и при изменении повторно выполняет их, после чего клиент получает новый manifest. В запускаемом файле вызов run() обязательно помещать под if __name__ == "__main__", чтобы watcher не запускал второй сервер.
HTTP-загрузка и исполнение произвольных .py файлов намеренно отсутствуют: такая ручка была бы удалённым выполнением кода. Live-preview работает с локальными исходниками проекта.
Свой клиент
Встроенные исходники находятся в docslib/templates/: index.html, client.css, client.js. Скопируйте папку, измените файлы и передайте template_dir="./my-template". Компилятор проверяет обязательные placeholders и всё равно выдаёт один автономный HTML.
Для тестов:
pytest