FastAPI使用tortoiseORM

Tortoise-ORM 海龟ORM -- pd的FastAPI笔记

文章目录

ORM框架

ORM(Object-Relational Mapping,对象关系映射)是一种编程技术,用于在面向对象编程语言和关系型数据库之间建立映射。它允许开发者通过操作对象的方式来与数据库进行交互,而无需直接编写复杂的 SQL 语句。主要特点包括:

  • 对象与数据库表的映射:ORM 将数据库中的表映射为编程语言中的类,每一行数据对应一个对象,表的列对应对象的属性
  • 简化数据库操作:开发者可以使用面向对象的方法(如创建、查询、更新、删除对象)来操作数据库,而无需手动编写 SQL
  • 跨数据库兼容:ORM 通常支持多种数据库(如 MySQL、PostgreSQL、SQLite),通过统一的接口减少数据库切换的成本

工作原理

  • ORM 框架定义了一个映射层,将类和数据库表关联起来
  • 开发者通过 ORM 的 API 操作对象,ORM 自动将操作翻译成 SQL 语句,执行数据库操作并返回结果

缺点

  • 性能可能略低于原生 SQL(因抽象层开销)
  • 对于非常复杂的查询,可能需要直接编写 SQL

ORM工具介绍

  • SQLAlchemy(同步/异步):≈80% 企业项目首选,功能完备、社区成熟,支持复杂查询和事务管理。
  • Tortoise(异步):语法类似 Django ORM,适合异步优先项目,集成简便
  • GINO(异步):轻量级,基于 SQLAlchemy Core 的异步扩展,适合高性能 API

选型建议

  • 传统企业项目 → SQLAlchemy(成熟稳定)
  • 全异步微服务 → Tortoise-ORM 或 GINO

注意:FastAPI没有官方ORM

Tortoise-ORM

为什么选择 Tortoise-ORM

  • 异步支持,与 FastAPI 无缝集成
  • 简单易用的模型定义,类似 Django ORM
  • 支持复杂关系(一对一、一对多、多对多)
  • 自动生成表结构,适合快速开发

环境配置

python 复制代码
pip install tortoise-orm==0.25
pip install aerich==0.9.0
pip install aiomysql==0.2.0
pip install tomlkit==0.13.2
  • tortoise-orm:用于异步Python应用的关系对象映射(ORM)框架(基于Django ORM设计,但完全异步)
  • aerich: 数据库迁移工具(类似Django的migrations)
  • aiomysql:异步MySQL驱动,为asyncio提供异步MySQL客户端,是Tortoise-ORM连接MySQL的底层驱动
  • tomlkit:配置文件管理,读写和维护TOML格式文件,Aerich使用pyproject.toml存储配置,管理迁移版本信息
python 复制代码
from tortoise.contrib.fastapi import register_tortoise
from typing import Dict
from fastapi import FastAPI


TORTOISE_ORM: Dict = {
    "connections": {
        # 开发环境使用 SQLite(基于文件,无需服务器)
        # "default": "sqlite://db.sqlite3",
        # 生产环境示例:PostgreSQL
        # "default": "postgres://user:password@localhost:5432/dbname",
        # 生产环境示例:MySQL
        "default": "mysql://root:xxxxxx@localhost:3306/blog_project",
    },
    "apps": {
        "models": {
            "models": [  # "app.models",
                "aerich.models"],  # 模型模块和 Aerich 迁移模型
            "default_connection": "default",
        }
    },
    # 连接池配置(推荐)
    "use_tz": False,  # 是否使用时区
    "timezone": "UTC",  # 默认时区
    "db_pool": {
        "max_size": 10,  # 最大连接数
        "min_size": 1,
        # 最小连接数
        "idle_timeout": 30  # 空闲连接超时(秒)
    }
}

app = FastAPI(debug=True)

# 配置 Tortoise ORM
register_tortoise(app,
                  config=TORTOISE_ORM,
                  generate_schemas=True,  # 开发环境自动生成表结构
                  add_exception_handlers=True  # 添加异常处理
                  )

if __name__ == "__main__":
    import uvicorn
    uvicorn.run("16ORMsetting:app", host="127.0.0.1",
                port=8003, reload=True)

Aerich使用

在项目根目录运行以下命令:

  • 初始化
python 复制代码
# main表示项目的入口文件,TORTOISE_ORM为Tortoise-ORM的配置字典
aerich init -t main.TORTOISE_ORM
# aerich init -t 16ORMsetting.TORTOISE_ORM

这将生成:

  • pyproject.toml:Aerich 配置文件,指定迁移配置。

  • migrations/:迁移文件目录,存放生成的 .sql 文件。

  • 生成和创建迁移脚本

python 复制代码
aerich init-db

后续操作:生成和迁移文件

  1. 生成迁移文件: 当模型发生变更时,运行以下命令生成迁移文件:
python 复制代码
aerich migrate --name "info"
  1. 应用迁移: 运行以下命令将迁移应用到数据库:

    aerich upgrade

  • 验证迁移: 检查迁移历史
python 复制代码
aerich history
  • 回滚迁移:回退到指定版本
python 复制代码
aerich downgrade -n 20230705_000000

创建模型

python 复制代码
# models.py 因为我数据库中的那个配置文件写在了16ORM配置文件中
# 对应修改配置文件中的"app.models" --> "models"
from tortoise.models import Model
from tortoise import fields
from tortoise.contrib.postgres.fields import ArrayField
from datetime import datetime
from typing import Optional


class User(Model):
    """用户模型"""
    
    # 基本信息
    id = fields.IntField(pk=True, description="主键ID")
    username = fields.CharField(
        max_length=50, 
        unique=True, 
        null=False, 
        description="用户名"
    )
    email = fields.CharField(
        max_length=100, 
        unique=True, 
        null=True, 
        description="邮箱"
    )
    phone = fields.CharField(
        max_length=20, 
        unique=True, 
        null=True, 
        description="手机号"
    )
    
    # 密码和安全
    password_hash = fields.CharField(
        max_length=255, 
        null=False, 
        description="密码哈希值"
    )
    salt = fields.CharField(
        max_length=50, 
        null=False, 
        description="密码盐值"
    )
    is_active = fields.BooleanField(
        default=True, 
        description="是否激活"
    )
    is_superuser = fields.BooleanField(
        default=False, 
        description="是否超级用户"
    )
    is_verified = fields.BooleanField(
        default=False, 
        description="是否已验证邮箱"
    )
    
    # 个人信息
    nickname = fields.CharField(
        max_length=50, 
        null=True, 
        default="", 
        description="昵称"
    )
    avatar = fields.CharField(
        max_length=500, 
        null=True, 
        description="头像URL"
    )
    bio = fields.TextField(
        null=True, 
        description="个人简介"
    )
    
    # 时间信息
    date_joined = fields.DatetimeField(
        auto_now_add=True, 
        description="注册时间"
    )
    last_login = fields.DatetimeField(
        null=True, 
        description="最后登录时间"
    )
    updated_at = fields.DatetimeField(
        auto_now=True, 
        description="更新时间"
    )
    
    # 状态信息
    login_count = fields.IntField(
        default=0, 
        description="登录次数"
    )
    failed_login_attempts = fields.IntField(
        default=0, 
        description="连续登录失败次数"
    )
    locked_until = fields.DatetimeField(
        null=True, 
        description="锁定直到(用于账户锁定)"
    )
    
    # 权限相关
    permissions = fields.JSONField(
        null=True, 
        default=list, 
        description="权限列表"
    )
    
    # 元数据
    meta_data = fields.JSONField(
        null=True, 
        default=dict, 
        description="元数据(扩展字段)"
    )
    
    class Meta:
        table = "users"
        table_description = "用户表"
        # 复合索引
        indexes = [
            ("username", "email"),  # 查询优化
        ]
        # 排序规则
        ordering = ["-date_joined"]
    
    def __str__(self):
        return f"User(id={self.id}, username={self.username})"
    
    def to_dict(self) -> dict:
        """转换为字典(排除敏感信息)"""
        return {
            "id": self.id,
            "username": self.username,
            "email": self.email,
            "phone": self.phone,
            "nickname": self.nickname,
            "avatar": self.avatar,
            "bio": self.bio,
            "is_active": self.is_active,
            "is_verified": self.is_verified,
            "date_joined": self.date_joined.isoformat() if self.date_joined else None,
            "last_login": self.last_login.isoformat() if self.last_login else None,
            "updated_at": self.updated_at.isoformat() if self.updated_at else None,
        }
    
    @classmethod
    async def get_by_username(cls, username: str) -> Optional["User"]:
        """通过用户名获取用户"""
        return await cls.get_or_none(username=username)
    
    @classmethod
    async def get_by_email(cls, email: str) -> Optional["User"]:
        """通过邮箱获取用户"""
        return await cls.get_or_none(email=email)
    
    @classmethod
    async def exists_by_username(cls, username: str) -> bool:
        """检查用户名是否存在"""
        return await cls.filter(username=username).exists()
    
    @classmethod
    async def exists_by_email(cls, email: str) -> bool:
        """检查邮箱是否存在"""
        return await cls.filter(email=email).exists()

