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.
- os_craft-0.1.0/.gitignore +10 -0
- os_craft-0.1.0/.python-version +1 -0
- os_craft-0.1.0/PKG-INFO +248 -0
- os_craft-0.1.0/README.md +230 -0
- os_craft-0.1.0/examples/__init__.py +0 -0
- os_craft-0.1.0/examples/fastapi_graceful_shutdown.py +160 -0
- os_craft-0.1.0/pyproject.toml +63 -0
- os_craft-0.1.0/src/os_craft/__init__.py +15 -0
- os_craft-0.1.0/src/os_craft/py.typed +0 -0
- os_craft-0.1.0/src/os_craft/shutdown.py +147 -0
- os_craft-0.1.0/tests/test_shutdown.py +72 -0
- os_craft-0.1.0/uv.lock +744 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
os_craft-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
+

|
|
22
|
+

|
|
23
|
+

|
|
24
|
+

|
|
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.
|
os_craft-0.1.0/README.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# os-craft
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
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
|
+
)
|