import asyncio from datetime import datetime,timezone #from sqlalchemy import create_engine #Не async from sqlalchemy.orm import DeclarativeBase, sessionmaker, relationship,selectinload from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy import Column, Integer, String, Boolean, select,func, DateTime, ForeignKey, event from pathlib import Path db_folder = Path(__file__).parent / "DB" db_folder.mkdir(parents=True, exist_ok=True) db_path = db_folder / "example.db" async_engine = create_async_engine(f"sqlite+aiosqlite:///{db_path}", echo=True) #sqlite+aiosqlite — тип БД + async-драйвер ///example.db — путь к файлу (три слэша, если путь относительный; четыре, если абсолютный #async_engine = create_async_engine( "postgresql+asyncpg://user:pass@host:5432/mydb", echo=True) #Можно указать Pgpool-II для psql или proxysql для mysql mariadb from passlib.context import CryptContext #Hash password pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") def hash_password(password: str) -> str: return pwd_context.hash(password) def verify_password(plain_password: str, hashed_password: str) -> bool: return pwd_context.verify(plain_password, hashed_password) class Base(DeclarativeBase): pass AsyncSessionLocal = sessionmaker(async_engine,class_=AsyncSession, expire_on_commit=False) class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) email = Column(String(254), unique=True, nullable=False) description = Column(String, nullable=False) activated = Column(Boolean, default=False) password = Column(String, nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) last_login = Column(DateTime(timezone=True)) # связь с Permission permissions = relationship( "Permission", back_populates="user", cascade="all, delete-orphan" #back populates = backref, только пишется в обоих классах с которыми связь )#cascade - если ты удаляешь юзера, SQLAlchemy автоматом удалит все его пермишены.Если ты отцепил пермишен от юзера — он считается “сиротой” и тоже удаляется. class Permission(Base): __tablename__ = "permissions" id = Column(Integer, primary_key=True) user_id = Column(Integer, ForeignKey("users.id"), nullable=False) can_edit = Column(Boolean, default=True) can_delete = Column(Boolean, default=True) can_view = Column(Boolean, default=True) is_admin = Column(Boolean, default=False) # обратная связь к User user = relationship("User", back_populates="permissions") async def create_user(user_info): async with AsyncSessionLocal() as session: new_user = User(email=user_info.email, description=user_info.description, activated=user_info.activated, password=hash_password(user_info.password)) session.add(new_user) await session.commit() await session.refresh(new_user) async def get_user_by_email(email): async with AsyncSessionLocal() as session: result = await session.execute(select(User).options(selectinload(User.permissions)).where(User.email==email)) user = result.scalar_one_or_none() return user async def get_all_rows(): async with AsyncSessionLocal() as session: result = await session.execute(select(User)) users = result.scalars().all() return users async def update_user(user_info, perm_info): async with AsyncSessionLocal() as session: result = await session.execute(select(User).options(selectinload(User.permissions)).where(User.email==user_info.email)) user = result.scalar_one_or_none() if user: user.email = user_info.email user.description = user_info.description user.activated = user_info.activated user.password = hash_password(user_info.password) perm = user.permissions[0] # если у юзера одна запись - Это связь один-ко-многим: у одного User может быть список из нескольких Permission. perm.can_edit = perm_info.can_edit perm.can_delete = perm_info.can_delete await session.commit() return user async def delete_user(email): async with AsyncSessionLocal() as session: result = await session.execute(select(User).where(User.email==email)) user = result.scalar_one_or_none() if user: await session.delete(user) await session.commit() async def login_user(user_info): async with AsyncSessionLocal() as session: result = await session.execute(select(User).where(User.email == user_info.email)) user = result.scalar_one_or_none() if user and verify_password(user_info.password, user.password): user.last_login=datetime.now(timezone.utc) await session.commit() return user return None async def reset_user(user_info): async with AsyncSessionLocal() as session: result = await session.execute(select(User).where(User.email == user_info.email)) user = result.scalar_one_or_none() if user: user.password = hash_password(user_info.new_password) await session.commit() return user return None @event.listens_for(User, "after_insert") #listener не работает в async def create_permission(mapper, connection, target): connection.execute( Permission.__table__.insert().values(user_id=target.id) )