Compare commits
6 Commits
47a7344755
...
arch-2
| Author | SHA1 | Date | |
|---|---|---|---|
| 72fe115a99 | |||
| 46a30c32ed | |||
| 5e217c7cce | |||
| 7d2747c8fe | |||
| 513ff3c144 | |||
| a0b6e04d99 |
@@ -1,39 +0,0 @@
|
|||||||
# 🧹 Сводка по очистке проекта
|
|
||||||
|
|
||||||
## ✅ Что было удалено из `python_parser/`:
|
|
||||||
|
|
||||||
### Файлы Streamlit:
|
|
||||||
- `streamlit_app.py` - основной файл Streamlit приложения
|
|
||||||
- `run_streamlit.py` - скрипт запуска Streamlit
|
|
||||||
- `Procfile` - конфигурация для Heroku (Streamlit)
|
|
||||||
- `runtime.txt` - версия Python для Heroku
|
|
||||||
- `manifest.yml` - манифест приложения
|
|
||||||
- `.streamlit/` - папка с конфигурацией Streamlit
|
|
||||||
|
|
||||||
### Зависимости:
|
|
||||||
- Удален `streamlit>=1.28.0` из `python_parser/requirements.txt`
|
|
||||||
|
|
||||||
## 🎯 Результат:
|
|
||||||
|
|
||||||
### `python_parser/` - теперь содержит ТОЛЬКО:
|
|
||||||
- FastAPI приложение
|
|
||||||
- Адаптеры для парсеров
|
|
||||||
- Основную бизнес-логику
|
|
||||||
- Dockerfile для FastAPI
|
|
||||||
- Зависимости только для FastAPI
|
|
||||||
|
|
||||||
### `streamlit_app/` - содержит ТОЛЬКО:
|
|
||||||
- Streamlit приложение
|
|
||||||
- Dockerfile для Streamlit
|
|
||||||
- Зависимости только для Streamlit
|
|
||||||
- Конфигурацию Streamlit
|
|
||||||
|
|
||||||
## 🔄 Полное разделение достигнуто:
|
|
||||||
|
|
||||||
- **FastAPI** и **Streamlit** теперь полностью независимы
|
|
||||||
- Каждый сервис имеет свои собственные зависимости
|
|
||||||
- Docker образы собираются отдельно
|
|
||||||
- Запускаются через единый `docker-compose.yml`
|
|
||||||
|
|
||||||
---
|
|
||||||
**Статус**: ✅ Проект полностью очищен и разделен
|
|
||||||
30
README.md
30
README.md
@@ -11,11 +11,25 @@
|
|||||||
- Docker и Docker Compose
|
- Docker и Docker Compose
|
||||||
- Git
|
- Git
|
||||||
|
|
||||||
### Запуск всех сервисов
|
### Запуск всех сервисов (продакшн)
|
||||||
```bash
|
```bash
|
||||||
docker-compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Запуск в режиме разработки
|
||||||
|
```bash
|
||||||
|
# Автоматический запуск
|
||||||
|
python start_dev.py
|
||||||
|
|
||||||
|
# Или вручную
|
||||||
|
docker compose -f docker-compose.dev.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
**Режим разработки** позволяет:
|
||||||
|
- Автоматически перезагружать Streamlit при изменении кода
|
||||||
|
- Монтировать исходный код напрямую в контейнер
|
||||||
|
- Видеть изменения без пересборки контейнеров
|
||||||
|
|
||||||
### Доступ к сервисам
|
### Доступ к сервисам
|
||||||
- **FastAPI**: http://localhost:8000
|
- **FastAPI**: http://localhost:8000
|
||||||
- **Streamlit**: http://localhost:8501
|
- **Streamlit**: http://localhost:8501
|
||||||
@@ -72,6 +86,18 @@ python_parser_cf/
|
|||||||
|
|
||||||
## 🛠️ Разработка
|
## 🛠️ Разработка
|
||||||
|
|
||||||
|
### Режим разработки (рекомендуется)
|
||||||
|
```bash
|
||||||
|
# Запуск режима разработки
|
||||||
|
python start_dev.py
|
||||||
|
|
||||||
|
# Остановка
|
||||||
|
docker compose -f docker-compose.dev.yml down
|
||||||
|
|
||||||
|
# Возврат к продакшн режиму
|
||||||
|
python start_prod.py
|
||||||
|
```
|
||||||
|
|
||||||
### Локальная разработка FastAPI
|
### Локальная разработка FastAPI
|
||||||
```bash
|
```bash
|
||||||
cd python_parser
|
cd python_parser
|
||||||
|
|||||||
58
docker-compose.dev.yml
Normal file
58
docker-compose.dev.yml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
services:
|
||||||
|
minio:
|
||||||
|
image: minio/minio:latest
|
||||||
|
container_name: svodka_minio_dev
|
||||||
|
ports:
|
||||||
|
- "9000:9000" # API порт
|
||||||
|
- "9001:9001" # Консоль порт
|
||||||
|
environment:
|
||||||
|
MINIO_ROOT_USER: minioadmin
|
||||||
|
MINIO_ROOT_PASSWORD: minioadmin
|
||||||
|
command: server /data --console-address ":9001"
|
||||||
|
volumes:
|
||||||
|
- ./minio_data:/data
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
fastapi:
|
||||||
|
build: ./python_parser
|
||||||
|
container_name: svodka_fastapi_dev
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
environment:
|
||||||
|
- MINIO_ENDPOINT=minio:9000
|
||||||
|
- MINIO_ACCESS_KEY=minioadmin
|
||||||
|
- MINIO_SECRET_KEY=minioadmin
|
||||||
|
- MINIO_SECURE=false
|
||||||
|
- MINIO_BUCKET=svodka-data
|
||||||
|
depends_on:
|
||||||
|
- minio
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
streamlit:
|
||||||
|
image: python:3.11-slim
|
||||||
|
container_name: svodka_streamlit_dev
|
||||||
|
ports:
|
||||||
|
- "8501:8501"
|
||||||
|
environment:
|
||||||
|
- API_BASE_URL=http://fastapi:8000
|
||||||
|
- API_PUBLIC_URL=http://localhost:8000
|
||||||
|
- MINIO_ENDPOINT=minio:9000
|
||||||
|
- MINIO_ACCESS_KEY=minioadmin
|
||||||
|
- MINIO_SECRET_KEY=minioadmin
|
||||||
|
- MINIO_SECURE=false
|
||||||
|
- MINIO_BUCKET=svodka-data
|
||||||
|
volumes:
|
||||||
|
# Монтируем исходный код для автоматической перезагрузки
|
||||||
|
- ./streamlit_app:/app
|
||||||
|
# Монтируем requirements.txt для установки зависимостей
|
||||||
|
- ./streamlit_app/requirements.txt:/app/requirements.txt
|
||||||
|
working_dir: /app
|
||||||
|
depends_on:
|
||||||
|
- minio
|
||||||
|
- fastapi
|
||||||
|
restart: unless-stopped
|
||||||
|
command: >
|
||||||
|
bash -c "
|
||||||
|
pip install --no-cache-dir -r requirements.txt &&
|
||||||
|
streamlit run streamlit_app.py --server.port=8501 --server.address=0.0.0.0 --server.runOnSave=true
|
||||||
|
"
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# Продакшн конфигурация
|
||||||
|
# Для разработки используйте: docker compose -f docker-compose.dev.yml up -d
|
||||||
services:
|
services:
|
||||||
minio:
|
minio:
|
||||||
image: minio/minio:latest
|
image: minio/minio:latest
|
||||||
@@ -35,6 +37,7 @@ services:
|
|||||||
- "8501:8501"
|
- "8501:8501"
|
||||||
environment:
|
environment:
|
||||||
- API_BASE_URL=http://fastapi:8000
|
- API_BASE_URL=http://fastapi:8000
|
||||||
|
- API_PUBLIC_URL=http://localhost:8000
|
||||||
- MINIO_ENDPOINT=minio:9000
|
- MINIO_ENDPOINT=minio:9000
|
||||||
- MINIO_ACCESS_KEY=minioadmin
|
- MINIO_ACCESS_KEY=minioadmin
|
||||||
- MINIO_SECRET_KEY=minioadmin
|
- MINIO_SECRET_KEY=minioadmin
|
||||||
|
|||||||
@@ -96,6 +96,54 @@ async def get_available_parsers():
|
|||||||
return {"parsers": parsers}
|
return {"parsers": parsers}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/parsers/{parser_name}/getters", tags=["Общее"],
|
||||||
|
summary="Информация о геттерах парсера",
|
||||||
|
description="Возвращает информацию о доступных геттерах для указанного парсера",
|
||||||
|
responses={
|
||||||
|
200: {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"example": {
|
||||||
|
"parser": "svodka_pm",
|
||||||
|
"getters": {
|
||||||
|
"single_og": {
|
||||||
|
"required_params": ["id", "codes", "columns"],
|
||||||
|
"optional_params": ["search"],
|
||||||
|
"description": "Получение данных по одному ОГ"
|
||||||
|
},
|
||||||
|
"total_ogs": {
|
||||||
|
"required_params": ["codes", "columns"],
|
||||||
|
"optional_params": ["search"],
|
||||||
|
"description": "Получение данных по всем ОГ"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
"description": "Парсер не найден"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
async def get_parser_getters(parser_name: str):
|
||||||
|
"""Получение информации о геттерах парсера"""
|
||||||
|
if parser_name not in PARSERS:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail=f"Парсер '{parser_name}' не найден"
|
||||||
|
)
|
||||||
|
|
||||||
|
parser_class = PARSERS[parser_name]
|
||||||
|
parser_instance = parser_class()
|
||||||
|
|
||||||
|
getters_info = parser_instance.get_available_getters()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"parser": parser_name,
|
||||||
|
"getters": getters_info
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/server-info", tags=["Общее"],
|
@app.get("/server-info", tags=["Общее"],
|
||||||
summary="Информация о сервере",
|
summary="Информация о сервере",
|
||||||
response_model=ServerInfoResponse,)
|
response_model=ServerInfoResponse,)
|
||||||
@@ -352,40 +400,40 @@ async def get_svodka_pm_total_ogs(
|
|||||||
raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
# @app.post("/svodka_pm/get_data", tags=[SvodkaPMParser.name])
|
@app.post("/svodka_pm/get_data", tags=[SvodkaPMParser.name])
|
||||||
# async def get_svodka_pm_data(
|
async def get_svodka_pm_data(
|
||||||
# request_data: dict
|
request_data: dict
|
||||||
# ):
|
):
|
||||||
# report_service = get_report_service()
|
report_service = get_report_service()
|
||||||
# """
|
"""
|
||||||
# Получение данных из отчета сводки факта СарНПЗ
|
Получение данных из отчета сводки факта СарНПЗ
|
||||||
|
|
||||||
# - indicator_id: ID индикатора
|
- indicator_id: ID индикатора
|
||||||
# - code: Код для поиска
|
- code: Код для поиска
|
||||||
# - search_value: Опциональное значение для поиска
|
- search_value: Опциональное значение для поиска
|
||||||
# """
|
"""
|
||||||
# try:
|
try:
|
||||||
# # Создаем запрос
|
# Создаем запрос
|
||||||
# request = DataRequest(
|
request = DataRequest(
|
||||||
# report_type='svodka_pm',
|
report_type='svodka_pm',
|
||||||
# get_params=request_data
|
get_params=request_data
|
||||||
# )
|
)
|
||||||
|
|
||||||
# # Получаем данные
|
# Получаем данные
|
||||||
# result = report_service.get_data(request)
|
result = report_service.get_data(request)
|
||||||
|
|
||||||
# if result.success:
|
if result.success:
|
||||||
# return {
|
return {
|
||||||
# "success": True,
|
"success": True,
|
||||||
# "data": result.data
|
"data": result.data
|
||||||
# }
|
}
|
||||||
# else:
|
else:
|
||||||
# raise HTTPException(status_code=404, detail=result.message)
|
raise HTTPException(status_code=404, detail=result.message)
|
||||||
|
|
||||||
# except HTTPException:
|
except HTTPException:
|
||||||
# raise
|
raise
|
||||||
# except Exception as e:
|
except Exception as e:
|
||||||
# raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
@app.post("/svodka_ca/upload", tags=[SvodkaCAParser.name],
|
@app.post("/svodka_ca/upload", tags=[SvodkaCAParser.name],
|
||||||
@@ -562,38 +610,38 @@ async def get_svodka_ca_data(
|
|||||||
# raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
# raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
# @app.post("/monitoring_fuel/get_data", tags=[MonitoringFuelParser.name])
|
@app.post("/monitoring_fuel/get_data", tags=[MonitoringFuelParser.name])
|
||||||
# async def get_monitoring_fuel_data(
|
async def get_monitoring_fuel_data(
|
||||||
# request_data: dict
|
request_data: dict
|
||||||
# ):
|
):
|
||||||
# report_service = get_report_service()
|
report_service = get_report_service()
|
||||||
# """
|
"""
|
||||||
# Получение данных из отчета мониторинга топлива
|
Получение данных из отчета мониторинга топлива
|
||||||
|
|
||||||
# - column: Название колонки для агрегации (normativ, total, total_svod)
|
- column: Название колонки для агрегации (normativ, total, total_svod)
|
||||||
# """
|
"""
|
||||||
# try:
|
try:
|
||||||
# # Создаем запрос
|
# Создаем запрос
|
||||||
# request = DataRequest(
|
request = DataRequest(
|
||||||
# report_type='monitoring_fuel',
|
report_type='monitoring_fuel',
|
||||||
# get_params=request_data
|
get_params=request_data
|
||||||
# )
|
)
|
||||||
|
|
||||||
# # Получаем данные
|
# Получаем данные
|
||||||
# result = report_service.get_data(request)
|
result = report_service.get_data(request)
|
||||||
|
|
||||||
# if result.success:
|
if result.success:
|
||||||
# return {
|
return {
|
||||||
# "success": True,
|
"success": True,
|
||||||
# "data": result.data
|
"data": result.data
|
||||||
# }
|
}
|
||||||
# else:
|
else:
|
||||||
# raise HTTPException(status_code=404, detail=result.message)
|
raise HTTPException(status_code=404, detail=result.message)
|
||||||
|
|
||||||
# except HTTPException:
|
except HTTPException:
|
||||||
# raise
|
raise
|
||||||
# except Exception as e:
|
except Exception as e:
|
||||||
# raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
# @app.post("/monitoring_fuel/upload_directory", tags=[MonitoringFuelParser.name])
|
# @app.post("/monitoring_fuel/upload_directory", tags=[MonitoringFuelParser.name])
|
||||||
|
|||||||
@@ -2,28 +2,93 @@
|
|||||||
Порты (интерфейсы) для hexagonal architecture
|
Порты (интерфейсы) для hexagonal architecture
|
||||||
"""
|
"""
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Optional
|
from typing import Optional, Dict, List, Any, Callable
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
class ParserPort(ABC):
|
class ParserPort(ABC):
|
||||||
"""Интерфейс для парсеров"""
|
"""Интерфейс для парсеров с поддержкой множественных геттеров"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Инициализация с пустым словарем геттеров"""
|
||||||
|
self.getters: Dict[str, Dict[str, Any]] = {}
|
||||||
|
self._register_default_getters()
|
||||||
|
|
||||||
|
def _register_default_getters(self):
|
||||||
|
"""Регистрация геттеров по умолчанию - переопределяется в наследниках"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def register_getter(self, name: str, method: Callable, required_params: List[str],
|
||||||
|
optional_params: List[str] = None, description: str = ""):
|
||||||
|
"""
|
||||||
|
Регистрация нового геттера
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Имя геттера
|
||||||
|
method: Метод для выполнения
|
||||||
|
required_params: Список обязательных параметров
|
||||||
|
optional_params: Список необязательных параметров
|
||||||
|
description: Описание геттера
|
||||||
|
"""
|
||||||
|
if optional_params is None:
|
||||||
|
optional_params = []
|
||||||
|
|
||||||
|
self.getters[name] = {
|
||||||
|
"method": method,
|
||||||
|
"required_params": required_params,
|
||||||
|
"optional_params": optional_params,
|
||||||
|
"description": description
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_available_getters(self) -> Dict[str, Dict[str, Any]]:
|
||||||
|
"""Получение списка доступных геттеров с их описанием"""
|
||||||
|
return {
|
||||||
|
name: {
|
||||||
|
"required_params": info["required_params"],
|
||||||
|
"optional_params": info["optional_params"],
|
||||||
|
"description": info["description"]
|
||||||
|
}
|
||||||
|
for name, info in self.getters.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Добавить схему
|
||||||
|
def get_value(self, getter_name: str, params: Dict[str, Any]):
|
||||||
|
"""
|
||||||
|
Получение значения через указанный геттер
|
||||||
|
|
||||||
|
Args:
|
||||||
|
getter_name: Имя геттера
|
||||||
|
params: Параметры для геттера
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Результат выполнения геттера
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: Если геттер не найден или параметры неверны
|
||||||
|
"""
|
||||||
|
if getter_name not in self.getters:
|
||||||
|
available = list(self.getters.keys())
|
||||||
|
raise ValueError(f"Геттер '{getter_name}' не найден. Доступные: {available}")
|
||||||
|
|
||||||
|
getter_info = self.getters[getter_name]
|
||||||
|
required = getter_info["required_params"]
|
||||||
|
|
||||||
|
# Проверка обязательных параметров
|
||||||
|
missing = [p for p in required if p not in params]
|
||||||
|
if missing:
|
||||||
|
raise ValueError(f"Отсутствуют обязательные параметры для геттера '{getter_name}': {missing}")
|
||||||
|
|
||||||
|
# Вызов метода геттера
|
||||||
|
try:
|
||||||
|
return getter_info["method"](params)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"Ошибка выполнения геттера '{getter_name}': {str(e)}")
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def parse(self, file_path: str, params: dict) -> pd.DataFrame:
|
def parse(self, file_path: str, params: dict) -> pd.DataFrame:
|
||||||
"""Парсинг файла и возврат DataFrame"""
|
"""Парсинг файла и возврат DataFrame"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_value(self, df: pd.DataFrame, params: dict):
|
|
||||||
"""Получение значения из DataFrame по параметрам"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
# @abstractmethod
|
|
||||||
# def get_schema(self) -> dict:
|
|
||||||
# """Возвращает схему входных параметров для парсера"""
|
|
||||||
# pass
|
|
||||||
|
|
||||||
|
|
||||||
class StoragePort(ABC):
|
class StoragePort(ABC):
|
||||||
"""Интерфейс для хранилища данных"""
|
"""Интерфейс для хранилища данных"""
|
||||||
|
|||||||
@@ -100,8 +100,34 @@ class ReportService:
|
|||||||
# Получаем парсер
|
# Получаем парсер
|
||||||
parser = get_parser(request.report_type)
|
parser = get_parser(request.report_type)
|
||||||
|
|
||||||
# Получаем значение
|
# Устанавливаем DataFrame в парсер для использования в геттерах
|
||||||
value = parser.get_value(df, request.get_params)
|
parser.df = df
|
||||||
|
|
||||||
|
# Получаем параметры запроса
|
||||||
|
get_params = request.get_params or {}
|
||||||
|
|
||||||
|
# Определяем имя геттера (по умолчанию используем первый доступный)
|
||||||
|
getter_name = get_params.pop("getter", None)
|
||||||
|
if not getter_name:
|
||||||
|
# Если геттер не указан, берем первый доступный
|
||||||
|
available_getters = list(parser.getters.keys())
|
||||||
|
if available_getters:
|
||||||
|
getter_name = available_getters[0]
|
||||||
|
print(f"⚠️ Геттер не указан, используем первый доступный: {getter_name}")
|
||||||
|
else:
|
||||||
|
return DataResult(
|
||||||
|
success=False,
|
||||||
|
message="Парсер не имеет доступных геттеров"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Получаем значение через указанный геттер
|
||||||
|
try:
|
||||||
|
value = parser.get_value(getter_name, get_params)
|
||||||
|
except ValueError as e:
|
||||||
|
return DataResult(
|
||||||
|
success=False,
|
||||||
|
message=f"Ошибка параметров: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
# Формируем результат
|
# Формируем результат
|
||||||
if value is not None:
|
if value is not None:
|
||||||
|
|||||||
49
start_dev.py
Normal file
49
start_dev.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Скрипт для запуска проекта в режиме разработки
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
def run_command(command, description):
|
||||||
|
"""Выполнение команды с выводом"""
|
||||||
|
print(f"🔄 {description}...")
|
||||||
|
try:
|
||||||
|
result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
|
||||||
|
print(f"✅ {description} выполнено успешно")
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"❌ Ошибка при {description.lower()}:")
|
||||||
|
print(f" Команда: {command}")
|
||||||
|
print(f" Ошибка: {e.stderr}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("🚀 Запуск проекта в режиме разработки")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Останавливаем продакшн контейнеры если они запущены
|
||||||
|
if run_command("docker compose ps", "Проверка статуса контейнеров"):
|
||||||
|
if "Up" in subprocess.run("docker compose ps", shell=True, capture_output=True, text=True).stdout:
|
||||||
|
print("🛑 Останавливаю продакшн контейнеры...")
|
||||||
|
run_command("docker compose down", "Остановка продакшн контейнеров")
|
||||||
|
|
||||||
|
# Запускаем режим разработки
|
||||||
|
print("\n🔧 Запуск режима разработки...")
|
||||||
|
if run_command("docker compose -f docker-compose.dev.yml up -d", "Запуск контейнеров разработки"):
|
||||||
|
print("\n🎉 Проект запущен в режиме разработки!")
|
||||||
|
print("\n📍 Доступные сервисы:")
|
||||||
|
print(" • Streamlit: http://localhost:8501")
|
||||||
|
print(" • FastAPI: http://localhost:8000")
|
||||||
|
print(" • MinIO Console: http://localhost:9001")
|
||||||
|
print("\n💡 Теперь изменения в streamlit_app/ будут автоматически перезагружаться!")
|
||||||
|
print("\n🛑 Для остановки используйте:")
|
||||||
|
print(" docker compose -f docker-compose.dev.yml down")
|
||||||
|
else:
|
||||||
|
print("\n❌ Не удалось запустить проект в режиме разработки")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
49
start_prod.py
Normal file
49
start_prod.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Скрипт для запуска проекта в продакшн режиме
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def run_command(command, description):
|
||||||
|
"""Выполнение команды с выводом"""
|
||||||
|
print(f"🔄 {description}...")
|
||||||
|
try:
|
||||||
|
result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
|
||||||
|
print(f"✅ {description} выполнено успешно")
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"❌ Ошибка при {description.lower()}:")
|
||||||
|
print(f" Команда: {command}")
|
||||||
|
print(f" Ошибка: {e.stderr}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("🚀 Запуск проекта в продакшн режиме")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Останавливаем контейнеры разработки если они запущены
|
||||||
|
if run_command("docker compose -f docker-compose.dev.yml ps", "Проверка статуса контейнеров разработки"):
|
||||||
|
if "Up" in subprocess.run("docker compose -f docker-compose.dev.yml ps", shell=True, capture_output=True, text=True).stdout:
|
||||||
|
print("🛑 Останавливаю контейнеры разработки...")
|
||||||
|
run_command("docker compose -f docker-compose.dev.yml down", "Остановка контейнеров разработки")
|
||||||
|
|
||||||
|
# Запускаем продакшн режим
|
||||||
|
print("\n🏭 Запуск продакшн режима...")
|
||||||
|
if run_command("docker compose up -d --build", "Запуск продакшн контейнеров"):
|
||||||
|
print("\n🎉 Проект запущен в продакшн режиме!")
|
||||||
|
print("\n📍 Доступные сервисы:")
|
||||||
|
print(" • Streamlit: http://localhost:8501")
|
||||||
|
print(" • FastAPI: http://localhost:8000")
|
||||||
|
print(" • MinIO Console: http://localhost:9001")
|
||||||
|
print("\n💡 Для разработки используйте:")
|
||||||
|
print(" python start_dev.py")
|
||||||
|
print("\n🛑 Для остановки используйте:")
|
||||||
|
print(" docker compose down")
|
||||||
|
else:
|
||||||
|
print("\n❌ Не удалось запустить проект в продакшн режиме")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -16,7 +16,8 @@ st.set_page_config(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Конфигурация API
|
# Конфигурация API
|
||||||
API_BASE_URL = os.getenv("API_BASE_URL", "http://fastapi:8000")
|
API_BASE_URL = os.getenv("API_BASE_URL", "http://fastapi:8000") # Внутренний адрес для Docker
|
||||||
|
API_PUBLIC_URL = os.getenv("API_PUBLIC_URL", "http://localhost:8000") # Внешний адрес для пользователя
|
||||||
|
|
||||||
def check_api_health():
|
def check_api_health():
|
||||||
"""Проверка доступности API"""
|
"""Проверка доступности API"""
|
||||||
@@ -73,7 +74,7 @@ def main():
|
|||||||
st.info("Убедитесь, что FastAPI сервер запущен")
|
st.info("Убедитесь, что FastAPI сервер запущен")
|
||||||
return
|
return
|
||||||
|
|
||||||
st.success(f"✅ API доступен по адресу {API_BASE_URL}")
|
st.success(f"✅ API доступен по адресу {API_PUBLIC_URL}")
|
||||||
|
|
||||||
# Боковая панель с информацией
|
# Боковая панель с информацией
|
||||||
with st.sidebar:
|
with st.sidebar:
|
||||||
@@ -373,7 +374,7 @@ def main():
|
|||||||
# Футер
|
# Футер
|
||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
st.markdown("### 📚 Документация API")
|
st.markdown("### 📚 Документация API")
|
||||||
st.markdown(f"Полная документация доступна по адресу: {API_BASE_URL}/docs")
|
st.markdown(f"Полная документация доступна по адресу: {API_PUBLIC_URL}/docs")
|
||||||
|
|
||||||
# Информация о проекте
|
# Информация о проекте
|
||||||
with st.expander("ℹ️ О проекте"):
|
with st.expander("ℹ️ О проекте"):
|
||||||
|
|||||||
Reference in New Issue
Block a user