1. mapped_column 语法解析
mapped_column 是 SQLAlchemy 2.0 的写法,用于定义数据库列。
基本语法结构
字段名: Mapped[类型] = mapped_column(列类型, 参数1=值1, 参数2=值2, ...)
- 字段名:Python 属性名
- Mapped[类型]:类型提示,表示该字段的类型
- mapped_column(...):定义数据库列
1.1那如何导入呢 ?
from sqlalchemy.orm import mapped_column
# mapped_column 是 sqlalchemy.orm 模块中的一个函数
1.2具体位置在哪里 ?
sqlalchemy/
└── orm/
├── mapped_column ← 这里!
├── Mapped
├── DeclarativeBase
├── MappedAsDataclass
└── ... 其他 ORM 相关类
相关导入对比
在 Account 类中,同时导入了:
from sqlalchemy.orm import Mapped, Session, mapped_column
- Mapped:类型提示类,用于类型注解
- Session:数据库会话类
- mapped_column:函数,用于定义数据库列
mapped_column 常用参数说明
| 参数 | 说明 | 示例 |
| 第一个位置参数 | 列类型(必需) | String(255), DateTime, StringUUID |
| nullable | 是否允许 NULL,数据库的值 | nullable=True |
| default | Python 默认值 | default=None, default="active" |
| server_default | 数据库默认值 | server_default=func.current_timestamp() |
| init | 是否在 __init__ 中生成参数,默认值是init = True【如果不写】。 # 创建对象时,不需要传入这些字段 |
init=False |
| onupdate | onupdate 表示:当记录被更新时,数据库自动将该字段设置为指定值。 |
onupdate=func.current_timestamp() |
| primary_key | 是否为主键 | primary_key=True |
详细解释 Account 类的定义,重点说明 mapped_column 的用法:
Account 类定义详解
2. 类声明部分
- 这里引用的DIFY Account表的数据库,列举详细的参数定义
from sqlalchemy.orm import DeclarativeBase,MappedAsDataclass from models.engine import metadata class TypeBase(DeclarativeBase,MappedAsDataclass): metadata = metadata from sqlalchemy.orm import Mapped, mapped_column class Account(UserMixin, TypeBase): __tablename__ = "accounts" __table_args__ = (sa.PrimaryKeyConstraint("id", name="account_pkey"), sa.Index("account_email_idx", "email")) id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"), init=False) name: Mapped[str] = mapped_column(String(255)) email: Mapped[str] = mapped_column(String(255)) password: Mapped[str | None] = mapped_column(String(255), default=None) password_salt: Mapped[str | None] = mapped_column(String(255), default=None) avatar: Mapped[str | None] = mapped_column(String(255), nullable=True, default=None) interface_language: Mapped[str | None] = mapped_column(String(255), default=None) interface_theme: Mapped[str | None] = mapped_column(String(255), nullable=True, default=None) timezone: Mapped[str | None] = mapped_column(String(255), default=None) last_login_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) last_login_ip: Mapped[str | None] = mapped_column(String(255), nullable=True, default=None) last_active_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.current_timestamp(), nullable=False, init=False ) status: Mapped[str] = mapped_column( String(16), server_default=sa.text("'active'::character varying"), default="active" ) initialized_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) created_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.current_timestamp(), nullable=False, init=False ) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.current_timestamp(), nullable=False, init=False, onupdate=func.current_timestamp() ) role: TenantAccountRole | None = field(default=None, init=False) _current_tenant: "Tenant | None" = field(default=None, init=False) UserMixin:Flask-Login 的混入,提供用户认证相关方法 TypeBase:SQLAlchemy 基类(支持 dataclass 特性) __tablename__:数据库表名 __table_args__:表级约束(主键、索引等)
3. 字段详解(逐行)
字段 1:id(主键)
id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"), init=False)
- Mapped[str]:Python 类型为字符串
- StringUUID:自定义类型(UUID 字符串)
- server_default=sa.text("uuid_generate_v4()"):数据库默认值(PostgreSQL 函数)
- init=False:dataclass 特性,不在 __init__ 中生成该参数
含义:主键,数据库自动生成 UUID,创建对象时不需要传入。
字段 2:name(用户名)
name: Mapped[str] = mapped_column(String(255))
- Mapped[str]:字符串类型
- String(255):VARCHAR(255)
- 无其他参数:必填、可空为 False、无默认值
含义:用户名,最大 255 字符,必填。
字段 3:email(邮箱)
email: Mapped[str] = mapped_column(String(255))
与 name 相同,必填字符串字段。
字段 4:password(密码)
password: Mapped[str | None] = mapped_column(String(255), default=None)
- Mapped[str | None]:可为字符串或 None
- default=None:Python 默认值为 None
- 未设置 nullable=True:数据库层面仍为 NOT NULL(需显式设置 nullable=True)
含义:密码,可为空,Python 默认 None。
字段 5:avatar(头像)
avatar: Mapped[str | None] = mapped_column(String(255), nullable=True, default=None)
- nullable=True:数据库允许 NULL
- default=None:Python 默认 None
含义:头像 URL,数据库和 Python 都允许为空。
字段 6:last_active_at(最后活跃时间)
last_active_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.current_timestamp(), nullable=False, init=False )
- DateTime:日期时间类型
- server_default=func.current_timestamp():数据库默认当前时间
- nullable=False:不允许 NULL
- init=False:不在 __init__ 中生成参数
含义:最后活跃时间,数据库自动设置为当前时间,创建时不需要传入。
字段 7:status(状态)
status: Mapped[str] = mapped_column( String(16), server_default=sa.text("'active'::character varying"), default="active" )
- String(16):最大 16 字符
- server_default=sa.text("'active'::character varying"):数据库默认 'active'
- default="active":Python 默认 'active'
含义:状态,数据库和 Python 都有默认值 'active'。
字段 8:updated_at(更新时间)
updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.current_timestamp(), nullable=False, init=False, onupdate=func.current_timestamp() )
- onupdate=func.current_timestamp():更新时自动设置为当前时间
含义:更新时间,创建和更新时自动设置。
4. 特殊字段(非数据库字段)
role: TenantAccountRole | None = field(default=None, init=False)
_current_tenant: "Tenant | None" = field(default=None, init=False)
- field():dataclass 的字段定义
- init=False:不在 __init__ 中生成参数
- 这些字段不存储在数据库中,用于运行时状态
5.为什么created_at、last_active_at 和 last_login_at 没有 onupdate?
5.1.为什么 created_at 没有 onupdate?
created_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.current_timestamp(), nullable=False, init=False )
# 注意:没有 onupdate 参数
原因:创建时间应该永远不变。如果加了 onupdate,每次更新都会改变创建时间,这是不合理的。设计原则:
- created_at:记录创建时间,永远不变
- updated_at:记录最后更新时间,每次更新都改变
5.2.last_login_at 的定义
last_login_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None)
- 没有 onupdate:不会在每次更新时自动设置
- 没有 server_default:数据库不会自动生成
- 可空:允许为 None
为什么这样设计?
只在用户登录时设置,不是每次更新都更新
需要业务代码显式设置:account.last_login_at = datetime.now()
5.3. last_active_at 的定义
last_active_at: Mapped[datetime] = mapped_column(
DateTime, server_default=func.current_timestamp(), nullable=False, init=False
)
- 有 server_default:创建时自动设置
- 没有 onupdate:不会在每次更新时自动更新
为什么这样设计?
- 只在特定操作时更新(如用户活跃时),不是每次数据库更新都更新
- 需要业务代码显式设置:account.last_active_at = datetime.now()
总结
- mapped_column 定义数据库列
- Mapped[类型] 提供类型提示
- default 是 Python 默认值,server_default 是数据库默认值
- init=False 表示该字段不在构造函数中
- nullable=True 允许数据库中的 NULL
onupdate 在更新时自动设置值
Account 表字段 onupdate 对比表
| 字段名 | 类型 | onupdate | server_default | nullable | init | 原因说明 |
| id | StringUUID | ❌ 无 | ✅ uuid_generate_v4() | False | False | 主键,创建后不变 |
| name | String(255) | ❌ 无 | ❌ 无 | False | True | 业务字段,手动更新 |
| String(255) | ❌ 无 | ❌ 无 | False | True | 业务字段,手动更新 | |
| password | String(255) | ❌ 无 | ❌ 无 | False | True | 业务字段,手动更新 |
| password_salt | String(255) | ❌ 无 | ❌ 无 | False | True | 业务字段,手动更新 |
| avatar | String(255) | ❌ 无 | ❌ 无 | True | True | 业务字段,手动更新 |
| interface_language | String(255) | ❌ 无 | ❌ 无 | False | True | 业务字段,手动更新 |
| interface_theme | String(255) | ❌ 无 | ❌ 无 | True | True | 业务字段,手动更新 |
| timezone | String(255) | ❌ 无 | ❌ 无 | False | True | 业务字段,手动更新 |
| last_login_at | DateTime | ❌ 无 | ❌ 无 | True | True | 仅在登录时更新 |
| last_login_ip | String(255) | ❌ 无 | ❌ 无 | True | True | 仅在登录时更新 |
| last_active_at | DateTime | ❌ 无 | ✅ current_timestamp() | False | False | 仅在活跃时更新 |
| status | String(16) | ❌ 无 | ✅ 'active' | False | True | 业务字段,手动更新 |
| initialized_at | DateTime | ❌ 无 | ❌ 无 | True | True | 仅在初始化时更新 |
| created_at | DateTime | ❌ 无 | ✅ current_timestamp() | False | False | 创建时间,不应改变 |
| updated_at | DateTime | ✅ current_timestamp() | ✅ current_timestamp() | False | False | 每次更新时自动更新 |
Question:name 字段没有定义其他属性值,那它的默认值分别有哪些 ?
name: Mapped[str] = mapped_column(String(255)
如果只写这些,各参数的默认值如下:
| 参数 | 实际值 | 说明 |
| 列类型 | String(255) | ✅ 已指定 |
| nullable | False | 因为 Mapped[str](非可选类型) |
| default | None | 未指定,默认 None |
| server_default | None | 未指定,默认 None |
| init | True | 未指定,默认 True |
| onupdate | None | 未指定,默认 None |
| primary_key | False | 未指定,默认 False |
# 定义 name: Mapped[str] = mapped_column(String(255)) # 等价于(显式写法) name: Mapped[str] = mapped_column( String(255), nullable=False, # 默认值 default=None, # 默认值 server_default=None, # 默认值 init=True, # 默认值 onupdate=None, # 默认值 primary_key=False # 默认值 )
使用 field 的字段(不存储在数据库)
role: TenantAccountRole | None = field(default=None, init=False) _current_tenant: "Tenant | None" = field(default=None, init=False)
这些字段会:
- 不在数据库表中创建列
- 只存在于 Python 对象中
- 用于运行时状态管理
- 不会持久化
为什么 Account 使用 field?
看代码中的使用:
role: TenantAccountRole | None = field(default=None, init=False) _current_tenant: "Tenant | None" = field(default=None, init=False) def current_tenant(self): return self._current_tenant .setter def current_tenant(self, tenant: "Tenant"): # ... 设置当前租户的逻辑 self._current_tenant = tenant
原因:
- 运行时状态:current_tenant 表示当前会话的租户,不需要持久化
- 性能优化:避免每次都查询数据库
- 业务逻辑:role 是当前租户下的角色,是计算属性,不是数据库字段
参考链接: