| |
| |
| import asyncio |
| import random |
| |
| from faker import Faker |
| from loguru import logger |
| |
| from sqlalchemy import text, select, insert, update, and_, or_, between |
| |
| from app.database.db_mysql import async_db_session |
| |
| from app.models import User |
| from app.models import Email |
| |
| from app.schemas.user import CreateUser |
| from app.schemas.email import CreateEmail |
| |
| |
| class InitData: |
| """ 初始化数据 """ |
| |
| def __init__(self): |
| self.fake = Faker('zh_CN') |
| |
| async def fake_user(self): |
| """ 自动创建锁定普通用户 """ |
| username = self.fake.user_name() |
| |
| user_obj = CreateUser( |
| name=username, |
| ) |
| |
| |
| |
| logger.debug(user_obj) |
| logger.debug(user_obj.dict()) |
| |
| async with async_db_session.begin() as db: |
| await db.execute( |
| |
| |
| |
| |
| |
| insert(User).values(**user_obj.dict()) |
| ) |
| await db.commit() |
| |
| async def fake_email(self): |
| """ 自动创建普通用户 """ |
| email_obj = CreateEmail( |
| pwd=self.fake.password(), |
| account=self.fake.email(), |
| user_id=random.randint(1, 4) |
| ) |
| |
| logger.debug(email_obj) |
| |
| async with async_db_session.begin() as db: |
| await db.execute( |
| insert(Email).values(**email_obj.dict()) |
| ) |
| await db.commit() |
| |
| async def get_username(self): |
| async with async_db_session.begin() as db: |
| stmt = "select username from sys_user" |
| sql_res = await db.execute( |
| text(stmt) |
| ) |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async def get_username_and_email(self): |
| async with async_db_session.begin() as db: |
| stmt = "select username,email from sys_user" |
| sql_res = await db.execute( |
| text(stmt) |
| ) |
| |
| |
| |
| |
| |
| |
| |
| |
| res = sql_res.first() |
| logger.debug(res._mapping) |
| |
| async def select_username(self): |
| stmt = "select * from user" |
| async with async_db_session.begin() as db: |
| sql_res = await db.execute( |
| text(stmt) |
| ) |
| |
| res = sql_res.first() |
| logger.debug(res._mapping) |
| |
| async def select_username2(self): |
| stmt = text("select * from tuser where name=:name") |
| async with async_db_session.begin() as db: |
| sql_res = await db.execute( |
| stmt, {'name': 'yuxia'} |
| ) |
| |
| res = sql_res.first() |
| logger.debug(res._mapping) |
| |
| async def select_username3(self): |
| async with async_db_session.begin() as db: |
| sql_res = await db.execute( |
| |
| |
| select(User.name, User.id).where(and_(User.name == 'dcai', User.id > 1)) |
| ) |
| res = sql_res.first() |
| if not res: |
| return None |
| logger.debug(res._mapping) |
| |
| async def select_username4(self): |
| async with async_db_session.begin() as db: |
| sql_res = await db.execute( |
| |
| select(User.name, User.id).where(or_(User.name == 'dca', User.id == 5)) |
| ) |
| res = sql_res.first() |
| if not res: |
| return None |
| logger.debug(res._mapping) |
| |
| async def select_username5(self): |
| async with async_db_session.begin() as db: |
| sql_res = await db.execute( |
| |
| select(User.name, User.id).where(User.id.between(2, 3)) |
| ) |
| |
| res = sql_res.mappings().all() |
| if not res: |
| return None |
| logger.debug(res) |
| |
| |
| async def select_username6(self): |
| async with async_db_session.begin() as db: |
| sql_res = await db.execute( |
| |
| select(User.name, User.id).where(User.id.between(2, 3)) |
| ) |
| res = sql_res.mappings().all() |
| if not res: |
| return None |
| logger.debug(res) |
| |
| async def select_username7(self): |
| async with async_db_session.begin() as db: |
| sql_res = await db.execute( |
| |
| select(User.name, User.id).where(User.name.in_(['yuxia', 'dcai'])) |
| ) |
| res = sql_res.mappings().all() |
| if not res: |
| return None |
| logger.debug(res) |
| |
| |
| async def insert_email(self): |
| async with async_db_session.begin() as db: |
| await db.execute( |
| |
| insert(Email).values( |
| account='song@qq.com', |
| pwd='123456', |
| user_id=1 |
| ) |
| ) |
| await db.commit() |
| |
| async def insert_email2(self): |
| async with async_db_session.begin() as db: |
| |
| await db.execute( |
| insert(Email).values([ |
| {"account": "song1@qq.com", "pwd": "1234568", "user_id": 2}, |
| {"account": "song2@qq.com", "pwd": "1234569", "user_id": 2}, |
| ]) |
| ) |
| await db.commit() |
| |
| async def init_data(self): |
| """ 自动创建数据 """ |
| |
| |
| |
| |
| |
| |
| await self.insert_email2() |
| |
| |
| if __name__ == '__main__': |
| init = InitData() |
| loop = asyncio.get_event_loop() |
| loop.run_until_complete(init.init_data()) |
| |
| |
| |
| import uuid |
| from datetime import datetime |
| from typing import Optional |
| |
| from sqlalchemy import func |
| from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, declared_attr, MappedAsDataclass |
| from typing_extensions import Annotated |
| |
| |
| |
| |
| id_key = Annotated[int, mapped_column(primary_key=True, index=True, autoincrement=True, comment='主键id')] |
| |
| |
| class _BaseMixin: |
| """ |
| Mixin 数据类 |
| |
| Mixin: 一种面向对象编程概念, 使结构变得更加清晰, `Wiki <https://en.wikipedia.org/wiki/Mixin/>`__ |
| """ |
| |
| |
| created_time: Mapped[datetime] = mapped_column(init=False, default=func.now(), comment='创建时间') |
| updated_time: Mapped[Optional[datetime]] = mapped_column(init=False, onupdate=func.now(), comment='更新时间') |
| |
| |
| class MappedBase(DeclarativeBase): |
| """ |
| 声明性基类, 原始 DeclarativeBase 类, 作为所有基类或数据模型类的父类而存在 |
| |
| `DeclarativeBase <https://docs.sqlalchemy.org/en/20/orm/declarative_config.html>`__ |
| `mapped_column() <https://docs.sqlalchemy.org/en/20/orm/mapping_api.html#sqlalchemy.orm.mapped_column>`__ |
| """ |
| |
| @declared_attr.directive |
| def __tablename__(cls) -> str: |
| return cls.__name__.lower() |
| |
| |
| class DataClassBase(MappedAsDataclass, MappedBase): |
| """ |
| 声明性数据类基类, 它将带有数据类集成, 允许使用更高级配置, 但你必须注意它的一些特性, 尤其是和 DeclarativeBase 一起使用时 |
| |
| `MappedAsDataclass <https://docs.sqlalchemy.org/en/20/orm/dataclasses.html#orm-declarative-native-dataclasses>`__ |
| """ |
| __abstract__ = True |
| |
| |
| class Base(_BaseMixin, MappedAsDataclass, MappedBase): |
| """ |
| 声明性 Mixin 数据类基类, 带有数据类集成, 并包含 MiXin 数据类基础表结构, 你可以简单的理解它为含有基础表结构的数据类基类 |
| """ |
| |
| __abstract__ = True |
| |
| |
| def use_uuid() -> str: |
| """ |
| 使用uuid |
| |
| :return: |
| """ |
| return uuid.uuid4().hex |
| |
| |
| |
| import sys |
| |
| from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker |
| |
| from ..common.log import log |
| from ..core.conf import settings |
| from ..core.path_conf import SqlitePath |
| from ..database.base_class import MappedBase |
| |
| """ |
| 说明:SqlAlchemy |
| """ |
| |
| SQLALCHEMY_DATABASE_URL = f'sqlite+aiosqlite:///{SqlitePath}/test.db' |
| |
| try: |
| |
| async_engine = create_async_engine(SQLALCHEMY_DATABASE_URL, echo=settings.DB_ECHO, future=True) |
| |
| except Exception as e: |
| log.error('❌ 数据库链接失败 {}', e) |
| sys.exit() |
| else: |
| async_db_session = async_sessionmaker(bind=async_engine, autoflush=False, expire_on_commit=False) |
| |
| |
| async def get_db() -> AsyncSession: |
| """ |
| session 生成器 |
| |
| :return: |
| """ |
| session = async_db_session() |
| try: |
| yield session |
| except Exception as se: |
| await session.rollback() |
| raise se |
| finally: |
| await session.close() |
| |
| |
| async def create_table(): |
| """ |
| 创建数据库表 |
| """ |
| async with async_engine.begin() as coon: |
| await coon.run_sync(MappedBase.metadata.create_all) |
| |
| |
| |
| import os |
| from pathlib import Path |
| |
| |
| |
| BasePath = Path(__file__).resolve().parent.parent |
| |
| |
| Versions = os.path.join(BasePath, 'app', 'alembic', 'versions') |
| |
| |
| LogPath = os.path.join(BasePath.parent, 'log') |
| |
| |
| SqlitePath = os.path.join(BasePath.parent, 'sqlite_db') |
| |
| |
| |
| from functools import lru_cache |
| |
| from pydantic import BaseSettings |
| |
| |
| class Settings(BaseSettings): |
| """ 配置类 """ |
| |
| TITLE: str = 'FastAPI' |
| VERSION: str = 'v0.0.1' |
| DESCRIPTION: str = """fastapi_sqlalchemy_mysql""" |
| DOCS_URL: str = '/v1/docs' |
| REDOCS_URL: str = None |
| OPENAPI_URL: str = '/v1/openapi' |
| |
| |
| UVICORN_HOST: str = '127.0.0.1' |
| UVICORN_PORT: int = 8000 |
| UVICORN_RELOAD: bool = True |
| |
| |
| |
| STATIC_FILES: bool = True |
| |
| |
| DB_ECHO: bool = False |
| DB_HOST: str = '127.0.0.1' |
| DB_PORT: int = 3306 |
| DB_USER: str = 'root' |
| DB_PASSWORD: str = '123456' |
| DB_DATABASE: str = 'fsm' |
| DB_CHARSET: str = 'utf8mb4' |
| |
| |
| REDIS_OPEN: bool = False |
| REDIS_HOST: str = '127.0.0.1' |
| REDIS_PORT: int = 6379 |
| REDIS_PASSWORD: str = '' |
| REDIS_DATABASE: int = 0 |
| REDIS_TIMEOUT: int = 5 |
| |
| |
| TOKEN_ALGORITHM: str = 'HS256' |
| TOKEN_SECRET_KEY: str = '1VkVF75nsNABBjK_7-qz7GtzNy3AMvktc9TCPwKczCk' |
| TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 1 |
| |
| |
| EMAIL_DESCRIPTION: str = 'fastapi_sqlalchemy_mysql' |
| EMAIL_SERVER: str = 'smtp.qq.com' |
| EMAIL_PORT: int = 465 |
| EMAIL_USER: str = '729519678@qq.com' |
| EMAIL_PASSWORD: str = 'gmrvkkppberzbega' |
| EMAIL_SSL: bool = True |
| |
| |
| EMAIL_LOGIN_CODE_MAX_AGE: int = 60 * 2 |
| |
| |
| COOKIES_MAX_AGE: int = 60 * 5 |
| |
| |
| MIDDLEWARE_CORS: bool = True |
| MIDDLEWARE_GZIP: bool = True |
| MIDDLEWARE_ACCESS: bool = False |
| |
| |
| @lru_cache |
| def get_settings(): |
| """ 读取配置优化写法 """ |
| return Settings() |
| |
| |
| settings = get_settings() |
| |
| |
| |
| from datetime import datetime |
| from typing import Optional, List |
| |
| from sqlalchemy import func, String, ForeignKey |
| from sqlalchemy.dialects.mysql import LONGTEXT |
| from sqlalchemy.orm import Mapped, mapped_column, relationship |
| |
| from app.database.base_class import use_uuid, id_key, DataClassBase |
| |
| |
| class User(DataClassBase): |
| """ User Table """ |
| __tablename__ = 'tuser' |
| |
| id: Mapped[id_key] = mapped_column(init=False) |
| name: Mapped[str] = mapped_column(String(20), unique=True, index=True, comment='用户名') |
| |
| emails: Mapped[List['Email']] = relationship('Email', back_populates='user', cascade='all,delete-orphan') |
| |
| |
| |
| """ |
| # 导入所有模型,并将 Base 放在最前面, 以便 Base 拥有它们 |
| # imported by Alembic |
| """ |
| from app.database.base_class import MappedBase |
| |
| from app.models.user import User |
| from app.models.email import Email |
| |
| |
| |
| from datetime import datetime |
| from typing import List, Optional |
| |
| from sqlalchemy import ForeignKey, func, String |
| from sqlalchemy.dialects.mysql import LONGTEXT |
| from sqlalchemy.orm import Mapped, mapped_column, relationship |
| |
| from app.database.base_class import id_key, DataClassBase |
| |
| |
| class Email(DataClassBase): |
| """ Email Table """ |
| __tablename__ = 'temail' |
| |
| id: Mapped[id_key] = mapped_column(init=False) |
| |
| account: Mapped[str] = mapped_column(String(20), unique=True, index=True, comment='email account') |
| pwd: Mapped[str] = mapped_column(String(20), unique=True, index=True, comment='email password') |
| |
| user_id: Mapped[int] = mapped_column(ForeignKey('tuser.id'), nullable=True, comment='the email owner id') |
| |
| |
| user: Mapped['User'] = relationship('User', back_populates='emails') |
| |
| |
| |
| import datetime |
| from typing import Optional |
| |
| from pydantic import BaseModel, EmailStr |
| |
| |
| class CreateUser(BaseModel): |
| name: str |
| |
| |
| class Auth(BaseModel): |
| username: str |
| password: str |
| |
| |
| class ELCode(BaseModel): |
| email: EmailStr |
| |
| |
| class Auth2(ELCode): |
| code: str |
| |
| |
| class UpdateUser(BaseModel): |
| username: str |
| email: str |
| mobile_number: Optional[str] = None |
| wechat: Optional[str] = None |
| qq: Optional[str] = None |
| blog_address: Optional[str] = None |
| introduction: Optional[str] = None |
| |
| |
| class GetUserInfo(UpdateUser): |
| id: int |
| uid: str |
| avatar: Optional[str] = None |
| time_joined: datetime.datetime = None |
| last_login: Optional[datetime.datetime] = None |
| is_superuser: bool |
| is_active: bool |
| |
| class Config: |
| orm_mode = True |
| |
| |
| class DeleteUser(BaseModel): |
| id: int |
| |
| |
| class ResetPassword(BaseModel): |
| code: str |
| password1: str |
| password2: str |
| |
| |
| |
| import datetime |
| from typing import Optional |
| |
| from pydantic import BaseModel, EmailStr |
| |
| |
| class CreateEmail(BaseModel): |
| account: str |
| pwd: str |
| user_id: int |
| |
| |
| |
| import os |
| |
| from loguru import logger |
| from app.core import path_conf |
| |
| |
| class Logger: |
| |
| @staticmethod |
| def log() -> logger: |
| if not os.path.exists(path_conf.LogPath): |
| os.mkdir(path_conf.LogPath) |
| |
| |
| log_file = os.path.join(path_conf.LogPath, "FastBlog.log") |
| |
| |
| |
| logger.add( |
| log_file, |
| encoding='utf-8', |
| level="DEBUG", |
| rotation='00:00', |
| retention="7 days", |
| enqueue=True, |
| backtrace=True, |
| diagnose=True, |
| ) |
| |
| return logger |
| |
| |
| log = Logger().log() |
| |
| |
| |
| from typing import Optional, NoReturn |
| |
| from sqlalchemy import func, select, update, delete, desc |
| from sqlalchemy.ext.asyncio import AsyncSession |
| from sqlalchemy.sql import Select |
| |
| from app.models import User |
| from app.schemas.user import CreateUser, DeleteUser, UpdateUser |
| |
| |
| async def get_user_by_id(db: AsyncSession, user_id: int) -> Optional[User]: |
| user = await db.execute(select(User).where(User.id == user_id)) |
| return user.scalars().first() |
| |
| |
| async def get_user_by_username(db: AsyncSession, username: str) -> Optional[User]: |
| user = await db.execute(select(User).where(User.name == username)) |
| return user.scalars().first() |
| |
| |
| async def update_user_login_time(db: AsyncSession, username: str) -> int: |
| user = await db.execute( |
| update(User) |
| .where(User.name == username) |
| .values(last_login=func.now()) |
| ) |
| return user.rowcount |
| |
| |
| async def get_email_by_username(db: AsyncSession, username: str) -> str: |
| user = await get_user_by_username(db, username) |
| return user.email |
| |
| |
| async def get_username_by_email(db: AsyncSession, email: str) -> str: |
| user = await db.execute(select(User).where(User.email == email)) |
| return user.scalars().first().name |
| |
| |
| async def get_avatar_by_username(db: AsyncSession, username: str) -> str: |
| user = await db.execute(select(User).where(User.name == username)) |
| return user.scalars().first().avatar |
| |
| |
| async def create_user(db: AsyncSession, create: CreateUser) -> NoReturn: |
| create.password = jwt.get_hash_password(create.password) |
| new_user = User(**create.dict()) |
| db.add(new_user) |
| |
| |
| async def update_userinfo(db: AsyncSession, current_user: User, obj: UpdateUser) -> int: |
| user = await db.execute( |
| update(User) |
| .where(User.id == current_user.id) |
| .values(**obj.dict()) |
| ) |
| return user.rowcount |
| |
| |
| async def update_avatar(db: AsyncSession, current_user: User, avatar: str) -> int: |
| user = await db.execute( |
| update(User) |
| .where(User.id == current_user.id) |
| .values(avatar=avatar) |
| ) |
| return user.rowcount |
| |
| |
| async def delete_user(db: AsyncSession, user_id: DeleteUser) -> int: |
| user = await db.execute(delete(User).where(User.id == user_id)) |
| return user.rowcount |
| |
| |
| async def check_email(db: AsyncSession, email: str) -> User: |
| mail = await db.execute(select(User).where(User.email == email)) |
| return mail.scalars().first() |
| |
| |
| async def delete_avatar(db: AsyncSession, user_id: int) -> int: |
| user = await db.execute( |
| update(User) |
| .where(User.id == user_id) |
| .values(avatar=None) |
| ) |
| return user.rowcount |
| |
| |
| async def reset_password(db: AsyncSession, username: str, password: str) -> int: |
| user = await db.execute( |
| update(User) |
| .where(User.name == username) |
| .values(password=jwt.get_hash_password(password)) |
| ) |
| return user.rowcount |
| |
| |
| def get_users() -> Select: |
| return select(User).order_by(desc(User.time_joined)) |
| |
| |
| async def get_user_is_super(db: AsyncSession, user_id: int) -> bool: |
| user = await get_user_by_id(db, user_id) |
| return user.is_superuser |
| |
| |
| async def get_user_is_active(db: AsyncSession, user_id: int) -> bool: |
| user = await get_user_by_id(db, user_id) |
| return user.is_active |
| |
| |
| async def super_set(db: AsyncSession, user_id: int) -> int: |
| super_status = await get_user_is_super(db, user_id) |
| user = await db.execute( |
| update(User) |
| .where(User.id == user_id) |
| .values(is_superuser=False if super_status else True) |
| ) |
| return user.rowcount |
| |
| |
| async def active_set(db: AsyncSession, user_id: int) -> int: |
| active_status = await get_user_is_active(db, user_id) |
| user = await db.execute( |
| update(User) |
| .where(User.id == user_id) |
| .values(is_active=False if active_status else True) |
| ) |
| return user.rowcount |
| |
| from app.models import MappedBase |
| from logging.config import fileConfig |
| |
| from sqlalchemy import engine_from_config |
| from sqlalchemy import pool |
| |
| from alembic import context |
| |
| |
| |
| config = context.config |
| |
| |
| |
| fileConfig(config.config_file_name) |
| |
| |
| |
| |
| |
| target_metadata = MappedBase.metadata |
| |
| |
| |
| |
| |
| |
| |
| |
| def run_migrations_offline(): |
| """Run migrations in 'offline' mode. |
| |
| This configures the context with just a URL |
| and not an Engine, though an Engine is acceptable |
| here as well. By skipping the Engine creation |
| we don't even need a DBAPI to be available. |
| |
| Calls to context.execute() here emit the given string to the |
| script output. |
| |
| """ |
| url = config.get_main_option("sqlalchemy.url") |
| context.configure( |
| url=url, |
| target_metadata=target_metadata, |
| literal_binds=True, |
| dialect_opts={"paramstyle": "named"}, |
| ) |
| |
| with context.begin_transaction(): |
| context.run_migrations() |
| |
| |
| def run_migrations_online(): |
| """Run migrations in 'online' mode. |
| |
| In this scenario we need to create an Engine |
| and associate a connection with the context. |
| |
| """ |
| connectable = engine_from_config( |
| config.get_section(config.config_ini_section), |
| prefix="sqlalchemy.", |
| poolclass=pool.NullPool, |
| ) |
| |
| with connectable.connect() as connection: |
| context.configure( |
| connection=connection, target_metadata=target_metadata |
| ) |
| |
| with context.begin_transaction(): |
| context.run_migrations() |
| |
| |
| if context.is_offline_mode(): |
| run_migrations_offline() |
| else: |
| run_migrations_online() |
| |
/Users/song/codelearn/sqlalchemy_learn/alembic/versions/3ec04987db9e_create_user_and_email_table.py
| """create user and email table |
| |
| Revision ID: 3ec04987db9e |
| Revises: |
| Create Date: 2023-03-25 18:11:20.807740 |
| |
| """ |
| from alembic import op |
| import sqlalchemy as sa |
| |
| |
| |
| revision = '3ec04987db9e' |
| down_revision = None |
| branch_labels = None |
| depends_on = None |
| |
| |
| def upgrade(): |
| |
| op.create_table('user', |
| sa.Column('id', sa.Integer(), nullable=False), |
| sa.Column('name', sa.String(length=20), nullable=False, comment='用户名'), |
| sa.PrimaryKeyConstraint('id') |
| ) |
| op.create_index(op.f('ix_user_id'), 'user', ['id'], unique=False) |
| op.create_index(op.f('ix_user_name'), 'user', ['name'], unique=True) |
| op.create_table('email', |
| sa.Column('id', sa.Integer(), nullable=False), |
| sa.Column('account', sa.String(length=20), nullable=False, comment='email account'), |
| sa.Column('pwd', sa.String(length=20), nullable=False, comment='email password'), |
| sa.Column('user_id', sa.Integer(), nullable=True, comment='the email owner id'), |
| sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), |
| sa.PrimaryKeyConstraint('id') |
| ) |
| op.create_index(op.f('ix_email_account'), 'email', ['account'], unique=True) |
| op.create_index(op.f('ix_email_id'), 'email', ['id'], unique=False) |
| op.create_index(op.f('ix_email_pwd'), 'email', ['pwd'], unique=True) |
| |
| |
| |
| def downgrade(): |
| |
| op.drop_index(op.f('ix_email_pwd'), table_name='email') |
| op.drop_index(op.f('ix_email_id'), table_name='email') |
| op.drop_index(op.f('ix_email_account'), table_name='email') |
| op.drop_table('email') |
| op.drop_index(op.f('ix_user_name'), table_name='user') |
| op.drop_index(op.f('ix_user_id'), table_name='user') |
| op.drop_table('user') |
| |
| |
| """rename user table 2 tUser table,email 2 tEmail table |
| |
| Revision ID: 896025c8be4b |
| Revises: 3ec04987db9e |
| Create Date: 2023-03-25 19:51:26.049487 |
| |
| """ |
| from alembic import op |
| import sqlalchemy as sa |
| |
| |
| |
| revision = '896025c8be4b' |
| down_revision = '3ec04987db9e' |
| branch_labels = None |
| depends_on = None |
| |
| |
| def upgrade(): |
| |
| op.create_table('tuser', |
| sa.Column('id', sa.Integer(), nullable=False), |
| sa.Column('name', sa.String(length=20), nullable=False, comment='用户名'), |
| sa.PrimaryKeyConstraint('id') |
| ) |
| op.create_index(op.f('ix_tuser_id'), 'tuser', ['id'], unique=False) |
| op.create_index(op.f('ix_tuser_name'), 'tuser', ['name'], unique=True) |
| op.create_table('temail', |
| sa.Column('id', sa.Integer(), nullable=False), |
| sa.Column('account', sa.String(length=20), nullable=False, comment='email account'), |
| sa.Column('pwd', sa.String(length=20), nullable=False, comment='email password'), |
| sa.Column('user_id', sa.Integer(), nullable=True, comment='the email owner id'), |
| sa.ForeignKeyConstraint(['user_id'], ['tuser.id'], ), |
| sa.PrimaryKeyConstraint('id') |
| ) |
| op.create_index(op.f('ix_temail_account'), 'temail', ['account'], unique=True) |
| op.create_index(op.f('ix_temail_id'), 'temail', ['id'], unique=False) |
| op.create_index(op.f('ix_temail_pwd'), 'temail', ['pwd'], unique=True) |
| op.drop_index('ix_email_account', table_name='email') |
| op.drop_index('ix_email_id', table_name='email') |
| op.drop_index('ix_email_pwd', table_name='email') |
| op.drop_table('email') |
| op.drop_index('ix_user_id', table_name='user') |
| op.drop_index('ix_user_name', table_name='user') |
| op.drop_table('user') |
| |
| |
| |
| def downgrade(): |
| |
| op.create_table('user', |
| sa.Column('id', sa.INTEGER(), nullable=False), |
| sa.Column('name', sa.VARCHAR(length=20), nullable=False), |
| sa.PrimaryKeyConstraint('id') |
| ) |
| op.create_index('ix_user_name', 'user', ['name'], unique=False) |
| op.create_index('ix_user_id', 'user', ['id'], unique=False) |
| op.create_table('email', |
| sa.Column('id', sa.INTEGER(), nullable=False), |
| sa.Column('account', sa.VARCHAR(length=20), nullable=False), |
| sa.Column('pwd', sa.VARCHAR(length=20), nullable=False), |
| sa.Column('user_id', sa.INTEGER(), nullable=True), |
| sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), |
| sa.PrimaryKeyConstraint('id') |
| ) |
| op.create_index('ix_email_pwd', 'email', ['pwd'], unique=False) |
| op.create_index('ix_email_id', 'email', ['id'], unique=False) |
| op.create_index('ix_email_account', 'email', ['account'], unique=False) |
| op.drop_index(op.f('ix_temail_pwd'), table_name='temail') |
| op.drop_index(op.f('ix_temail_id'), table_name='temail') |
| op.drop_index(op.f('ix_temail_account'), table_name='temail') |
| op.drop_table('temail') |
| op.drop_index(op.f('ix_tuser_name'), table_name='tuser') |
| op.drop_index(op.f('ix_tuser_id'), table_name='tuser') |
| op.drop_table('tuser') |
| |
| |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战