二,属性 解释
role:Mapped[str]=mapped_column(
String(32),
comment="列类别 (primary_key,forergn_key,measure,dimension)"
)
role: Mapped[str](Python 类型注解)
Mapped 是 SQLAlchemy 提供的一个泛型类型,用于在声明式模型中标注该属性在 Python 中的数据类型。
Mapped[str] 表示这个字段在 Python 层面是一个字符串类型。SQLAlchemy 会根据这个类型提示,自动推断出对应的数据库数据类型(如果不显式指定的话)以及该列是否允许为 NULL。
mapped_column(...)(数据库列配置)
这个函数用于描述该列在数据库中的具体约束和规格。
String(32):显式指定了数据库中的数据类型为 VARCHAR,且最大长度限制为 32 个字符。如果这里不传参数,SQLAlchemy 会默认使用 Mapped[str] 推导出的类型,但显式指定长度是更严谨的做法。
comment="列类别...":为该数据库字段添加注释说明。这在生成数据库 DDL(如 CREATE TABLE 语句)时会作为列注释写入数据库,有助于后续维护时理解该字段的业务含义。
primary_key (主键):
代表该列是表的主键,用于唯一标识表中的每一行记录。
forergn_key (外键):
注:这里存在一个拼写错误,正确的英文应该是 foreign_key。
代表该列是外键,用于关联其他表,建立表与表之间的关系。
measure (度量):
在数据仓库中,度量通常指可被聚合计算的数值型数据。例如:销售额、利润、订单数量、温度等。它们通常用于图表中的 Y 轴或指标卡片。
dimension (维度):
维度是指用于观察和分析数据的上下文或分类属性。例如:时间(年/月/日)、地区、产品类别、客户名称等。它们通常用于图表中的 X 轴、分组(Group By)或筛选器。
三,各层的数据例子 column 列 来举例:
1,业务层,和应用层web,小程序 等交互的,column_info.py:
# 列对象 的类,业务层,用来表示 各个列 的对象 的封装
# 8给 个属性
from dataclasses import dataclass
from typing import Any
@dataclass
class ColumnInfo:
id:str
name:str
type:str # 列 数据类型
role:str
# 列类别 (
# primary_key 主键,forergn_key 外键 ,measure 度量:数值,dimension 维度:维度表中的属性
examples:list[Any]
# 数据示例
description:str
alias:list[str]
table_id:str
2,持久层,和数据库交互的 column_info_mysql.py
from sqlalchemy.orm import Mapped, mapped_column
from day05_Info_model.models.base import Base
from sqlalchemy import String,Text
from sqlalchemy.types import JSON
class ColumnInfoMySQL(Base):
__tablename__ = "coloumn_info"
id:Mapped[str]=mapped_column(
String(64),
primary_key=True,
comment="列的编号"
)
name:Mapped[str |None]=mapped_column(
String(128),
comment="列名称"
)
type:Mapped[str | None]=mapped_column(
String(64),
comment="列 数据类型"
)
role:Mapped[str | None]=mapped_column(
String(32),
comment="列类别 (primary_key 主键,forergn_key 外键 ,measure 度量:数值,dimension 维度:维度表中的属性 )"
)
examples:Mapped[dict| list | None]=mapped_column(
JSON,
comment="列对象的 举例说明,数据示例"
)
description:Mapped[str |None]=mapped_column(
Text,
comment="列 的 描述 说明,是做什么的"
)
alias:Mapped[dict | list | None]=mapped_column(
JSON,
comment="列别名"
)
table_id:Mapped[str | None]=mapped_column(
String(64),
comment="列 所在的 对应的表 的编号 id"
)
3,业务层和持久层的相互转换,column_info_mapper.py:
from dataclasses import asdict
from day05_Info_model.entities.column_info import ColumnInfo
from day05_Info_model.models.coloumn_info_mysql import ColumnInfoMySQL
class ColumnInfoMapper:
@staticmethod
def to_entity(column_info_mysql:ColumnInfoMySQL)->ColumnInfo:
return ColumnInfo(
id=column_info_mysql.id,
name=column_info_mysql.name,
type=column_info_mysql.type,
role=column_info_mysql.role,
examples=column_info_mysql.examples,
description=column_info_mysql.description,
alias=column_info_mysql.alias,
table_id=column_info_mysql.table_id
)
@staticmethod
def to_model(column_info:ColumnInfo)->ColumnInfoMySQL:
return ColumnInfoMySQL(**asdict(column_info))
四,mapper:业务层和持久层转换 验证测试:
1,1_column_mapper_test.py:
# main_test.py
from dataclasses import asdict
from day05_Info_model.entities.column_info import ColumnInfo
from day05_Info_model.models.coloumn_info_mysql import ColumnInfoMySQL
from day05_Info_model.mappers.column_info_mapper import ColumnInfoMapper
# 1. 构造业务层对象 (Entity)
entity = ColumnInfo(
id="col_001",
name="sales_amount",
type="DECIMAL(10,2)",
role="measure",
examples=[199.99, 250.00],
description="销售总金额",
alias=["销售额", "营收"],
table_id="tbl_order"
)
print("=== 1. 原始业务实体 (Entity) ===")
print(entity) # 触发 dataclass 的 __repr__
print()
# 2. Entity -> Model (业务层转持久层)
model = ColumnInfoMapper.to_model(entity)
print("=== 2. 转换后的数据库模型 (Model) ===")
print(model) # 触发 SQLAlchemy 声明式对象的 __repr__
print()
# 3. Model -> Entity (持久层转回业务层)
new_entity = ColumnInfoMapper.to_entity(model)
print("=== 3. 转换回业务实体 (Entity) ===")
print(new_entity)
'''
=== 1. 原始业务实体 (Entity) ===
ColumnInfo(id='col_001', name='sales_amount', type='DECIMAL(10,2)', role='measure', examples=[199.99, 250.0], description='销售总金额', alias=['销售额', '营收'], table_id='tbl_order')
=== 2. 转换后的数据库模型 (Model) ===
<day05_Info_model.models.coloumn_info_mysql.ColumnInfoMySQL object at 0x00000210DB15AD50>
=== 3. 转换回业务实体 (Entity) ===
ColumnInfo(id='col_001', name='sales_amount', type='DECIMAL(10,2)', role='measure', examples=[199.99, 250.0], description='销售总金额', alias=['销售额', '营收'], table_id='tbl_order')
'''
2,2_test_column_info_mapper.py:
# test_column_info_mapper.py
import unittest
from day05_Info_model.entities.column_info import ColumnInfo
from day05_Info_model.models.coloumn_info_mysql import ColumnInfoMySQL
from day05_Info_model.mappers.column_info_mapper import ColumnInfoMapper
class TestColumnInfoMapper(unittest.TestCase):
def setUp(self):
"""测试前的准备工作:构造一个标准的业务实体"""
self.entity = ColumnInfo(
id="col_001",
name="sales_amount",
type="DECIMAL(10,2)",
role="measure",
examples=[199.99, 250.00],
description="销售总金额",
alias=["销售额", "营收"],
table_id="tbl_order"
)
def test_to_model(self):
"""测试:业务实体 -> 数据库模型"""
model = ColumnInfoMapper.to_model(self.entity)
# 验证类型转换是否正确
self.assertIsInstance(model, ColumnInfoMySQL)
# 验证字段值是否完全一致
self.assertEqual(model.id, "col_001")
self.assertEqual(model.name, "sales_amount")
self.assertEqual(model.type, "DECIMAL(10,2)")
self.assertEqual(model.role, "measure")
self.assertEqual(model.examples, [199.99, 250.00])
self.assertEqual(model.description, "销售总金额")
self.assertEqual(model.alias, ["销售额", "营收"])
self.assertEqual(model.table_id, "tbl_order")
def test_to_entity(self):
"""测试:数据库模型 -> 业务实体"""
# 模拟从数据库查出来的对象(这里用字典模拟 SQLAlchemy 对象的行为)
class MockDBModel:
def __init__(self):
self.id = "col_002"
self.name = "user_name"
self.type = "VARCHAR(64)"
self.role = "dimension"
self.examples = ["Alice", "Bob"]
self.description = "用户姓名"
self.alias = ["姓名"]
self.table_id = "tbl_user"
mock_model = MockDBModel()
entity = ColumnInfoMapper.to_entity(mock_model)
# 验证类型转换是否正确
self.assertIsInstance(entity, ColumnInfo)
# 验证字段值
self.assertEqual(entity.id, "col_002")
self.assertEqual(entity.role, "dimension")
self.assertEqual(entity.alias, ["姓名"])
def test_round_trip_conversion(self):
"""测试:双向转换(Entity -> Model -> Entity),确保数据不丢失"""
model = ColumnInfoMapper.to_model(self.entity)
converted_entity = ColumnInfoMapper.to_entity(model)
# 验证经过一轮转换后,数据是否和原始实体一致
self.assertEqual(self.entity.id, converted_entity.id)
self.assertEqual(self.entity.name, converted_entity.name)
self.assertEqual(self.entity.examples, converted_entity.examples)
self.assertEqual(self.entity.alias, converted_entity.alias)
if __name__ == '__main__':
unittest.main()
'''
点击pycharm运行弹出这样的窗口
这个错误 Error: Provide a qualified name of a function, class or module 是因为 PyCharm 的运行配置中,模块路径(Module Name)填写的格式不对。
PyCharm 试图把你输入的长字符串当作一个"模块"去加载,但它包含了类名和方法名,导致 Python 找不到这个模块。
✅ 解决方法(二选一)
方法一:修改当前配置(推荐)
请按照以下步骤调整你截图中的设置:
切换模式:将下拉菜单从 module 改为 Script path(脚本路径)。
选择文件:点击右侧的文件夹图标 📂,选择你的测试文件 2_test_column_info_mapper.py。
指定特定测试(可选):如果你想只运行这一个特定的测试方法,在下方的 Additional arguments(附加参数)框中输入:
text
编辑
TestColumnInfoMapper.test_to_entity
点击 Run。
方法二:最简单的"懒人"办法
不需要打开这个复杂的配置窗口:
关闭当前的 "Edit Configuration" 窗口。
回到代码编辑界面。
在代码左侧的行号栏旁边,找到 class TestColumnInfoMapper 或者 def test_to_entity 左边的 绿色三角形图标 ▶️。
直接点击那个绿色三角形,选择 "Run 'Unittests in ...'"。
PyCharm 会自动为你生成正确的配置并运行。
💡 为什么会报错?(原理解析)
在你截图的配置中:
Type: 选择了 module(这意味着后面只能填类似 unittest 或 my_package.my_module 这种纯包路径)。
Name: 你填了 ...TestColumnInfoMapper.test_to_entity。
Python 会尝试执行 import ...TestColumnInfoMapper.test_to_entity,这显然是不合法的导入语法,所以它提示你需要提供一个合法的"函数、类或模块名称"。
如果改完还报错,可以把新的报错截图发给我看看。
'''