执行完以下操作后,就可以在数据库中看到user和aerich(存储迁移历史)表了

python 复制代码
aerich init -t 16ORMsetting.TORTOISE_ORM
aerich init-db

# 更新模型
aerich migrate -t 16ORMsetting.TORTOISE_ORM
aerich upgrade

CRUD操作

python 复制代码
# utils_user.py
from models import User
from tortoise.exceptions import IntegrityError
from typing import Optional


async def create_user(
    username: str,
    email: str,
    phone: Optional[str] = None,
    password_hash: str = "",
    salt: str = "",
    nickname: str = "",
    avatar: Optional[str] = None,
    bio: Optional[str] = None,
    is_active: bool = True,
    is_superuser: bool = False,
    is_verified: bool = False,
    permissions: Optional[list] = None,
    meta_data: Optional[dict] = None,
) -> Optional[User]:
    """
    创建新用户

    参数:
        username (str): 用户名(唯一)
        email (str): 邮箱(唯一)
        phone (Optional[str]): 手机号(唯一,可选)
        password_hash (str): 密码哈希值
        salt (str): 密码盐值
        nickname (str): 昵称
        avatar (Optional[str]): 头像URL
        bio (Optional[str]): 个人简介
        is_active (bool): 是否激活
        is_superuser (bool): 是否超级用户
        is_verified (bool): 是否已验证邮箱
        permissions (Optional[list]): 权限列表
        meta_data (Optional[dict]): 元数据(扩展字段)

    返回:
        Optional[User]: 成功返回用户对象,失败返回 None
    """
    try:
        # 构造用户数据
        user_data = {
            "username": username,
            "email": email,
            "phone": phone,
            "password_hash": password_hash,
            "salt": salt,
            "nickname": nickname,
            "avatar": avatar,
            "bio": bio,
            "is_active": is_active,
            "is_superuser": is_superuser,
            "is_verified": is_verified,
            "permissions": permissions or [],
            "meta_data": meta_data or {},
        }

        # 创建用户
        user = await User.create(**user_data)
        return user

    except IntegrityError as e:
        # 捕获唯一约束冲突(如重复的 username 或 email)
        print(f"创建用户失败:{e}")
        return None

    except Exception as e:
        # 捕获其他异常
        print(f"未知错误:{e}")
        return None

