Изменения:

-добавлены нетворки в докер композ
-исправлен рутинг (баг пайчарма)
-запросы к ии агентам не проходят из-за ссл сертификата (пробовали отключить, но пока не выходит, нужно доделать)
This commit is contained in:
2025-12-19 00:57:13 +03:00
parent 24f4ce118f
commit a649fb1192
16 changed files with 433 additions and 62 deletions

5
.idea/misc.xml generated
View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_22" project-jdk-name="openjdk-23" project-jdk-type="JavaSDK"> <component name="Black">
<output url="file://$PROJECT_DIR$/out" /> <option name="sdkName" value="Python 3.14 (New-planet-api)" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.14 (New-planet-api)" project-jdk-type="Python SDK" />
</project> </project>

View File

@@ -2,8 +2,11 @@
<module type="JAVA_MODULE" version="4"> <module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true"> <component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$">
<orderEntry type="inheritedJdk" /> <sourceFolder url="file://$MODULE_DIR$/new-planet-backend" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.14 (New-planet-api)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

View File

@@ -1,21 +1,27 @@
# Database 
GIGACHAT_CLIENT_ID=019966f4-1c5c-7382-9006-b84419fbe5d1
GIGACHAT_CLIENT_SECRET=MDE5OTY2ZjQtMWM1Yy03MzgyLTkwMDYtYjg0NDE5ZmJlNWQxOjJjODBmOWE2LWU4YWMtNDE4YS1iOGVkLWE4NTE0YzVkNDAwNw==
GIGACHAT_AUTH_URL=https://ngw.devices.sberbank.ru:9443/api/v2/oauth
GIGACHAT_BASE_URL=https://gigachat.devices.sberbank.ru/api/v1
GIGACHAT_MODEL_CHAT=GigaChat-2-Lite
GIGACHAT_MODEL_SCHEDULE=GigaChat-2-Pro
# Security
SECRET_KEY=3db8542397edddbd6162ad823157e36f8d47232aa646725d4799266229ba7aa4
# Database
POSTGRES_USER=postgres POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres POSTGRES_PASSWORD=postgres
POSTGRES_DB=newplanet POSTGRES_DB=newplanet
POSTGRES_HOST=localhost POSTGRES_HOST=localhost
POSTGRES_PORT=5432 POSTGRES_PORT=5432
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/newplanet
# Redis # Redis
REDIS_HOST=localhost REDIS_HOST=localhost
REDIS_PORT=6379 REDIS_PORT=6379
REDIS_DB=0 REDIS_DB=0
REDIS_URL=redis://localhost:6379
# Security # Storage
SECRET_KEY=jwt-secret-key
# Storage (MinIO/S3)
STORAGE_ENDPOINT=localhost:9000 STORAGE_ENDPOINT=localhost:9000
STORAGE_ACCESS_KEY=minioadmin STORAGE_ACCESS_KEY=minioadmin
STORAGE_SECRET_KEY=minioadmin STORAGE_SECRET_KEY=minioadmin
@@ -23,6 +29,3 @@ STORAGE_BUCKET=new-planet-images
STORAGE_USE_SSL=false STORAGE_USE_SSL=false
STORAGE_REGION=us-east-1 STORAGE_REGION=us-east-1
# GigaChat API
GIGACHAT_CLIENT_ID=gigachat-client-id
GIGACHAT_CLIENT_SECRET="gigachat-token-here"

View File

