os-craft 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,248 @@
1
+ Metadata-Version: 2.5
2
+ Name: os-craft
3
+ Version: 0.1.0
4
+ Summary: Легковесные, надежные утилиты для взаимодействия Python-приложений с ОС
5
+ Author-email: Oleg Suvorinov <suvorinovoleg@yandex.ru>
6
+ License: MIT
7
+ Keywords: asyncio,infrastructure,os,shutdown,signals
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+
19
+ # os-craft
20
+
21
+ ![Python Versions](https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12%20|%203.13%20|%203.14-blue)
22
+ ![License](https://img.shields.io/badge/license-MIT-green)
23
+ ![PyPI Status](https://img.shields.io/badge/pypi-coming_soon-orange)
24
+ ![Tests](https://img.shields.io/badge/tests-passing-brightgreen)
25
+
26
+ **RU:** Легковесные, надежные и типизированные утилиты для взаимодействия Python-приложений с операционной системой.
27
+ **EN:** Lightweight, robust, and strictly typed utilities for seamless Python-to-OS interaction.
28
+
29
+ ---
30
+
31
+ ## 🚀 Installation / Установка
32
+
33
+ ```bash
34
+ # Рекомендуемый способ (через uv - молниеносный пакетный менеджер)
35
+ uv add os-craft
36
+
37
+ # Классический способ
38
+ pip install os-craft
39
+ ```
40
+
41
+ ## 🛡️ Core Feature: Graceful Shutdown Manager
42
+ ### Почему это важно? (Why it matters)
43
+ Когда оркестратор (Kubernetes, systemd) или пользователь нажимает Ctrl+C, ОС отправляет процессу сигнал SIGTERM или SIGINT. По умолчанию Python просто прерывает выполнение. Если в этот момент ваша программа:
44
+
45
+ Писала данные в базу данных → транзакция оборвется, данные могут повредиться.
46
+ Обрабатывала фоновую задачу → результат будет потерян.
47
+ Держала открытые файловые дескрипторы → возможны утечки ресурсов.
48
+
49
+ ShutdownManager из os-craft перехватывает эти сигналы и гарантирует упорядоченное (LIFO), ограниченное по времени (timeout) и безопасное освобождение ресурсов. После завершения всех хуков процесс автоматически завершается с кодом выхода 0.
50
+
51
+ ### Quick Start / Быстрый старт
52
+
53
+ ```python
54
+ import asyncio
55
+ import logging
56
+ from os_craft import ShutdownManager
57
+
58
+ logging.basicConfig(level=logging.INFO)
59
+ logger = logging.getLogger(__name__)
60
+
61
+ async def close_database():
62
+ """Пример хука: безопасное закрытие соединения с БД."""
63
+ logger.info("Закрываем соединения с БД...")
64
+ await asyncio.sleep(1) # Имитация работы
65
+ logger.info("БД безопасно отключена.")
66
+
67
+ async def stop_background_worker():
68
+ """Пример хука: остановка фонового воркера."""
69
+ logger.info("Останавливаем фоновый воркер...")
70
+ await asyncio.sleep(0.5) # Имитация завершения текущей задачи
71
+ logger.info("Воркер остановлен.")
72
+
73
+ async def main():
74
+ # 1. Создаем менеджер с общим таймаутом 5 секунд
75
+ manager = ShutdownManager(total_timeout=5.0)
76
+
77
+ # 2. Регистрируем хуки. Они выполняются в обратном порядке (LIFO).
78
+ # Сначала остановим воркера, потом закроем БД.
79
+ manager.add_hook(close_database)
80
+ manager.add_hook(stop_background_worker)
81
+
82
+ # 3. Подписываемся на сигналы ОС (SIGINT, SIGTERM)
83
+ loop = asyncio.get_running_loop()
84
+ manager.attach_to_signals(loop)
85
+
86
+ logger.info("Приложение запущено. Нажмите Ctrl+C для корректного завершения.")
87
+
88
+ # Имитация долгой работы приложения
89
+ try:
90
+ while True:
91
+ await asyncio.sleep(1)
92
+ except asyncio.CancelledError:
93
+ pass # Ожидаем, пока manager завершит все хуки
94
+
95
+ if __name__ == "__main__":
96
+ asyncio.run(main())
97
+ ```
98
+ #### Что произойдет при нажатии Ctrl+C:
99
+
100
+ 1. INFO | Получен сигнал остановки ОС. Инициируем graceful shutdown...
101
+ 2. INFO | Начинаем graceful shutdown. Зарегистрировано хуков: 2
102
+ 3. INFO | Выполняем хук: stop_background_worker (доступно 5.00s)
103
+ 4. INFO | Останавливаем фоновый воркер...
104
+ 5. INFO | Воркер остановлен.
105
+ 6. INFO | Выполняем хук: close_database (доступно 4.50s)
106
+ 7. INFO | Закрываем соединения с БД...
107
+ 8. INFO | БД безопасно отключена.
108
+ 9.INFO | Процесс graceful shutdown успешно завершен.
109
+
110
+ После этого процесс автоматически завершится с кодом выхода 0.
111
+
112
+ ## 📖 Real-World Example: FastAPI Integration
113
+
114
+ Полный пример интеграции с FastAPI (с фоновыми задачами и имитацией БД) доступен в директории examples/fastapi_graceful_shutdown.py.
115
+ Запуск:
116
+
117
+ ```bash
118
+ uv run python examples/fastapi_graceful_shutdown.py
119
+ ```
120
+
121
+ Проверка:
122
+
123
+ 1. Откройте другой терминал: curl http://127.0.0.1:8000/
124
+ 2. Вернитесь в терминал с сервером и нажмите Ctrl+C
125
+ 3. Наблюдайте за корректным завершением всех ресурсов
126
+
127
+ ## ⚙️ Технические детали (Technical Details)
128
+
129
+ 1. LIFO Execution Order (Порядок LIFO)
130
+
131
+ Хуки выполняются в порядке "последний добавлен — первый выполнен". Это критично для корректного освобождения ресурсов:
132
+
133
+ * Сначала останавливаем прием новых задач (закрываем порт веб-сервера).
134
+ * Затем дожидаемся завершения текущих операций.
135
+ * Только потом закрываем соединения с базами данных и кэшем.
136
+
137
+
138
+ 1. Unified Timeout (Единый таймаут)
139
+
140
+ Вместо таймаута на каждый хук используется общий таймаут на весь процесс shutdown. Это предотвращает ситуацию, когда первый хук "съедает" все время, а на важные последние хуки (закрытие БД) времени не остается.
141
+ Если общий таймаут превышен, выполнение оставшихся хуков прерывается, и процесс завершается.
142
+
143
+ 1. Async/Sync Agnostic (Поддержка синхронного и асинхронного кода)
144
+
145
+ Менеджер автоматически определяет тип функции через inspect.iscoroutinefunction:
146
+
147
+ * Async-хуки выполняются напрямую с await.
148
+ * Sync-хуки выполняются в loop.run_in_executor, чтобы никогда не блокировать asyncio event loop, даже если разработчик написал тяжелую синхронную операцию.
149
+
150
+ 1. Fail-Safe Design (Отказоустойчивость)
151
+
152
+ Если один из хуков выбрасывает исключение или превышает таймаут, ShutdownManager:
153
+
154
+ * Логирует ошибку.
155
+ * Продолжает выполнение следующих хуков.
156
+ * Никогда не прерывает весь процесс очистки из-за одной ошибки.
157
+
158
+ 1. Automatic Process Termination (Автоматическое завершение процесса)
159
+
160
+
161
+ 1. OS Signal Handling (Обработка сигналов ОС)
162
+
163
+ Менеджер подписывается на:
164
+
165
+ * SIGINT (Ctrl+C) — для локальной разработки.
166
+ * SIGTERM (сигнал 15) — для продакшена (Kubernetes, systemd, Docker).
167
+
168
+ > ⚠️ Важно: Сигнал SIGKILL (сигнал 9, kill -9) невозможно перехватить. Это защита ОС от зависших процессов. Всегда проектируйте систему так, чтобы она могла пережить внезапное завершение (например, используйте транзакции в БД).
169
+
170
+
171
+ ## 🧪 Development / Разработка
172
+
173
+ Мы используем uv для молниеносного управления зависимостями и сборки.
174
+
175
+ ### Установка окружения
176
+
177
+ ```bash
178
+ # Клонирование репозитория
179
+ git clone <repository_url>
180
+ cd os-craft
181
+
182
+ # Установка зависимостей (создаст .venv и установит все пакеты)
183
+ uv sync
184
+
185
+ # Установка пакета в editable-режиме (для локальной разработки)
186
+ uv pip install -e .
187
+ ```
188
+
189
+ ### Запуск тестов
190
+ ```bash
191
+ uv run pytest -v
192
+ ```
193
+
194
+ ### Проверка типов и линтинг
195
+ ```bash
196
+ # Проверка типов (строгий режим)
197
+ uv run mypy src/os_craft
198
+
199
+ # Линтер (автоматическое исправление проблем)
200
+ uv run ruff check . --fix
201
+ ```
202
+ ### Сборка пакета
203
+ ```bash
204
+ uv build
205
+ ```
206
+ Это создаст папку dist/ с .tar.gz и .whl файлами, готовыми для публикации на PyPI.
207
+
208
+ ## 🏗️ Project Structure / Структура проекта
209
+
210
+ os-craft/
211
+ ├── pyproject.toml # Конфигурация проекта, зависимостей и инструментов
212
+ ├── uv.lock # Lock-файл для воспроизводимых сборок
213
+ ├── README.md # Этот файл
214
+ ├── LICENSE # MIT License
215
+ ├── src/
216
+ │ └── os_craft/
217
+ │ ├── __init__.py # Публичный API пакета
218
+ │ ├── shutdown.py # ShutdownManager
219
+ │ └── py.typed # Маркер для mypy (PEP 561)
220
+ ├── tests/
221
+ │ └── test_shutdown.py # Тесты для ShutdownManager
222
+ └── examples/
223
+ └── fastapi_graceful_shutdown.py # Пример интеграции с FastAPI
224
+
225
+ ## 🤝 Contributing / Участие в разработке
226
+
227
+ Мы приветствуем вклад в развитие проекта! Если вы нашли баг или хотите добавить новую утилиту:
228
+
229
+ 1. Форкните репозиторий.
230
+ 2. Создайте ветку для вашей фичи: git checkout -b feature/amazing-feature
231
+ 3. Убедитесь, что все тесты проходят: uv run pytest
232
+ 4. Проверьте линтер и типы: uv run ruff check . && uv run mypy src/os_craft
233
+ 5. Сделайте коммит: git commit -m 'Add amazing feature'
234
+ 6. Отправьте в main: git push origin feature/amazing-feature
235
+ 7. Откройте Pull Request.
236
+
237
+ ## 📄 License / Лицензия
238
+
239
+ MIT License. Свободно используйте в коммерческих и open-source проектах.
240
+ См. файл LICENSE для подробностей.
241
+
242
+ ## 🙏 Acknowledgments / Благодарности
243
+
244
+ Проект создан с любовью к чистому коду, принципу KISS и уважению к разработчикам, которые хотят писать надежные Python-приложения.
245
+ Спасибо сообществу Python за потрясающие инструменты: asyncio, contextvars, uv, ruff, mypy.
246
+
247
+ **RU:** Если у вас есть вопросы или предложения, открывайте Issue или пишите в обсуждения.
248
+ **EN:** If you have any questions or suggestions, feel free to open an Issue or start a Discussion.
@@ -0,0 +1,230 @@
1
+ # os-craft
2
+
3
+ ![Python Versions](https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12%20|%203.13%20|%203.14-blue)
4
+ ![License](https://img.shields.io/badge/license-MIT-green)
5
+ ![PyPI Status](https://img.shields.io/badge/pypi-coming_soon-orange)
6
+ ![Tests](https://img.shields.io/badge/tests-passing-brightgreen)
7
+
8
+ **RU:** Легковесные, надежные и типизированные утилиты для взаимодействия Python-приложений с операционной системой.
9
+ **EN:** Lightweight, robust, and strictly typed utilities for seamless Python-to-OS interaction.
10
+
11
+ ---
12
+
13
+ ## 🚀 Installation / Установка
14
+
15
+ ```bash
16
+ # Рекомендуемый способ (через uv - молниеносный пакетный менеджер)
17
+ uv add os-craft
18
+
19
+ # Классический способ
20
+ pip install os-craft
21
+ ```
22
+
23
+ ## 🛡️ Core Feature: Graceful Shutdown Manager
24
+ ### Почему это важно? (Why it matters)
25
+ Когда оркестратор (Kubernetes, systemd) или пользователь нажимает Ctrl+C, ОС отправляет процессу сигнал SIGTERM или SIGINT. По умолчанию Python просто прерывает выполнение. Если в этот момент ваша программа:
26
+
27
+ Писала данные в базу данных → транзакция оборвется, данные могут повредиться.
28
+ Обрабатывала фоновую задачу → результат будет потерян.
29
+ Держала открытые файловые дескрипторы → возможны утечки ресурсов.
30
+
31
+ ShutdownManager из os-craft перехватывает эти сигналы и гарантирует упорядоченное (LIFO), ограниченное по времени (timeout) и безопасное освобождение ресурсов. После завершения всех хуков процесс автоматически завершается с кодом выхода 0.
32
+
33
+ ### Quick Start / Быстрый старт
34
+
35
+ ```python
36
+ import asyncio
37
+ import logging
38
+ from os_craft import ShutdownManager
39
+
40
+ logging.basicConfig(level=logging.INFO)
41
+ logger = logging.getLogger(__name__)
42
+
43
+ async def close_database():
44
+ """Пример хука: безопасное закрытие соединения с БД."""
45
+ logger.info("Закрываем соединения с БД...")
46
+ await asyncio.sleep(1) # Имитация работы
47
+ logger.info("БД безопасно отключена.")
48
+
49
+ async def stop_background_worker():
50
+ """Пример хука: остановка фонового воркера."""
51
+ logger.info("Останавливаем фоновый воркер...")
52
+ await asyncio.sleep(0.5) # Имитация завершения текущей задачи
53
+ logger.info("Воркер остановлен.")
54
+
55
+ async def main():
56
+ # 1. Создаем менеджер с общим таймаутом 5 секунд
57
+ manager = ShutdownManager(total_timeout=5.0)
58
+
59
+ # 2. Регистрируем хуки. Они выполняются в обратном порядке (LIFO).
60
+ # Сначала остановим воркера, потом закроем БД.
61
+ manager.add_hook(close_database)
62
+ manager.add_hook(stop_background_worker)
63
+
64
+ # 3. Подписываемся на сигналы ОС (SIGINT, SIGTERM)
65
+ loop = asyncio.get_running_loop()
66
+ manager.attach_to_signals(loop)
67
+
68
+ logger.info("Приложение запущено. Нажмите Ctrl+C для корректного завершения.")
69
+
70
+ # Имитация долгой работы приложения
71
+ try:
72
+ while True:
73
+ await asyncio.sleep(1)
74
+ except asyncio.CancelledError:
75
+ pass # Ожидаем, пока manager завершит все хуки
76
+
77
+ if __name__ == "__main__":
78
+ asyncio.run(main())
79
+ ```
80
+ #### Что произойдет при нажатии Ctrl+C:
81
+
82
+ 1. INFO | Получен сигнал остановки ОС. Инициируем graceful shutdown...
83
+ 2. INFO | Начинаем graceful shutdown. Зарегистрировано хуков: 2
84
+ 3. INFO | Выполняем хук: stop_background_worker (доступно 5.00s)
85
+ 4. INFO | Останавливаем фоновый воркер...
86
+ 5. INFO | Воркер остановлен.
87
+ 6. INFO | Выполняем хук: close_database (доступно 4.50s)
88
+ 7. INFO | Закрываем соединения с БД...
89
+ 8. INFO | БД безопасно отключена.
90
+ 9.INFO | Процесс graceful shutdown успешно завершен.
91
+
92
+ После этого процесс автоматически завершится с кодом выхода 0.
93
+
94
+ ## 📖 Real-World Example: FastAPI Integration
95
+
96
+ Полный пример интеграции с FastAPI (с фоновыми задачами и имитацией БД) доступен в директории examples/fastapi_graceful_shutdown.py.
97
+ Запуск:
98
+
99
+ ```bash
100
+ uv run python examples/fastapi_graceful_shutdown.py
101
+ ```
102
+
103
+ Проверка:
104
+
105
+ 1. Откройте другой терминал: curl http://127.0.0.1:8000/
106
+ 2. Вернитесь в терминал с сервером и нажмите Ctrl+C
107
+ 3. Наблюдайте за корректным завершением всех ресурсов
108
+
109
+ ## ⚙️ Технические детали (Technical Details)
110
+
111
+ 1. LIFO Execution Order (Порядок LIFO)
112
+
113
+ Хуки выполняются в порядке "последний добавлен — первый выполнен". Это критично для корректного освобождения ресурсов:
114
+
115
+ * Сначала останавливаем прием новых задач (закрываем порт веб-сервера).
116
+ * Затем дожидаемся завершения текущих операций.
117
+ * Только потом закрываем соединения с базами данных и кэшем.
118
+
119
+
120
+ 1. Unified Timeout (Единый таймаут)
121
+
122
+ Вместо таймаута на каждый хук используется общий таймаут на весь процесс shutdown. Это предотвращает ситуацию, когда первый хук "съедает" все время, а на важные последние хуки (закрытие БД) времени не остается.
123
+ Если общий таймаут превышен, выполнение оставшихся хуков прерывается, и процесс завершается.
124
+
125
+ 1. Async/Sync Agnostic (Поддержка синхронного и асинхронного кода)
126
+
127
+ Менеджер автоматически определяет тип функции через inspect.iscoroutinefunction:
128
+
129
+ * Async-хуки выполняются напрямую с await.
130
+ * Sync-хуки выполняются в loop.run_in_executor, чтобы никогда не блокировать asyncio event loop, даже если разработчик написал тяжелую синхронную операцию.
131
+
132
+ 1. Fail-Safe Design (Отказоустойчивость)
133
+
134
+ Если один из хуков выбрасывает исключение или превышает таймаут, ShutdownManager:
135
+
136
+ * Логирует ошибку.
137
+ * Продолжает выполнение следующих хуков.
138
+ * Никогда не прерывает весь процесс очистки из-за одной ошибки.
139
+
140
+ 1. Automatic Process Termination (Автоматическое завершение процесса)
141
+
142
+
143
+ 1. OS Signal Handling (Обработка сигналов ОС)
144
+
145
+ Менеджер подписывается на:
146
+
147
+ * SIGINT (Ctrl+C) — для локальной разработки.
148
+ * SIGTERM (сигнал 15) — для продакшена (Kubernetes, systemd, Docker).
149
+
150
+ > ⚠️ Важно: Сигнал SIGKILL (сигнал 9, kill -9) невозможно перехватить. Это защита ОС от зависших процессов. Всегда проектируйте систему так, чтобы она могла пережить внезапное завершение (например, используйте транзакции в БД).
151
+
152
+
153
+ ## 🧪 Development / Разработка
154
+
155
+ Мы используем uv для молниеносного управления зависимостями и сборки.
156
+
157
+ ### Установка окружения
158
+
159
+ ```bash
160
+ # Клонирование репозитория
161
+ git clone <repository_url>
162
+ cd os-craft
163
+
164
+ # Установка зависимостей (создаст .venv и установит все пакеты)
165
+ uv sync
166
+
167
+ # Установка пакета в editable-режиме (для локальной разработки)
168
+ uv pip install -e .
169
+ ```
170
+
171
+ ### Запуск тестов
172
+ ```bash
173
+ uv run pytest -v
174
+ ```
175
+
176
+ ### Проверка типов и линтинг
177
+ ```bash
178
+ # Проверка типов (строгий режим)
179
+ uv run mypy src/os_craft
180
+
181
+ # Линтер (автоматическое исправление проблем)
182
+ uv run ruff check . --fix
183
+ ```
184
+ ### Сборка пакета
185
+ ```bash
186
+ uv build
187
+ ```
188
+ Это создаст папку dist/ с .tar.gz и .whl файлами, готовыми для публикации на PyPI.
189
+
190
+ ## 🏗️ Project Structure / Структура проекта
191
+
192
+ os-craft/
193
+ ├── pyproject.toml # Конфигурация проекта, зависимостей и инструментов
194
+ ├── uv.lock # Lock-файл для воспроизводимых сборок
195
+ ├── README.md # Этот файл
196
+ ├── LICENSE # MIT License
197
+ ├── src/
198
+ │ └── os_craft/
199
+ │ ├── __init__.py # Публичный API пакета
200
+ │ ├── shutdown.py # ShutdownManager
201
+ │ └── py.typed # Маркер для mypy (PEP 561)
202
+ ├── tests/
203
+ │ └── test_shutdown.py # Тесты для ShutdownManager
204
+ └── examples/
205
+ └── fastapi_graceful_shutdown.py # Пример интеграции с FastAPI
206
+
207
+ ## 🤝 Contributing / Участие в разработке
208
+
209
+ Мы приветствуем вклад в развитие проекта! Если вы нашли баг или хотите добавить новую утилиту:
210
+
211
+ 1. Форкните репозиторий.
212
+ 2. Создайте ветку для вашей фичи: git checkout -b feature/amazing-feature
213
+ 3. Убедитесь, что все тесты проходят: uv run pytest
214
+ 4. Проверьте линтер и типы: uv run ruff check . && uv run mypy src/os_craft
215
+ 5. Сделайте коммит: git commit -m 'Add amazing feature'
216
+ 6. Отправьте в main: git push origin feature/amazing-feature
217
+ 7. Откройте Pull Request.
218
+
219
+ ## 📄 License / Лицензия
220
+
221
+ MIT License. Свободно используйте в коммерческих и open-source проектах.
222
+ См. файл LICENSE для подробностей.
223
+
224
+ ## 🙏 Acknowledgments / Благодарности
225
+
226
+ Проект создан с любовью к чистому коду, принципу KISS и уважению к разработчикам, которые хотят писать надежные Python-приложения.
227
+ Спасибо сообществу Python за потрясающие инструменты: asyncio, contextvars, uv, ruff, mypy.
228
+
229
+ **RU:** Если у вас есть вопросы или предложения, открывайте Issue или пишите в обсуждения.
230
+ **EN:** If you have any questions or suggestions, feel free to open an Issue or start a Discussion.
File without changes
@@ -0,0 +1,160 @@
1
+ """
2
+ Пример интеграции os-craft с FastAPI для грациозного завершения работы.
3
+
4
+ Этот скрипт демонстрирует, как корректно завершать:
5
+ 1. HTTP-сервер (Uvicorn).
6
+ 2. Фоновые периодические задачи (например, воркеры или очистка кэша).
7
+ 3. "Соединения с базой данных" (имитация).
8
+
9
+ Запуск:
10
+ uv run python examples/fastapi_graceful_shutdown.py
11
+
12
+ Проверка работы:
13
+ 1. Запустите скрипт.
14
+ 2. В другом терминале выполните: curl http://127.0.0.1:8000/
15
+ 3. Нажмите Ctrl+C в терминале со скриптом.
16
+ 4. Наблюдайте за логами: вы увидите, как система дожидается завершения
17
+ текущих операций перед полным остановом, вместо резкого обрыва.
18
+ """
19
+
20
+ import asyncio
21
+ import logging
22
+ import signal
23
+ from contextlib import asynccontextmanager
24
+
25
+ from fastapi import FastAPI
26
+ import uvicorn
27
+
28
+ # Импортируем наш менеджер. В реальном проекте это будет: from os_craft import ShutdownManager
29
+ # import sys
30
+ # from pathlib import Path
31
+ # sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
32
+ from os_craft import ShutdownManager
33
+
34
+ # Настраиваем красивое логирование, чтобы видеть процесс shutdown в реальном времени
35
+ logging.basicConfig(
36
+ level=logging.INFO,
37
+ format="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
38
+ datefmt="%H:%M:%S"
39
+ )
40
+ logger = logging.getLogger("example_app")
41
+
42
+
43
+ # --- Имитация внешних ресурсов ---
44
+
45
+ class MockDatabase:
46
+ """Имитация пула соединений с базой данных."""
47
+ def __init__(self):
48
+ self.is_connected = False
49
+
50
+ async def connect(self):
51
+ logger.info("🔌 Подключение к базе данных...")
52
+ await asyncio.sleep(0.5) # Имитация задержки сети
53
+ self.is_connected = True
54
+ logger.info("✅ База данных подключена.")
55
+
56
+ async def disconnect(self):
57
+ logger.info("🔌 Закрытие соединений с базой данных...")
58
+ await asyncio.sleep(1.0) # Имитация безопасного закрытия транзакций
59
+ self.is_connected = False
60
+ logger.info("✅ База данных отключена безопасно.")
61
+
62
+ db = MockDatabase()
63
+
64
+
65
+ async def background_worker():
66
+ """
67
+ Имитация фоновой задачи (например, обработка очереди сообщений).
68
+ Эта задача должна корректно завершиться при получении сигнала остановки,
69
+ а не быть убита посередине обработки.
70
+ """
71
+ logger.info("⚙️ Фоновый воркер запущен.")
72
+ try:
73
+ while True:
74
+ logger.info("🔄 Воркер обрабатывает задачу...")
75
+ await asyncio.sleep(2.0) # Имитация полезной работы
76
+ except asyncio.CancelledError:
77
+ # НЕОЧЕВИДНОЕ РЕШЕНИЕ: Мы ловим CancelledError, чтобы завершить
78
+ # текущую "итерацию" чисто, прежде чем выйти из цикла.
79
+ logger.info("🛑 Воркер получил сигнал отмены. Завершаем текущую задачу...")
80
+ await asyncio.sleep(0.5) # Имитация сохранения прогресса
81
+ logger.info("✅ Воркер корректно остановлен.")
82
+ raise # Пробрасываем дальше, чтобы asyncio знал, что задача отменена
83
+
84
+
85
+ # --- Интеграция с FastAPI и os-craft ---
86
+
87
+ # Создаем экземпляр менеджера.
88
+ # Таймаут 5 секунд: если за это время не успеем закрыться, ОС нас убьет (SIGKILL).
89
+ shutdown_manager = ShutdownManager(total_timeout=5.0)
90
+
91
+ @asynccontextmanager
92
+ async def lifespan(app: FastAPI):
93
+ """
94
+ Lifespan контекст FastAPI (замена устаревшим @app.on_event("startup")).
95
+ Здесь мы инициализируем ресурсы и регистрируем хуки завершения.
96
+ """
97
+ # 1. Инициализация при старте
98
+ await db.connect()
99
+
100
+ # Запускаем фоновую задачу и сохраняем ссылку на неё, чтобы потом отменить
101
+ worker_task = asyncio.create_task(background_worker(), name="background_worker")
102
+
103
+ # 2. Регистрация хуков завершения (ВАЖНО: порядок имеет значение!)
104
+ # Хуки выполняются в обратном порядке (LIFO).
105
+ # Сначала мы должны остановить воркера, потом закрыть БД.
106
+
107
+ # Хук 2 (выполнится вторым): закрытие БД
108
+ shutdown_manager.add_hook(db.disconnect)
109
+
110
+ # Хук 1 (выполнится первым): отмена фоновой задачи
111
+ def cancel_worker():
112
+ logger.info("🛑 Инициируем отмену фонового воркера...")
113
+ worker_task.cancel()
114
+
115
+ shutdown_manager.add_hook(cancel_worker)
116
+
117
+ # 3. Подписка на сигналы ОС (SIGTERM, SIGINT)
118
+ # Мы делаем это здесь, потому что event loop уже запущен FastAPI
119
+ loop = asyncio.get_running_loop()
120
+ shutdown_manager.attach_to_signals(loop)
121
+
122
+ logger.info("🚀 Приложение полностью готово к работе!")
123
+
124
+ yield # Здесь управление передается FastAPI/Uvicorn
125
+
126
+ # Этот блок выполнится после yield, но мы делегируем реальную очистку
127
+ # нашему shutdown_manager, чтобы централизовать логику и таймауты.
128
+ logger.info("🔄 Завершение работы приложения через lifespan...")
129
+ await shutdown_manager._execute_hooks()
130
+
131
+
132
+ # Инициализация приложения с нашим lifespan
133
+ app = FastAPI(
134
+ title="os-craft FastAPI Example",
135
+ description="Демонстрация грациозного завершения работы с помощью os-craft",
136
+ version="0.1.0",
137
+ lifespan=lifespan
138
+ )
139
+
140
+
141
+ @app.get("/")
142
+ async def root():
143
+ """Простой эндпоинт для проверки работоспособности."""
144
+ if db.is_connected:
145
+ return {"status": "ok", "message": "Сервер работает и подключен к БД"}
146
+ return {"status": "error", "message": "База данных недоступна"}
147
+
148
+
149
+ if __name__ == "__main__":
150
+ # Запускаем Uvicorn.
151
+ # workers=1 важен для примера, чтобы логи не перемешивались и shutdown был предсказуемым.
152
+ # В продакшене с os-craft лучше использовать gunicorn с uvicorn workers,
153
+ # но логика graceful shutdown на уровне процесса останется той же.
154
+ uvicorn.run(
155
+ app,
156
+ host="127.0.0.1",
157
+ port=8000,
158
+ log_level="info",
159
+ workers=1
160
+ )