feat(root):add mysql
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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", ""),
|
||||
}
|
||||
Reference in New Issue
Block a user