@@ -23,12 +23,13 @@ Backend API для мобильного приложения **Новая Пла
### Установка ### Установка
1. Клонируйте репозиторий 1. Клонируйте репозиторий
2. Установите зависимости: 2. Установить окружение ("python -m venv venv")
3. Установите зависимости:
```bash ```bash
pip install -r requirements.txt pip install -r requirements.txt
``` ```
3. Настройте `.env`: 4. Настройте `.env`:
```bash ```bash
cp .env.example .env cp .env.example .env
#В целом вам нужно поменять GIGACHAT API секцию, JWT Secret key сгенерить, просто в поисковике генератор на 256 байт сделаете JWT #В целом вам нужно поменять GIGACHAT API секцию, JWT Secret key сгенерить, просто в поисковике генератор на 256 байт сделаете JWT
@@ -36,7 +37,7 @@ cp .env.example .env
# Отредактируйте .env с вашими настройками # Отредактируйте .env с вашими настройками
``` ```
4. Запустите инфраструктуру (Docker): 5. Запустите инфраструктуру (Docker):
```bash ```bash
docker-compose -f docker/docker-compose.yml up -d docker-compose -f docker/docker-compose.yml up -d
# или используйте вариант ниже,но лучше вариант выше для избежания непредвиденного # или используйте вариант ниже,но лучше вариант выше для избежания непредвиденного
@@ -44,12 +45,12 @@ docker-compose -f docker/docker-compose.yml up -d
docker-compose up docker-compose up
``` ```
5. Примените миграции: 6. Примените миграции:
```bash ```bash
alembic upgrade head alembic upgrade head
``` ```
6. Запустите сервер: 7. Запустите сервер:
```bash ```bash
uvicorn app.main:app --reload uvicorn app.main:app --reload
# если не запустилось, проверяйте есть ли .venv(установлено ли окружение для питона), также попробуйте в венве uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 # если не запустилось, проверяйте есть ли .venv(установлено ли окружение для питона), также попробуйте в венве uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

View File