async def delete_user(user_id: int) -> bool:
    """
    根据用户 ID 删除用户
    
    参数:
        user_id (int): 用户主键 ID

    返回:
        bool: 成功返回 True,失败返回 False
    """
    try:
        # 查找并删除用户
        user = await User.get(id=user_id)
        await user.delete()
        return True

    except DoesNotExist:
        # 用户不存在
        print(f"用户 ID {user_id} 不存在")
        return False

    except Exception as e:
        # 捕获其他异常
        print(f"删除用户失败:{e}")
        return False

async def update_user(
    user_id: int,
    username: Optional[str] = None,
    email: Optional[str] = None,
    phone: Optional[str] = None,
    password_hash: Optional[str] = None,
    salt: Optional[str] = None,
    nickname: Optional[str] = None,
    avatar: Optional[str] = None,
    bio: Optional[str] = None,
    is_active: Optional[bool] = None,
    is_superuser: Optional[bool] = None,
    is_verified: Optional[bool] = None,
    permissions: Optional[list] = None,
    meta_data: Optional[dict] = None,
) -> Optional[User]:
    """
    根据用户 ID 更新用户信息
    
    参数:
        user_id (int): 用户主键 ID
        其他字段为可选参数,仅传递需要更新的字段

    返回:
        Optional[User]: 成功返回更新后的用户对象,失败返回 None
    """
    try:
        # 查找用户
        user = await User.get(id=user_id)

        # 动态更新字段(仅更新非 None 的字段)
        update_fields = []
        field_mapping = {
            "username": username,
            "email": email,
            "phone": phone,
            "password_hash": password_hash,
            "salt": salt,
            "nickname": nickname,
            "avatar": avatar,
            "bio": bio,
            "is_active": is_active,
            "is_superuser": is_superuser,
            "is_verified": is_verified,
            "permissions": permissions,
            "meta_data": meta_data,
        }

        for field_name, value in field_mapping.items():
            if value is not None:
                setattr(user, field_name, value)
                update_fields.append(field_name)

        # 保存更改
        if update_fields:
            await user.save(update_fields=update_fields)
        return user

    except DoesNotExist:
        # 用户不存在
        print(f"用户 ID {user_id} 不存在")
        return None

    except Exception as e:
        # 捕获其他异常
        print(f"更新用户失败:{e}")
        return None

通过接口调用:

python 复制代码
from utils_user import create_user, update_user, delete_user
@app.post("/create_user")
async def create_user(request):
    return create_user(**request.json)

@app.post("/update_user")
async def update_user(request):
    return update_user(**request.json)

@app.post("/delete_user/{id}")
async def delete_user(id: int):
    return delete_user(id)

查询操作

python 复制代码
python
from models import User
from tortoise.exceptions import DoesNotExist
from typing import Optional

async def get_user_by_id(user_id: int) -> Optional[User]:
    """
    根据用户 ID 查询单条用户数据
    
    参数:
        user_id (int): 用户主键 ID

    返回:
        Optional[User]: 成功返回用户对象,失败返回 None
    """
    try:
        user = await User.get(id=user_id)
        return user
    except DoesNotExist:
        print(f"用户 ID {user_id} 不存在")
        return None
    except Exception as e:
        print(f"查询用户失败:{e}")
        return None

async def search_users(keyword: str) -> List[User]:
    """
    根据关键字模糊搜索用户(用户名、邮箱、手机号)
    
    参数:
        keyword (str): 搜索关键字

    返回:
        List[User]: 匹配的用户对象列表
    """
    try:
        users = await User.filter(
            Q(username__icontains=keyword)
            | Q(email__icontains=keyword)
            | Q(phone__icontains=keyword)
        )
        return users

    except Exception as e:
        print(f"搜索用户失败:{e}")
        return []

