feat(root):add mysql

This commit is contained in:
shahzeydyfah
2026-02-17 22:37:43 +03:30
parent 7e25cd6674
commit abb1c01753
3 changed files with 173 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
"""
اتصال MySQL فقط-خواندنی (read-only).
از پول کانکشن استفاده می‌کند و در هر کانکشن SESSION را به حالت فقط-خواندنی تنظیم می‌کند.
"""
from contextlib import asynccontextmanager
from typing import Optional
import aiomysql
from core.utils.mysql import get_mysql_config
_pool: Optional[aiomysql.Pool] = None
_pool_connection_name: Optional[str] = None
async def get_mysql_pool(connection_name: str = None) -> aiomysql.Pool:
"""پول کانکشن MySQL را برمی‌گرداند؛ در اولین فراخوانی ساخته می‌شود."""
global _pool, _pool_connection_name
name = connection_name or "mysql"
if _pool is None or _pool_connection_name != name:
if _pool is not None:
_pool.close()
await _pool.wait_closed()
cfg = get_mysql_config(name)
_pool = await aiomysql.create_pool(
host=cfg["host"],
port=cfg["port"],
user=cfg["user"],
password=cfg["password"],
db=cfg["db"],
charset="utf8mb4",
autocommit=True,
minsize=1,
maxsize=10,
)
_pool_connection_name = name
return _pool
@asynccontextmanager
async def get_mysql_connection(connection_name: str = None):
"""
کانکشن فقط-خواندنی از پول می‌گیرد.
در ابتدا SESSION را به حالت فقط-خواندنی تنظیم می‌کند (برای اطمینان بیشتر).
استفاده:
async with get_mysql_connection() as conn:
async with conn.cursor(aiomysql.DictCursor) as cur:
await cur.execute("SELECT * FROM t LIMIT 10")
rows = await cur.fetchall()
"""
pool = await get_mysql_pool(connection_name)
conn = await pool.acquire()
try:
async with conn.cursor() as cur:
await cur.execute("SET SESSION TRANSACTION READ ONLY")
yield conn
finally:
pool.release(conn)
async def close_mysql_pool():
"""بستن پول (مثلاً در shutdown اپ)."""
global _pool
if _pool is not None:
_pool.close()
await _pool.wait_closed()
_pool = None
+81
View File
@@ -0,0 +1,81 @@
"""
SQLAlchemy async setup برای MySQL (فقط خواندنی).
از asyncmy به عنوان async driver استفاده می‌کند.
"""
from contextlib import asynccontextmanager
from typing import Optional
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import declarative_base, sessionmaker
from core.utils.mysql import get_mysql_config
# Base class برای تمام مدل‌های ORM
Base = declarative_base()
_engine = None
_async_session_maker = None
def get_mysql_url(connection_name: str = None) -> str:
"""ساخت URL برای SQLAlchemy از تنظیمات MySQL"""
cfg = get_mysql_config(connection_name)
return f"mysql+asyncmy://{cfg['user']}:{cfg['password']}@{cfg['host']}:{cfg['port']}/{cfg['db']}?charset=utf8mb4"
def get_async_engine(connection_name: str = None):
"""برگرداندن async engine (در اولین فراخوانی ساخته می‌شود)"""
global _engine
if _engine is None:
url = get_mysql_url(connection_name)
_engine = create_async_engine(
url,
echo=False, # برای debug می‌تونی True کنی
pool_pre_ping=True, # چک کردن کانکشن قبل از استفاده
pool_size=5,
max_overflow=10,
)
return _engine
def get_async_session_maker(connection_name: str = None):
"""برگرداندن async sessionmaker"""
global _async_session_maker
if _async_session_maker is None:
engine = get_async_engine(connection_name)
_async_session_maker = sessionmaker(
engine,
class_=AsyncSession,
expire_on_commit=False,
autocommit=False,
autoflush=False,
)
return _async_session_maker
@asynccontextmanager
async def get_mysql_session(connection_name: str = None):
"""
Context manager برای گرفتن async session (فقط خواندنی).
استفاده:
async with get_mysql_session() as session:
result = await session.execute(select(User).where(User.id == 1))
user = result.scalar_one_or_none()
"""
session_maker = get_async_session_maker(connection_name)
async with session_maker() as session:
# تنظیم session به حالت فقط-خواندنی
await session.execute("SET SESSION TRANSACTION READ ONLY")
try:
yield session
finally:
await session.rollback() # برای read-only همیشه rollback می‌کنیم
async def close_mysql_engine():
"""بستن engine (مثلاً در shutdown اپ)."""
global _engine, _async_session_maker
if _engine is not None:
await _engine.dispose()
_engine = None
_async_session_maker = None
+25
View File
@@ -0,0 +1,25 @@
"""
تنظیمات اتصال MySQL (فقط خواندنی).
از بخش جداگانه mysql در config.yml (هم‌سطح با database) استفاده می‌کند.
"""
from config import conf
def get_mysql_config(connection_name: str = None):
"""
بر اساس محیط (dev/production) تنظیمات MySQL را برمی‌گرداند.
Returns: dict با کلیدهای host, port, user, password, db
"""
if not conf.mysql:
raise ValueError("تنظیمات mysql در config.yml یافت نشد.")
env = "production" if conf.server.is_prod else "dev"
mode = getattr(conf.mysql, env, None) or getattr(conf.mysql, "dev", None)
if not mode:
raise ValueError(f"تنظیمات mysql.{env} در config.yml یافت نشد.")
return {
"host": getattr(mode, "host", "127.0.0.1"),
"port": int(getattr(mode, "port", 3306)),
"user": getattr(mode, "user", ""),
"password": getattr(mode, "password", ""),
"db": getattr(mode, "db", ""),
}