@@ -7,7 +7,7 @@ from app.crud import user as crud_user
from app.services.auth_service import auth_service from app.services.auth_service import auth_service
from app.models.user import User from app.models.user import User
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/v1/auth/login") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
async def get_current_user( async def get_current_user(

View File

@@ -3,9 +3,27 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.db.session import get_db from app.db.session import get_db
from app.api.deps import get_current_active_user from app.api.deps import get_current_active_user
from app.models.user import User from app.models.user import User
from app.schemas.ai import ChatRequest, ChatResponse, ScheduleGenerateRequest, ScheduleGenerateResponse from app.models.ai_conversation import AIConversation
from app.schemas.ai import (
ChatRequest,
ChatResponse,
ScheduleGenerateRequest,
ScheduleGenerateResponse,
ConversationHistory,
ConversationListItem,
ScheduleUpdateRequest,
ScheduleUpdateResponse,
RecommendationRequest,
RecommendationResponse,
)
from app.services.chat_service import chat_service from app.services.chat_service import chat_service
from app.services.schedule_generator import schedule_generator from app.services.schedule_generator import schedule_generator
from app.services.cache_service import cache_service
from app.services.gigachat_service import gigachat_service
from app.crud import schedule as crud_schedule, task as crud_task
from app.schemas.task import TaskCreate
from app.schemas.schedule import ScheduleUpdate
from app.core.config import settings
router = APIRouter() router = APIRouter()
@@ -30,7 +48,6 @@ async def chat_with_ai(
detail=f"Chat error: {str(e)}" detail=f"Chat error: {str(e)}"
) )
@router.post("/schedule/generate", response_model=ScheduleGenerateResponse) @router.post("/schedule/generate", response_model=ScheduleGenerateResponse)
async def generate_schedule_ai( async def generate_schedule_ai(
request: ScheduleGenerateRequest, request: ScheduleGenerateRequest,
@@ -58,3 +75,4 @@ async def generate_schedule_ai(
detail=f"Failed to generate schedule: {str(e)}" detail=f"Failed to generate schedule: {str(e)}"
) )

View File

@@ -10,15 +10,15 @@ class Settings(BaseSettings):
DEBUG: bool = False DEBUG: bool = False
# Security # Security
SECRET_KEY: str SECRET_KEY: str = "3db8542397edddbd6162ad823157e36f8d47232aa646725d4799266229ba7aa4"
ALGORITHM: str = "HS256" ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
REFRESH_TOKEN_EXPIRE_DAYS: int = 7 REFRESH_TOKEN_EXPIRE_DAYS: int = 7
# Database # Database
POSTGRES_USER: str POSTGRES_USER: str = "postgres"
POSTGRES_PASSWORD: str POSTGRES_PASSWORD: str = "postgres"
POSTGRES_DB: str POSTGRES_DB: str = "newplanet"
POSTGRES_HOST: str = "localhost" POSTGRES_HOST: str = "localhost"
POSTGRES_PORT: int = 5432 POSTGRES_PORT: int = 5432
DATABASE_URL: Optional[str] = None DATABASE_URL: Optional[str] = None
@@ -43,15 +43,15 @@ class Settings(BaseSettings):
# Storage (MinIO/S3) # Storage (MinIO/S3)
STORAGE_ENDPOINT: str = "localhost:9000" STORAGE_ENDPOINT: str = "localhost:9000"
STORAGE_ACCESS_KEY: str STORAGE_ACCESS_KEY: str = "minioadmin"
STORAGE_SECRET_KEY: str STORAGE_SECRET_KEY: str = "minioadmin"
STORAGE_BUCKET: str = "new-planet-images" STORAGE_BUCKET: str = "new-planet-images"
STORAGE_USE_SSL: bool = False STORAGE_USE_SSL: bool = False
STORAGE_REGION: str = "us-east-1" STORAGE_REGION: str = "us-east-1"
# GigaChat # GigaChat
GIGACHAT_CLIENT_ID: str GIGACHAT_CLIENT_ID: str = "019966f4-1c5c-7382-9006-b84419fbe5d1"
GIGACHAT_CLIENT_SECRET: str GIGACHAT_CLIENT_SECRET: str = "MDE5OTY2ZjQtMWM1Yy03MzgyLTkwMDYtYjg0NDE5ZmJlNWQxOjJjODBmOWE2LWU4YWMtNDE4YS1iOGVkLWE4NTE0YzVkNDAwNw=="
GIGACHAT_AUTH_URL: str = "https://ngw.devices.sberbank.ru:9443/api/v2/oauth" GIGACHAT_AUTH_URL: str = "https://ngw.devices.sberbank.ru:9443/api/v2/oauth"
GIGACHAT_BASE_URL: str = "https://gigachat.devices.sberbank.ru/api/v1" GIGACHAT_BASE_URL: str = "https://gigachat.devices.sberbank.ru/api/v1"
GIGACHAT_MODEL_CHAT: str = "GigaChat-2-Lite" GIGACHAT_MODEL_CHAT: str = "GigaChat-2-Lite"

View File

@@ -2,7 +2,7 @@ from datetime import datetime, timedelta
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
from jose import JWTError, jwt from jose import JWTError, jwt
import bcrypt import bcrypt
from app.core.config import settings from config import settings
def verify_password(plain_password: str, hashed_password: str) -> bool: def verify_password(plain_password: str, hashed_password: str) -> bool:

View File

@@ -1,4 +1,4 @@
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import declarative_base
from sqlalchemy import Column, DateTime, func, String from sqlalchemy import Column, DateTime, func, String
import uuid import uuid

View File

@@ -1,5 +1,5 @@
from app.db.base import Base from base import Base
from app.db.session import engine from session import engine
from app.models import user, schedule, task, reward, ai_conversation from app.models import user, schedule, task, reward, ai_conversation

View File

@@ -3,8 +3,7 @@ from app.core.config import settings
engine = create_async_engine( engine = create_async_engine(
settings.database_url, settings.database_url,
echo=settings.DEBUG, echo=settings.DEBUG
future=True
) )
AsyncSessionLocal = async_sessionmaker( AsyncSessionLocal = async_sessionmaker(

View File

@@ -1,3 +1,18 @@
import sys
from pathlib import Path
# Добавляем корневую директорию проекта в PYTHONPATH при прямом запуске
# Это нужно, чтобы Python мог найти модуль 'app'
# Проверяем, запускается ли файл напрямую, проверяя имя скрипта
if sys.argv and len(sys.argv) > 0:
script_path = Path(sys.argv[0]).resolve()
current_file = Path(__file__).resolve()
# Если скрипт запускается напрямую (не через модуль)
if script_path == current_file or script_path.name == current_file.name:
project_root = current_file.parent.parent # new-planet-backend
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.exceptions import HTTPException as StarletteHTTPException
@@ -73,7 +88,7 @@ if __name__ == "__main__":
import uvicorn import uvicorn
uvicorn.run( uvicorn.run(
"app.main:app", "app.main:app",
host="0.0.0.0", host="127.0.0.1",
port=8000, port=8000,
reload=settings.DEBUG reload=settings.DEBUG
) )

View File

@@ -38,3 +38,41 @@ class ConversationHistory(BaseModel):
class Config: class Config:
from_attributes = True from_attributes = True
class ConversationListItem(BaseModel):
"""Элемент списка разговоров"""
conversation_id: str
last_message: Optional[str] = None
created_at: datetime
updated_at: datetime
message_count: int = 0
class Config:
from_attributes = True
class ScheduleUpdateRequest(BaseModel):
"""Запрос на обновление расписания через ИИ"""
user_request: str = Field(..., min_length=1, max_length=1000, description="Описание желаемых изменений")
class ScheduleUpdateResponse(BaseModel):
"""Ответ после обновления расписания"""
schedule_id: str
title: str
tasks: List[Dict[str, Any]]
tokens_used: Optional[int] = None
class RecommendationRequest(BaseModel):
"""Запрос на получение рекомендаций"""
preferences: List[str] = Field(default_factory=list, description="Предпочтения пользователя")
category: Optional[str] = Field(None, description="Категория заданий")
completed_tasks: Optional[List[str]] = Field(default_factory=list, description="Уже выполненные задания")
top_k: int = Field(5, ge=1, le=20, description="Количество рекомендаций")
class RecommendationResponse(BaseModel):
"""Ответ с рекомендациями"""
recommendations: List[Dict[str, Any]]
total: int

View File

@@ -1,11 +1,17 @@
import os
import aiohttp import aiohttp
import ssl
import base64 import base64
import uuid import uuid
import time import time
from urllib.parse import urlencode
from typing import Optional, List, Dict, Any from typing import Optional, List, Dict, Any
from dotenv import load_dotenv
from app.core.config import settings from app.core.config import settings
load_dotenv()
class GigaChatService: class GigaChatService:
def __init__(self): def __init__(self):
@@ -19,8 +25,19 @@ class GigaChatService:
if time.time() < (self.token_expires_at - 60): if time.time() < (self.token_expires_at - 60):
return self.access_token return self.access_token
credentials = f"{settings.GIGACHAT_CLIENT_ID}:{settings.GIGACHAT_CLIENT_SECRET}" # Проверяем наличие credentials
encoded_credentials = base64.b64encode(credentials.encode()).decode() client_id = os.getenv("GIGACHAT_CLIENT_ID")
client_secret = os.getenv("GIGACHAT_CLIENT_SECRET")
if not client_id or not client_secret:
raise Exception(
"GigaChat credentials not configured. "
"Please set GIGACHAT_CLIENT_ID and GIGACHAT_CLIENT_SECRET in .env file"
)
# Формируем credentials и кодируем в Base64 с явным указанием UTF-8
credentials = f"{client_id}:{client_secret}".strip().encode('utf-8') # Обрезаем лишние символы
encoded_credentials = base64.b64encode(credentials).decode('utf-8')
headers = { headers = {
"Authorization": f"Basic {encoded_credentials}", "Authorization": f"Basic {encoded_credentials}",
@@ -29,31 +46,61 @@ class GigaChatService:
"RqUID": str(uuid.uuid4()) "RqUID": str(uuid.uuid4())
} }
data = {"scope": "GIGACHAT_API_PERS"} # Правильно кодируем данные формы (как в рабочем примере)
form_data = {
"grant_type": "client_credentials",
"scope": "GIGACHAT_API_PERS"
}
# Создаем SSL контекст без проверки сертификата (только для разработки!) # Отключаем проверку SSL (только для разработки!)
ssl_context = ssl.create_default_context() # Используем ssl=False для полного отключения проверки сертификата
ssl_context.check_hostname = False connector = aiohttp.TCPConnector(ssl=False)
ssl_context.verify_mode = ssl.CERT_NONE
connector = aiohttp.TCPConnector(ssl=ssl_context)
async with aiohttp.ClientSession(connector=connector) as session: async with aiohttp.ClientSession(connector=connector) as session:
async with session.post( async with session.post(
settings.GIGACHAT_AUTH_URL, os.getenv("GIGACHAT_BASE_URL"),
headers=headers, headers=headers,
data=data data=form_data
) as response: ) as response:
if response.status != 200: if response.status != 200:
raise Exception(f"Failed to get token: {response.status}") # Получаем детали ошибки из ответа
try:
error_body = await response.text()
# Пытаемся распарсить как JSON, если не получается - возвращаем текст
try:
error_json = await response.json()
error_detail = error_json.get("error_description") or error_json.get("error") or str(error_json)
except:
error_detail = error_body
except:
error_detail = "No error details available"
raise Exception(
f"Failed to get token: HTTP {response.status}. "
f"Error details: {error_detail}. "
f"Check your GIGACHAT_CLIENT_ID and GIGACHAT_CLIENT_SECRET_2 in .env file"
)
result = await response.json() result = await response.json()
self.access_token = result.get("access_token") self.access_token = result.get("access_token")
expires_in = result.get("expires_at", 1800) if not self.access_token:
# expires_at может быть timestamp или количество секунд raise Exception(f"Token not found in response: {result}")
if expires_in > 1000000000: # Это timestamp
self.token_expires_at = expires_in # Обрабатываем время истечения токена (может быть expires_at или expires_in)
else: # Это количество секунд expires_at = result.get("expires_at")
expires_in = result.get("expires_in")
if expires_at:
# expires_at может быть timestamp или количество секунд
if expires_at > 1000000000: # Это timestamp
self.token_expires_at = expires_at
else: # Это количество секунд
self.token_expires_at = time.time() + expires_at
elif expires_in:
# expires_in - это всегда количество секунд до истечения
self.token_expires_at = time.time() + expires_in self.token_expires_at = time.time() + expires_in
else:
# По умолчанию 30 минут (1800 секунд)
self.token_expires_at = time.time() + 1800
return self.access_token return self.access_token
@@ -65,7 +112,7 @@ class GigaChatService:
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Отправить сообщение в GigaChat""" """Отправить сообщение в GigaChat"""
token = await self._get_token() token = await self._get_token()
model = model or settings.GIGACHAT_MODEL_CHAT model = model or settings.GIGACHAT_MODEL_CHAT or "GigaChat"
messages = context or [] messages = context or []
messages.append({"role": "user", "content": message}) messages.append({"role": "user", "content": message})
@@ -82,12 +129,9 @@ class GigaChatService:
"max_tokens": 2000 "max_tokens": 2000
} }
# Создаем SSL контекст без проверки сертификата (только для разработки!) # Отключаем проверку SSL (только для разработки!)
ssl_context = ssl.create_default_context() # Используем ssl=False для полного отключения проверки сертификата
ssl_context.check_hostname = False connector = aiohttp.TCPConnector(ssl=False)
ssl_context.verify_mode = ssl.CERT_NONE
connector = aiohttp.TCPConnector(ssl=ssl_context)
async with aiohttp.ClientSession(connector=connector) as session: async with aiohttp.ClientSession(connector=connector) as session:
async with session.post( async with session.post(
f"{settings.GIGACHAT_BASE_URL}/chat/completions", f"{settings.GIGACHAT_BASE_URL}/chat/completions",

View File

@@ -17,6 +17,8 @@ services:
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
networks:
- new-planet-network
redis: redis:
image: redis:7-alpine image: redis:7-alpine
@@ -30,6 +32,8 @@ services:
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
networks:
- new-planet-network
minio: minio:
image: minio/minio:latest image: minio/minio:latest
@@ -48,9 +52,15 @@ services:
interval: 30s interval: 30s
timeout: 20s timeout: 20s
retries: 3 retries: 3
networks:
- new-planet-network
volumes: volumes:
postgres_data: postgres_data:
redis_data: redis_data:
minio_data: minio_data:
networks:
new-planet-network:
driver: bridge

View File

@@ -1341,3 +1341,242 @@ WHERE users.id = $1::UUID]
2025-12-18 13:49:33 - root - INFO - Shutting down... 2025-12-18 13:49:33 - root - INFO - Shutting down...
2025-12-18 13:49:38 - root - INFO - Starting up... 2025-12-18 13:49:38 - root - INFO - Starting up...
2025-12-18 13:51:42 - root - INFO - Shutting down... 2025-12-18 13:51:42 - root - INFO - Shutting down...
2025-12-18 18:44:49 - root - INFO - Starting up...
2025-12-18 19:39:26 - root - INFO - Starting up...
2025-12-18 19:40:16 - app.middleware.error_handler - ERROR - Unhandled exception: Error 111 connecting to localhost:6379. Connection refused.
+ Exception Group Traceback (most recent call last):
| File "/usr/local/lib/python3.11/site-packages/starlette/_utils.py", line 79, in collapse_excgroups
| yield
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 192, in __call__
| async with anyio.create_task_group() as task_group:
| File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 783, in __aexit__
| raise BaseExceptionGroup(
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/errors.py", line 164, in __call__
| await self.app(scope, receive, _send)
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 191, in __call__
| with recv_stream, send_stream, collapse_excgroups():
| File "/usr/local/lib/python3.11/contextlib.py", line 158, in __exit__
| self.gen.throw(typ, value, traceback)
| File "/usr/local/lib/python3.11/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
| raise exc
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 193, in __call__
| response = await self.dispatch_func(request, call_next)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/app/app/middleware/rate_limiter.py", line 20, in dispatch
| current_requests = await cache_service.get(cache_key)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/app/app/services/cache_service.py", line 28, in get
| return await self.redis_client.get(key)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/client.py", line 720, in execute_command
| conn = self.connection or await pool.get_connection()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1198, in get_connection
| await self.ensure_connection(connection)
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1231, in ensure_connection
| await connection.connect()
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 298, in connect
| await self.connect_check_health(check_health=True)
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 317, in connect_check_health
| raise ConnectionError(self._error_message(e))
| redis.exceptions.ConnectionError: Error 111 connecting to localhost:6379. Connection refused.
+------------------------------------
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 191, in __call__
with recv_stream, send_stream, collapse_excgroups():
File "/usr/local/lib/python3.11/contextlib.py", line 158, in __exit__
self.gen.throw(typ, value, traceback)
File "/usr/local/lib/python3.11/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
raise exc
File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/app/middleware/rate_limiter.py", line 20, in dispatch
current_requests = await cache_service.get(cache_key)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/app/services/cache_service.py", line 28, in get
return await self.redis_client.get(key)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/client.py", line 720, in execute_command
conn = self.connection or await pool.get_connection()
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1198, in get_connection
await self.ensure_connection(connection)
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1231, in ensure_connection
await connection.connect()
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 298, in connect
await self.connect_check_health(check_health=True)
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 317, in connect_check_health
raise ConnectionError(self._error_message(e))
redis.exceptions.ConnectionError: Error 111 connecting to localhost:6379. Connection refused.
2025-12-18 19:40:16 - app.middleware.error_handler - ERROR - Unhandled exception: Error 111 connecting to localhost:6379. Connection refused.
+ Exception Group Traceback (most recent call last):
| File "/usr/local/lib/python3.11/site-packages/starlette/_utils.py", line 79, in collapse_excgroups
| yield
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 192, in __call__
| async with anyio.create_task_group() as task_group:
| File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 783, in __aexit__
| raise BaseExceptionGroup(
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/errors.py", line 164, in __call__
| await self.app(scope, receive, _send)
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 191, in __call__
| with recv_stream, send_stream, collapse_excgroups():
| File "/usr/local/lib/python3.11/contextlib.py", line 158, in __exit__
| self.gen.throw(typ, value, traceback)
| File "/usr/local/lib/python3.11/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
| raise exc
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 193, in __call__
| response = await self.dispatch_func(request, call_next)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/app/app/middleware/rate_limiter.py", line 20, in dispatch
| current_requests = await cache_service.get(cache_key)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/app/app/services/cache_service.py", line 28, in get
| return await self.redis_client.get(key)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/client.py", line 720, in execute_command
| conn = self.connection or await pool.get_connection()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1198, in get_connection
| await self.ensure_connection(connection)
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1231, in ensure_connection
| await connection.connect()
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 298, in connect
| await self.connect_check_health(check_health=True)
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 317, in connect_check_health
| raise ConnectionError(self._error_message(e))
| redis.exceptions.ConnectionError: Error 111 connecting to localhost:6379. Connection refused.
+------------------------------------
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 191, in __call__
with recv_stream, send_stream, collapse_excgroups():
File "/usr/local/lib/python3.11/contextlib.py", line 158, in __exit__
self.gen.throw(typ, value, traceback)
File "/usr/local/lib/python3.11/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
raise exc
File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/app/middleware/rate_limiter.py", line 20, in dispatch
current_requests = await cache_service.get(cache_key)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/app/services/cache_service.py", line 28, in get
return await self.redis_client.get(key)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/client.py", line 720, in execute_command
conn = self.connection or await pool.get_connection()
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1198, in get_connection
await self.ensure_connection(connection)
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1231, in ensure_connection
await connection.connect()
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 298, in connect
await self.connect_check_health(check_health=True)
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 317, in connect_check_health
raise ConnectionError(self._error_message(e))
redis.exceptions.ConnectionError: Error 111 connecting to localhost:6379. Connection refused.
2025-12-18 19:40:16 - app.middleware.error_handler - ERROR - Unhandled exception: Error 111 connecting to localhost:6379. Connection refused.
+ Exception Group Traceback (most recent call last):
| File "/usr/local/lib/python3.11/site-packages/starlette/_utils.py", line 79, in collapse_excgroups
| yield
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 192, in __call__
| async with anyio.create_task_group() as task_group:
| File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 783, in __aexit__
| raise BaseExceptionGroup(
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/errors.py", line 164, in __call__
| await self.app(scope, receive, _send)
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 191, in __call__
| with recv_stream, send_stream, collapse_excgroups():
| File "/usr/local/lib/python3.11/contextlib.py", line 158, in __exit__
| self.gen.throw(typ, value, traceback)
| File "/usr/local/lib/python3.11/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
| raise exc
| File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 193, in __call__
| response = await self.dispatch_func(request, call_next)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/app/app/middleware/rate_limiter.py", line 20, in dispatch
| current_requests = await cache_service.get(cache_key)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/app/app/services/cache_service.py", line 28, in get
| return await self.redis_client.get(key)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/client.py", line 720, in execute_command
| conn = self.connection or await pool.get_connection()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1198, in get_connection
| await self.ensure_connection(connection)
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1231, in ensure_connection
| await connection.connect()
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 298, in connect
| await self.connect_check_health(check_health=True)
| File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 317, in connect_check_health
| raise ConnectionError(self._error_message(e))
| redis.exceptions.ConnectionError: Error 111 connecting to localhost:6379. Connection refused.
+------------------------------------
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 191, in __call__
with recv_stream, send_stream, collapse_excgroups():
File "/usr/local/lib/python3.11/contextlib.py", line 158, in __exit__
self.gen.throw(typ, value, traceback)
File "/usr/local/lib/python3.11/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
raise exc
File "/usr/local/lib/python3.11/site-packages/starlette/middleware/base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/app/middleware/rate_limiter.py", line 20, in dispatch
current_requests = await cache_service.get(cache_key)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/app/services/cache_service.py", line 28, in get
return await self.redis_client.get(key)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/client.py", line 720, in execute_command
conn = self.connection or await pool.get_connection()
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1198, in get_connection
await self.ensure_connection(connection)
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 1231, in ensure_connection
await connection.connect()
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 298, in connect
await self.connect_check_health(check_health=True)
File "/usr/local/lib/python3.11/site-packages/redis/asyncio/connection.py", line 317, in connect_check_health
raise ConnectionError(self._error_message(e))
redis.exceptions.ConnectionError: Error 111 connecting to localhost:6379. Connection refused.
2025-12-18 22:55:17 - root - INFO - Starting up...
2025-12-18 22:55:17 - root - INFO - Starting up...
2025-12-18 22:56:56 - root - INFO - Starting up...
2025-12-18 22:56:56 - root - INFO - Shutting down...
2025-12-18 22:59:02 - root - INFO - Starting up...
2025-12-18 22:59:02 - root - INFO - Shutting down...
2025-12-18 22:59:53 - root - INFO - Starting up...
2025-12-18 22:59:53 - root - INFO - Starting up...
2025-12-18 22:59:53 - root - INFO - Shutting down...
2025-12-18 22:59:53 - root - INFO - Shutting down...
2025-12-18 20:00:09 - root - INFO - Shutting down...
2025-12-18 23:00:15 - root - INFO - Starting up...
2025-12-18 23:00:15 - root - INFO - Starting up...
2025-12-18 23:00:15 - root - INFO - Shutting down...
2025-12-18 23:00:15 - root - INFO - Shutting down...