async def list_users(
    limit: Optional[int] = None,
    offset: Optional[int] = None,
    is_active: Optional[bool] = None,
    is_superuser: Optional[bool] = None,
    is_verified: Optional[bool] = None,
) -> List[User]:
    """
    查询多条用户数据(支持分页和条件过滤)
    
    参数:
        limit (Optional[int]): 限制返回数量
        offset (Optional[int]): 偏移量(用于分页)
        is_active (Optional[bool]): 是否激活
        is_superuser (Optional[bool]): 是否超级用户
        is_verified (Optional[bool]): 是否已验证邮箱

    返回:
        List[User]: 用户对象列表
    """
    try:
        # 要是不需要过滤条件,就返回所有用户
        query = User.all()

        # 添加过滤条件
        if is_active is not None:
            query = query.filter(is_active=is_active)
        if is_superuser is not None:
            query = query.filter(is_superuser=is_superuser)
        if is_verified is not None:
            query = query.filter(is_verified=is_verified)

        # 分页处理
        if limit is not None:
            query = query.limit(limit)
        if offset is not None:
            query = query.offset(offset)

        users = await query
        return users

    except Exception as e:
        print(f"查询用户列表失败:{e}")
        return []

ORM关联关系

模拟一个学校系统,包含以下实体:

  • 学生(Student):每个学生有唯一的个人信息档案(1对1)。
  • 成绩(Grade):一个学生可以有多份成绩记录(1对多)。
  • 课程(Course):学生和课程之间是多对多关系(通过成绩表关联)。
python 复制代码
# models_school.py
from tortoise.models import Model
from tortoise import fields
from typing import Optional, List
from datetime import datetime


class Student(Model):
    """学生模型"""

    id = fields.IntField(pk=True, description="主键ID")
    name = fields.CharField(max_length=50, null=False, description="姓名")
    age = fields.IntField(null=False, description="年龄")
    gender = fields.CharField(max_length=10, null=False, description="性别")
    email = fields.CharField(max_length=100, unique=True, null=True, description="邮箱")
    phone = fields.CharField(max_length=20, unique=True, null=True, description="手机号")
    created_at = fields.DatetimeField(auto_now_add=True, description="创建时间")
    updated_at = fields.DatetimeField(auto_now=True, description="更新时间")

    # 一对一关系:学生 <-> 个人信息档案
    profile = fields.OneToOneField("models.StudentProfile", related_name="student", description="个人信息档案")

    class Meta:
        table = "students"
        table_description = "学生表"
        ordering = ["-created_at"]

    def __str__(self):
        return f"Student(id={self.id}, name={self.name})"

    def to_dict(self) -> dict:
        """转换为字典"""
        return {
            "id": self.id,
            "name": self.name,
            "age": self.age,
            "gender": self.gender,
            "email": self.email,
            "phone": self.phone,
            "created_at": self.created_at.isoformat() if self.created_at else None,
            "updated_at": self.updated_at.isoformat() if self.updated_at else None,
        }


class StudentProfile(Model):
    """学生个人信息档案模型(与 Student 一对一)"""

    id = fields.IntField(pk=True, description="主键ID")
    address = fields.CharField(max_length=200, null=True, description="地址")
    emergency_contact = fields.CharField(max_length=50, null=True, description="紧急联系人")
    emergency_phone = fields.CharField(max_length=20, null=True, description="紧急联系电话")
    created_at = fields.DatetimeField(auto_now_add=True, description="创建时间")
    updated_at = fields.DatetimeField(auto_now=True, description="更新时间")

    class Meta:
        table = "student_profiles"
        table_description = "学生个人信息档案表"

    def __str__(self):
        return f"StudentProfile(id={self.id})"

    def to_dict(self) -> dict:
        """转换为字典"""
        return {
            "id": self.id,
            "address": self.address,
            "emergency_contact": self.emergency_contact,
            "emergency_phone": self.emergency_phone,
            "created_at": self.created_at.isoformat() if self.created_at else None,
            "updated_at": self.updated_at.isoformat() if self.updated_at else None,
        }


