Compare commits
4 Commits
807dbee647
...
40d45e8379
Author | SHA1 | Date | |
---|---|---|---|
40d45e8379 | |||
28acd7d04d | |||
e2d0669064 | |||
c33e898218 |
@ -9,6 +9,7 @@ DB_ECHO=True
|
|||||||
# Системные переменные
|
# Системные переменные
|
||||||
SECRET_KEY=1234567890abcdefghigklmnopqrstuvwxyz
|
SECRET_KEY=1234567890abcdefghigklmnopqrstuvwxyz
|
||||||
FRONTEND_URL=http://127.0.0.1:8000/api/v1
|
FRONTEND_URL=http://127.0.0.1:8000/api/v1
|
||||||
|
ACCESS_TOKEN_EXPIRE=3600
|
||||||
|
|
||||||
# Переменные для почты
|
# Переменные для почты
|
||||||
EMAIL_HOST=smtp.yandex.ru
|
EMAIL_HOST=smtp.yandex.ru
|
||||||
|
@ -49,3 +49,8 @@ repos:
|
|||||||
args: [ "--fix", "--line-length=120" ]
|
args: [ "--fix", "--line-length=120" ]
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
args: [ "--line-length=120" ]
|
args: [ "--line-length=120" ]
|
||||||
|
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
|
rev: v1.15.0
|
||||||
|
hooks:
|
||||||
|
- id: mypy
|
||||||
|
@ -30,6 +30,10 @@ PostgreSQL, Poetry, Pydantic и других.
|
|||||||
- **smtplib** — стандартный модуль Python для отправки электронной почты через протокол SMTP.
|
- **smtplib** — стандартный модуль Python для отправки электронной почты через протокол SMTP.
|
||||||
- **jinja2** — современный и гибкий шаблонизатор, который позволяет динамически генерировать HTML и другие текстовые
|
- **jinja2** — современный и гибкий шаблонизатор, который позволяет динамически генерировать HTML и другие текстовые
|
||||||
форматы.
|
форматы.
|
||||||
|
- **pyJWT** — библиотека для создания, подписи и верификации JSON Web Tokens (JWT). Используется для генерации токенов
|
||||||
|
доступа, проверки их
|
||||||
|
целостности, срока действия и подписи, а также работы с закодированными данными (payload) в соответствии со
|
||||||
|
стандартами JWT.
|
||||||
|
|
||||||
## Репозитории
|
## Репозитории
|
||||||
|
|
||||||
@ -48,6 +52,7 @@ PostgreSQL, Poetry, Pydantic и других.
|
|||||||
5. [FastAPI 5. Приложение аутентификации и Pydantic схемы](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-5-prilozhenie-autentifikacii-i-pydantic-sh/)
|
5. [FastAPI 5. Приложение аутентификации и Pydantic схемы](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-5-prilozhenie-autentifikacii-i-pydantic-sh/)
|
||||||
6. [FastAPI 6. Пользовательский сервис и маршруты регистрации](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-6-polzovatelskij-servis-i-marshruty-regist/)
|
6. [FastAPI 6. Пользовательский сервис и маршруты регистрации](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-6-polzovatelskij-servis-i-marshruty-regist/)
|
||||||
7. [FastAPI 7. Электронная почта, подтверждение регистрации, Celery и Redis](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-7-elektronnaya-pochta-podtverzhdenie-registracii-celery-i-redis/)
|
7. [FastAPI 7. Электронная почта, подтверждение регистрации, Celery и Redis](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-7-elektronnaya-pochta-podtverzhdenie-registracii-celery-i-redis/)
|
||||||
|
8. [FastAPI 8. Маршрут авторизации и JWT](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-8-marshrut-avtorizacii-i-jwt/)
|
||||||
|
|
||||||
## Установка
|
## Установка
|
||||||
|
|
||||||
|
@ -1,5 +1,18 @@
|
|||||||
|
"""
|
||||||
|
Проект: Lkeep
|
||||||
|
Автор: Иван Ашихмин
|
||||||
|
Год: 2025
|
||||||
|
Специально для проекта "Код на салфетке"
|
||||||
|
https://pressanybutton.ru/category/servis-na-fastapi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import jwt
|
||||||
from passlib.context import CryptContext
|
from passlib.context import CryptContext
|
||||||
|
|
||||||
|
from lkeep.apps.auth.named_tuples import CreateTokenTuple
|
||||||
from lkeep.core.settings import settings
|
from lkeep.core.settings import settings
|
||||||
|
|
||||||
|
|
||||||
@ -26,3 +39,34 @@ class AuthHandler:
|
|||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
return self.pwd_context.hash(password)
|
return self.pwd_context.hash(password)
|
||||||
|
|
||||||
|
async def verify_password(self, raw_password: str, hashed_password: str) -> bool:
|
||||||
|
"""
|
||||||
|
Проверяет соответствие введенного пароля захэшированному паролю.
|
||||||
|
|
||||||
|
:param raw_password: Введенный пользователем пароль.
|
||||||
|
:type raw_password: str
|
||||||
|
:param hashed_password: Хэш, с которым сравнивается введенный пароль.
|
||||||
|
:type hashed_password: str
|
||||||
|
:returns: Логическое значение, указывающее на успешность проверки.
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
return self.pwd_context.verify(raw_password, hashed_password)
|
||||||
|
|
||||||
|
async def create_access_token(self, user_id: uuid.UUID | str) -> CreateTokenTuple:
|
||||||
|
"""
|
||||||
|
Создаёт JWT-токен доступа для пользователя.
|
||||||
|
|
||||||
|
:param user_id: Уникальный идентификатор пользователя (UUID).
|
||||||
|
:type user_id: uuid.UUID
|
||||||
|
:returns: Кортеж, содержащий закодированный JWT-токен и уникальный session_id.
|
||||||
|
:rtype: CreateTokenTuple
|
||||||
|
"""
|
||||||
|
expire = datetime.datetime.now(datetime.UTC) + datetime.timedelta(seconds=settings.access_token_expire)
|
||||||
|
session_id = str(uuid.uuid4())
|
||||||
|
|
||||||
|
data = {"exp": expire, "session_id": session_id, "user_id": str(user_id)}
|
||||||
|
|
||||||
|
encoded_jwt = jwt.encode(payload=data, key=self.secret, algorithm="HS256")
|
||||||
|
|
||||||
|
return CreateTokenTuple(encoded_jwt=encoded_jwt, session_id=session_id)
|
||||||
|
@ -6,12 +6,15 @@
|
|||||||
https://pressanybutton.ru/category/servis-na-fastapi/
|
https://pressanybutton.ru/category/servis-na-fastapi/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
from fastapi import Depends, HTTPException
|
from fastapi import Depends, HTTPException
|
||||||
from sqlalchemy import insert, update
|
from sqlalchemy import insert, select, update
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
|
||||||
from lkeep.apps.auth.schemas import CreateUser, UserReturnData
|
from lkeep.apps.auth.schemas import CreateUser, GetUserWithIDAndEmail, UserReturnData
|
||||||
from lkeep.core.core_dependency.db_dependency import DBDependency
|
from lkeep.core.core_dependency.db_dependency import DBDependency
|
||||||
|
from lkeep.core.core_dependency.redis_dependency import RedisDependency
|
||||||
from lkeep.database.models import User
|
from lkeep.database.models import User
|
||||||
|
|
||||||
|
|
||||||
@ -20,15 +23,20 @@ class UserManager:
|
|||||||
Класс для управления пользователями.
|
Класс для управления пользователями.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, db: DBDependency = Depends(DBDependency)) -> None:
|
def __init__(
|
||||||
|
self, db: DBDependency = Depends(DBDependency), redis: RedisDependency = Depends(RedisDependency)
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Инициализирует экземпляр класса.
|
Инициализирует экземпляр класса.
|
||||||
|
|
||||||
:param db: Зависимость от базы данных. По умолчанию используется Depends(DBDependency).
|
:param db: Зависимость для базы данных. По умолчанию используется Depends(DBDependency).
|
||||||
:type db: DBDependency
|
:type db: DBDependency
|
||||||
|
:param redis: Зависимость для Redis. По умолчанию используется Depends(RedisDependency).
|
||||||
|
:type redis: RedisDependency
|
||||||
"""
|
"""
|
||||||
self.db = db
|
self.db = db
|
||||||
self.model = User
|
self.model = User
|
||||||
|
self.redis = redis
|
||||||
|
|
||||||
async def create_user(self, user: CreateUser) -> UserReturnData:
|
async def create_user(self, user: CreateUser) -> UserReturnData:
|
||||||
"""
|
"""
|
||||||
@ -64,3 +72,37 @@ class UserManager:
|
|||||||
query = update(self.model).where(self.model.email == email).values(is_verified=True, is_active=True)
|
query = update(self.model).where(self.model.email == email).values(is_verified=True, is_active=True)
|
||||||
await session.execute(query)
|
await session.execute(query)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
|
async def get_user_by_email(self, email: str) -> GetUserWithIDAndEmail | None:
|
||||||
|
"""
|
||||||
|
Возвращает пользователя по указанному адресу электронной почты.
|
||||||
|
|
||||||
|
:param email: Адрес электронной почты пользователя для поиска.
|
||||||
|
:type email: str
|
||||||
|
:return: Объект пользователя с полями id и email, если пользователь найден; None в противном случае.
|
||||||
|
:rtype: GetUserWithIDAndEmail | None
|
||||||
|
"""
|
||||||
|
async with self.db.db_session() as session:
|
||||||
|
query = select(self.model.id, self.model.email, self.model.hashed_password).where(self.model.email == email)
|
||||||
|
|
||||||
|
result = await session.execute(query)
|
||||||
|
user = result.mappings().first()
|
||||||
|
|
||||||
|
if user:
|
||||||
|
return GetUserWithIDAndEmail(**user)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def store_access_token(self, token: str, user_id: uuid.UUID | str, session_id: str) -> None:
|
||||||
|
"""
|
||||||
|
Сохраняет токен доступа в хранилище (Redis).
|
||||||
|
|
||||||
|
:param token: Токен доступа для сохранения.
|
||||||
|
:type token: str
|
||||||
|
:param user_id: Идентификатор пользователя, которому принадлежит токен.
|
||||||
|
:type user_id: uuid.UUID
|
||||||
|
:param session_id: Идентификатор сессии, связанной с токеном.
|
||||||
|
:type session_id: str
|
||||||
|
"""
|
||||||
|
async with self.redis.get_client() as client:
|
||||||
|
await client.set(f"{user_id}:{session_id}", token)
|
||||||
|
27
lkeep/apps/auth/named_tuples.py
Normal file
27
lkeep/apps/auth/named_tuples.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
"""
|
||||||
|
Проект: Lkeep
|
||||||
|
Автор: Иван Ашихмин
|
||||||
|
Год: 2025
|
||||||
|
Специально для проекта "Код на салфетке"
|
||||||
|
https://pressanybutton.ru/category/servis-na-fastapi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
|
||||||
|
class CreateTokenTuple(NamedTuple):
|
||||||
|
"""
|
||||||
|
Класс для создания кортежа токенов, содержащего закодированный JWT и идентификатор сессии.
|
||||||
|
|
||||||
|
Класс наследует от `NamedTuple` и представляет собой неизменяемый контейнер для хранения двух значений:
|
||||||
|
- закодированного JSON Web Token (JWT)
|
||||||
|
- уникального идентификатора сессии.
|
||||||
|
|
||||||
|
:ivar encoded_jwt: Закодированный JWT-токен.
|
||||||
|
:type encoded_jwt: str
|
||||||
|
:ivar session_id: Уникальный идентификатор сессии.
|
||||||
|
:type session_id: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
encoded_jwt: str
|
||||||
|
session_id: str
|
@ -8,20 +8,21 @@ https://pressanybutton.ru/category/servis-na-fastapi/
|
|||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
from starlette import status
|
from starlette import status
|
||||||
|
from starlette.responses import JSONResponse
|
||||||
|
|
||||||
from lkeep.apps.auth.schemas import RegisterUser, UserReturnData
|
from lkeep.apps.auth.schemas import AuthUser, UserReturnData
|
||||||
from lkeep.apps.auth.services import UserService
|
from lkeep.apps.auth.services import UserService
|
||||||
|
|
||||||
auth_router = APIRouter(prefix="/auth", tags=["auth"])
|
auth_router = APIRouter(prefix="/auth", tags=["auth"])
|
||||||
|
|
||||||
|
|
||||||
@auth_router.post(path="/register", response_model=UserReturnData, status_code=status.HTTP_201_CREATED)
|
@auth_router.post(path="/register", response_model=UserReturnData, status_code=status.HTTP_201_CREATED)
|
||||||
async def registration(user: RegisterUser, service: UserService = Depends(UserService)) -> UserReturnData:
|
async def registration(user: AuthUser, service: UserService = Depends(UserService)) -> UserReturnData:
|
||||||
"""
|
"""
|
||||||
Регистрация нового пользователя.
|
Регистрация нового пользователя.
|
||||||
|
|
||||||
:param user: Данные нового пользователя, который нужно зарегистрировать.
|
:param user: Данные нового пользователя, который нужно зарегистрировать.
|
||||||
:type user: RegisterUser
|
:type user: AuthUser
|
||||||
:param service: Сервис для взаимодействия с пользователями.
|
:param service: Сервис для взаимодействия с пользователями.
|
||||||
:type service: UserService
|
:type service: UserService
|
||||||
:returns: Данные зарегистрированного пользователя.
|
:returns: Данные зарегистрированного пользователя.
|
||||||
@ -38,9 +39,27 @@ async def confirm_registration(token: str, service: UserService = Depends(UserSe
|
|||||||
|
|
||||||
:param token: Токен подтверждения регистрации, полученный после отправки на электронную почту.
|
:param token: Токен подтверждения регистрации, полученный после отправки на электронную почту.
|
||||||
:type token: str
|
:type token: str
|
||||||
|
:param service: Сервис для взаимодействия с пользователями.
|
||||||
:raises HTTPException: Если токен недействителен или срок действия истек.
|
:raises HTTPException: Если токен недействителен или срок действия истек.
|
||||||
:return: Словарь с сообщением о успешной подтверждении электронной почты.
|
:return: Словарь с сообщением о успешной подтверждении электронной почты.
|
||||||
:rtype: dict[str, str]
|
:rtype: dict[str, str]
|
||||||
"""
|
"""
|
||||||
await service.confirm_user(token=token)
|
await service.confirm_user(token=token)
|
||||||
|
|
||||||
return {"message": "Электронная почта подтверждена"}
|
return {"message": "Электронная почта подтверждена"}
|
||||||
|
|
||||||
|
|
||||||
|
@auth_router.post(path="/login", status_code=status.HTTP_200_OK)
|
||||||
|
async def login(user: AuthUser, service: UserService = Depends(UserService)) -> JSONResponse:
|
||||||
|
"""
|
||||||
|
Вход пользователя в систему.
|
||||||
|
|
||||||
|
:param user: Объект данных пользователя для входа.
|
||||||
|
:type user: AuthUser
|
||||||
|
:param service: Сервисный объект для управления пользователями.
|
||||||
|
:type service: UserService
|
||||||
|
:returns: JSON-ответ с токеном доступа в Cookies, если вход выполнен успешно.
|
||||||
|
:rtype: JSONResponse
|
||||||
|
:raises HTTPException: Если учетные данные не верны или произошла другая ошибка при входе.
|
||||||
|
"""
|
||||||
|
return await service.login_user(user=user)
|
||||||
|
@ -34,7 +34,7 @@ class GetUserByEmail(BaseModel):
|
|||||||
email: EmailStr
|
email: EmailStr
|
||||||
|
|
||||||
|
|
||||||
class RegisterUser(GetUserByEmail):
|
class AuthUser(GetUserByEmail):
|
||||||
"""
|
"""
|
||||||
Класс для регистрации пользователя, наследующий класс GetUserByEmail.
|
Класс для регистрации пользователя, наследующий класс GetUserByEmail.
|
||||||
|
|
||||||
@ -56,6 +56,21 @@ class CreateUser(GetUserByEmail):
|
|||||||
hashed_password: str
|
hashed_password: str
|
||||||
|
|
||||||
|
|
||||||
|
class GetUserWithIDAndEmail(GetUserByID, CreateUser):
|
||||||
|
"""
|
||||||
|
Класс для получения пользователя по его ID и email.
|
||||||
|
|
||||||
|
:ivar id: Уникальный идентификатор пользователя.
|
||||||
|
:type id: int
|
||||||
|
:ivar email: Адрес электронной почты пользователя.
|
||||||
|
:type email: str
|
||||||
|
:ivar hashed_password: Хэшированный пароль пользователя.
|
||||||
|
:type hashed_password: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UserReturnData(GetUserByID, GetUserByEmail):
|
class UserReturnData(GetUserByID, GetUserByEmail):
|
||||||
"""
|
"""
|
||||||
Класс для представления данных пользователя, возвращаемых из API.
|
Класс для представления данных пользователя, возвращаемых из API.
|
||||||
|
@ -8,10 +8,12 @@ https://pressanybutton.ru/category/servis-na-fastapi/
|
|||||||
|
|
||||||
from fastapi import Depends, HTTPException
|
from fastapi import Depends, HTTPException
|
||||||
from itsdangerous import BadSignature, URLSafeTimedSerializer
|
from itsdangerous import BadSignature, URLSafeTimedSerializer
|
||||||
|
from starlette import status
|
||||||
|
from starlette.responses import JSONResponse
|
||||||
|
|
||||||
from lkeep.apps.auth.handlers import AuthHandler
|
from lkeep.apps.auth.handlers import AuthHandler
|
||||||
from lkeep.apps.auth.managers import UserManager
|
from lkeep.apps.auth.managers import UserManager
|
||||||
from lkeep.apps.auth.schemas import CreateUser, RegisterUser, UserReturnData
|
from lkeep.apps.auth.schemas import AuthUser, CreateUser, UserReturnData
|
||||||
from lkeep.apps.auth.tasks import send_confirmation_email
|
from lkeep.apps.auth.tasks import send_confirmation_email
|
||||||
from lkeep.core.settings import settings
|
from lkeep.core.settings import settings
|
||||||
|
|
||||||
@ -36,12 +38,12 @@ class UserService:
|
|||||||
self.handler = handler
|
self.handler = handler
|
||||||
self.serializer = URLSafeTimedSerializer(secret_key=settings.secret_key.get_secret_value())
|
self.serializer = URLSafeTimedSerializer(secret_key=settings.secret_key.get_secret_value())
|
||||||
|
|
||||||
async def register_user(self, user: RegisterUser) -> UserReturnData:
|
async def register_user(self, user: AuthUser) -> UserReturnData:
|
||||||
"""
|
"""
|
||||||
Регистрирует нового пользователя в системе.
|
Регистрирует нового пользователя в системе.
|
||||||
|
|
||||||
:param user: Информация о пользователе, который нужно зарегистрировать.
|
:param user: Информация о пользователе, который нужно зарегистрировать.
|
||||||
:type user: RegisterUser
|
:type user: AuthUser
|
||||||
:returns: Данные о созданном пользователе.
|
:returns: Данные о созданном пользователе.
|
||||||
:rtype: UserReturnData
|
:rtype: UserReturnData
|
||||||
"""
|
"""
|
||||||
@ -70,3 +72,34 @@ class UserService:
|
|||||||
raise HTTPException(status_code=400, detail="Неверный или просроченный токен")
|
raise HTTPException(status_code=400, detail="Неверный или просроченный токен")
|
||||||
|
|
||||||
await self.manager.confirm_user(email=email)
|
await self.manager.confirm_user(email=email)
|
||||||
|
|
||||||
|
async def login_user(self, user: AuthUser) -> JSONResponse:
|
||||||
|
"""
|
||||||
|
Вход пользователя в систему.
|
||||||
|
|
||||||
|
:param user: Объект пользователя с входными данными для аутентификации.
|
||||||
|
:type user: AuthUser
|
||||||
|
:returns: Ответ сервера, указывающий на успешность или неудачу входа.
|
||||||
|
:rtype: JSONResponse
|
||||||
|
:raises HTTPException: Если предоставленные учетные данные неверны (HTTP 401 Unauthorized).
|
||||||
|
"""
|
||||||
|
exist_user = await self.manager.get_user_by_email(email=user.email)
|
||||||
|
|
||||||
|
if exist_user is None or not self.handler.verify_password(
|
||||||
|
hashed_password=exist_user.hashed_password, raw_password=user.password
|
||||||
|
):
|
||||||
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Wrong email or password")
|
||||||
|
|
||||||
|
token, session_id = await self.handler.create_access_token(user_id=exist_user.id)
|
||||||
|
|
||||||
|
await self.manager.store_access_token(token=token, user_id=exist_user.id, session_id=session_id)
|
||||||
|
|
||||||
|
response = JSONResponse(content={"message": "Вход успешен"})
|
||||||
|
response.set_cookie(
|
||||||
|
key="Authorization",
|
||||||
|
value=token,
|
||||||
|
httponly=True,
|
||||||
|
max_age=settings.access_token_expire,
|
||||||
|
)
|
||||||
|
|
||||||
|
return response
|
||||||
|
55
lkeep/core/core_dependency/redis_dependency.py
Normal file
55
lkeep/core/core_dependency/redis_dependency.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
"""
|
||||||
|
Проект: Lkeep
|
||||||
|
Автор: Иван Ашихмин
|
||||||
|
Год: 2025
|
||||||
|
Специально для проекта "Код на салфетке"
|
||||||
|
https://pressanybutton.ru/category/servis-na-fastapi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from collections.abc import AsyncGenerator
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
|
from redis.asyncio import ConnectionPool, Redis
|
||||||
|
|
||||||
|
from lkeep.core.settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
class RedisDependency:
|
||||||
|
"""
|
||||||
|
Класс, предоставляющий инструменты для работы с Redis через асинхронный клиент.
|
||||||
|
|
||||||
|
:ivar _url: URL подключения к Redis серверу.
|
||||||
|
:type _url: str
|
||||||
|
:ivar _pool: Пул соединений для управления соединениями с Redis.
|
||||||
|
:type _pool: ConnectionPool
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""
|
||||||
|
Инициализирует экземпляр класса для работы с Redis.
|
||||||
|
"""
|
||||||
|
self._url = settings.redis_settings.redis_url
|
||||||
|
self._pool: ConnectionPool = self._init_pool()
|
||||||
|
|
||||||
|
def _init_pool(self) -> ConnectionPool:
|
||||||
|
"""
|
||||||
|
Инициализирует пул соединений Redis.
|
||||||
|
|
||||||
|
:returns: Пул соединений для работы с Redis.
|
||||||
|
:rtype: ConnectionPool
|
||||||
|
"""
|
||||||
|
return ConnectionPool.from_url(url=self._url, encoding="utf-8", decode_responses=True)
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def get_client(self) -> AsyncGenerator[Redis, None]:
|
||||||
|
"""
|
||||||
|
Получает клиентскую сессию Redis для взаимодействия с базой данных.
|
||||||
|
|
||||||
|
:returns: Асинхронный генератор клиента Redis.
|
||||||
|
:rtype: AsyncGenerator[Redis, None]
|
||||||
|
"""
|
||||||
|
redis_client = Redis(connection_pool=self._pool)
|
||||||
|
try:
|
||||||
|
yield redis_client
|
||||||
|
finally:
|
||||||
|
await redis_client.aclose()
|
@ -117,6 +117,8 @@ class Settings(BaseSettings):
|
|||||||
:type templates_dir: str
|
:type templates_dir: str
|
||||||
:ivar frontend_url: Адрес фронтенд-приложения.
|
:ivar frontend_url: Адрес фронтенд-приложения.
|
||||||
:type frontend_url: str
|
:type frontend_url: str
|
||||||
|
:ivar access_token_expire: Срок жизни JWT-токена
|
||||||
|
:type access_token_expire: int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
db_settings: DBSettings = DBSettings()
|
db_settings: DBSettings = DBSettings()
|
||||||
@ -125,6 +127,7 @@ class Settings(BaseSettings):
|
|||||||
secret_key: SecretStr
|
secret_key: SecretStr
|
||||||
templates_dir: str = "templates"
|
templates_dir: str = "templates"
|
||||||
frontend_url: str
|
frontend_url: str
|
||||||
|
access_token_expire: int
|
||||||
|
|
||||||
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf8", extra="ignore")
|
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf8", extra="ignore")
|
||||||
|
|
||||||
|
874
poetry.lock
generated
874
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -22,6 +22,7 @@ dependencies = [
|
|||||||
"celery (>=5.4.0,<6.0.0)",
|
"celery (>=5.4.0,<6.0.0)",
|
||||||
"redis (>=5.2.1,<6.0.0)",
|
"redis (>=5.2.1,<6.0.0)",
|
||||||
"itsdangerous (>=2.2.0,<3.0.0)",
|
"itsdangerous (>=2.2.0,<3.0.0)",
|
||||||
|
"pyjwt (>=2.10.1,<3.0.0)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user