class Course(Model):
    """课程模型"""

    id = fields.IntField(pk=True, description="主键ID")
    name = fields.CharField(max_length=100, null=False, description="课程名称")
    description = fields.TextField(null=True, description="课程描述")
    credits = fields.IntField(default=0, description="学分")
    created_at = fields.DatetimeField(auto_now_add=True, description="创建时间")
    updated_at = fields.DatetimeField(auto_now=True, description="更新时间")

    # 多对多关系:学生 <-> 课程(通过 Grade 表关联)
    students = fields.ManyToManyField(
        "models.Student",
        through="models.Grade",
        related_name="courses",
        description="选课学生"
    )

    class Meta:
        table = "courses"
        table_description = "课程表"
        ordering = ["name"]

    def __str__(self):
        return f"Course(id={self.id}, name={self.name})"

    def to_dict(self) -> dict:
        """转换为字典"""
        return {
            "id": self.id,
            "name": self.name,
            "description": self.description,
            "credits": self.credits,
            "created_at": self.created_at.isoformat() if self.created_at else None,
            "updated_at": self.updated_at.isoformat() if self.updated_at else None,
        }


class Grade(Model):
    """成绩模型(学生 <-> 课程的中间表)"""

    id = fields.IntField(pk=True, description="主键ID")
    score = fields.FloatField(null=False, description="分数")
    grade_letter = fields.CharField(max_length=2, null=True, description="等级(如 A, B, C)")
    exam_date = fields.DateField(null=False, description="考试日期")
    created_at = fields.DatetimeField(auto_now_add=True, description="创建时间")
    updated_at = fields.DatetimeField(auto_now=True, description="更新时间")

    # 外键:关联学生和课程
    student = fields.ForeignKeyField(
        "models.Student",
        related_name="grades",
        on_delete=fields.CASCADE,
        description="学生"
    )
    course = fields.ForeignKeyField(
        "models.Course",
        related_name="grades",
        on_delete=fields.CASCADE,
        description="课程"
    )

    class Meta:
        table = "grades"
        table_description = "成绩表"
        unique_together = ("student", "course", "exam_date")  # 防止重复记录
        ordering = ["-exam_date"]

    def __str__(self):
        return f"Grade(id={self.id}, score={self.score})"

    def to_dict(self) -> dict:
        """转换为字典"""
        return {
            "id": self.id,
            "score": self.score,
            "grade_letter": self.grade_letter,
            "exam_date": self.exam_date.isoformat() if self.exam_date else None,
            "student_id": self.student_id,
            "course_id": self.course_id,
            "created_at": self.created_at.isoformat() if self.created_at else None,
            "updated_at": self.updated_at.isoformat() if self.updated_at else None,
        }

关联关系的增删改查

创建实体:

python 复制代码
from models_school import Student, StudentProfile
from datetime import date

async def create_student_with_profile(
    name: str,
    age: int,
    gender: str,
    email: Optional[str] = None,
    phone: Optional[str] = None,
    address: Optional[str] = None,
    emergency_contact: Optional[str] = None,
    emergency_phone: Optional[str] = None,
) -> Student:
    """
    创建学生及其个人信息档案(1对1关系)
    
    参数:
        name (str): 姓名
        age (int): 年龄
        gender (str): 性别
        email (Optional[str]): 邮箱
        phone (Optional[str]): 手机号
        address (Optional[str]): 地址
        emergency_contact (Optional[str]): 紧急联系人
        emergency_phone (Optional[str]): 紧急联系电话

    返回:
        Student: 创建的学生对象
    """
    try:
        # 创建个人信息档案
        profile = await StudentProfile.create(
            address=address,
            emergency_contact=emergency_contact,
            emergency_phone=emergency_phone,
        )

        # 创建学生并关联档案
        student = await Student.create(
            name=name,
            age=age,
            gender=gender,
            email=email,
            phone=phone,
            profile=profile,
        )
        return student

    except Exception as e:
        print(f"创建学生失败:{e}")
        raise

async def create_course(
    name: str,
    description: Optional[str] = None,
    credits: int = 0,
) -> Course:
    """
    创建课程
    
    参数:
        name (str): 课程名称
        description (Optional[str]): 课程描述
        credits (int): 学分

    返回:
        Course: 创建的课程对象
    """
    try:
        course = await Course.create(
            name=name,
            description=description,
            credits=credits,
        )
        return course

    except Exception as e:
        print(f"创建课程失败:{e}")
        raise

创建关联关系:

python 复制代码
from models import Student, Course
from typing import List

async def enroll_student_in_course(student_id: int, course_id: int) -> bool:
    """
    学生选课(建立学生与课程的多对多关系)
    
    参数:
        student_id (int): 学生 ID
        course_id (int): 课程 ID

    返回:
        bool: 成功返回 True,失败返回 False
    """
    try:
        student = await Student.get(id=student_id)
        course = await Course.get(id=course_id)

        # 建立关联关系
        await student.courses.add(course)
        return True

    except Exception as e:
        print(f"学生选课失败:{e}")
        return False

async def get_student_courses(student_id: int) -> List[Course]:
    """
    查询学生选修的所有课程
    
    参数:
        student_id (int): 学生 ID

    返回:
        List[Course]: 课程列表
    """
    try:
        student = await Student.get(id=student_id).prefetch_related("courses")
        return list(student.courses)

    except Exception as e:
        print(f"查询学生课程失败:{e}")
        return []

async def get_course_students(course_id: int) -> List[Student]:
    """
    查询选修某门课程的所有学生
    
    参数:
        course_id (int): 课程 ID

    返回:
        List[Student]: 学生列表
    """
    try:
        course = await Course.get(id=course_id).prefetch_related("students")
        return list(course.students)

    except Exception as e:
        print(f"查询课程学生失败:{e}")
        return []

async def create_grade(
    student_id: int,
    course_id: int,
    score: float,
    exam_date: date,
    grade_letter: Optional[str] = None,
) -> Grade:
    """
    创建成绩并关联学生和课程(多对多关系)
    
    参数:
        student_id (int): 学生 ID
        course_id (int): 课程 ID
        score (float): 分数
        exam_date (date): 考试日期
        grade_letter (Optional[str]): 等级(如 A, B, C)

    返回:
        Grade: 创建的成绩对象
    """
    try:
        grade = await Grade.create(
            student_id=student_id,
            course_id=course_id,
            score=score,
            exam_date=exam_date,
            grade_letter=grade_letter,
        )
        return grade

    except Exception as e:
        print(f"创建成绩失败:{e}")
        raise
相关推荐
剩下了什么10 小时前
MySQL JSON_SET() 函数
数据库·mysql·json
山峰哥11 小时前
数据库工程与SQL调优——从索引策略到查询优化的深度实践
数据库·sql·性能优化·编辑器
较劲男子汉11 小时前
CANN Runtime零拷贝传输技术源码实战 彻底打通Host与Device的数据传输壁垒
运维·服务器·数据库·cann
java搬砖工-苤-初心不变11 小时前
MySQL 主从复制配置完全指南:从原理到实践
数据库·mysql
山岚的运维笔记13 小时前
SQL Server笔记 -- 第18章:Views
数据库·笔记·sql·microsoft·sqlserver
roman_日积跬步-终至千里14 小时前
【LangGraph4j】LangGraph4j 核心概念与图编排原理
java·服务器·数据库
汇智信科14 小时前
打破信息孤岛,重构企业效率:汇智信科企业信息系统一体化运营平台
数据库·重构
野犬寒鸦14 小时前
从零起步学习并发编程 || 第六章:ReentrantLock与synchronized 的辨析及运用
java·服务器·数据库·后端·学习·算法
晚霞的不甘16 小时前
揭秘 CANN 内存管理:如何让大模型在小设备上“轻装上阵”?
前端·数据库·经验分享·flutter·3d